Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_new_camera_movements
This commit is contained in:
commit
2aa79ec483
@ -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")
|
||||
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_po_with_pot PRE_BUILD
|
||||
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
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
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
Binary file not shown.
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
@ -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,
|
||||
// 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);
|
||||
|
||||
std::vector<ExPolygons> brim_area_innermost(print.objects().size());
|
||||
ExPolygons brim_area;
|
||||
ExPolygons no_brim_area;
|
||||
Polygons holes;
|
||||
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);
|
||||
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)
|
||||
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 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());
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
return diff_ex(intersection_ex(to_polygons(std::move(brim_area)), holes), no_brim_area);
|
||||
// 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.
|
||||
@ -361,15 +467,26 @@ static void make_inner_brim(const Print &print,
|
||||
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()));
|
||||
std::vector<InnerBrimExPolygons> inner_brims_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) {
|
||||
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());
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -17,9 +17,11 @@ namespace GUI {
|
||||
|
||||
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user