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 ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

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

View File

@ -2404,11 +2404,11 @@ msgid "Filament"
msgstr "Filament"
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: 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 ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: 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"
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: 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 "필라멘트"
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

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

View File

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

View File

@ -2363,11 +2363,11 @@ msgid "Filament"
msgstr "Filament"
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: 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"
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Hide Custom GCode"
msgid "Hide Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3954
msgid "Show Custom GCode"
msgid "Show Custom G-code"
msgstr ""
#: src/slic3r/GUI/GCodeViewer.cpp:3967

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3909,7 +3909,7 @@ void GCodeProcessor::post_process()
std::stringstream ss(cmd.substr(1));
int tool_number = -1;
ss >> tool_number;
if (tool_number != -1)
if (tool_number != -1) {
if (tool_number < 0 || (int)m_extruder_temps_config.size() <= tool_number) {
// found an invalid value, clamp it to a valid one
tool_number = std::clamp<int>(0, m_extruder_temps_config.size() - 1, tool_number);
@ -3922,28 +3922,29 @@ void GCodeProcessor::post_process()
if (m_print != nullptr)
m_print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning);
}
export_lines.insert_lines(backtrace, cmd,
// line inserter
[tool_number, this](unsigned int id, float time, float time_diff) {
int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
const std::string out = "M104 T" + std::to_string(tool_number) + " P" + std::to_string(int(std::round(time_diff))) + " S" + std::to_string(temperature) + "\n";
return out;
},
// line replacer
[this, tool_number](const std::string& line) {
if (GCodeReader::GCodeLine::cmd_is(line, "M104")) {
GCodeReader::GCodeLine gline;
GCodeReader reader;
reader.parse_line(line, [&gline](GCodeReader& reader, const GCodeReader::GCodeLine& l) { gline = l; });
}
export_lines.insert_lines(backtrace, cmd,
// line inserter
[tool_number, this](unsigned int id, float time, float time_diff) {
int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
const std::string out = "M104 T" + std::to_string(tool_number) + " P" + std::to_string(int(std::round(time_diff))) + " S" + std::to_string(temperature) + "\n";
return out;
},
// line replacer
[this, tool_number](const std::string& line) {
if (GCodeReader::GCodeLine::cmd_is(line, "M104")) {
GCodeReader::GCodeLine gline;
GCodeReader reader;
reader.parse_line(line, [&gline](GCodeReader& reader, const GCodeReader::GCodeLine& l) { gline = l; });
float val;
if (gline.has_value('T', val) && gline.raw().find("cooldown") != std::string::npos && m_is_XL_printer) {
if (static_cast<int>(val) == tool_number)
return std::string("; removed M104\n");
}
float val;
if (gline.has_value('T', val) && gline.raw().find("cooldown") != std::string::npos && m_is_XL_printer) {
if (static_cast<int>(val) == tool_number)
return std::string("; removed M104\n");
}
return line;
});
}
return line;
});
}
};

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));
// 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 j = 0; j<number_of_extruders; ++j) {
float w = wipe_volumes[i][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);
}
}
for (unsigned int i = 0; i<number_of_extruders; ++i)
for (unsigned int j = 0; j<number_of_extruders; ++j)
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j], config.filament_minimal_purge_on_wipe_tower.get_at(j));
return wipe_volumes;
}

View File

@ -84,16 +84,16 @@ void Layer::make_slices()
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.
// 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
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) {
contours.clear();
co.Clear();
co.AddPath(expoly.contour.points, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
co.Execute(contours, - delta);
size_t num_prev = out.size();
// size_t num_prev = out.size();
if (! contours.empty()) {
holes.clear();
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)
if (contour_aabb.overlap(other_layer.lslices_ex[i].bbox))
// 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) {
// Find the contour with the largest overlap. It is expected that the other overlap will be very small.
double a = area(overlap);
@ -460,6 +460,7 @@ static void connect_layer_slices(
i_largest = i;
break;
}
}
assert(i_largest >= 0);
return i_largest;
}
@ -500,10 +501,10 @@ static void connect_layer_slices(
#endif // NDEBUG
// 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)
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)
below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area });
// Sort the links.
@ -935,7 +936,7 @@ void Layer::sort_perimeters_into_islands(
island.fill_region_id = LayerIsland::fill_region_composite_id;
for (uint32_t fill_idx : fill_range) {
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;
break;
} 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.
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;
{

View File

@ -23,6 +23,8 @@
#include <boost/log/trivial.hpp>
#include <boost/nowide/iostream.hpp>
#include <tbb/concurrent_vector.h>
#include "SVG.hpp"
#include <Eigen/Dense>
#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.
Polygon ModelObject::convex_hull_2d(const Transform3d& trafo_instance) const
{
Points pts;
for (const ModelVolume* v : volumes) {
if (v->is_model_part())
append(pts, its_convex_hull_2d_above(v->mesh().its, (trafo_instance * v->get_matrix()).cast<float>(), 0.0f).points);
}
return Geometry::convex_hull(std::move(pts));
tbb::concurrent_vector<Polygon> chs;
chs.reserve(volumes.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, volumes.size()), [&](const tbb::blocked_range<size_t>& range) {
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));
}
});
Polygons polygons;
polygons.assign(chs.begin(), chs.end());
return Geometry::convex_hull(polygons);
}
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(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(); }
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(); } }

View File

@ -582,7 +582,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
}
// 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) {
start_point = pt.first;
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);
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;
}

View File

@ -301,7 +301,7 @@ void ThickPolyline::start_at_index(int index)
{
assert(index >= 0 && index < this->points.size());
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();
assert(this->points.size() * 2 == this->width.size());
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.");
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.");
if (! sequential_print_vertical_clearance_valid(*this))
return _u8L("Some objects are too tall and cannot be printed without extruder collisions.");
if (!sequential_print_vertical_clearance_valid(*this))
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) {
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;
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%");
assert(first_elem != translated_list.npos);
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
auto second_elem = translated_list.find("%2%");
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);
} else {
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 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);
protected:
@ -658,6 +659,9 @@ private:
// Estimated print time, filament consumed.
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.
friend class GCode;
// 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
// 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
@ -1703,7 +1703,7 @@ void PrintObject::bridge_over_infill()
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));
#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
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::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 = 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 = 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);
};
@ -2276,8 +2276,8 @@ void PrintObject::bridge_over_infill()
lightning_area.insert(lightning_area.end(), l.begin(), l.end());
}
}
total_fill_area = closing(total_fill_area, SCALED_EPSILON);
expansion_area = closing(expansion_area, SCALED_EPSILON);
total_fill_area = closing(total_fill_area, float(SCALED_EPSILON));
expansion_area = closing(expansion_area, float(SCALED_EPSILON));
expansion_area = intersection(expansion_area, deep_infill_area);
Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing));
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()) {
// 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;
// The layer height returned is sampled with high density for the UI layer height painting
// 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]() {
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()) {
bool last_z_matches = is_approx(*(layer_height_profile.end() - 2), z);
bool last_h_matches = is_approx(layer_height_profile.back(), layer_height);

View File

@ -26,6 +26,433 @@ namespace Slic3r
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.
// 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.
@ -46,6 +473,7 @@ static bool circles_intersect(
assert(n1.dot(p2) >= n1.dot(lowest_point2));
return n1.dot(lowest_point2) <= 0;
}
#endif
template<bool flip_normals>
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];
// static int irun = 0;
float zmin, zmax;
float zmin = 0;
float zmax = 0;
for (size_t ipath = 1; ipath < path.size(); ++ ipath) {
const SupportElement &prev = *path[ipath - 1];
@ -390,7 +819,7 @@ static void organic_smooth_branches_avoid_collisions(
collision_sphere.prev_position = collision_sphere.position;
std::atomic<size_t> num_moved{ 0 };
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)
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
// Calculate collision of multiple 2D layers against a collision sphere.
@ -426,7 +855,7 @@ static void organic_smooth_branches_avoid_collisions(
}
// Laplacian smoothing
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];
double weight = 0.;
for (auto iparent : collision_sphere.element.parents) {
@ -599,7 +1028,7 @@ void organic_draw_branches(
std::vector<std::pair<SupportElement*, int>> map_downwards_new;
linear_data_layers.emplace_back(0);
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();
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];
@ -675,7 +1104,7 @@ void organic_draw_branches(
// 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 = move_bounds[start_element.state.layer_idx];
SupportElements &layer_above = move_bounds[start_element.state.layer_idx + 1];
bool root = out.branches.empty();
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());
assert(!trees.back().branches.empty());
//FIXME debugging
#if 1
#if 0
if (start_element.state.lost) {
}
else if (start_element.state.verylost) {
@ -758,7 +1187,7 @@ void organic_draw_branches(
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
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;
std::vector<float> slice_z;
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_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.
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) {
rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false));
double rest_support_area = area(rest_support);
@ -895,11 +1324,11 @@ void organic_draw_branches(
Slice &dst = tree.slices[i - new_begin];
if (++ dst.num_branches > 1) {
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]));
} else {
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]);
}
}

View File

@ -298,7 +298,7 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
else {
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; });
return std::move(out);
return out;
}
};
interface_layers = merge_remove_empty(interface_layers, top_interface_layers);
@ -664,7 +664,7 @@ static inline void tree_supports_generate_paths(
// Draw the perimeters.
Polylines polylines;
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.
const Polygon &loop = expoly.contour_or_hole(idx_loop);
Polyline pl(loop.points);
@ -680,11 +680,11 @@ static inline void tree_supports_generate_paths(
ClipperLib_Z::Path *closest_contour = nullptr;
Vec2d closest_point;
int closest_point_idx = -1;
double closest_point_t;
double closest_point_t = 0.;
double d2min = std::numeric_limits<double>::max();
Vec2d seam_pt = pl.back().cast<double>();
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);
if (path[i].z() == idx_loop || path[j].z() == idx_loop) {
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::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>>{};
const auto &layer = m_data[key.second];
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.
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);
if (key.second >= m_data.size())
if (key.second >= LayerIndex(m_data.size()))
return {};
const auto &layer = m_data[key.second];
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!
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),
[&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]
@ -387,6 +387,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
return result;
}
#if 0
/*!
* \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);
return result;
}
#endif
/*!
* \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
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
// reduce the stepsize to ensure it is offset the required amount of times
step_size = distance / min_amount_offset;
@ -1175,7 +1177,7 @@ void sample_overhang_area(
}
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.
interface_placer.add_roof_build_plate(std::move(overhang_area), dtt_roof);
else {
@ -1282,7 +1284,7 @@ static void generate_initial_areas(
tbb::parallel_for(tbb::blocked_range<size_t>(0, raw_overhangs.size()),
[&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) {
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;
@ -2611,13 +2613,13 @@ static void remove_deleted_elements(std::vector<SupportElements> &move_bounds)
std::iota(map_current.begin(), map_current.end(), 0);
}
// 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();
// Mark as deleted in the map.
map_current[layer.size()] = -1;
}
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());
layer.pop_back();
// 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) {
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)
for (SupportElement &elem : *layer_above)
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;
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) {
const SupportElement &parent = (*layer_above)[parent_idx];
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_new;
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();
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; });
@ -3472,6 +3474,17 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
if (support_params.has_base_interfaces() || has_raft)
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{
print_object.slicing_parameters(), support_params, config,
// Outputs
@ -3523,14 +3536,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
throw_on_cancel);
}
auto remove_undefined_layers = [](SupportGeneratorLayersPtr& 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);
remove_undefined_layers();
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);
@ -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.)";
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.
continue;

View File

@ -78,14 +78,12 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
}
TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mesh_group_settings, const SlicingParameters &slicing_params)
: angle(mesh_group_settings.support_tree_angle),
angle_slow(mesh_group_settings.support_tree_angle_slow),
support_line_width(mesh_group_settings.support_line_width),
: support_line_width(mesh_group_settings.support_line_width),
layer_height(mesh_group_settings.layer_height),
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
maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(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((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((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),
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),
@ -155,7 +153,7 @@ TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings &mes
// Layers between the raft contacts and bottom of the object.
auto nsteps = int(ceil(dist_to_go / slicing_params.max_suport_layer_height));
double step = dist_to_go / nsteps;
for (size_t i = 0; i < nsteps; ++ i) {
for (int i = 0; i < nsteps; ++ i) {
z += step;
this->raft_layers.emplace_back(z);
}

View File

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

View File

@ -26,6 +26,8 @@
#include <boost/nowide/cstdio.hpp>
#include <boost/predef/other/endian.h>
#include <tbb/concurrent_vector.h>
#include <Eigen/Core>
#include <Eigen/Dense>
@ -871,11 +873,38 @@ void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, c
}
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;
its_collect_mesh_projection_points_above(its, transform_fn, z, all_pts);
return Geometry::convex_hull(std::move(all_pts));
auto collect_mesh_projection_points_above = [&](const tbb::blocked_range<size_t>& range) {
Points 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)

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()
{
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_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 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 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_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f });
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 (logarithmic)"),
_u8L("Tool"),
_u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest);
_u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f);
ImGui::PopStyleColor(2);
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);
if (custom_it != m_roles.end()) {
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();
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)) :

View File

@ -132,6 +132,9 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
delete m_slicing_parameters;
m_slicing_parameters = nullptr;
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)
@ -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.
// 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());
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())) {
m_layer_height_profile.clear();
@ -543,10 +547,10 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G
void GLCanvas3D::LayersEditing::adjust_layer_height_profile()
{
this->update_slicing_parameters();
PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile);
Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action);
m_layer_height_profile_modified = true;
this->update_slicing_parameters();
PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile);
Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action);
m_layer_height_profile_modified = true;
m_layers_texture.valid = false;
}
@ -581,14 +585,15 @@ void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas,
void GLCanvas3D::LayersEditing::generate_layer_height_texture()
{
this->update_slicing_parameters();
// Always try to update the layer height profile.
this->update_slicing_parameters();
// Always try to update the layer height profile.
bool update = ! m_layers_texture.valid;
if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) {
// Initialized to the default value.
m_layer_height_profile_modified = false;
update = true;
}
// 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)
// Texture is valid, don't update.
@ -615,8 +620,8 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas)
if (m_layer_height_profile_modified) {
wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit"));
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().plater()->schedule_background_process();
}
}
m_layer_height_profile_modified = false;
@ -880,20 +885,22 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
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();
if (polygons.empty())
if (contours.empty())
return;
if (m_render_fill) {
if (generate_fill) {
GLModel::Geometry fill_data;
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
const ExPolygons polygons_union = union_ex(polygons);
const ExPolygons polygons_union = union_ex(contours.contours);
unsigned int vertices_counter = 0;
for (const ExPolygon& poly : polygons_union) {
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);
}
}
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()
{
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 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_EVALUATING_COLOR = { 1.0f, 1.0f, 0.0f, 1.0f };
if (m_contours.empty() || m_instances.empty())
return;
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
@ -933,9 +971,34 @@ void GLCanvas3D::SequentialPrintClearance::render()
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR);
m_perimeter.render();
m_fill.render();
if (!m_evaluating)
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(::glEnable(GL_CULL_FACE));
@ -1240,7 +1303,7 @@ void GLCanvas3D::SLAView::render_switch_button()
imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
const float icon_size = 1.5 * ImGui::GetTextLineHeight();
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;
imgui.draw_icon(window, pos, size, icon_id);
})) {
@ -1450,14 +1513,97 @@ void GLCanvas3D::reset_volumes()
_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;
if (m_initialized)
m_volumes.check_outside_state(m_bed.build_volume(), &state);
if (m_initialized && !m_volumes.empty())
check_volumes_outside_state(m_bed.build_volume(), &state, selection_only);
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)
{
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
if (!m_volumes.empty()) {
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 fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside);
@ -3430,17 +3576,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// Not only detection of some modifiers !!!
if (evt.Dragging()) {
GLGizmosManager::EType c = m_gizmos.get_current_type();
if (current_printer_technology() == ptFFF &&
fff_print()->config().complete_objects){
if (c == GLGizmosManager::EType::Move ||
c == GLGizmosManager::EType::Scale ||
c == GLGizmosManager::EType::Rotate )
update_sequential_clearance();
} else {
if (c == GLGizmosManager::EType::Move ||
c == GLGizmosManager::EType::Scale ||
c == GLGizmosManager::EType::Rotate)
show_sinking_contours();
if (c == GLGizmosManager::EType::Move ||
c == GLGizmosManager::EType::Scale ||
c == GLGizmosManager::EType::Rotate) {
show_sinking_contours();
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects)
update_sequential_clearance(true);
}
}
else if (evt.LeftUp() &&
@ -3643,7 +3784,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
trafo_type.set_relative();
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type);
if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects)
update_sequential_clearance();
update_sequential_clearance(false);
wxGetApp().obj_manipul()->set_dirty();
m_dirty = true;
}
@ -3949,7 +4090,10 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
if (wipe_tower_origin != Vec3d::Zero())
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;
}
@ -4034,6 +4178,11 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
if (!done.empty())
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;
}
@ -4106,6 +4255,11 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type)
if (!done.empty())
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;
}
@ -4373,16 +4527,33 @@ void GLCanvas3D::mouse_up_cleanup()
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)
return;
if (m_layers_editing.is_enabled() || m_gizmos.is_dragging())
if (m_layers_editing.is_enabled())
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
// first define temporary cache
// first: define temporary cache
unsigned int instances_count = 0;
std::vector<std::vector<std::optional<Geometry::Transformation>>> instance_transforms;
for (size_t obj = 0; obj < m_model->objects.size(); ++obj) {
@ -4397,67 +4568,94 @@ void GLCanvas3D::update_sequential_clearance()
if (instances_count == 1)
return;
// second fill temporary cache with data from volumes
// second: fill temporary cache with data from volumes
for (const GLVolume* v : m_volumes.volumes) {
if (v->is_modifier || v->is_wipe_tower)
if (v->is_wipe_tower)
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())
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())
// this is done only the first time this method is called while moving the mouse,
// the results are then cached for following displacements
if (m_sequential_print_clearance_first_displacement) {
m_sequential_print_clearance.m_hull_2d_cache.clear();
if (force_contours_generation || m_sequential_print_clearance_first_displacement) {
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 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) {
ModelObject* model_object = m_model->objects[i];
ModelInstance* model_instance0 = model_object->instances.front();
Geometry::Transformation trafo = model_instance0->get_transformation();
trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() });
const Polygon hull_2d = offset(model_object->convex_hull_2d(trafo.get_matrix()),
Geometry::Transformation trafo = instance_transform_from_volumes((int)i, 0);
trafo.set_offset({ 0.0, 0.0, trafo.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;
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
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
shrink_factor,
jtRound, mitter_limit).front();
const Polygons offset_res = offset(hull_2d, shrink_factor, jtRound, mitter_limit);
if (!offset_res.empty())
hull_2d = offset_res.front();
}
Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s());
cache_hull_2d.reserve(hull_2d.points.size());
const Transform3d inv_trafo = trafo.get_matrix().inverse();
new_hull_2d.reserve(hull_2d.points.size());
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));
}
}
ContoursList contours;
contours.contours.reserve(instance_transforms.size());
contours.trafos = std::vector<std::pair<size_t, Transform3d>>();
(*contours.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];
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];
for (const auto& instance : instances) {
(*contours.trafos).emplace_back(i, instance_trafo(hull_trafo, *instance));
}
}
set_sequential_print_clearance_contours(contours, false);
m_sequential_print_clearance_first_displacement = false;
}
// calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid())
Polygons polygons;
polygons.reserve(instances_count);
for (size_t i = 0; i < instance_transforms.size(); ++i) {
const auto& instances = instance_transforms[i];
for (const auto& instance : instances) {
const Transform3d& trafo = instance->get_matrix();
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()));
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));
}
}
polygons.emplace_back(Geometry::convex_hull(std::move(inst_pts)));
m_sequential_print_clearance.update_instances_trafos(trafos);
}
}
// sends instances 2d hulls to be rendered
set_sequential_print_clearance_visible(true);
set_sequential_print_clearance_render_fill(false);
set_sequential_print_clearance_polygons(polygons);
}
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) {
m_volumes.check_outside_state(build_volume, nullptr);
check_volumes_outside_state(build_volume, nullptr);
m_requires_check_outside_state = false;
}
}
@ -6032,15 +6230,20 @@ void GLCanvas3D::_render_selection()
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;
switch (m_gizmos.get_current_type())
{
case GLGizmosManager::EType::Flatten:
case GLGizmosManager::EType::Cut:
case GLGizmosManager::EType::Hollow:
case GLGizmosManager::EType::SlaSupports:
case GLGizmosManager::EType::MmuSegmentation:
case GLGizmosManager::EType::Measure:
case GLGizmosManager::EType::Emboss:
case GLGizmosManager::EType::Simplify:
case GLGizmosManager::EType::FdmSupports:
case GLGizmosManager::EType::Seam: { return; }
default: { break; }

View File

@ -619,23 +619,35 @@ public:
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:
void load_arrange_settings();
class SequentialPrintClearance
{
GLModel m_fill;
GLModel m_perimeter;
bool m_render_fill{ true };
bool m_visible{ false };
// list of unique contours
std::vector<GLModel> m_contours;
// 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:
void set_polygons(const Polygons& polygons);
void set_render_fill(bool render_fill) { m_render_fill = render_fill; }
void set_visible(bool visible) { m_visible = visible; }
void set_contours(const ContoursList& contours, bool generate_fill);
void update_instances_trafos(const std::vector<Transform3d>& trafos);
void render();
bool empty() const { return m_contours.empty(); }
friend class GLCanvas3D;
};
@ -725,7 +737,10 @@ public:
unsigned int get_volumes_count() const;
const GLVolumeCollection& get_volumes() const { return m_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 reset_gcode_toolpaths() { m_gcode_viewer.reset(); }
@ -959,24 +974,24 @@ public:
}
void reset_sequential_print_clearance() {
m_sequential_print_clearance.set_visible(false);
m_sequential_print_clearance.set_render_fill(false);
m_sequential_print_clearance.set_polygons(Polygons());
m_sequential_print_clearance.m_evaluating = false;
m_sequential_print_clearance.set_contours(ContoursList(), false);
}
void set_sequential_print_clearance_visible(bool visible) {
m_sequential_print_clearance.set_visible(visible);
void set_sequential_print_clearance_contours(const ContoursList& contours, bool generate_fill) {
m_sequential_print_clearance.set_contours(contours, generate_fill);
}
void set_sequential_print_clearance_render_fill(bool render_fill) {
m_sequential_print_clearance.set_render_fill(render_fill);
bool is_sequential_print_clearance_empty() const {
return m_sequential_print_clearance.empty();
}
void set_sequential_print_clearance_polygons(const Polygons& polygons) {
m_sequential_print_clearance.set_polygons(polygons);
bool is_sequential_print_clearance_evaluating() const {
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 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)
{
if (is_initialized()) {

View File

@ -227,6 +227,7 @@ namespace GUI {
void init_from(const TriangleMesh& mesh);
#endif // ENABLE_SMOOTH_NORMALS
void init_from(const indexed_triangle_set& its);
void init_from(const Polygon& polygon, float z);
void init_from(const Polygons& polygons, float z);
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
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
bool evt_context_menu = (canvas != nullptr) ? !canvas->is_mouse_dragging() : true;
if (!evt_context_menu)
canvas->mouse_up_cleanup();
// if (!evt_context_menu)
// canvas->mouse_up_cleanup();
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);
}
wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state();
wxGetApp().plater()->canvas3D()->render();
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
canvas->update_gizmos_on_off_state();
canvas->check_volumes_outside_state();
canvas->render();
}
void ObjectList::select_item(const wxDataViewItem& item)

View File

@ -859,13 +859,13 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type)
#if ENABLE_OBJECT_MANIPULATION_DEBUG
void ObjectManipulation::render_debug_window()
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
// ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once);
imgui.begin(std::string("ObjectManipulation"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Coordinates type");
ImGui::SameLine();
imgui.text(coordinate_type_str(m_coordinates_type));
imgui.end();
ImGuiWrapper& imgui = *wxGetApp().imgui();
// ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once);
imgui.begin(std::string("ObjectManipulation"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Coordinates type");
ImGui::SameLine();
imgui.text(coordinate_type_str(m_coordinates_type));
imgui.end();
}
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG

View File

@ -22,6 +22,7 @@ namespace GUI {
static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW();
static const ColorRGBA UPPER_PART_COLOR = ColorRGBA::CYAN();
static const ColorRGBA LOWER_PART_COLOR = ColorRGBA::MAGENTA();
static const ColorRGBA MODIFIER_COLOR = ColorRGBA(0.75f, 0.75f, 0.75f, 0.5f);
// connector colors
static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW();
@ -1405,7 +1406,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor
m_parts.clear();
for (const ModelVolume* volume : volumes) {
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.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;
for (size_t id=0; id<m_parts.size(); ++id) {
if (normal && (( is_looking_forward && m_parts[id].selected) ||
(!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) ) )
continue;
const Vec3d volume_offset = model_object()->volumes[id]->get_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();
if (m_parts[id].is_modifier)
glsafe(::glDisable(GL_BLEND));
}
shader->stop_using();
@ -1551,7 +1558,7 @@ bool GLGizmoCut3D::PartSelection::is_one_object() const
if (m_parts.size() < 2)
return true;
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;
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());
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());
@ -2535,29 +2542,66 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
};
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) {
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]));
}
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;
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);
}
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++)
delete *(volumes.begin() + id);
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);
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)
upper->add_volume(*volume, volume->type());
// Connectors from lower object
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
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 Dowel-connectors as separate objects to cut_object_ptrs
if (cut_connectors_obj.size() >= 3)
for (size_t id = 2; id < cut_connectors_obj.size(); 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) {
TriangleMesh mesh;
// Merge all SolidPart but not Connectors
for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part()) {
if (mv->is_model_part() && !mv->is_cut_connector()) {
TriangleMesh m = mv->mesh();
m.transform(mv->get_matrix());
mesh.merge(m);
@ -2576,9 +2621,13 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
}
if (! mesh.empty()) {
ModelVolume* new_volume = mo->add_volume(mesh);
for (int i=int(mo->volumes.size())-2; i>=0; --i)
if (mo->volumes[i]->type() == ModelVolumeType::MODEL_PART)
new_volume->name = mo->name;
// 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);
}
}
}
}

View File

@ -149,6 +149,7 @@ class GLGizmoCut3D : public GLGizmoBase
GLModel glmodel;
MeshRaycaster raycaster;
bool selected;
bool is_modifier;
};
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();
}
m_plater->update();
m_plater->update((unsigned int)Plater::UpdateParams::FORCE_FULL_SCREEN_REFRESH);
wxGetApp().obj_manipul()->set_dirty();
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) {
// NoNewReleaseAvailable must not show if alfa / beta is on.
NotificationType nttype = notification->get_type();
if (type == NotificationType::NoNewReleaseAvailable
&& (notification->get_type() == NotificationType::NewAlphaAvailable
|| 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(); }
#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 select_view(const std::string& direction);
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);
model.delete_object(obj_idx);
update();
object_list_changed();
return true;
}
@ -3253,7 +3250,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
if (view3D->is_layers_editing_enabled())
view3D->get_wxglcanvas()->Refresh();
if (background_process.empty())
if (invalidated == Print::APPLY_STATUS_CHANGED || background_process.empty())
view3D->get_canvas3d()->reset_sequential_print_clearance();
if (invalidated == Print::APPLY_STATUS_INVALIDATED) {
@ -3292,29 +3289,43 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// or hide the old one.
process_validation_warning(warning);
if (printer_technology == ptFFF) {
view3D->get_canvas3d()->reset_sequential_print_clearance();
view3D->get_canvas3d()->set_as_dirty();
view3D->get_canvas3d()->request_extra_frame();
GLCanvas3D* canvas = view3D->get_canvas3d();
canvas->reset_sequential_print_clearance();
canvas->set_as_dirty();
canvas->request_extra_frame();
}
}
else {
// The print is not valid.
// Show error as notification.
// The print is not valid.
// Show error as notification.
notification_manager->push_validate_error_notification(err);
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
if (printer_technology == ptFFF) {
const Print* print = background_process.fff_print();
Polygons polygons;
if (print->config().complete_objects)
Print::sequential_print_horizontal_clearance_valid(*print, &polygons);
view3D->get_canvas3d()->set_sequential_print_clearance_visible(true);
view3D->get_canvas3d()->set_sequential_print_clearance_render_fill(true);
view3D->get_canvas3d()->set_sequential_print_clearance_polygons(polygons);
GLCanvas3D* canvas = view3D->get_canvas3d();
if (canvas->is_sequential_print_clearance_empty() || 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();
}
}
}
}
else {
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 err = background_process.validate(&warning);
if (!err.empty())
@ -4416,7 +4427,6 @@ void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
void Plater::priv::on_3dcanvas_mouse_dragging_started(SimpleEvent&)
{
view3D->get_canvas3d()->reset_sequential_print_clearance();
}
// Update the scene from the background processing,
@ -6005,7 +6015,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
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; }
@ -7031,7 +7041,6 @@ void Plater::force_filament_colors_update()
if (extruders_filaments.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == extruders_filaments.size())
{
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
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; }
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...)
// 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);
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)
@ -1630,7 +1635,6 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field)
else if (is_single_volume_or_modifier()) {
if (!wxGetApp().obj_manipul()->is_world_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.translation() = Vec3d::Zero();
}

View File

@ -2983,7 +2983,7 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
update();
});
auto has_changes = [this, extruder_idx]() {
auto has_changes = [this]() {
auto dirty_options = m_presets->current_dirty_options(true);
#if 1
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;
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;
try {

View File

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