Merge branch 'stable'

This commit is contained in:
Lukas Matena 2022-02-24 12:55:53 +01:00
commit e058f794d9
47 changed files with 18897 additions and 18251 deletions

View File

@ -523,21 +523,28 @@ add_custom_target(gettext_make_pot
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Generate pot file from strings in the source tree"
)
add_custom_target(gettext_merge_po_with_pot
add_custom_target(gettext_merge_community_po_with_pot
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Merge localization po with new generted pot file"
COMMENT "Merge community po with new generated pot file"
)
file(GLOB L10N_PO_FILES "${L10N_DIR}/*/PrusaSlicer*.po")
# list of names of directories, which are licalized by PS internally
list(APPEND PS_L10N_DIRS "cs" "de" "es" "fr" "it" "ja" "pl")
foreach(po_file ${L10N_PO_FILES})
#GET_FILENAME_COMPONENT(po_dir "${po_file}" DIRECTORY)
#SET(po_new_file "${po_dir}/PrusaSlicer_.po")
add_custom_command(
TARGET gettext_merge_po_with_pot PRE_BUILD
COMMAND msgmerge -N -o ${po_file} ${po_file} "${L10N_DIR}/PrusaSlicer.pot"
# delete obsolete lines from resulting PO to avoid conflicts after a merging of it with wxWidgets.po
COMMAND msgattrib --no-obsolete -o ${po_file} ${po_file}
DEPENDS ${po_file}
)
GET_FILENAME_COMPONENT(po_dir "${po_file}" DIRECTORY)
GET_FILENAME_COMPONENT(po_dir_name "${po_dir}" NAME)
list(FIND PS_L10N_DIRS ${po_dir_name} found_dir_id)
# found_dir_id==-1 means that po_dir_name wasn't found in PS_L10N_DIRS
if(found_dir_id LESS 0)
add_custom_command(
TARGET gettext_merge_community_po_with_pot PRE_BUILD
COMMAND msgmerge -N -o ${po_file} ${po_file} "${L10N_DIR}/PrusaSlicer.pot"
# delete obsolete lines from resulting PO to avoid conflicts after a merging of it with wxWidgets.po
COMMAND msgattrib --no-obsolete -o ${po_file} ${po_file}
DEPENDS ${po_file}
)
endif()
endforeach()
add_custom_target(gettext_concat_wx_po_with_po
@ -545,7 +552,6 @@ add_custom_target(gettext_concat_wx_po_with_po
COMMENT "Concatenate and merge wxWidgets localization po with PrusaSlicer po file"
)
file(GLOB L10N_PO_FILES "${L10N_DIR}/*/PrusaSlicer*.po")
file(GLOB L10N_WX_PO_FILES "${L10N_DIR}/*/PrusaSlicer*.po")
foreach(po_file ${L10N_PO_FILES})
GET_FILENAME_COMPONENT(po_dir "${po_file}" DIRECTORY)
GET_FILENAME_COMPONENT(po_dir_name "${po_dir}" NAME)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,10 @@
#include <algorithm>
#include <numeric>
#include <unordered_set>
#include <mutex>
#include <tbb/parallel_for.h>
#include <boost/thread/lock_guard.hpp>
#ifndef NDEBUG
// #define BRIM_DEBUG_TO_SVG
@ -200,20 +203,94 @@ static ExPolygons top_level_outer_brim_area(const Print &print
return diff_ex(brim_area, no_brim_area);
}
static ExPolygons inner_brim_area(const Print &print,
const ConstPrintObjectPtrs &top_level_objects_with_brim,
const std::vector<ExPolygons> &bottom_layers_expolygons,
const float no_brim_offset)
// Return vector of booleans indicated if polygons from bottom_layers_expolygons contain another polygon or not.
// Every ExPolygon is counted as several Polygons (contour and holes). Contour polygon is always processed before holes.
static std::vector<bool> has_polygons_nothing_inside(const Print &print, const std::vector<ExPolygons> &bottom_layers_expolygons)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
Polygons islands;
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx];
const Polygons islands_object = to_polygons(bottom_layers_expolygons[print_object_idx]);
islands.reserve(islands.size() + object->instances().size() * islands_object.size());
for (const PrintInstance &instance : object->instances())
append_and_translate(islands, islands_object, instance);
}
ClipperLib_Z::Paths islands_clip;
islands_clip.reserve(islands.size());
for (const Polygon &poly : islands) {
size_t island_idx = &poly - &islands.front();
ClipperLib_Z::Path island_clip;
for (const Point &pt : poly.points)
island_clip.emplace_back(pt.x(), pt.y(), island_idx + 1);
islands_clip.emplace_back(island_clip);
}
ClipperLib_Z::Clipper clipper;
// Always assign zero to detect cases when two polygons are overlapping.
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) {
pt.z() = 0;
});
clipper.AddPaths(islands_clip, ClipperLib_Z::ptSubject, true);
ClipperLib_Z::PolyTree islands_polytree;
clipper.Execute(ClipperLib_Z::ctUnion, islands_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
std::vector<bool> has_nothing_inside(islands.size());
std::function<void(const ClipperLib_Z::PolyNode&)> check_contours = [&check_contours, &has_nothing_inside](const ClipperLib_Z::PolyNode &parent_node)->void {
if (!parent_node.Childs.empty())
for(const ClipperLib_Z::PolyNode *child_node : parent_node.Childs)
check_contours(*child_node);
if (parent_node.Childs.empty() && !parent_node.Contour.empty() && parent_node.Contour.front().z() != 0) {
int polygon_idx = parent_node.Contour.front().z();
assert(polygon_idx > 0 && polygon_idx <= int(has_nothing_inside.size()));
// The whole contour must have the same ID. In other cases, some counters overlap.
for (const ClipperLib_Z::IntPoint &point : parent_node.Contour)
if (polygon_idx != point.z())
return;
has_nothing_inside[polygon_idx - 1] = true;
}
};
check_contours(islands_polytree);
return has_nothing_inside;
}
// INNERMOST means that ExPolygon doesn't contain any other ExPolygons.
// NORMAL is for other cases.
enum class InnerBrimType {NORMAL, INNERMOST};
struct InnerBrimExPolygons
{
ExPolygons brim_area;
InnerBrimType type = InnerBrimType::NORMAL;
double brim_width = 0.;
};
static std::vector<InnerBrimExPolygons> inner_brim_area(const Print &print,
const ConstPrintObjectPtrs &top_level_objects_with_brim,
const std::vector<ExPolygons> &bottom_layers_expolygons,
const float no_brim_offset)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
std::vector<bool> has_nothing_inside = has_polygons_nothing_inside(print, bottom_layers_expolygons);
std::unordered_set<size_t> top_level_objects_idx;
top_level_objects_idx.reserve(top_level_objects_with_brim.size());
for (const PrintObject *object : top_level_objects_with_brim)
top_level_objects_idx.insert(object->id().id);
ExPolygons brim_area;
ExPolygons no_brim_area;
Polygons holes;
std::vector<ExPolygons> brim_area_innermost(print.objects().size());
ExPolygons brim_area;
ExPolygons no_brim_area;
Polygons holes_reversed;
// polygon_idx must correspond to idx generated inside has_polygons_nothing_inside()
size_t polygon_idx = 0;
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx];
const BrimType brim_type = object->config().brim_type.value;
@ -221,9 +298,10 @@ static ExPolygons inner_brim_area(const Print &print,
const float brim_width = scale_(object->config().brim_width.value);
const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
ExPolygons brim_area_innermost_object;
ExPolygons brim_area_object;
ExPolygons no_brim_area_object;
Polygons holes_object;
Polygons holes_reversed_object;
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) {
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) {
if (top_outer_brim)
@ -235,8 +313,20 @@ static ExPolygons inner_brim_area(const Print &print,
// After 7ff76d07684858fd937ef2f5d863f105a10f798e offset and shrink don't work with CW polygons (holes), so let's make it CCW.
Polygons ex_poly_holes_reversed = ex_poly.holes;
polygons_reverse(ex_poly_holes_reversed);
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
append(brim_area_object, diff_ex(shrink_ex(ex_poly_holes_reversed, brim_separation, ClipperLib::jtSquare), shrink_ex(ex_poly_holes_reversed, brim_width + brim_separation, ClipperLib::jtSquare)));
for (const PrintInstance &instance : object->instances()) {
++polygon_idx; // Increase idx because of the contour of the ExPolygon.
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
for(const Polygon &hole : ex_poly_holes_reversed) {
size_t hole_idx = &hole - &ex_poly_holes_reversed.front();
if (has_nothing_inside[polygon_idx + hole_idx])
append(brim_area_innermost_object, shrink_ex({hole}, brim_separation, ClipperLib::jtSquare));
else
append(brim_area_object, diff_ex(shrink_ex({hole}, brim_separation, ClipperLib::jtSquare), shrink_ex({hole}, brim_width + brim_separation, ClipperLib::jtSquare)));
}
polygon_idx += ex_poly.holes.size(); // Increase idx for every hole of the ExPolygon.
}
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly_holes_reversed));
@ -244,18 +334,34 @@ static ExPolygons inner_brim_area(const Print &print,
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(ExPolygon(ex_poly.contour), shrink_ex(ex_poly_holes_reversed, no_brim_offset, ClipperLib::jtSquare)));
append(holes_object, ex_poly_holes_reversed);
append(holes_reversed_object, ex_poly_holes_reversed);
}
append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_separation, ClipperLib::jtSquare));
for (const PrintInstance &instance : object->instances()) {
append_and_translate(brim_area_innermost[print_object_idx], brim_area_innermost_object, instance);
append_and_translate(brim_area, brim_area_object, instance);
append_and_translate(no_brim_area, no_brim_area_object, instance);
append_and_translate(holes, holes_object, instance);
append_and_translate(holes_reversed, holes_reversed_object, instance);
}
}
assert(polygon_idx == has_nothing_inside.size());
return diff_ex(intersection_ex(to_polygons(std::move(brim_area)), holes), no_brim_area);
ExPolygons brim_area_innermost_merged;
// Append all innermost brim areas.
std::vector<InnerBrimExPolygons> brim_area_out;
for (size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx)
if (const double brim_width = print.objects()[print_object_idx]->config().brim_width.value; !brim_area_innermost[print_object_idx].empty()) {
append(brim_area_innermost_merged, brim_area_innermost[print_object_idx]);
brim_area_out.push_back({std::move(brim_area_innermost[print_object_idx]), InnerBrimType::INNERMOST, brim_width});
}
// Append all normal brim areas.
brim_area_out.push_back({diff_ex(intersection_ex(to_polygons(std::move(brim_area)), holes_reversed), no_brim_area), InnerBrimType::NORMAL});
// Cut out a huge brim areas that overflows into the INNERMOST holes.
brim_area_out.back().brim_area = diff_ex(brim_area_out.back().brim_area, brim_area_innermost_merged);
return brim_area_out;
}
// Flip orientation of open polylines to minimize travel distance.
@ -359,17 +465,28 @@ static void make_inner_brim(const Print &print,
ExtrusionEntityCollection &brim)
{
assert(print.objects().size() == bottom_layers_expolygons.size());
const auto scaled_resolution = scaled<double>(print.config().gcode_resolution.value);
Flow flow = print.brim_flow();
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
Polygons loops;
islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (size_t i = 0; !islands_ex.empty(); ++i) {
for (ExPolygon &poly_ex : islands_ex)
poly_ex.douglas_peucker(scaled_resolution);
polygons_append(loops, to_polygons(islands_ex));
islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), ClipperLib::jtSquare);
}
const auto scaled_resolution = scaled<double>(print.config().gcode_resolution.value);
Flow flow = print.brim_flow();
std::vector<InnerBrimExPolygons> inner_brims_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
Polygons loops;
std::mutex loops_mutex;
tbb::parallel_for(tbb::blocked_range<size_t>(0, inner_brims_ex.size()), [&inner_brims_ex, &flow, &scaled_resolution, &loops, &loops_mutex](const tbb::blocked_range<size_t> &range) {
for (size_t brim_idx = range.begin(); brim_idx < range.end(); ++brim_idx) {
const InnerBrimExPolygons &inner_brim_ex = inner_brims_ex[brim_idx];
auto num_loops = size_t(floor(inner_brim_ex.brim_width / flow.spacing()));
ExPolygons islands_ex = offset_ex(inner_brim_ex.brim_area, -0.5f * float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (size_t i = 0; (inner_brim_ex.type == InnerBrimType::INNERMOST ? i < num_loops : !islands_ex.empty()); ++i) {
for (ExPolygon &poly_ex : islands_ex)
poly_ex.douglas_peucker(scaled_resolution);
{
boost::lock_guard<std::mutex> lock(loops_mutex);
polygons_append(loops, to_polygons(islands_ex));
}
islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), ClipperLib::jtSquare);
}
}
}); // end of parallel_for
loops = union_pt_chained_outside_in(loops);
std::reverse(loops.begin(), loops.end());

View File

@ -1047,7 +1047,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
if (! print.config().gcode_substitutions.values.empty()) {
m_find_replace = make_unique<GCodeFindReplace>(print.config());
file.set_find_replace(m_find_replace.get());
file.set_find_replace(m_find_replace.get(), false);
}
// resets analyzer's tracking data
@ -1155,6 +1155,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
print.throw_if_canceled();
// Starting now, the G-code find / replace post-processor will be enabled.
file.find_replace_enable();
// adds tags for time estimators
if (print.config().remaining_times.value)
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
@ -1461,6 +1464,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
// From now to the end of G-code, the G-code find / replace post-processor will be disabled.
// Thus the PrusaSlicer generated config will NOT be processed by the G-code post-processor, see GH issue #7952.
file.find_replace_supress();
// Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
{
@ -1518,7 +1525,7 @@ void GCode::process_layers(
);
// The pipeline elements are joined using const references, thus no copying is performed.
output_stream.set_find_replace(nullptr);
output_stream.find_replace_supress();
if (m_spiral_vase && m_find_replace)
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output);
else if (m_spiral_vase)
@ -1527,7 +1534,7 @@ void GCode::process_layers(
tbb::parallel_pipeline(12, generator & cooling & find_replace & output);
else
tbb::parallel_pipeline(12, generator & cooling & output);
output_stream.set_find_replace(m_find_replace.get());
output_stream.find_replace_enable();
}
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
@ -1571,7 +1578,7 @@ void GCode::process_layers(
);
// The pipeline elements are joined using const references, thus no copying is performed.
output_stream.set_find_replace(nullptr);
output_stream.find_replace_supress();
if (m_spiral_vase && m_find_replace)
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output);
else if (m_spiral_vase)
@ -1580,7 +1587,7 @@ void GCode::process_layers(
tbb::parallel_pipeline(12, generator & cooling & find_replace & output);
else
tbb::parallel_pipeline(12, generator & cooling & output);
output_stream.set_find_replace(m_find_replace.get());
output_stream.find_replace_enable();
}
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)

View File

@ -196,7 +196,9 @@ private:
// Set a find-replace post-processor to modify the G-code before GCodePostProcessor.
// It is being set to null inside process_layers(), because the find-replace process
// is being called on a secondary thread to improve performance.
void set_find_replace(GCodeFindReplace *find_replace) { m_find_replace = find_replace; }
void set_find_replace(GCodeFindReplace *find_replace, bool enabled) { m_find_replace_backup = find_replace; m_find_replace = enabled ? find_replace : nullptr; }
void find_replace_enable() { m_find_replace = m_find_replace_backup; }
void find_replace_supress() { m_find_replace = nullptr; }
bool is_open() const { return f; }
bool is_error() const;
@ -220,6 +222,8 @@ private:
FILE *f { nullptr };
// Find-replace post-processor to be called before GCodePostProcessor.
GCodeFindReplace *m_find_replace { nullptr };
// If suppressed, the backoup holds m_find_replace.
GCodeFindReplace *m_find_replace_backup { nullptr };
GCodeProcessor &m_processor;
};
void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb);

View File

@ -605,7 +605,7 @@ struct MMU_Graph
if (arcs[arc_idx].to_idx == to_idx)
return;
for (const size_t &arc_idx : this->nodes[to_idx].arc_idxs)
if (arcs[arc_idx].to_idx == to_idx)
if (arcs[arc_idx].to_idx == from_idx)
return;
this->nodes[from_idx].arc_idxs.push_back(this->arcs.size());
@ -1201,7 +1201,7 @@ static inline double compute_edge_length(const MMU_Graph &graph, const size_t st
used_arcs[start_arc_idx] = true;
const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx];
size_t idx = start_idx;
double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();;
double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();
while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) {
bool found = false;
for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) {
@ -1711,7 +1711,7 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
// Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices.
// This consequently leads to issues with the extraction of colored segments by function extract_colored_segments.
// Calling expolygons_simplify fixed these issues.
input_expolygons[layer_idx] = smooth_outward(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), 10 * coord_t(SCALED_EPSILON));
input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled<coord_t>(0.01), PI/6);
#ifdef MMU_SEGMENTATION_DEBUG_INPUT
{

View File

@ -37,6 +37,36 @@ void remove_duplicates(MutablePolygon &polygon, double eps)
}
}
// Remove nearly duplicate points. If a distance between two points is less than scaled_eps
// and if the angle between its surrounding lines is less than max_angle, the point will be removed.
// May reduce the polygon down to empty polygon.
void remove_duplicates(MutablePolygon &polygon, coord_t scaled_eps, const double max_angle)
{
if (polygon.size() >= 3) {
auto cos_max_angle_2 = Slic3r::sqr<double>(cos(max_angle));
auto scaled_eps_sqr = Slic3r::sqr<int64_t>(scaled_eps);
auto begin = polygon.begin();
auto it = begin;
for (++it; it != begin;) {
auto prev = it.prev();
auto next = it.next();
Vec2i64 v1 = (*it - *prev).cast<int64_t>();
int64_t v1_sqr_norm = v1.squaredNorm();
if (v1_sqr_norm < scaled_eps_sqr) {
if (Vec2i64 v2 = (*next - *prev).cast<int64_t>();
Slic3r::sqr<double>(double(v1.dot(v2))) > cos_max_angle_2 * double(v1_sqr_norm) * double(v2.squaredNorm())) {
it = it.remove();
continue;
}
}
it = next;
}
}
if (polygon.size() < 3)
polygon.clear();
}
// Adapted from Cura ConstPolygonRef::smooth_corner_complex() by Tim Kuipers.
// A concave corner at it1 with position p1 has been removed by the caller between it0 and it2, where |p2 - p0| < shortcut_length.
// Now try to close a concave crack by walking left from it0 and right from it2 as long as the new clipping edge is smaller than shortcut_length

View File

@ -309,6 +309,28 @@ inline bool operator!=(const MutablePolygon &p1, const MutablePolygon &p2) { ret
void remove_duplicates(MutablePolygon &polygon);
void remove_duplicates(MutablePolygon &polygon, double eps);
// Remove nearly duplicate points. If a distance between two points is less than scaled_eps
// and if the angle between its surrounding lines is less than max_angle, the point will be removed.
// May reduce the polygon down to empty polygon.
void remove_duplicates(MutablePolygon &polygon, coord_t scaled_eps, const double max_angle);
inline ExPolygons remove_duplicates(ExPolygons expolygons, coord_t scaled_eps, double max_angle)
{
MutablePolygon mp;
for (ExPolygon &expolygon : expolygons) {
mp.assign(expolygon.contour, expolygon.contour.size() * 2);
remove_duplicates(mp, scaled_eps, max_angle);
mp.polygon(expolygon.contour);
for (Polygon &hole : expolygon.holes) {
mp.assign(hole, hole.size() * 2);
remove_duplicates(mp, scaled_eps, max_angle);
mp.polygon(hole);
}
expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end());
}
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end());
return expolygons;
}
void smooth_outward(MutablePolygon &polygon, coord_t clip_dist_scaled);
inline Polygon smooth_outward(Polygon polygon, coord_t clip_dist_scaled)

View File

@ -412,6 +412,8 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
for (auto it = this->renamed_from.begin(); ! is_visible && it != this->renamed_from.end(); ++ it)
is_visible = has(*it);
}
else
is_visible = false;
}
}
@ -791,7 +793,8 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
// The source config may contain keys from many possible preset types. Just copy those that relate to this preset.
this->get_edited_preset().config.apply_only(combined_config, keys, true);
this->update_dirty();
update_saved_preset_from_current_preset();
// Don't save the newly loaded project as a "saved into project" state.
//update_saved_preset_from_current_preset();
assert(this->get_edited_preset().is_dirty);
return std::make_pair(&(*it), this->get_edited_preset().is_dirty);
}
@ -1226,7 +1229,6 @@ Preset& PresetCollection::select_preset(size_t idx)
idx = first_visible_idx();
m_idx_selected = idx;
m_edited_preset = m_presets[idx];
update_saved_preset_from_current_preset();
bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets;
for (size_t i = 0; i < m_num_default_presets; ++i)
m_presets[i].is_visible = default_visible;

View File

@ -397,6 +397,7 @@ public:
void discard_current_changes() {
m_presets[m_idx_selected].reset_dirty();
m_edited_preset = m_presets[m_idx_selected];
// Don't save the resetted preset state as a "saved into project" state.
// update_saved_preset_from_current_preset();
}

View File

@ -660,7 +660,8 @@ std::string Print::validate(std::string* warning) const
bool layer_gcode_resets_extruder = boost::regex_search(m_config.layer_gcode.value, regex_g92e0);
if (m_config.use_relative_e_distances) {
// See GH issues #6336 #5073
if (! before_layer_gcode_resets_extruder && ! layer_gcode_resets_extruder)
if ((m_config.gcode_flavor == gcfMarlinLegacy || m_config.gcode_flavor == gcfMarlinFirmware) &&
! before_layer_gcode_resets_extruder && ! layer_gcode_resets_extruder)
return L("Relative extruder addressing requires resetting the extruder position at each layer to prevent loss of floating point accuracy. Add \"G92 E0\" to layer_gcode.");
} else if (before_layer_gcode_resets_extruder)
return L("\"G92 E0\" was found in before_layer_gcode, which is incompatible with absolute extruder addressing.");

View File

@ -1285,8 +1285,13 @@ bool GUI_App::on_init_inner()
else
load_current_presets();
// Save the active profiles as a "saved into project".
update_saved_preset_from_current_preset();
if (plater_ != nullptr) {
// Save the names of active presets and project specific config into ProjectDirtyStateManager.
plater_->reset_project_dirty_initial_presets();
// Update Project dirty state, update application title bar.
plater_->update_project_dirty_from_presets();
}
@ -2447,16 +2452,13 @@ void GUI_App::update_saved_preset_from_current_preset()
}
}
std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets() const
std::vector<const PresetCollection*> GUI_App::get_active_preset_collections() const
{
std::vector<std::pair<unsigned int, std::string>> ret;
std::vector<const PresetCollection*> ret;
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
for (Tab* tab : tabs_list) {
if (tab->supports_printer_technology(printer_technology)) {
const PresetCollection* presets = tab->get_presets();
ret.push_back({ static_cast<unsigned int>(presets->type()), presets->get_selected_preset_name() });
}
}
for (const Tab* tab : tabs_list)
if (tab->supports_printer_technology(printer_technology))
ret.push_back(tab->get_presets());
return ret;
}

View File

@ -252,7 +252,7 @@ public:
bool has_unsaved_preset_changes() const;
bool has_current_preset_changes() const;
void update_saved_preset_from_current_preset();
std::vector<std::pair<unsigned int, std::string>> get_selected_presets() const;
std::vector<const PresetCollection*> get_active_preset_collections() const;
bool check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice = true, bool use_dont_save_insted_of_discard = false);
void apply_keeped_preset_modifications();
bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr);

View File

@ -141,6 +141,8 @@ bool ObjectSettings::update_settings_list()
{
Option option = optgroup->get_option(opt);
option.opt.width = 12;
if (!option.opt.full_label.empty())
option.opt.label = option.opt.full_label;
if (is_extruders_cat)
option.opt.max = wxGetApp().extruders_edited_cnt();
optgroup->append_single_option_line(option);

View File

@ -378,6 +378,8 @@ void GLGizmoCut::update_contours()
MeshSlicingParams slicing_params;
slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix();
slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z()));
const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params);
if (!polys.empty()) {
m_cut_contours.contours.init_from(polys, static_cast<float>(m_cut_z));

View File

@ -859,7 +859,10 @@ bool MainFrame::save_project_as(const wxString& filename)
{
bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false;
if (ret) {
// wxGetApp().update_saved_preset_from_current_preset();
// Make a copy of the active presets for detecting changes in preset values.
wxGetApp().update_saved_preset_from_current_preset();
// Save the names of active presets and project specific config into ProjectDirtyStateManager.
// Reset ProjectDirtyStateManager's state as saved, mark active UndoRedo step as saved with project.
m_plater->reset_project_dirty_after_save();
}
return ret;

View File

@ -311,7 +311,11 @@ wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/)
overwrite = true;
if (newline)
break;
} else if (in[j] == '/') {
} else if (in[j] == '/'
#ifdef _WIN32
|| in[j] == '\\'
#endif // _WIN32
) {
// Insert after the slash.
ibreak = ++ j;
overwrite = false;

View File

@ -499,6 +499,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
std::vector<float> extruders = dlg.get_extruders();
(project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
(project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
// Update Project dirty state, update application title bar.
wxGetApp().plater()->update_project_dirty_from_presets();
wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent));
}
@ -2351,7 +2352,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
const auto loading = _L("Loading") + dots;
wxProgressDialog dlg(loading, "", 100, find_toplevel_parent(q), wxPD_AUTO_HIDE);
wxProgressDialog progress_dlg(loading, "", 100, find_toplevel_parent(q), wxPD_AUTO_HIDE);
wxBusyCursor busy;
auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
@ -2360,7 +2361,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
int answer_convert_from_meters = wxOK_DEFAULT;
int answer_convert_from_imperial_units = wxOK_DEFAULT;
for (size_t i = 0; i < input_files.size(); ++i) {
size_t input_files_size = input_files.size();
for (size_t i = 0; i < input_files_size; ++i) {
#ifdef _WIN32
auto path = input_files[i];
// On Windows, we swap slashes to back slashes, see GH #6803 as read_from_file() does not understand slashes on Windows thus it assignes full path to names of loaded objects.
@ -2370,8 +2372,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
const auto &path = input_files[i];
#endif // _WIN32
const auto filename = path.filename();
dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), _L("Loading file") + ": " + from_path(filename));
dlg.Fit();
progress_dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), _L("Loading file") + ": " + from_path(filename));
progress_dlg.Fit();
const bool type_3mf = std::regex_match(path.string(), pattern_3mf);
const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf);
@ -2382,17 +2384,28 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
bool is_project_file = type_prusa;
try {
if (type_3mf || type_zip_amf) {
#ifdef __linux__
// On Linux Constructor of the ProgressDialog calls DisableOtherWindows() function which causes a disabling of all children of the find_toplevel_parent(q)
// And a destructor of the ProgressDialog calls ReenableOtherWindows() function which revert previously disabled children.
// But if printer technology will be changes during project loading,
// then related SLA Print and Materials Settings or FFF Print and Filaments Settings will be unparent from the wxNoteBook
// and that is why they will never be enabled after destruction of the ProgressDialog.
// So, distroy progress_gialog if we are loading project file
if (input_files_size == 1)
progress_dlg.Destroy();
#endif
DynamicPrintConfig config;
PrinterTechnology loaded_printer_technology {ptFFF};
{
DynamicPrintConfig config_loaded;
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion));
if (load_config && !config_loaded.empty()) {
// Based on the printer technology field found in the loaded config, select the base for the config,
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
loaded_printer_technology = Preset::printer_technology(config_loaded);
// We can't to load SLA project if there is at least one multi-part object on the bed
if (printer_technology == ptSLA) {
if (loaded_printer_technology == ptSLA) {
const ModelObjectPtrs& objects = q->model().objects;
for (auto object : objects)
if (object->volumes.size() > 1) {
@ -2404,7 +2417,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
}
config.apply(printer_technology == ptFFF ?
config.apply(loaded_printer_technology == ptFFF ?
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
// and place the loaded config over the base.
@ -2435,7 +2448,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
};
std::vector<std::string> names;
if (printer_technology == ptFFF) {
if (loaded_printer_technology == ptFFF) {
update_selected_preset_visibility(preset_bundle->prints, names);
for (const std::string& filament : preset_bundle->filament_presets) {
Preset* preset = preset_bundle->filaments.find_preset(filament);
@ -2466,7 +2479,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
}
if (printer_technology == ptFFF)
if (loaded_printer_technology == ptFFF)
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &preset_bundle->project_config);
// For exporting from the amf/3mf we shouldn't check printer_presets for the containing information about "Print Host upload"
@ -5257,8 +5270,11 @@ void Plater::new_project()
take_snapshot(_L("New Project"), UndoRedo::SnapshotType::ProjectSeparator);
Plater::SuppressSnapshots suppress(this);
reset();
// Save the names of active presets and project specific config into ProjectDirtyStateManager.
reset_project_dirty_initial_presets();
// Make a copy of the active presets for detecting changes in preset values.
wxGetApp().update_saved_preset_from_current_preset();
// Update Project dirty state, update application title bar.
update_project_dirty_from_presets();
}
@ -5287,7 +5303,11 @@ void Plater::load_project(const wxString& filename)
if (! load_files({ into_path(filename) }).empty()) {
// At least one file was loaded.
p->set_project_filename(filename);
// Save the names of active presets and project specific config into ProjectDirtyStateManager.
reset_project_dirty_initial_presets();
// Make a copy of the active presets for detecting changes in preset values.
wxGetApp().update_saved_preset_from_current_preset();
// Update Project dirty state, update application title bar.
update_project_dirty_from_presets();
}
}
@ -5972,7 +5992,6 @@ void Plater::export_stl_obj(bool extended, bool selection_only)
}
else if (0 <= instance_id && instance_id < int(mo.instances.size()))
mesh.transform(mo.instances[instance_id]->get_matrix(), true);
return mesh;
};

View File

@ -17,9 +17,11 @@ namespace GUI {
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
{
m_plater_dirty = dirty;
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title();
if (m_plater_dirty != dirty) {
m_plater_dirty = dirty;
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title();
}
}
void ProjectDirtyStateManager::update_from_presets()
@ -27,11 +29,11 @@ void ProjectDirtyStateManager::update_from_presets()
m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new
GUI_App &app = wxGetApp();
if (!app.plater()->get_project_filename().IsEmpty()) {
for (const auto& [type, name] : app.get_selected_presets())
m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
bool has_project = ! app.plater()->get_project_filename().IsEmpty();
for (const PresetCollection *preset_collection : app.get_active_preset_collections()) {
auto type = preset_collection->type();
m_presets_dirty |= (has_project && !m_initial_presets[type].empty() && m_initial_presets[type] != preset_collection->get_selected_preset_name()) || preset_collection->saved_is_dirty();
}
m_presets_dirty |= app.has_unsaved_preset_changes();
m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config;
app.mainframe->update_title();
}
@ -49,8 +51,8 @@ void ProjectDirtyStateManager::reset_initial_presets()
{
m_initial_presets.fill(std::string{});
GUI_App &app = wxGetApp();
for (const auto& [type, name] : app.get_selected_presets())
m_initial_presets[type] = name;
for (const PresetCollection *preset_collection : app.get_active_preset_collections())
m_initial_presets[preset_collection->type()] = preset_collection->get_selected_preset_name();
m_initial_project_config = app.preset_bundle->project_config;
}

View File

@ -1247,6 +1247,7 @@ void Tab::on_presets_changed()
// to avoid needless preset loading from update() function
m_dependent_tabs.clear();
// Update Project dirty state, update application title bar.
wxGetApp().plater()->update_project_dirty_from_presets();
}
@ -4113,7 +4114,7 @@ wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent)
create_btn(&m_del_all_substitutions_btn, _L("Delete all"), "cross");
m_del_all_substitutions_btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) {
if (MessageDialog(parent, _L("Are you sure you want to delete all substitutions?"), SLIC3R_APP_NAME, wxYES_NO | wxICON_QUESTION).
if (MessageDialog(parent, _L("Are you sure you want to delete all substitutions?"), SLIC3R_APP_NAME, wxYES_NO | wxCANCEL | wxICON_QUESTION).
ShowModal() != wxID_YES)
return;
m_subst_manager.delete_all();

View File

@ -371,6 +371,7 @@ public:
DynamicPrintConfig* get_config() { return m_config; }
PresetCollection* get_presets() { return m_presets; }
const PresetCollection* get_presets() const { return m_presets; }
void on_value_change(const std::string& opt_key, const boost::any& value);

View File

@ -1,3 +1,18 @@
#include <catch_main.hpp>
#include "libslic3r/libslic3r.h"
// __has_feature() is used later for Clang, this is for compatibility with other compilers (such as GCC and MSVC)
#ifndef __has_feature
# define __has_feature(x) 0
#endif
// Print reports about memory leaks but exit with zero exit code when any memory leaks is found to make unit tests pass.
// After merging the stable branch (2.4.1) with the master branch, this should be deleted.
#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
extern "C" {
const char *__lsan_default_options() {
return "exitcode=0";
}
}
#endif