diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 4078af3df9..7189458286 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -129,19 +129,6 @@ else() include("deps-linux.cmake") endif() -# Patch the boost::polygon library with a custom one. -ExternalProject_Add(dep_boost_polygon - EXCLUDE_FROM_ALL ON - GIT_REPOSITORY "https://github.com/prusa3d/polygon" - GIT_TAG prusaslicer_gmp - DEPENDS dep_boost - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_BINARY_DIR}/dep_boost_polygon-prefix/src/dep_boost_polygon/include/boost/polygon" - "${DESTDIR}/usr/local/include/boost/polygon" -) - set(ZLIB_PKG "") if (NOT ZLIB_FOUND) include(ZLIB/ZLIB.cmake) @@ -170,50 +157,49 @@ if (NOT "${ZLIB_PKG}" STREQUAL "") add_dependencies(dep_openexr ${ZLIB_PKG}) endif () +set(_dep_list + dep_boost + dep_tbb + dep_libcurl + dep_wxWidgets + dep_gtest + dep_cereal + dep_nlopt + dep_openvdb + dep_OpenCSG + dep_CGAL + ${PNG_PKG} + ${ZLIB_PKG} + ${EXPAT_PKG} + ) + +if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + # Patch the boost::polygon library with a custom one. + ExternalProject_Add(dep_boost_polygon + EXCLUDE_FROM_ALL ON + GIT_REPOSITORY "https://github.com/prusa3d/polygon" + GIT_TAG prusaslicer_gmp + DEPENDS dep_boost + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_BINARY_DIR}/dep_boost_polygon-prefix/src/dep_boost_polygon/include/boost/polygon" + "${DESTDIR}/usr/local/include/boost/polygon" + ) + # Only override boost::Polygon Voronoi implementation with Vojtech's GMP hacks on 64bit platforms. + list(APPEND _dep_list "dep_boost_polygon") +endif () + if (MSVC) - - add_custom_target(deps ALL - DEPENDS - dep_boost - dep_boost_polygon - dep_tbb - dep_libcurl - dep_wxWidgets - dep_gtest - dep_cereal - dep_nlopt - # dep_qhull # Experimental - dep_openvdb - dep_OpenCSG - dep_CGAL - ${PNG_PKG} - ${ZLIB_PKG} - ${EXPAT_PKG} - ) - + # Experimental + #list(APPEND _dep_list "dep_qhull") else() - - add_custom_target(deps ALL - DEPENDS - dep_boost - dep_boost_polygon - dep_tbb - dep_libcurl - dep_wxWidgets - dep_gtest - dep_cereal - dep_nlopt - dep_qhull - dep_openvdb - dep_OpenCSG - dep_CGAL - ${PNG_PKG} - ${ZLIB_PKG} - ${EXPAT_PKG} - # dep_libigl # Not working, static build has different Eigen - ) - + list(APPEND _dep_list "dep_qhull") + # Not working, static build has different Eigen + #list(APPEND _dep_list "dep_libigl") endif() +add_custom_target(deps ALL DEPENDS ${_dep_list}) + # Note: I'm not using any of the LOG_xxx options in ExternalProject_Add() commands # because they seem to generate bogus build files (possibly a bug in ExternalProject). diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index ef00b80d50..a71a0ebfc0 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -9,6 +9,8 @@ set(DEP_CMAKE_OPTS "-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEP_OSX_TARGET}" "-DCMAKE_CXX_FLAGS=${DEP_WERRORS_SDK}" "-DCMAKE_C_FLAGS=${DEP_WERRORS_SDK}" + "-DCMAKE_FIND_FRAMEWORK=LAST" + "-DCMAKE_FIND_APPBUNDLE=LAST" ) include("deps-unix-common.cmake") diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index ce7c960fa3..bf8907c3fe 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -53,7 +53,7 @@ void BridgeDetector::initialize() this->_edges = intersection_pl(to_polylines(grown), contours); #ifdef SLIC3R_DEBUG - printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); + printf(" bridge has %zu support(s)\n", this->_edges.size()); #endif // detect anchors as intersection between our bridge expolygon and the lower slices diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1605c52cde..cfbd4f0414 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -194,6 +194,7 @@ add_library(libslic3r STATIC MTUtils.hpp VoronoiOffset.cpp VoronoiOffset.hpp + VoronoiVisualUtils.hpp Zipper.hpp Zipper.cpp MinAreaBoundingBox.hpp diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 2409510538..486a7b1aa8 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -1586,12 +1586,17 @@ std::vector> ++ cnt; } } - len /= double(cnt); - bbox.offset(20); - EdgeGrid::Grid grid; - grid.set_bbox(bbox); - grid.create(polygons, len); - return grid.intersecting_edges(); + + std::vector> out; + if (cnt > 0) { + len /= double(cnt); + bbox.offset(20); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(polygons, len); + out = grid.intersecting_edges(); + } + return out; } // Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 4a89660447..daaab47555 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -404,7 +404,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const { TPPLPoly p; p.Init(int(ex->contour.points.size())); - //printf(PRINTF_ZU "\n0\n", ex->contour.points.size()); + //printf("%zu\n0\n", ex->contour.points.size()); for (const Point &point : ex->contour.points) { size_t i = &point - &ex->contour.points.front(); p[i].x = point(0); @@ -419,7 +419,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) { TPPLPoly p; p.Init(hole->points.size()); - //printf(PRINTF_ZU "\n1\n", hole->points.size()); + //printf("%zu\n1\n", hole->points.size()); for (const Point &point : hole->points) { size_t i = &point - &hole->points.front(); p[i].x = point(0); diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index c482f7edba..69b3a6455d 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -312,8 +312,8 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) case erOverhangPerimeter : return L("Overhang perimeter"); case erInternalInfill : return L("Internal infill"); case erSolidInfill : return L("Solid infill"); - case erIroning : return L("Ironing"); case erTopSolidInfill : return L("Top solid infill"); + case erIroning : return L("Ironing"); case erBridgeInfill : return L("Bridge infill"); case erGapFill : return L("Gap fill"); case erSkirt : return L("Skirt"); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 657e9ec983..edf55ba37e 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -4,6 +4,7 @@ #include "../GCode.hpp" #include "../Geometry.hpp" #include "../GCode/ThumbnailData.hpp" +#include "../Time.hpp" #include "../I18N.hpp" @@ -1991,7 +1992,7 @@ namespace Slic3r { bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); bool _add_relationships_file_to_archive(mz_zip_archive& archive); - bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data); + bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); @@ -2054,7 +2055,7 @@ namespace Slic3r { // Adds model file ("3D/3dmodel.model"). // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. IdToObjectDataMap objects_data; - if (!_add_model_file_to_archive(archive, model, objects_data)) + if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { close_zip_writer(&archive); boost::filesystem::remove(filename); @@ -2203,7 +2204,7 @@ namespace Slic3r { return true; } - bool _3MF_Exporter::_add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data) + bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) { std::stringstream stream; // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 @@ -2214,6 +2215,19 @@ namespace Slic3r { stream << "\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; + std::string name = boost::filesystem::path(filename).stem().string(); + stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; + std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); + // keep only the date part of the string + date = date.substr(0, 10); + stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; stream << " <" << RESOURCES_TAG << ">\n"; // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 41025f043b..af7b9b1b60 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -1218,7 +1218,7 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, for (ModelInstance *instance : object->instances) { char buf[512]; sprintf(buf, - " \n" + " \n" " %lf\n" " %lf\n" " %lf\n" diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9a0c9eaa7d..35dc5a53bd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -617,6 +617,15 @@ std::vector GCode::collect_layers_to_print(const PrintObjec layers_to_print.emplace_back(layer_to_print); + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); + + // Check that there are extrusions on the very first layer. + if (layers_to_print.size() == 1u) { + if (! has_extrusions) + throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); + } + // In case there are extrusions on this layer, check there is a layer to lay it on. if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. @@ -630,18 +639,18 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Negative support_contact_z is not taken into account, it can result in false positives in cases // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) - // Only check this layer in case it has some extrusions. - bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - - if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) - throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { + const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + _(L("Empty layers detected, the output would not be printable.")) + "\n\n" + _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " "usually caused by negligibly small extrusions or by a faulty model. Try to repair " "the model or change its orientation on the bed."))); + } + // Remember last layer with extrusions. - last_extrusion_layer = &layers_to_print.back(); + if (has_extrusions) + last_extrusion_layer = &layers_to_print.back(); } } @@ -1939,7 +1948,6 @@ void GCode::process_layer( const size_t single_object_instance_idx) { assert(! layers.empty()); -// assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp index 0988091ce3..9567e07d28 100644 --- a/src/libslic3r/GCodeSender.cpp +++ b/src/libslic3r/GCodeSender.cpp @@ -393,7 +393,7 @@ GCodeSender::on_read(const boost::system::error_code& error, } this->send(); } else { - printf("Cannot resend " PRINTF_ZU " (oldest we have is " PRINTF_ZU ")\n", toresend, this->sent - this->last_sent.size()); + printf("Cannot resend %zu (oldest we have is %zu)\n", toresend, this->sent - this->last_sent.size()); } } else if (boost::starts_with(line, "wait")) { // ignore diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index b9e4d6e78c..00a4ad47c3 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -471,7 +471,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) - throw std::invalid_argument(PRINTF_ZU " parts won't fit in your print area!\n", num_parts); + throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 87fb0c9c72..75f3708d25 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -115,32 +115,94 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const return true; } - -inline int segments_could_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ - Vec2i64 iv = (ip2 - ip1).cast(); - Vec2i64 vij1 = (jp1 - ip1).cast(); - Vec2i64 vij2 = (jp2 - ip1).cast(); - int64_t tij1 = cross2(iv, vij1); - int64_t tij2 = cross2(iv, vij2); - int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum - int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); - return sij1 * sij2; -} - inline bool segments_intersect( const Slic3r::Point &ip1, const Slic3r::Point &ip2, const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + assert(ip1 != ip2); + assert(jp1 != jp2); + + auto segments_could_intersect = []( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) -> std::pair + { + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + return std::make_pair( + // signum + (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0), + (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0)); + }; + + std::pair sign1 = segments_could_intersect(ip1, ip2, jp1, jp2); + std::pair sign2 = segments_could_intersect(jp1, jp2, ip1, ip2); + int test1 = sign1.first * sign1.second; + int test2 = sign2.first * sign2.second; + if (test1 <= 0 && test2 <= 0) { + // The segments possibly intersect. They may also be collinear, but not intersect. + if (test1 != 0 || test2 != 0) + // Certainly not collinear, then the segments intersect. + return true; + // If the first segment is collinear with the other, the other is collinear with the first segment. + assert((sign1.first == 0 && sign1.second == 0) == (sign2.first == 0 && sign2.second == 0)); + if (sign1.first == 0 && sign1.second == 0) { + // The segments are certainly collinear. Now verify whether they overlap. + Slic3r::Point vi = ip2 - ip1; + // Project both on the longer coordinate of vi. + int axis = std::abs(vi.x()) > std::abs(vi.y()) ? 0 : 1; + coord_t i = ip1(axis); + coord_t j = ip2(axis); + coord_t k = jp1(axis); + coord_t l = jp2(axis); + if (i > j) + std::swap(i, j); + if (k > l) + std::swap(k, l); + return (k >= i && k <= j) || (i >= k && i <= l); + } + } + return false; +} + +template inline T foot_pt(const T &line_pt, const T &line_dir, const T &pt) { - return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && - segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; + T v = pt - line_pt; + auto l2 = line_dir.squaredNorm(); + auto t = (l2 == 0) ? 0 : v.dot(line_dir) / l2; + return line_pt + line_dir * t; +} + +inline Vec2d foot_pt(const Line &iline, const Point &ipt) +{ + return foot_pt(iline.a.cast(), (iline.b - iline.a).cast(), ipt.cast()); +} + +template inline auto ray_point_distance_squared(const T &ray_pt, const T &ray_dir, const T &pt) +{ + return (foot_pt(ray_pt, ray_dir, pt) - pt).squaredNorm(); +} + +template inline auto ray_point_distance(const T &ray_pt, const T &ray_dir, const T &pt) +{ + return (foot_pt(ray_pt, ray_dir, pt) - pt).norm(); +} + +inline double ray_point_distance_squared(const Line &iline, const Point &ipt) +{ + return (foot_pt(iline, ipt) - ipt.cast()).squaredNorm(); +} + +inline double ray_point_distance(const Line &iline, const Point &ipt) +{ + return (foot_pt(iline, ipt) - ipt.cast()).norm(); } // Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html template -bool liang_barsky_line_clipping( +inline bool liang_barsky_line_clipping( // Start and end points of the source line, result will be stored there as well. Eigen::Matrix &x0, Eigen::Matrix &x1, diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 19907d6de7..5fda69f779 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -264,7 +264,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); + printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); if (bd.detect_angle(custom_angle)) { diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 429cde3727..974f585dc2 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -135,4 +135,4 @@ BoundingBox get_extents(const Lines &lines) return bbox; } -} +} // namespace Slic3r diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index f255bee93e..caab809f5c 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -103,7 +103,7 @@ public: Vec3d b; }; -extern BoundingBox get_extents(const Lines &lines); +BoundingBox get_extents(const Lines &lines); } // namespace Slic3r @@ -125,4 +125,4 @@ namespace boost { namespace polygon { } } // end Boost -#endif +#endif // slic3r_Line_hpp_ diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index c6678e2d83..ab7c171e3c 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -153,9 +153,11 @@ inline Lines to_lines(const Polygon &poly) { Lines lines; lines.reserve(poly.points.size()); - for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) - lines.push_back(Line(*it, *(it + 1))); - lines.push_back(Line(poly.points.back(), poly.points.front())); + if (poly.points.size() > 2) { + for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) + lines.push_back(Line(*it, *(it + 1))); + lines.push_back(Line(poly.points.back(), poly.points.front())); + } return lines; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9da85bfba5..bf541e1222 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -395,6 +395,13 @@ public: const PrintObjectPtrs& objects() const { return m_objects; } PrintObject* get_object(size_t idx) { return m_objects[idx]; } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } + // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects + // in the notification center. + const PrintObject* get_object(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const PrintObject *obj) { return *static_cast(obj) == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const PrintRegionPtrs& regions() const { return m_regions; } // How many of PrintObject::copies() over all print objects are there? // If zero, then the print is empty and the print shall not be executed. diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 81affe04d4..14339f3c62 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -88,6 +88,14 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin return path; } +void PrintBase::status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message) +{ + if (this->m_status_callback) + m_status_callback(SlicingStatus(*this, step)); + else if (! message.empty()) + printf("%s warning: %s\n", (object_id == ObjectID(*this)) ? "print" : "print object", message.c_str()); +} + tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print) { return print->state_mutex(); @@ -98,4 +106,9 @@ std::function PrintObjectBase::cancel_callback(PrintBase *print) return print->cancel_callback(); } +void PrintObjectBase::status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message) +{ + print->status_update_warnings(*this, step, warning_level, message); +} + } // namespace Slic3r diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 05d884cc88..d7f3483e87 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -13,6 +13,7 @@ #endif #include "tbb/mutex.h" +#include "ObjectID.hpp" #include "Model.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" @@ -32,6 +33,11 @@ public: DONE, }; + enum class WarningLevel { + NON_CRITICAL, + CRITICAL + }; + typedef size_t TimeStamp; // A new unique timestamp is being assigned to the step every time the step changes its state. @@ -42,6 +48,28 @@ public: TimeStamp timestamp; }; + struct Warning + { + // Critical warnings will be displayed on G-code export in a modal dialog, so that the user cannot miss them. + WarningLevel level; + // If the warning is not current, then it is in an unknown state. It may or may not be valid. + // A current warning will become non-current if its milestone gets invalidated. + // A non-current warning will either become current or it will be removed at the end of a milestone. + bool current; + // Message to be shown to the user, UTF8, localized. + std::string message; + // If message_id == 0, then the message is expected to identify the warning uniquely. + // Otherwise message_id identifies the message. For example, if the message contains a varying number, then + // it cannot itself identify the message type. + int message_id; + }; + + struct StateWithWarnings : public StateWithTimeStamp + { + void mark_warnings_non_current() { for (auto &w : warnings) w.current = false; } + std::vector warnings; + }; + protected: //FIXME last timestamp is shared between Print & SLAPrint, // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp @@ -56,12 +84,18 @@ class PrintState : public PrintStateBase public: PrintState() {} - StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { + StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { tbb::mutex::scoped_lock lock(mtx); StateWithTimeStamp state = m_state[step]; return state; } + StateWithWarnings state_with_warnings(StepType step, tbb::mutex &mtx) const { + tbb::mutex::scoped_lock lock(mtx); + StateWithWarnings state = m_state[step]; + return state; + } + bool is_started(StepType step, tbb::mutex &mtx) const { return this->state_with_timestamp(step, mtx).state == STARTED; } @@ -91,24 +125,53 @@ public: tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); +#ifndef NDEBUG +// The following test is not necessarily valid after the background processing thread +// is stopped with throw_if_canceled(), as the CanceledException is not being catched +// by the Print or PrintObject to update m_step_active or m_state[...].state. +// This should not be a problem as long as the caller calls set_started() / set_done() / +// active_step_add_warning() consistently. From the robustness point of view it would be +// be better to catch CanceledException and do the updates. From the performance point of view, +// the current implementation is optimal. +// +// assert(m_step_active == -1); +// for (int i = 0; i < int(COUNT); ++ i) +// assert(m_state[i].state != STARTED); +#endif // NDEBUG if (m_state[step].state == DONE) return false; - m_state[step].state = STARTED; - m_state[step].timestamp = ++ g_last_timestamp; + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = STARTED; + state.timestamp = ++ g_last_timestamp; + state.mark_warnings_non_current(); + m_step_active = static_cast(step); return true; } // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being // modified by the UI thread. + // Return value: + // Timestamp when this stepentered the DONE state. + // bool indicates whether the UI has to update the slicing warnings of this step or not. template - TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { + std::pair set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); - assert(m_state[step].state != DONE); - m_state[step].state = DONE; - m_state[step].timestamp = ++ g_last_timestamp; - return m_state[step].timestamp; + assert(m_state[step].state == STARTED); + assert(m_step_active == static_cast(step)); + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = DONE; + state.timestamp = ++ g_last_timestamp; + m_step_active = -1; + // Remove all non-current warnings. + auto it = std::remove_if(state.warnings.begin(), state.warnings.end(), [](const auto &w) { return ! w.current; }); + bool update_warning_ui = false; + if (it != state.warnings.end()) { + state.warnings.erase(it, state.warnings.end()); + update_warning_ui = true; + } + return std::make_pair(state.timestamp, update_warning_ui); } // Make the step invalid. @@ -124,13 +187,18 @@ public: printf("Not held!\n"); } #endif - m_state[step].state = INVALID; - m_state[step].timestamp = ++ g_last_timestamp; + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = INVALID; + state.timestamp = ++ g_last_timestamp; // Raise the mutex, so that the following cancel() callback could cancel // the background processing. // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let - // the working thread to proceed. + // the working thread proceed. cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to modify it. + state.mark_warnings_non_current(); + m_step_active = -1; } return invalidated; } @@ -157,6 +225,11 @@ public: // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let // the working thread to proceed. cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to modify the warnings. + for (StepTypeIterator it = step_begin; it != step_end; ++ it) + m_state[*it].mark_warnings_non_current(); + m_step_active = -1; } return invalidated; } @@ -176,18 +249,62 @@ public: state.timestamp = ++ g_last_timestamp; } } - if (invalidated) + if (invalidated) { cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to modify the warnings. + for (size_t i = 0; i < COUNT; ++ i) + m_state[i].mark_warnings_non_current(); + m_step_active = -1; + } return invalidated; } + // Update list of warnings of the current milestone with a new warning. + // The warning may already exist in the list, marked as current or not current. + // If it already exists, mark it as current. + // Return value: + // Current milestone (StepType). + // bool indicates whether the UI has to be updated or not. + std::pair active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id, tbb::mutex &mtx) + { + tbb::mutex::scoped_lock lock(mtx); + assert(m_step_active != -1); + StateWithWarnings &state = m_state[m_step_active]; + assert(state.state == STARTED); + std::pair retval(static_cast(m_step_active), true); + // Does a warning of the same level and message or message_id exist already? + auto it = (message_id == 0) ? + std::find_if(state.warnings.begin(), state.warnings.end(), [&message](const auto &w) { return w.message_id == 0 && w.message == message; }) : + std::find_if(state.warnings.begin(), state.warnings.end(), [message_id](const auto& w) { return w.message_id == message_id; }); + if (it == state.warnings.end()) + // No, create a new warning and update UI. + state.warnings.emplace_back(PrintStateBase::Warning{ warning_level, true, message, message_id }); + else if (it->message != message || it->level != warning_level) { + // Yes, however it needs an update. + it->message = message; + it->level = warning_level; + it->current = true; + } else if (it->current) + // Yes, and it is current. Don't update UI. + retval.second = false; + else + // Yes, but it is not current. Mark it as current. + it->current = true; + return retval; + } + private: - StateWithTimeStamp m_state[COUNT]; + StateWithWarnings m_state[COUNT]; + // Active class StepType or -1 if none is active. + // If the background processing is canceled, m_step_active may not be resetted + // to -1, see the comment in this->set_started(). + int m_step_active = -1; }; class PrintBase; -class PrintObjectBase +class PrintObjectBase : public ObjectID { public: const ModelObject* model_object() const { return m_model_object; } @@ -197,8 +314,12 @@ protected: PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {} virtual ~PrintObjectBase() {} // Declared here to allow access from PrintBase through friendship. - static tbb::mutex& state_mutex(PrintBase *print); - static std::function cancel_callback(PrintBase *print); + static tbb::mutex& state_mutex(PrintBase *print); + static std::function cancel_callback(PrintBase *print); + // Notify UI about a new warning of a milestone "step" on this PrintObjectBase. + // The UI will be notified by calling a status callback registered on print. + // If no status callback is registered, the message is printed to console. + void status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); ModelObject *m_model_object; }; @@ -214,7 +335,7 @@ protected: * The PrintBase class will abstract this flow for different technologies. * */ -class PrintBase +class PrintBase : public ObjectID { public: PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } @@ -264,17 +385,29 @@ public: struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} - int percent; + SlicingStatus(const PrintBase &print, int warning_step) : + flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print), warning_step(warning_step) {} + SlicingStatus(const PrintObjectBase &print_object, int warning_step) : + flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object), warning_step(warning_step) {} + int percent { -1 }; std::string text; // Bitmap of flags. enum FlagBits { - DEFAULT = 0, - RELOAD_SCENE = 1 << 1, - RELOAD_SLA_SUPPORT_POINTS = 1 << 2, - RELOAD_SLA_PREVIEW = 1 << 3, + DEFAULT = 0, + RELOAD_SCENE = 1 << 1, + RELOAD_SLA_SUPPORT_POINTS = 1 << 2, + RELOAD_SLA_PREVIEW = 1 << 3, + // UPDATE_PRINT_STEP_WARNINGS is mutually exclusive with UPDATE_PRINT_OBJECT_STEP_WARNINGS. + UPDATE_PRINT_STEP_WARNINGS = 1 << 4, + UPDATE_PRINT_OBJECT_STEP_WARNINGS = 1 << 5 }; // Bitmap of FlagBits unsigned int flags; + // set to an ObjectID of a Print or a PrintObject based on flags + // (whether UPDATE_PRINT_STEP_WARNINGS or UPDATE_PRINT_OBJECT_STEP_WARNINGS is set). + ObjectID warning_object_id; + // For which Print or PrintObject step a new warning is beeing issued? + int warning_step { -1 }; }; typedef std::function status_callback_type; // Default status console print out in the form of percent => message. @@ -329,6 +462,10 @@ protected: tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } void call_cancel_callback() { m_cancel_callback(); } + // Notify UI about a new warning of a milestone "step" on this PrintBase. + // The UI will be notified by calling a status callback. + // If no status callback is registered, the message is printed to console. + void status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. @@ -343,11 +480,12 @@ protected: DynamicPrintConfig m_full_print_config; PlaceholderParser m_placeholder_parser; -private: - tbb::atomic m_cancel_status; // Callback to be evoked regularly to update state of the UI thread. status_callback_type m_status_callback; +private: + tbb::atomic m_cancel_status; + // Callback to be evoked to stop the background processing before a state is updated. cancel_callback_type m_cancel_callback = [](){}; @@ -363,10 +501,16 @@ class PrintBaseWithState : public PrintBase public: bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); } + PrintStateBase::StateWithWarnings step_state_with_warnings(PrintStepEnum step) const { return m_state.state_with_warnings(step, this->state_mutex()); } protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } - PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintStepEnum step) { + std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); + if (status.second) + this->status_update_warnings(*this, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; + } bool invalidate_step(PrintStepEnum step) { return m_state.invalidate(step, this->cancel_callback()); } template @@ -380,6 +524,15 @@ protected: bool is_step_started_unguarded(PrintStepEnum step) const { return m_state.is_started_unguarded(step); } bool is_step_done_unguarded(PrintStepEnum step) const { return m_state.is_done_unguarded(step); } + // Add a slicing warning to the active Print step and send a status notification. + // This method could be called multiple times between this->set_started() and this->set_done(). + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); + if (active_step.second) + // Update UI. + this->status_update_warnings(*this, static_cast(active_step.first), warning_level, message); + } + private: PrintState m_state; }; @@ -394,14 +547,19 @@ public: typedef PrintState PrintObjectState; bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); } + PrintStateBase::StateWithWarnings step_state_with_warnings(PrintObjectStepEnum step) const { return m_state.state_with_warnings(step, PrintObjectBase::state_mutex(m_print)); } protected: PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {} bool set_started(PrintObjectStepEnum step) { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } - PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) - { return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) { + std::pair status = m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); + if (status.second) + this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; + } bool invalidate_step(PrintObjectStepEnum step) { return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); } @@ -416,6 +574,14 @@ protected: bool is_step_started_unguarded(PrintObjectStepEnum step) const { return m_state.is_started_unguarded(step); } bool is_step_done_unguarded(PrintObjectStepEnum step) const { return m_state.is_done_unguarded(step); } + // Add a slicing warning to the active PrintObject step and send a status notification. + // This method could be called multiple times between this->set_started() and this->set_done(). + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, PrintObjectBase::state_mutex(m_print)); + if (active_step.second) + this->status_update_warnings(m_print, static_cast(active_step.first), warning_level, message); + } + protected: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 01b17ec0de..275a30cac1 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1153,7 +1153,7 @@ void PrintConfigDef::init_fff_params() def = this->add("ironing_spacing", coFloat); def->label = L("Spacing between ironing passes"); - def->tooltip = L("Distance between ironing lins"); + def->tooltip = L("Distance between ironing lines"); def->sidetext = L("mm"); def->min = 0; def->mode = comExpert; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a5cb544858..d2bdb6d531 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1431,7 +1431,7 @@ void PrintObject::bridge_over_infill() } #ifdef SLIC3R_DEBUG - printf("Bridging " PRINTF_ZU " internal areas at layer " PRINTF_ZU "\n", to_bridge.size(), layer->id()); + printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif // compute the remaning internal solid surfaces as difference @@ -1577,7 +1577,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c bool updated = false; if (layer_height_profile.empty()) { - layer_height_profile = model_object.layer_height_profile; + // use the constructor because the assignement is crashing on ASAN OsX + layer_height_profile = std::vector(model_object.layer_height_profile); +// layer_height_profile = model_object.layer_height_profile; updated = true; } diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index c4a616d93a..0dd9436a1d 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -97,7 +97,7 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, hc.closing_distance)); - if (meshptr) { + if (meshptr && !meshptr->empty()) { // This flips the normals to be outward facing... meshptr->require_shared_vertices(); diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index fa919a1170..702d1bce18 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -28,17 +28,25 @@ void reproject_support_points(const EigenMesh3D &mesh, std::vector &p inline void reproject_points_and_holes(ModelObject *object) { bool has_sppoints = !object->sla_support_points.empty(); - bool has_holes = !object->sla_drain_holes.empty(); - if (!object || (!has_holes && !has_sppoints)) return; + // Disabling reprojection of holes as they have a significant offset away + // from the model body which tolerates minor geometrical changes. + // + // TODO: uncomment and ensure the right offset of the hole points if + // reprojection would still be necessary. + // bool has_holes = !object->sla_drain_holes.empty(); - EigenMesh3D emesh{object->raw_mesh()}; + if (!object || (/*!has_holes &&*/ !has_sppoints)) return; + + TriangleMesh rmsh = object->raw_mesh(); + rmsh.require_shared_vertices(); + EigenMesh3D emesh{rmsh}; if (has_sppoints) reproject_support_points(emesh, object->sla_support_points); - if (has_holes) - reproject_support_points(emesh, object->sla_drain_holes); +// if (has_holes) +// reproject_support_points(emesh, object->sla_drain_holes); } }} diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a272075656..573fc2b0c8 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -429,6 +429,13 @@ public: bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } const PrintObjects& objects() const { return m_objects; } + // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects + // in the notification center. + const SLAPrintObject* get_object(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const SLAPrintObject *obj) { return *static_cast(obj) == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const SLAPrintConfig& print_config() const { return m_print_config; } const SLAPrinterConfig& printer_config() const { return m_printer_config; } diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6e4b973eac..1c1c906c9f 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -21,6 +21,7 @@ bool SVG::open(const char* afilename) " \n" " \n" ); + fprintf(this->f, "\n", 2000.f, 2000.f); return true; } @@ -42,6 +43,7 @@ bool SVG::open(const char* afilename, const BoundingBox &bbox, const coord_t bbo " \n" " \n", h, w); + fprintf(this->f, "\n", w, h); return true; } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e0d534e001..c6991c057b 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -51,5 +51,11 @@ // Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5 #define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1) +// Enable built-in DPI changed event handler of wxWidgets 3.1.3 +#define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1) + +// Enable changing application layout without the need to restart +#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1) + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index f2deb5cbab..17edf1b5a8 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -952,7 +952,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, SlicingMode mode, co [&layers_p, mode, closing_radius, layers, throw_on_cancel, this](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); + printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif throw_on_cancel(); ExPolygons &expolygons = (*layers)[layer_id]; @@ -1779,7 +1779,7 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float clos size_t holes_count = 0; for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) holes_count += e->holes.size(); - printf(PRINTF_ZU " surface(s) having " PRINTF_ZU " holes detected from " PRINTF_ZU " polylines\n", + printf("%zu surface(s) having %zu holes detected from %zu polylines\n", ex_slices.size(), holes_count, loops.size()); #endif diff --git a/src/libslic3r/VoronoiOffset.cpp b/src/libslic3r/VoronoiOffset.cpp index cd96e3cdc3..c0541bd9f9 100644 --- a/src/libslic3r/VoronoiOffset.cpp +++ b/src/libslic3r/VoronoiOffset.cpp @@ -1,17 +1,22 @@ -// Polygon offsetting code inspired by OpenVoronoi by Anders Wallin -// https://github.com/aewallin/openvoronoi -// This offsetter uses results of boost::polygon Voronoi. +// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. #include "VoronoiOffset.hpp" #include +// #define VORONOI_DEBUG_OUT + +#ifdef VORONOI_DEBUG_OUT +#include +#endif + namespace Slic3r { using VD = Geometry::VoronoiDiagram; namespace detail { - // Intersect a circle with a ray, return the two parameters + // Intersect a circle with a ray, return the two parameters. + // Currently used for unbounded Voronoi edges only. double first_circle_segment_intersection_parameter( const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v) { @@ -48,258 +53,711 @@ namespace detail { } } - Vec2d voronoi_edge_offset_point( - const VD &vd, - const Lines &lines, - // Distance of a VD vertex to the closest site (input polygon edge or vertex). - const std::vector &vertex_dist, - // Minium distance of a VD edge to the closest site (input polygon edge or vertex). - // For a parabolic segment the distance may be smaller than the distance of the two end points. - const std::vector &edge_dist, - // Edge for which to calculate the offset point. If the distance towards the input polygon - // is not monotonical, pick the offset point closer to edge.vertex0(). - const VD::edge_type &edge, - // Distance from the input polygon along the edge. - const double offset_distance) - { - const VD::vertex_type *v0 = edge.vertex0(); - const VD::vertex_type *v1 = edge.vertex1(); - const VD::cell_type *cell = edge.cell(); - const VD::cell_type *cell2 = edge.twin()->cell(); - const Line &line0 = lines[cell->source_index()]; - const Line &line1 = lines[cell2->source_index()]; - if (v0 == nullptr || v1 == nullptr) { - assert(edge.is_infinite()); - assert(v0 != nullptr || v1 != nullptr); - // Offsetting on an unconstrained edge. - assert(offset_distance > vertex_dist[(v0 ? v0 : v1) - &vd.vertices().front()] - EPSILON); - Vec2d pt, dir; - double t; - if (cell->contains_point() && cell2->contains_point()) { - const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - // Direction vector of this unconstrained Voronoi edge. - dir = Vec2d(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); - if (v0 == nullptr) { - v0 = v1; - dir = - dir; - } - pt = Vec2d(v0->x(), v0->y()); - t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); - } else { - // Infinite edges could not be created by two segment sites. - assert(cell->contains_point() != cell2->contains_point()); - // Linear edge goes through the endpoint of a segment. - assert(edge.is_linear()); - assert(edge.is_secondary()); - const Line &line = cell->contains_segment() ? line0 : line1; - const Point &ipt = cell->contains_segment() ? - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); - assert(line.a == ipt || line.b == ipt); - pt = Vec2d(ipt.x(), ipt.y()); - dir = Vec2d(line.a.y() - line.b.y(), line.b.x() - line.a.x()); - assert(dir.norm() > 0.); - t = offset_distance / dir.norm(); - if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) - t = - t; - } - return pt + t * dir; - } else { - // Constrained edge. - Vec2d p0(v0->x(), v0->y()); - Vec2d p1(v1->x(), v1->y()); - double d0 = vertex_dist[v0 - &vd.vertices().front()]; - double d1 = vertex_dist[v1 - &vd.vertices().front()]; - if (cell->contains_segment() && cell2->contains_segment()) { - // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. - double ddif = d1 - d0; - assert(offset_distance > std::min(d0, d1) - EPSILON && offset_distance < std::max(d0, d1) + EPSILON); - double t = (ddif == 0) ? 0. : clamp(0., 1., (offset_distance - d0) / ddif); - return Slic3r::lerp(p0, p1, t); - } else { - // One cell contains a point, the other contains an edge or a point. - assert(cell->contains_point() || cell2->contains_point()); - const Point &ipt = cell->contains_point() ? - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); - double t = detail::first_circle_segment_intersection_parameter( - Vec2d(ipt.x(), ipt.y()), offset_distance, p0, p1 - p0); - return Slic3r::lerp(p0, p1, t); - } - } - } -}; - -Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error) -{ - // Distance of a VD vertex to the closest site (input polygon edge or vertex). - std::vector vertex_dist(vd.num_vertices(), std::numeric_limits::max()); - - // Minium distance of a VD edge to the closest site (input polygon edge or vertex). - // For a parabolic segment the distance may be smaller than the distance of the two end points. - std::vector edge_dist(vd.num_edges(), std::numeric_limits::max()); - - // Calculate minimum distance of input polygons to voronoi vertices and voronoi edges. - for (const VD::edge_type &edge : vd.edges()) { - const VD::vertex_type *v0 = edge.vertex0(); - const VD::vertex_type *v1 = edge.vertex1(); - const VD::cell_type *cell = edge.cell(); - const VD::cell_type *cell2 = edge.twin()->cell(); - const Line &line0 = lines[cell->source_index()]; - const Line &line1 = lines[cell2->source_index()]; - double d0, d1, dmin; - if (v0 == nullptr || v1 == nullptr) { - assert(edge.is_infinite()); - if (cell->contains_point() && cell2->contains_point()) { - const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - d0 = d1 = std::numeric_limits::max(); - if (v0 == nullptr && v1 == nullptr) { - dmin = (pt1.cast() - pt0.cast()).norm(); - } else { - Vec2d pt((pt0 + pt1).cast() * 0.5); - Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); - Vec2d pt0d(pt0.x(), pt0.y()); - if (v0) { - Vec2d a(v0->x(), v0->y()); - d0 = (a - pt0d).norm(); - dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0; - vertex_dist[v0 - &vd.vertices().front()] = d0; - } else { - Vec2d a(v1->x(), v1->y()); - d1 = (a - pt0d).norm(); - dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1; - vertex_dist[v1 - &vd.vertices().front()] = d1; - } - } - } else { - // Infinite edges could not be created by two segment sites. - assert(cell->contains_point() != cell2->contains_point()); - // Linear edge goes through the endpoint of a segment. - assert(edge.is_linear()); - assert(edge.is_secondary()); -#ifndef NDEBUG - if (cell->contains_segment()) { - const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; - assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || - (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); - } else { - const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; - assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || - (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); - } - const Point &pt = cell->contains_segment() ? - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); -#endif /* NDEBUG */ - if (v0) { - assert((Point(v0->x(), v0->y()) - pt).cast().norm() < SCALED_EPSILON); - d0 = dmin = 0.; - vertex_dist[v0 - &vd.vertices().front()] = d0; - } else { - assert((Point(v1->x(), v1->y()) - pt).cast().norm() < SCALED_EPSILON); - d1 = dmin = 0.; - vertex_dist[v1 - &vd.vertices().front()] = d1; - } - } - } else { - // Finite edge has valid points at both sides. - if (cell->contains_segment() && cell2->contains_segment()) { - // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. - Vec2d pt(line0.a.cast()); - Vec2d dir(line0.b.cast() - pt); - Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; - Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; - double l2 = dir.squaredNorm(); - assert(l2 > 0.); - d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm(); - d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm(); - dmin = std::min(d0, d1); - } else { - assert(cell->contains_point() || cell2->contains_point()); - const Point &pt0 = cell->contains_point() ? - ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : - ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); - // Project p0 to line segment . - Vec2d p0(v0->x(), v0->y()); - Vec2d p1(v1->x(), v1->y()); - Vec2d px(pt0.x(), pt0.y()); - Vec2d v = p1 - p0; - d0 = (p0 - px).norm(); - d1 = (p1 - px).norm(); - double t = v.dot(px - p0); - double l2 = v.squaredNorm(); - if (t > 0. && t < l2) { - // Foot point on the line segment. - Vec2d foot = p0 + (t / l2) * v; - dmin = (foot - px).norm(); - } else - dmin = std::min(d0, d1); - } - vertex_dist[v0 - &vd.vertices().front()] = d0; - vertex_dist[v1 - &vd.vertices().front()] = d1; - } - edge_dist[&edge - &vd.edges().front()] = dmin; - } - - // Mark cells intersected by the offset curve. - std::vector seed_cells(vd.num_cells(), false); - for (const VD::cell_type &cell : vd.cells()) { - const VD::edge_type *first_edge = cell.incident_edge(); - const VD::edge_type *edge = first_edge; - do { - double dmin = edge_dist[edge - &vd.edges().front()]; - double dmax = std::numeric_limits::max(); - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - if (v0 != nullptr) - dmax = vertex_dist[v0 - &vd.vertices().front()]; - if (v1 != nullptr) - dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]); - if (offset_distance >= dmin && offset_distance <= dmax) { - // This cell is being intersected by the offset curve. - seed_cells[&cell - &vd.cells().front()] = true; - break; - } - edge = edge->next(); - } while (edge != first_edge); - } - - auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) { - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); - double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); - if (d0 < offset_distance && offset_distance < d1) - return true; - else if (d1 < offset_distance && offset_distance < d0) - return false; - else { - assert(false); - return false; - } + struct Intersections + { + int count; + Vec2d pts[2]; }; - /// \brief starting at e, find the next edge on the face that brackets t - /// - /// we can be in one of two modes. - /// if direction==false then we are looking for an edge where src_t < t < trg_t - /// if direction==true we are looning for an edge where trg_t < t < src_t - auto next_offset_edge = - [&vd, &vertex_dist, &edge_dist, offset_distance] - (const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* { - const VD::edge_type *edge = start_edge; - do { - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); - double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); - if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1)) - return edge; - edge = edge->next(); - } while (edge != start_edge); - assert(false); + // Return maximum two points, that are at distance "d" from both points + Intersections point_point_equal_distance_points(const Point &pt1, const Point &pt2, const double d) + { + // Calculate the two intersection points. + // With the help of Python package sympy: + // res = solve([(x - cx)**2 + (y - cy)**2 - d**2, x**2 + y**2 - d**2], [x, y]) + // ccode(cse((res[0][0], res[0][1], res[1][0], res[1][1]))) + // where cx, cy is the center of pt1 relative to pt2, + // d is distance from the line and the point (0, 0). + // The result is then shifted to pt2. + auto cx = double(pt1.x() - pt2.x()); + auto cy = double(pt1.y() - pt2.y()); + double cl = cx * cx + cy * cy; + double discr = 4. * d * d - cl; + if (discr < 0.) { + // No intersection point found, the two circles are too far away. + return Intersections { 0, { Vec2d(), Vec2d() } }; + } + // Avoid division by zero if a gets too small. + bool xy_swapped = std::abs(cx) < std::abs(cy); + if (xy_swapped) + std::swap(cx, cy); + double u; + int cnt; + if (discr == 0.) { + cnt = 1; + u = 0; + } else { + cnt = 2; + u = 0.5 * cx * sqrt(cl * discr) / cl; + } + double v = 0.5 * cy - u; + double w = 2. * cy; + double e = 0.5 / cx; + double f = 0.5 * cy + u; + Intersections out { cnt, { Vec2d(-e * (v * w - cl), v), + Vec2d(-e * (w * f - cl), f) } }; + if (xy_swapped) { + std::swap(out.pts[0].x(), out.pts[0].y()); + std::swap(out.pts[1].x(), out.pts[1].y()); + } + out.pts[0] += pt2.cast(); + out.pts[1] += pt2.cast(); + + assert(std::abs((out.pts[0] - pt1.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - pt1.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[0] - pt2.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - pt2.cast()).norm() - d) < SCALED_EPSILON); + return out; + } + + // Return maximum two points, that are at distance "d" from both the line and point. + Intersections line_point_equal_distance_points(const Line &line, const Point &ipt, const double d) + { + assert(line.a != ipt && line.b != ipt); + // Calculating two points of distance "d" to a ray and a point. + // Point. + Vec2d pt = ipt.cast(); + Vec2d lv = (line.b - line.a).cast(); + double l2 = lv.squaredNorm(); + Vec2d lpv = (line.a - ipt).cast(); + double c = cross2(lpv, lv); + if (c < 0) { + lv = - lv; + c = - c; + } + + // Line equation (ax + by + c - d * sqrt(l2)). + auto a = - lv.y(); + auto b = lv.x(); + // Line point shifted by -ipt is on the line. + assert(std::abs(lpv.x() * a + lpv.y() * b + c) < SCALED_EPSILON); + // Line vector (a, b) points towards ipt. + assert(a * lpv.x() + b * lpv.y() < - SCALED_EPSILON); + +#ifndef NDEBUG + { + // Foot point of ipt on line. + Vec2d ft = Geometry::foot_pt(line, ipt); + // Center point between ipt and line, its distance to both line and ipt is equal. + Vec2d centerpt = 0.5 * (ft + pt) - pt; + double dcenter = 0.5 * (ft - pt).norm(); + // Verify that the center point + assert(std::abs(centerpt.x() * a + centerpt.y() * b + c - dcenter * sqrt(l2)) < SCALED_EPSILON * sqrt(l2)); + } +#endif // NDEBUG + + // Calculate the two intersection points. + // With the help of Python package sympy: + // res = solve([a * x + b * y + c - d * sqrt(a**2 + b**2), x**2 + y**2 - d**2], [x, y]) + // ccode(cse((res[0][0], res[0][1], res[1][0], res[1][1]))) + // where (a, b, c, d) is the line equation, not normalized (vector a,b is not normalized), + // d is distance from the line and the point (0, 0). + // The result is then shifted to ipt. + + double dscaled = d * sqrt(l2); + double s = c * (2. * dscaled - c); + if (s < 0.) + // Distance of pt from line is bigger than 2 * d. + return Intersections { 0 }; + double u; + int cnt; + // Avoid division by zero if a gets too small. + bool xy_swapped = std::abs(a) < std::abs(b); + if (xy_swapped) + std::swap(a, b); + if (s == 0.) { + // Distance of pt from line is 2 * d. + cnt = 1; + u = 0.; + } else { + // Distance of pt from line is smaller than 2 * d. + cnt = 2; + u = a * sqrt(s) / l2; + } + double e = dscaled - c; + double f = b * e / l2; + double g = f - u; + double h = f + u; + Intersections out { cnt, { Vec2d((- b * g + e) / a, g), + Vec2d((- b * h + e) / a, h) } }; + if (xy_swapped) { + std::swap(out.pts[0].x(), out.pts[0].y()); + std::swap(out.pts[1].x(), out.pts[1].y()); + } + out.pts[0] += pt; + out.pts[1] += pt; + + assert(std::abs(Geometry::ray_point_distance(line.a.cast(), (line.b - line.a).cast(), out.pts[0]) - d) < SCALED_EPSILON); + assert(std::abs(Geometry::ray_point_distance(line.a.cast(), (line.b - line.a).cast(), out.pts[1]) - d) < SCALED_EPSILON); + assert(std::abs((out.pts[0] - ipt.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - ipt.cast()).norm() - d) < SCALED_EPSILON); + return out; + } + +} // namespace detail + +Polygons voronoi_offset( + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, + double discretization_error) +{ +#ifndef NDEBUG + // Verify that twin halfedges are stored next to the other in vd. + for (size_t i = 0; i < vd.num_edges(); i += 2) { + const VD::edge_type &e = vd.edges()[i]; + const VD::edge_type &e2 = vd.edges()[i + 1]; + assert(e.twin() == &e2); + assert(e2.twin() == &e); + assert(e.is_secondary() == e2.is_secondary()); + if (e.is_secondary()) { + assert(e.cell()->contains_point() != e2.cell()->contains_point()); + const VD::edge_type &ex = (e.cell()->contains_point() ? e : e2); + // Verify that the Point defining the cell left of ex is an end point of a segment + // defining the cell right of ex. + const Line &line0 = lines[ex.cell()->source_index()]; + const Line &line1 = lines[ex.twin()->cell()->source_index()]; + const Point &pt = (ex.cell()->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert(pt == line1.a || pt == line1.b); + } + } +#endif // NDEBUG + + enum class EdgeState : unsigned char { + // Initial state, don't know. + Unknown, + // This edge will certainly not be intersected by the offset curve. + Inactive, + // This edge will certainly be intersected by the offset curve. + Active, + // This edge will possibly be intersected by the offset curve. + Possible + }; + + enum class CellState : unsigned char { + // Initial state, don't know. + Unknown, + // Inactive cell is inside for outside curves and outside for inside curves. + Inactive, + // Active cell is outside for outside curves and inside for inside curves. + Active, + // Boundary cell is intersected by the input segment, part of it is active. + Boundary + }; + + // Mark edges with outward vertex pointing outside the polygons, thus there is a chance + // that such an edge will have an intersection with our desired offset curve. + bool outside = offset_distance > 0.; + std::vector edge_state(vd.num_edges(), EdgeState::Unknown); + std::vector cell_state(vd.num_cells(), CellState::Unknown); + const VD::edge_type *front_edge = &vd.edges().front(); + const VD::cell_type *front_cell = &vd.cells().front(); + auto set_edge_state_initial = [&edge_state, front_edge](const VD::edge_type *edge, EdgeState new_edge_type) { + EdgeState &edge_type = edge_state[edge - front_edge]; + assert(edge_type == EdgeState::Unknown || edge_type == new_edge_type); + assert(new_edge_type == EdgeState::Possible || new_edge_type == EdgeState::Inactive); + edge_type = new_edge_type; + }; + auto set_edge_state_final = [&edge_state, front_edge](const size_t edge_id, EdgeState new_edge_type) { + EdgeState &edge_type = edge_state[edge_id]; + assert(edge_type == EdgeState::Possible || edge_type == new_edge_type); + assert(new_edge_type == EdgeState::Active || new_edge_type == EdgeState::Inactive); + edge_type = new_edge_type; + }; + auto set_cell_state = [&cell_state, front_cell](const VD::cell_type *cell, CellState new_cell_type) -> bool { + CellState &cell_type = cell_state[cell - front_cell]; + assert(cell_type == CellState::Active || cell_type == CellState::Inactive || cell_type == CellState::Boundary || cell_type == CellState::Unknown); + assert(new_cell_type == CellState::Active || new_cell_type == CellState::Inactive || new_cell_type == CellState::Boundary); + switch (cell_type) { + case CellState::Unknown: + break; + case CellState::Active: + if (new_cell_type == CellState::Inactive) + new_cell_type = CellState::Boundary; + break; + case CellState::Inactive: + if (new_cell_type == CellState::Active) + new_cell_type = CellState::Boundary; + break; + case CellState::Boundary: + return false; + } + if (cell_type != new_cell_type) { + cell_type = new_cell_type; + return true; + } + return false; + }; + + for (const VD::edge_type &edge : vd.edges()) + if (edge.vertex1() == nullptr) { + // Infinite Voronoi edge separating two Point sites or a Point site and a Segment site. + // Infinite edge is always outside and it has at least one valid vertex. + assert(edge.vertex0() != nullptr); + set_edge_state_initial(&edge, outside ? EdgeState::Possible : EdgeState::Inactive); + // Opposite edge of an infinite edge is certainly not active. + set_edge_state_initial(edge.twin(), EdgeState::Inactive); + if (edge.is_secondary()) { + // edge.vertex0() must lie on source contour. + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + if (cell->contains_segment()) + std::swap(cell, cell2); + // State of a cell containing a boundary point is known. + assert(cell->contains_point()); + set_cell_state(cell, outside ? CellState::Active : CellState::Inactive); + // State of a cell containing a boundary edge is Boundary. + assert(cell2->contains_segment()); + set_cell_state(cell2, CellState::Boundary); + } + } else if (edge.vertex0() != nullptr) { + // Finite edge. + const VD::cell_type *cell = edge.cell(); + const Line *line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; + if (line == nullptr) { + cell = edge.twin()->cell(); + line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; + } + if (line) { + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell2 = (cell == edge.cell()) ? edge.twin()->cell() : edge.cell(); + assert(v1); + const Point *pt_on_contour = nullptr; + if (cell == edge.cell() && edge.twin()->cell()->contains_segment()) { + // Constrained bisector of two segments. + // If the two segments share a point, then one end of the current Voronoi edge shares this point as well. + // Find pt_on_contour if it exists. + const Line &line2 = lines[cell2->source_index()]; + if (line->a == line2.b) + pt_on_contour = &line->a; + else if (line->b == line2.a) + pt_on_contour = &line->b; + } else if (edge.is_secondary()) { + assert(edge.is_linear()); + // One end of the current Voronoi edge shares a point of a contour. + assert(edge.cell()->contains_point() != edge.twin()->cell()->contains_point()); + const Line &line2 = lines[cell2->source_index()]; + pt_on_contour = &((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line2.a : line2.b); + } + if (pt_on_contour) { + // One end of the current Voronoi edge shares a point of a contour. + // Find out which one it is. + const VD::vertex_type *v0 = edge.vertex0(); + Vec2d vec0(v0->x() - pt_on_contour->x(), v0->y() - pt_on_contour->y()); + Vec2d vec1(v1->x() - pt_on_contour->x(), v1->y() - pt_on_contour->y()); + double d0 = vec0.squaredNorm(); + double d1 = vec1.squaredNorm(); + assert(std::min(d0, d1) < SCALED_EPSILON * SCALED_EPSILON); + if (d0 < d1) { + // v0 is equal to pt. + } else { + // Skip secondary edge pointing to a contour point. + set_edge_state_initial(&edge, EdgeState::Inactive); + continue; + } + } + Vec2d l0(line->a.cast()); + Vec2d lv((line->b - line->a).cast()); + double side = cross2(lv, Vec2d(v1->x(), v1->y()) - l0); + bool edge_active = outside ? (side < 0.) : (side > 0.); + set_edge_state_initial(&edge, edge_active ? EdgeState::Possible : EdgeState::Inactive); + assert(cell->contains_segment()); + set_cell_state(cell, + pt_on_contour ? CellState::Boundary : + edge_active ? CellState::Active : CellState::Inactive); + set_cell_state(cell2, + (pt_on_contour && cell2->contains_segment()) ? + CellState::Boundary : + edge_active ? CellState::Active : CellState::Inactive); + } + } + { + // Perform one round of expansion marking Voronoi edges and cells next to boundary cells as active / inactive. + std::vector cell_queue; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Unknown) { + assert(edge.cell()->contains_point() && edge.twin()->cell()->contains_point()); + // Edge separating two point sources, not yet classified as inside / outside. + CellState cs = cell_state[edge.cell() - front_cell]; + CellState cs2 = cell_state[edge.twin()->cell() - front_cell]; + if (cs != CellState::Unknown || cs2 != CellState::Unknown) { + if (cs == CellState::Unknown) { + cs = cs2; + if (set_cell_state(edge.cell(), cs)) + cell_queue.emplace_back(edge.cell()); + } else if (set_cell_state(edge.twin()->cell(), cs)) + cell_queue.emplace_back(edge.twin()->cell()); + EdgeState es = (cs == CellState::Active) ? EdgeState::Possible : EdgeState::Inactive; + set_edge_state_initial(&edge, es); + set_edge_state_initial(edge.twin(), es); + } else { + const VD::edge_type *e = edge.twin()->rot_prev(); + do { + EdgeState es = edge_state[e->twin() - front_edge]; + if (es != EdgeState::Unknown) { + assert(es == EdgeState::Possible || es == EdgeState::Inactive); + set_edge_state_initial(&edge, es); + CellState cs = (es == EdgeState::Possible) ? CellState::Active : CellState::Inactive; + if (set_cell_state(edge.cell(), cs)) + cell_queue.emplace_back(edge.cell()); + if (set_cell_state(edge.twin()->cell(), cs)) + cell_queue.emplace_back(edge.twin()->cell()); + break; + } + e = e->rot_prev(); + } while (e != edge.twin()); + } + } + // Do a final seed fill over Voronoi cells and unmarked Voronoi edges. + while (! cell_queue.empty()) { + const VD::cell_type *cell = cell_queue.back(); + const CellState cs = cell_state[cell - front_cell]; + cell_queue.pop_back(); + const VD::edge_type *first_edge = cell->incident_edge(); + const VD::edge_type *edge = cell->incident_edge(); + EdgeState es = (cs == CellState::Active) ? EdgeState::Possible : EdgeState::Inactive; + do { + if (set_cell_state(edge->twin()->cell(), cs)) { + set_edge_state_initial(edge, es); + set_edge_state_initial(edge->twin(), es); + cell_queue.emplace_back(edge->twin()->cell()); + } + edge = edge->next(); + } while (edge != first_edge); + } + } + + if (! outside) + offset_distance = - offset_distance; + +#ifdef VORONOI_DEBUG_OUT + BoundingBox bbox; + { + bbox.merge(get_extents(lines)); + bbox.min -= (0.01 * bbox.size().cast()).cast(); + bbox.max += (0.01 * bbox.size().cast()).cast(); + } + static int irun = 0; + ++ irun; + { + Lines helper_lines; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Possible) { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + assert(v0 != nullptr); + Vec2d pt1(v0->x(), v0->y()); + Vec2d pt2; + if (v1 == nullptr) { + // Unconstrained edge. Calculate a trimmed position. + assert(edge.is_linear()); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + // Direction vector of this unconstrained Voronoi edge. + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + pt2 = Vec2d(v0->x(), v0->y()) + dir.normalized() * scale_(10.); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_secondary()); + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. + const Line &line = cell->contains_segment() ? line0 : line1; + assert(line.a == ipt || line.b == ipt); + // dir is perpendicular to line. + Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x()); + assert(dir.norm() > 0.); + if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) + dir = - dir; + pt2 = ipt.cast() + dir.normalized() * scale_(10.); + } + } else { + pt2 = Vec2d(v1->x(), v1->y()); + // Clip the line by the bounding box, so that the coloring of the line will be visible. + Geometry::liang_barsky_line_clipping(pt1, pt2, BoundingBoxf(bbox.min.cast(), bbox.max.cast())); + } + helper_lines.emplace_back(Line(Point(pt1.cast()), Point(((pt1 + pt2) * 0.5).cast()))); + } + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates1-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), helper_lines); + } +#endif // VORONOI_DEBUG_OUT + + std::vector edge_offset_point(vd.num_edges(), Vec2d()); + const double offset_distance2 = offset_distance * offset_distance; + for (const VD::edge_type &edge : vd.edges()) { + assert(edge_state[&edge - front_edge] != EdgeState::Unknown); + size_t edge_idx = &edge - front_edge; + if (edge_state[edge_idx] == EdgeState::Possible) { + // Edge candidate, intersection points were not calculated yet. + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + assert(v0 != nullptr); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + size_t edge_idx2 = edge.twin() - front_edge; + if (v1 == nullptr) { + assert(edge.is_infinite()); + assert(edge.is_linear()); + assert(edge_state[edge_idx2] == EdgeState::Inactive); + if (cell->contains_point() && cell2->contains_point()) { + assert(! edge.is_secondary()); + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + double dmin2 = (Vec2d(v0->x(), v0->y()) - pt0.cast()).squaredNorm(); + assert(dmin2 >= SCALED_EPSILON * SCALED_EPSILON); + if (dmin2 <= offset_distance2) { + // There shall be an intersection of this unconstrained edge with the offset curve. + // Direction vector of this unconstrained Voronoi edge. + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + Vec2d pt(v0->x(), v0->y()); + double t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); + edge_offset_point[edge_idx] = pt + t * dir; + set_edge_state_final(edge_idx, EdgeState::Active); + } else + set_edge_state_final(edge_idx, EdgeState::Inactive); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_secondary()); + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + #ifndef NDEBUG + if (cell->contains_segment()) { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || + (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); + } else { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || + (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); + } + assert((Vec2d(v0->x(), v0->y()) - ipt.cast()).norm() < SCALED_EPSILON); + #endif /* NDEBUG */ + // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. + const Line &line = cell->contains_segment() ? line0 : line1; + assert(line.a == ipt || line.b == ipt); + edge_offset_point[edge_idx] = ipt.cast() + offset_distance * Vec2d(line.b.y() - line.a.y(), line.a.x() - line.b.x()).normalized(); + set_edge_state_final(edge_idx, EdgeState::Active); + } + // The other edge of an unconstrained edge starting with null vertex shall never be intersected. + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } else if (edge.is_secondary()) { + assert(edge.is_linear()); + assert(cell->contains_point() != cell2->contains_point()); + const Line &line0 = lines[edge.cell()->source_index()]; + const Line &line1 = lines[edge.twin()->cell()->source_index()]; + const Point &pt = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + const Line &line = cell->contains_segment() ? line0 : line1; + assert(pt == line.a || pt == line.b); + assert((pt.cast() - Vec2d(v0->x(), v0->y())).norm() < SCALED_EPSILON); + Vec2d dir(v1->x() - v0->x(), v1->y() - v0->y()); + double l2 = dir.squaredNorm(); + if (offset_distance2 <= l2) { + edge_offset_point[edge_idx] = pt.cast() + (offset_distance / sqrt(l2)) * dir; + set_edge_state_final(edge_idx, EdgeState::Active); + } else { + set_edge_state_final(edge_idx, EdgeState::Inactive); + } + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } else { + // Finite edge has valid points at both sides. + bool done = false; + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. + Vec2d pt(line0.a.cast()); + Vec2d dir(line0.b.cast() - pt); + Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; + Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; + double l2 = dir.squaredNorm(); + assert(l2 > 0.); + double dmin = (dir * (vec0.dot(dir) / l2) - vec0).squaredNorm(); + double dmax = (dir * (vec1.dot(dir) / l2) - vec1).squaredNorm(); + bool flip = dmin > dmax; + if (flip) + std::swap(dmin, dmax); + if (offset_distance2 >= dmin && offset_distance2 <= dmax) { + // Intersect. Maximum one intersection will be found. + // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. + dmin = sqrt(dmin); + dmax = sqrt(dmax); + assert(offset_distance > dmin - EPSILON && offset_distance < dmax + EPSILON); + double ddif = dmax - dmin; + if (ddif == 0.) { + // line, line2 are exactly parallel. This is a singular case, the offset curve should miss it. + } else { + if (flip) { + std::swap(edge_idx, edge_idx2); + std::swap(v0, v1); + } + double t = clamp(0., 1., (offset_distance - dmin) / ddif); + edge_offset_point[edge_idx] = Vec2d(lerp(v0->x(), v1->x(), t), lerp(v0->y(), v1->y(), t)); + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + done = true; + } + } + } else { + assert(cell->contains_point() || cell2->contains_point()); + bool point_vs_segment = cell->contains_point() != cell2->contains_point(); + const Point &pt0 = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + // Project p0 to line segment . + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + Vec2d px(pt0.x(), pt0.y()); + double d0 = (p0 - px).squaredNorm(); + double d1 = (p1 - px).squaredNorm(); + double dmin = std::min(d0, d1); + double dmax = std::max(d0, d1); + bool has_intersection = false; + bool possibly_two_points = false; + if (offset_distance2 <= dmax) { + if (offset_distance2 >= dmin) { + has_intersection = true; + } else { + double dmin_new = dmin; + if (point_vs_segment) { + // Project on the source segment. + const Line &line = cell->contains_segment() ? line0 : line1; + const Vec2d pt_line = line.a.cast(); + const Vec2d v_line = (line.b - line.a).cast(); + double t0 = (p0 - pt_line).dot(v_line); + double t1 = (p1 - pt_line).dot(v_line); + double tx = (px - pt_line).dot(v_line); + if ((tx >= t0 && tx <= t1) || (tx >= t1 && tx <= t0)) { + // Projection of the Point site falls between the projections of the Voronoi edge end points + // onto the Line site. + Vec2d ft = pt_line + (tx / v_line.squaredNorm()) * v_line; + dmin_new = (ft - px).squaredNorm() * 0.25; + } + } else { + // Point-Point Voronoi sites. Project point site onto the current Voronoi edge. + Vec2d v = p1 - p0; + auto l2 = v.squaredNorm(); + assert(l2 > 0); + auto t = v.dot(px - p0); + if (t >= 0. && t <= l2) { + // Projection falls onto the Voronoi edge. Calculate foot point and distance. + Vec2d ft = p0 + (t / l2) * v; + dmin_new = (ft - px).squaredNorm(); + } + } + assert(dmin_new < dmax + SCALED_EPSILON); + assert(dmin_new < dmin + SCALED_EPSILON); + if (dmin_new < dmin) { + dmin = dmin_new; + has_intersection = possibly_two_points = offset_distance2 >= dmin; + } + } + } + if (has_intersection) { + detail::Intersections intersections; + if (point_vs_segment) { + assert(cell->contains_point() || cell2->contains_point()); + intersections = detail::line_point_equal_distance_points(cell->contains_segment() ? line0 : line1, pt0, offset_distance); + } else { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + intersections = detail::point_point_equal_distance_points(pt0, pt1, offset_distance); + } + // If the span of distances of start / end point / foot point to the point site indicate an intersection, + // we should find one. + assert(intersections.count > 0); + if (intersections.count == 2) { + // Now decide which points fall on this Voronoi edge. + // Tangential points (single intersection) are ignored. + if (possibly_two_points) { + Vec2d v = p1 - p0; + double l2 = v.squaredNorm(); + double t0 = v.dot(intersections.pts[0] - p0); + double t1 = v.dot(intersections.pts[1] - p0); + if (t0 > t1) { + std::swap(t0, t1); + std::swap(intersections.pts[0], intersections.pts[1]); + } + // Remove points outside of the line range. + if (t0 < 0. || t0 > l2) { + if (t1 < 0. || t1 > l2) + intersections.count = 0; + else { + -- intersections.count; + t0 = t1; + intersections.pts[0] = intersections.pts[1]; + } + } else if (t1 < 0. || t1 > l2) + -- intersections.count; + } else { + // Take the point furthest from the end points of the Voronoi edge or a Voronoi parabolic arc. + double d0 = std::max((intersections.pts[0] - p0).squaredNorm(), (intersections.pts[0] - p1).squaredNorm()); + double d1 = std::max((intersections.pts[1] - p0).squaredNorm(), (intersections.pts[1] - p1).squaredNorm()); + if (d0 > d1) + intersections.pts[0] = intersections.pts[1]; + -- intersections.count; + } + assert(intersections.count > 0); + if (intersections.count == 2) { + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Active); + edge_offset_point[edge_idx] = intersections.pts[1]; + edge_offset_point[edge_idx2] = intersections.pts[0]; + done = true; + } else if (intersections.count == 1) { + if (d1 < d0) + std::swap(edge_idx, edge_idx2); + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + edge_offset_point[edge_idx] = intersections.pts[0]; + done = true; + } + } + } + } + if (! done) { + set_edge_state_final(edge_idx, EdgeState::Inactive); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } + } + } + } + +#ifndef NDEBUG + for (const VD::edge_type &edge : vd.edges()) { + assert(edge_state[&edge - front_edge] == EdgeState::Inactive || edge_state[&edge - front_edge] == EdgeState::Active); + // None of a new edge candidate may start with null vertex. + assert(edge_state[&edge - front_edge] == EdgeState::Inactive || edge.vertex0() != nullptr); + assert(edge_state[edge.twin() - front_edge] == EdgeState::Inactive || edge.twin()->vertex0() != nullptr); + } +#endif // NDEBUG + +#ifdef VORONOI_DEBUG_OUT + { + Lines helper_lines; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Active) + helper_lines.emplace_back(Line(Point(edge.vertex0()->x(), edge.vertex0()->y()), Point(edge_offset_point[&edge - front_edge].cast()))); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates2-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), helper_lines); + } +#endif // VORONOI_DEBUG_OUT + + auto next_offset_edge = [&edge_state, front_edge](const VD::edge_type *start_edge) -> const VD::edge_type* { + for (const VD::edge_type *edge = start_edge->next(); edge != start_edge; edge = edge->next()) + if (edge_state[edge->twin() - front_edge] == EdgeState::Active) + return edge->twin(); + // assert(false); return nullptr; }; @@ -308,64 +766,66 @@ Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance const Line &line = lines[cell.source_index()]; return cell.contains_point() ? (((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast() - point).norm() : - line.distance_to(point.cast()); + (Geometry::foot_pt(line.a.cast(), (line.b - line.a).cast(), point) - point).norm(); }; #endif /* NDEBUG */ // Track the offset curves. Polygons out; double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); - double sin_threshold = sin(angle_step) + EPSILON; - for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx) - if (seed_cells[seed_cell_idx]) { - seed_cells[seed_cell_idx] = false; - // Initial direction should not matter, an offset curve shall intersect a cell at least at two points - // (if it is not just touching the cell at a single vertex), and such two intersection points shall have - // opposite direction. - bool direction = false; - // the first edge on the start-face - const VD::cell_type &cell = vd.cells()[seed_cell_idx]; - const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction); - assert(start_edge->cell() == &cell); + double cos_threshold = cos(angle_step); + for (size_t seed_edge_idx = 0; seed_edge_idx < vd.num_edges(); ++ seed_edge_idx) + if (edge_state[seed_edge_idx] == EdgeState::Active) { + const VD::edge_type *start_edge = &vd.edges()[seed_edge_idx]; const VD::edge_type *edge = start_edge; Polygon poly; do { - direction = edge_dir(edge); // find the next edge - const VD::edge_type *next_edge = next_offset_edge(edge->next(), direction); + const VD::edge_type *next_edge = next_offset_edge(edge); +#ifdef VORONOI_DEBUG_OUT + if (next_edge == nullptr) { + Lines helper_lines; + dump_voronoi_to_svg(debug_out_path("voronoi-offset-open-loop-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), to_lines(poly)); + } +#endif // VORONOI_DEBUG_OUT + assert(next_edge); //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; // Interpolate a circular segment or insert a linear segment between edge and next_edge. const VD::cell_type *cell = edge->cell(); - Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance); - Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance); + edge_state[next_edge - front_edge] = EdgeState::Inactive; + Vec2d p1 = edge_offset_point[edge - front_edge]; + Vec2d p2 = edge_offset_point[next_edge - front_edge]; #ifndef NDEBUG { - double err = dist_to_site(*cell, p1) - offset_distance; - assert(std::abs(err) < SCALED_EPSILON); - err = dist_to_site(*cell, p2) - offset_distance; + double err = dist_to_site(*cell, p1) - offset_distance; + double err2 = dist_to_site(*cell, p2) - offset_distance; +#ifdef VORONOI_DEBUG_OUT + if (std::max(err, err2) >= SCALED_EPSILON) { + Lines helper_lines; + dump_voronoi_to_svg(debug_out_path("voronoi-offset-incorrect_pt-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), to_lines(poly)); + } +#endif // VORONOI_DEBUG_OUT assert(std::abs(err) < SCALED_EPSILON); + assert(std::abs(err2) < SCALED_EPSILON); } #endif /* NDEBUG */ if (cell->contains_point()) { // Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error. - // The arc should cover angle < PI. - //FIXME we should be able to produce correctly oriented output curves based on the first edge taken! + // The extracted contour is CCW oriented, extracted holes are CW oriented. + // The extracted arc will have the same orientation. As the Voronoi regions are convex, the angle covered by the arc will be convex as well. const Line &line0 = lines[cell->source_index()]; const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast(); const Vec2d v1 = p1 - center; const Vec2d v2 = p2 - center; - double orient = cross2(v1, v2); - double orient_norm = v1.norm() * v2.norm(); - bool ccw = orient > 0; - bool obtuse = v1.dot(v2) < 0.; - if (! ccw) - orient = - orient; - assert(orient != 0.); - if (obtuse || orient > orient_norm * sin_threshold) { + bool ccw = cross2(v1, v2) > 0; + double cos_a = v1.dot(v2); + double norm = v1.norm() * v2.norm(); + assert(norm > 0.); + if (cos_a < cos_threshold * norm) { // Angle is bigger than the threshold, therefore the arc will be discretized. - double angle = asin(orient / orient_norm); - if (obtuse) - angle = M_PI - angle; + cos_a /= norm; + assert(cos_a > -1. - EPSILON && cos_a < 1. + EPSILON); + double angle = acos(std::max(-1., std::min(1., cos_a))); size_t n_steps = size_t(ceil(angle / angle_step)); double astep = angle / n_steps; if (! ccw) @@ -377,12 +837,14 @@ Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance Vec2d p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y()); poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y()))); } - } + } } - poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); - // although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here. - seed_cells[cell - &vd.cells().front()] = false; - edge = next_edge->twin(); + { + Point pt_last(coord_t(p2.x()), coord_t(p2.y())); + if (poly.empty() || poly.points.back() != pt_last) + poly.points.emplace_back(pt_last); + } + edge = next_edge; } while (edge != start_edge); out.emplace_back(std::move(poly)); } diff --git a/src/libslic3r/VoronoiOffset.hpp b/src/libslic3r/VoronoiOffset.hpp index 9f5485c00d..a21b44f93e 100644 --- a/src/libslic3r/VoronoiOffset.hpp +++ b/src/libslic3r/VoronoiOffset.hpp @@ -1,3 +1,5 @@ +// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. + #ifndef slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_ @@ -7,7 +9,16 @@ namespace Slic3r { -Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error); +// Offset a polygon or a set of polygons possibly with holes by traversing a Voronoi diagram. +// The input polygons are stored in lines and lines are referenced by vd. +// Outer curve will be extracted for a positive offset_distance, +// inner curve will be extracted for a negative offset_distance. +// Circular arches will be discretized to achieve discretization_error. +Polygons voronoi_offset( + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, + double discretization_error); } // namespace Slic3r diff --git a/src/libslic3r/VoronoiVisualUtils.hpp b/src/libslic3r/VoronoiVisualUtils.hpp new file mode 100644 index 0000000000..fa6a342418 --- /dev/null +++ b/src/libslic3r/VoronoiVisualUtils.hpp @@ -0,0 +1,415 @@ +#include + +#include +#include +#include +#include + +namespace boost { namespace polygon { + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_graphic_utils.hpp header file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +template +class voronoi_visual_utils { + public: + // Discretize parabolic Voronoi edge. + // Parabolic Voronoi edges are always formed by one point and one segment + // from the initial input set. + // + // Args: + // point: input point. + // segment: input segment. + // max_dist: maximum discretization distance. + // discretization: point discretization of the given Voronoi edge. + // + // Template arguments: + // InCT: coordinate type of the input geometries (usually integer). + // Point: point type, should model point concept. + // Segment: segment type, should model segment concept. + // + // Important: + // discretization should contain both edge endpoints initially. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + void + >::type discretize( + const Point& point, + const Segment& segment, + const CT max_dist, + std::vector< Point >* discretization) { + // Apply the linear transformation to move start point of the segment to + // the point with coordinates (0, 0) and the direction of the segment to + // coincide the positive direction of the x-axis. + CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; + + // Compute x-coordinates of the endpoints of the edge + // in the transformed space. + CT projection_start = sqr_segment_length * + get_point_projection((*discretization)[0], segment); + CT projection_end = sqr_segment_length * + get_point_projection((*discretization)[1], segment); + + // Compute parabola parameters in the transformed space. + // Parabola has next representation: + // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). + CT point_vec_x = cast(x(point)) - cast(x(low(segment))); + CT point_vec_y = cast(y(point)) - cast(y(low(segment))); + CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; + CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; + + // Save the last point. + Point last_point = (*discretization)[1]; + discretization->pop_back(); + + // Use stack to avoid recursion. + std::stack point_stack; + point_stack.push(projection_end); + CT cur_x = projection_start; + CT cur_y = parabola_y(cur_x, rot_x, rot_y); + + // Adjust max_dist parameter in the transformed space. + const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; + while (!point_stack.empty()) { + CT new_x = point_stack.top(); + CT new_y = parabola_y(new_x, rot_x, rot_y); + + // Compute coordinates of the point of the parabola that is + // furthest from the current line segment. + CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; + CT mid_y = parabola_y(mid_x, rot_x, rot_y); + + // Compute maximum distance between the given parabolic arc + // and line segment that discretize it. + CT dist = (new_y - cur_y) * (mid_x - cur_x) - + (new_x - cur_x) * (mid_y - cur_y); + dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + + (new_x - cur_x) * (new_x - cur_x)); + if (dist <= max_dist_transformed) { + // Distance between parabola and line segment is less than max_dist. + point_stack.pop(); + CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / + sqr_segment_length + cast(x(low(segment))); + CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / + sqr_segment_length + cast(y(low(segment))); + discretization->push_back(Point(inter_x, inter_y)); + cur_x = new_x; + cur_y = new_y; + } else { + point_stack.push(mid_x); + } + } + + // Update last point. + discretization->back() = last_point; + } + + private: + // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). + static CT parabola_y(CT x, CT a, CT b) { + return ((x - a) * (x - a) + b * b) / (b + b); + } + + // Get normalized length of the distance between: + // 1) point projection onto the segment + // 2) start point of the segment + // Return this length divided by the segment length. This is made to avoid + // sqrt computation during transformation from the initial space to the + // transformed one and vice versa. The assumption is made that projection of + // the point lies between the start-point and endpoint of the segment. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + CT + >::type get_point_projection( + const Point& point, const Segment& segment) { + CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT point_vec_x = x(point) - cast(x(low(segment))); + CT point_vec_y = y(point) - cast(y(low(segment))); + CT sqr_segment_length = + segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; + CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; + return vec_dot / sqr_segment_length; + } + + template + static CT cast(const InCT& value) { + return static_cast(value); + } +}; + +} } // namespace boost::polygon + + +namespace Slic3r +{ + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_visualizer.cpp file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +namespace Voronoi { namespace Internal { + + using VD = Geometry::VoronoiDiagram; + typedef double coordinate_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; + typedef VD::cell_type cell_type; + typedef VD::cell_type::source_index_type source_index_type; + typedef VD::cell_type::source_category_type source_category_type; + typedef VD::edge_type edge_type; + typedef VD::cell_container_type cell_container_type; + typedef VD::cell_container_type vertex_container_type; + typedef VD::edge_container_type edge_container_type; + typedef VD::const_cell_iterator const_cell_iterator; + typedef VD::const_vertex_iterator const_vertex_iterator; + typedef VD::const_edge_iterator const_edge_iterator; + + static const std::size_t EXTERNAL_COLOR = 1; + + inline void color_exterior(const VD::edge_type* edge) + { + if (edge->color() == EXTERNAL_COLOR) + return; + edge->color(EXTERNAL_COLOR); + edge->twin()->color(EXTERNAL_COLOR); + const VD::vertex_type* v = edge->vertex1(); + if (v == NULL || !edge->is_primary()) + return; + v->color(EXTERNAL_COLOR); + const VD::edge_type* e = v->incident_edge(); + do { + color_exterior(e); + e = e->rot_next(); + } while (e != v->incident_edge()); + } + + inline point_type retrieve_point(const Points &points, const std::vector &segments, const cell_type& cell) + { + assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT || + cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); + return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ? + Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) : + (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? + low(segments[cell.source_index()]) : high(segments[cell.source_index()]); + } + + inline void clip_infinite_edge(const Points &points, const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) + { + const cell_type& cell1 = *edge.cell(); + const cell_type& cell2 = *edge.twin()->cell(); + point_type origin, direction; + // Infinite edges could not be created by two segment sites. + if (! cell1.contains_point() && ! cell2.contains_point()) { + printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n"); + return; + } + if (cell1.contains_point() && cell2.contains_point()) { + point_type p1 = retrieve_point(points, segments, cell1); + point_type p2 = retrieve_point(points, segments, cell2); + origin.x((p1.x() + p2.x()) * 0.5); + origin.y((p1.y() + p2.y()) * 0.5); + direction.x(p1.y() - p2.y()); + direction.y(p2.x() - p1.x()); + } else { + origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1); + segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; + coordinate_type dx = high(segment).x() - low(segment).x(); + coordinate_type dy = high(segment).y() - low(segment).y(); + if ((low(segment) == origin) ^ cell1.contains_point()) { + direction.x(dy); + direction.y(-dx); + } else { + direction.x(-dy); + direction.y(dx); + } + } + coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); + if (edge.vertex0() == NULL) { + clipped_edge->push_back(point_type( + origin.x() - direction.x() * koef, + origin.y() - direction.y() * koef)); + } else { + clipped_edge->push_back( + point_type(edge.vertex0()->x(), edge.vertex0()->y())); + } + if (edge.vertex1() == NULL) { + clipped_edge->push_back(point_type( + origin.x() + direction.x() * koef, + origin.y() + direction.y() * koef)); + } else { + clipped_edge->push_back( + point_type(edge.vertex1()->x(), edge.vertex1()->y())); + } + } + + inline void sample_curved_edge(const Points &points, const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) + { + point_type point = edge.cell()->contains_point() ? + retrieve_point(points, segments, *edge.cell()) : + retrieve_point(points, segments, *edge.twin()->cell()); + segment_type segment = edge.cell()->contains_point() ? + segments[edge.twin()->cell()->source_index()] : + segments[edge.cell()->source_index()]; + ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); + } + +} /* namespace Internal */ } // namespace Voronoi + +BoundingBox get_extents(const Lines &lines); + +static inline void dump_voronoi_to_svg( + const char *path, + const Geometry::VoronoiDiagram &vd, + const Points &points, + const Lines &lines, + const Polygons &offset_curves = Polygons(), + const Lines &helper_lines = Lines(), + double scale = 0) +{ + BoundingBox bbox; + bbox.merge(get_extents(points)); + bbox.merge(get_extents(lines)); + bbox.merge(get_extents(offset_curves)); + bbox.merge(get_extents(helper_lines)); + bbox.min -= (0.01 * bbox.size().cast()).cast(); + bbox.max += (0.01 * bbox.size().cast()).cast(); + + if (scale == 0) + scale = +// 0.1 + 0.01 + * std::min(bbox.size().x(), bbox.size().y()); + else + scale /= SCALING_FACTOR; + + const std::string inputSegmentPointColor = "lightseagreen"; + const coord_t inputSegmentPointRadius = coord_t(0.09 * scale); + const std::string inputSegmentColor = "lightseagreen"; + const coord_t inputSegmentLineWidth = coord_t(0.03 * scale); + + const std::string voronoiPointColor = "black"; + const coord_t voronoiPointRadius = coord_t(0.06 * scale); + const std::string voronoiLineColorPrimary = "black"; + const std::string voronoiLineColorSecondary = "green"; + const std::string voronoiArcColor = "red"; + const coord_t voronoiLineWidth = coord_t(0.02 * scale); + + const std::string offsetCurveColor = "magenta"; + const coord_t offsetCurveLineWidth = coord_t(0.02 * scale); + + const std::string helperLineColor = "orange"; + const coord_t helperLineWidth = coord_t(0.04 * scale); + + const bool internalEdgesOnly = false; + const bool primaryEdgesOnly = false; + + ::Slic3r::SVG svg(path, bbox); + + // For clipping of half-lines to some reasonable value. + // The line will then be clipped by the SVG viewer anyway. + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); + // For the discretization of the Voronoi parabolic segments. + const double discretization_step = 0.0002 * bbox_dim_max; + + // Make a copy of the input segments with the double type. + std::vector segments; + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) + segments.push_back(Voronoi::Internal::segment_type( + Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))), + Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1))))); + + // Color exterior edges. + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) + if (!it->is_finite()) + Voronoi::Internal::color_exterior(&(*it)); + + // Draw the end points of the input polygon. + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { + svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); + svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); + } + // Draw the input polygon. + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) + svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth); + +#if 1 + // Draw voronoi vertices. + for (boost::polygon::voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) + if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) + svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); + + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { + if (primaryEdgesOnly && !it->is_primary()) + continue; + if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) + continue; + std::vector samples; + std::string color = voronoiLineColorPrimary; + if (!it->is_finite()) { + Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples); + if (! it->is_primary()) + color = voronoiLineColorSecondary; + } else { + // Store both points of the segment into samples. sample_curved_edge will split the initial line + // until the discretization_step is reached. + samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); + samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); + if (it->is_curved()) { + Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step); + color = voronoiArcColor; + } else if (! it->is_primary()) + color = voronoiLineColorSecondary; + } + for (std::size_t i = 0; i + 1 < samples.size(); ++i) + svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); + } +#endif + + svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); + svg.draw(helper_lines, helperLineColor, helperLineWidth); + + svg.Close(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 3ea70d9985..5ceb62a5cf 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -27,7 +27,7 @@ // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). using coord_t = int32_t; #else -//FIXME At least FillRectilinear2 requires coord_t to be 32bit. +//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. typedef int64_t coord_t; #endif @@ -74,13 +74,6 @@ inline std::string debug_out_path(const char *name, ...) return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); } -#ifdef _MSC_VER - // Visual Studio older than 2015 does not support the prinf type specifier %zu. Use %Iu instead. - #define PRINTF_ZU "%Iu" -#else - #define PRINTF_ZU "%zu" -#endif - #ifndef UNUSED #define UNUSED(x) (void)(x) #endif /* UNUSED */ diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 88c4576681..30d44b9ab4 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -239,7 +239,7 @@ void show_error_id(int id, const std::string& message) void show_info(wxWindow* parent, const wxString& message, const wxString& title) { - wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _(L("Notice")) : title), wxOK | wxICON_INFORMATION); + wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION); msg_wingow.ShowModal(); } @@ -251,7 +251,7 @@ void show_info(wxWindow* parent, const char* message, const char* title) void warning_catcher(wxWindow* parent, const wxString& message) { - wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); + wxMessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING); msg.ShowModal(); } @@ -261,14 +261,14 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string return; wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup; - if (popup != nullptr) - { - // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. + if (popup != nullptr) { + // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. comboCtrl->UseAltPopupWindow(); - comboCtrl->EnablePopupAnimation(false); - comboCtrl->SetPopupControl(popup); + // the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3 +// comboCtrl->EnablePopupAnimation(false); + comboCtrl->SetPopupControl(popup); popup->SetStringValue(from_u8(text)); popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); @@ -278,13 +278,11 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string std::vector items_str; boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); - for (const std::string& item : items_str) - { + for (const std::string& item : items_str) { popup->Append(from_u8(item)); } - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { + for (unsigned int i = 0; i < popup->GetCount(); ++i) { popup->Check(i, initial_value); } } @@ -295,10 +293,8 @@ int combochecklist_get_flags(wxComboCtrl* comboCtrl) int flags = 0; wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); - if (popup != nullptr) - { - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { + if (popup != nullptr) { + for (unsigned int i = 0; i < popup->GetCount(); ++i) { if (popup->IsChecked(i)) flags |= 1 << i; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 157875e708..089a38c6f7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -106,6 +106,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } #ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) static void register_win32_dpi_event() { enum { WM_DPICHANGED_ = 0x02e0 }; @@ -121,13 +122,12 @@ static void register_win32_dpi_event() return true; }); } +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; static void register_win32_device_notification_event() { - enum { WM_DPICHANGED_ = 0x02e0 }; - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. auto main_frame = dynamic_cast(win); @@ -410,7 +410,9 @@ bool GUI_App::on_init_inner() } #ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) register_win32_dpi_event(); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN register_win32_device_notification_event(); #endif // WIN32 @@ -631,9 +633,9 @@ void GUI_App::recreate_GUI(const wxString& msg_name) { mainframe->shutdown(); - wxProgressDialog dlg(msg_name, msg_name); + wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); dlg.Pulse(); - dlg.Update(10, _(L("Recreating")) + dots); + dlg.Update(10, _L("Recreating") + dots); MainFrame *old_main_frame = mainframe; mainframe = new MainFrame(); @@ -643,17 +645,17 @@ void GUI_App::recreate_GUI(const wxString& msg_name) sidebar().obj_list()->init_objects(); SetTopWindow(mainframe); - dlg.Update(30, _(L("Recreating")) + dots); + dlg.Update(30, _L("Recreating") + dots); old_main_frame->Destroy(); // For this moment ConfigWizard is deleted, invalidate it. m_wizard = nullptr; - dlg.Update(80, _(L("Loading of current presets")) + dots); + dlg.Update(80, _L("Loading of current presets") + dots); m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); load_current_presets(); mainframe->Show(true); - dlg.Update(90, _(L("Loading of a mode view")) + dots); + dlg.Update(90, _L("Loading of a mode view") + dots); /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: * change min hight of object list to the normal min value (15 * wxGetApp().em_unit()) * after first whole Mainframe updating/layouting @@ -1059,17 +1061,34 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { +#if ENABLE_LAYOUT_NO_RESTART + bool app_layout_changed = false; +#else bool recreate_app = false; +#endif // ENABLE_LAYOUT_NO_RESTART { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope PreferencesDialog dlg(mainframe); dlg.ShowModal(); +#if ENABLE_LAYOUT_NO_RESTART + app_layout_changed = dlg.settings_layout_changed(); +#else recreate_app = dlg.settings_layout_changed(); +#endif // ENABLE_LAYOUT_NO_RESTART } +#if ENABLE_LAYOUT_NO_RESTART + if (app_layout_changed) { + mainframe->GetSizer()->Hide((size_t)0); + mainframe->update_layout(); + mainframe->select_tab(0); + mainframe->GetSizer()->Show((size_t)0); + } +#else if (recreate_app) recreate_GUI(_L("Changing of the settings layout") + dots); +#endif // ENABLE_LAYOUT_NO_RESTART break; } case ConfigMenuLanguage: @@ -1386,7 +1405,9 @@ void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &na return; } - window->SetSize(metrics->get_rect()); + const wxRect& rect = metrics->get_rect(); + window->SetPosition(rect.GetPosition()); + window->SetSize(rect.GetSize()); window->Maximize(metrics->get_maximized()); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b87565b03a..1ddcc0f6db 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -347,7 +347,7 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorGetItemType(item); - assert(type & itObject | itInstance | itInstanceRoot); + assert(type & (itObject | itInstance | itInstanceRoot)); obj_idxs.emplace_back(type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f068ef37dc..c7c21b20fb 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -250,6 +250,7 @@ bool Preview::init(wxWindow* parent, Model* model) _(L("Internal infill")) + "|" + _(L("Solid infill")) + "|" + _(L("Top solid infill")) + "|" + + _(L("Ironing")) + "|" + _(L("Bridge infill")) + "|" + _(L("Gap fill")) + "|" + _(L("Skirt")) + "|" + diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 5b23aeee04..7db3d57ffc 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -61,7 +61,9 @@ void on_window_geometry(wxTopLevelWindow *tlw, std::function callback) #endif } +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) wxDEFINE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN #ifdef _WIN32 template typename F::FN winapi_get_function(const wchar_t *dll, const char *fn_name) { diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 934845fb37..2737b3edbf 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -24,6 +24,11 @@ class wxCheckBox; class wxTopLevelWindow; class wxRect; +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION > major) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION > minor)) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION == minor) && (wxRELEASE_NUMBER >= release))) +#else +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) 0 +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT namespace Slic3r { namespace GUI { @@ -51,6 +56,7 @@ enum { DPI_DEFAULT = 96 }; int get_dpi_for_window(wxWindow *window); wxFont get_default_font_for_dpi(int dpi); +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) struct DpiChangedEvent : public wxEvent { int dpi; wxRect rect; @@ -66,6 +72,7 @@ struct DpiChangedEvent : public wxEvent { }; wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN template class DPIAware : public P { @@ -86,11 +93,33 @@ public: this->SetFont(m_normal_font); #endif // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + m_em_unit = std::max(10, 10.0f * m_scale_factor); +#else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // recalc_font(); - this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent &evt) { +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { + m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; + + m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); + + if (!m_can_rescale) + return; + +#if ENABLE_LAYOUT_NO_RESTART + if (m_force_rescale || is_new_scale_factor()) + rescale(wxRect()); +#else + if (is_new_scale_factor()) + rescale(wxRect()); +#endif // ENABLE_LAYOUT_NO_RESTART + }); +#else + this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); @@ -98,9 +127,15 @@ public: if (!m_can_rescale) return; +#if ENABLE_LAYOUT_NO_RESTART + if (m_force_rescale || is_new_scale_factor()) + rescale(evt.rect); +#else if (is_new_scale_factor()) rescale(evt.rect); - }); +#endif // ENABLE_LAYOUT_NO_RESTART + }); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event) { @@ -140,6 +175,9 @@ public: int em_unit() const { return m_em_unit; } // int font_size() const { return m_font_size; } const wxFont& normal_font() const { return m_normal_font; } +#if ENABLE_LAYOUT_NO_RESTART + void enable_force_rescale() { m_force_rescale = true; } +#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; @@ -153,6 +191,9 @@ private: wxFont m_normal_font; float m_prev_scale_factor; bool m_can_rescale{ true }; +#if ENABLE_LAYOUT_NO_RESTART + bool m_force_rescale{ false }; +#endif // ENABLE_LAYOUT_NO_RESTART int m_new_font_point_size; @@ -192,17 +233,27 @@ private: { this->Freeze(); - // rescale fonts of all controls - scale_controls_fonts(this, m_new_font_point_size); - // rescale current window font - scale_win_font(this, m_new_font_point_size); - +#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + if (m_force_rescale) { +#endif // ENABLE_LAYOUT_NO_RESTART + // rescale fonts of all controls + scale_controls_fonts(this, m_new_font_point_size); + // rescale current window font + scale_win_font(this, m_new_font_point_size); +#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + m_force_rescale = false; + } +#endif // ENABLE_LAYOUT_NO_RESTART // set normal application font as a current window font m_normal_font = this->GetFont(); // update em_unit value for new window font +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + m_em_unit = std::max(10, 10.0f * m_scale_factor); +#else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // rescale missed controls sizes and images on_dpi_changed(suggested_rect); @@ -341,7 +392,7 @@ public: static WindowMetrics from_window(wxTopLevelWindow *window); static boost::optional deserialize(const std::string &str); - wxRect get_rect() const { return rect; } + const wxRect& get_rect() const { return rect; } bool get_maximized() const { return maximized; } void sanitize_for_display(const wxRect &screen_rect); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 6742f5cdef..a25d9105fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -448,6 +448,7 @@ void SupportsClipper::render_cut() const // Get transformation of supports Geometry::Transformation supports_trafo = trafo; + supports_trafo.set_scaling_factor(Vec3d::Ones()); supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); // I don't know why, but following seems to be correct. diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 08caf299b5..cb1161bc93 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -42,10 +42,44 @@ namespace Slic3r { namespace GUI { +#if ENABLE_LAYOUT_NO_RESTART +enum class ERescaleTarget +{ + Mainframe, + SettingsDialog +}; + +static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog& dialog, ERescaleTarget target) +{ + int mainframe_dpi = get_dpi_for_window(&mainframe); + int dialog_dpi = get_dpi_for_window(&dialog); + if (mainframe_dpi != dialog_dpi) { + if (target == ERescaleTarget::SettingsDialog) { + dialog.enable_force_rescale(); +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + dialog.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(mainframe_dpi, mainframe_dpi), wxSize(dialog_dpi, dialog_dpi))); +#else + dialog.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, dialog_dpi, dialog.GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } else { +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + mainframe.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(dialog_dpi, dialog_dpi), wxSize(mainframe_dpi, mainframe_dpi))); +#else + mainframe.enable_force_rescale(); + mainframe.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, mainframe_dpi, mainframe.GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } + } +} +#endif // ENABLE_LAYOUT_NO_RESTART + MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) , m_recent_projects(9) +#if ENABLE_LAYOUT_NO_RESTART + , m_settings_dialog(this) +#endif // ENABLE_LAYOUT_NO_RESTART { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); @@ -85,6 +119,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_loaded = true; +#if !ENABLE_LAYOUT_NO_RESTART #ifdef __APPLE__ // Using SetMinSize() on Mac messes up the window position in some cases // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 @@ -98,8 +133,19 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_tabpanel->SetMinSize(size); } #endif +#endif // !ENABLE_LAYOUT_NO_RESTART + // initialize layout - auto sizer = new wxBoxSizer(wxVERTICAL); + m_main_sizer = new wxBoxSizer(wxVERTICAL); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(m_main_sizer, 1, wxEXPAND); +#if ENABLE_LAYOUT_NO_RESTART + SetSizer(sizer); + // initialize layout from config + update_layout(); + sizer->SetSizeHints(this); + Fit(); +#else if (m_plater && m_layout != slOld) sizer->Add(m_plater, 1, wxEXPAND); @@ -109,6 +155,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S sizer->SetSizeHints(this); SetSizer(sizer); Fit(); +#endif // !ENABLE_LAYOUT_NO_RESTART const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); #ifdef __APPLE__ @@ -200,8 +247,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S }); wxGetApp().persist_window_geometry(this, true); +#if ENABLE_LAYOUT_NO_RESTART + wxGetApp().persist_window_geometry(&m_settings_dialog, true); +#else if (m_settings_dialog != nullptr) wxGetApp().persist_window_geometry(m_settings_dialog, true); +#endif // ENABLE_LAYOUT_NO_RESTART update_ui_from_settings(); // FIXME (?) @@ -209,6 +260,123 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_plater->show_action_buttons(true); } +#if ENABLE_LAYOUT_NO_RESTART +void MainFrame::update_layout() +{ + auto restore_to_creation = [this]() { + auto clean_sizer = [](wxSizer* sizer) { + while (!sizer->GetChildren().IsEmpty()) { + sizer->Detach(0); + } + }; + + // On Linux m_plater needs to be removed from m_tabpanel before to reparent it + int plater_page_id = m_tabpanel->FindPage(m_plater); + if (plater_page_id != wxNOT_FOUND) + m_tabpanel->RemovePage(plater_page_id); + + if (m_plater->GetParent() != this) + m_plater->Reparent(this); + + if (m_tabpanel->GetParent() != this) + m_tabpanel->Reparent(this); + + plater_page_id = (m_plater_page != nullptr) ? m_tabpanel->FindPage(m_plater_page) : wxNOT_FOUND; + if (plater_page_id != wxNOT_FOUND) { + m_tabpanel->DeletePage(plater_page_id); + m_plater_page = nullptr; + } + + if (m_layout == ESettingsLayout::Dlg) + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); + + clean_sizer(m_main_sizer); + clean_sizer(m_settings_dialog.GetSizer()); + + if (m_settings_dialog.IsShown()) + m_settings_dialog.Close(); + + m_tabpanel->Hide(); + m_plater->Hide(); + + Layout(); + }; + + ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old; + + if (m_layout == layout) + return; + + wxBusyCursor busy; + + Freeze(); + + // Remove old settings + if (m_layout != ESettingsLayout::Unknown) + restore_to_creation(); + + m_layout = layout; + + // From the very beginning the Print settings should be selected + m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1; + + // Set new settings + switch (m_layout) + { + case ESettingsLayout::Old: + { + m_plater->Reparent(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater, _L("Plater")); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); + m_plater->Show(); + m_tabpanel->Show(); + break; + } + case ESettingsLayout::New: + { + m_main_sizer->Add(m_plater, 1, wxEXPAND); + m_tabpanel->Hide(); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); + m_plater_page = new wxPanel(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ + m_plater->Show(); + break; + } + case ESettingsLayout::Dlg: + { + m_main_sizer->Add(m_plater, 1, wxEXPAND); + m_tabpanel->Reparent(&m_settings_dialog); + m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); + + m_tabpanel->Show(); + m_plater->Show(); + break; + } + } + +//#ifdef __APPLE__ +// // Using SetMinSize() on Mac messes up the window position in some cases +// // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 +// // So, if we haven't possibility to set MinSize() for the MainFrame, +// // set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode +// // Otherwise, MainFrame will be maximized by height +// if (m_layout == ESettingsLayout::New) { +// wxSize size = wxGetApp().get_min_size(); +// size.SetHeight(int(0.5 * size.GetHeight())); +// m_plater->SetMinSize(size); +// m_tabpanel->SetMinSize(size); +// } +//#endif + + Layout(); + Thaw(); +} +#endif // ENABLE_LAYOUT_NO_RESTART + // Called when closing the application and when switching the application language. void MainFrame::shutdown() { @@ -241,6 +409,11 @@ void MainFrame::shutdown() // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); +#if ENABLE_LAYOUT_NO_RESTART + if (m_settings_dialog.IsShown()) + // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() + m_settings_dialog.Close(); +#else if (m_settings_dialog != nullptr) { if (m_settings_dialog->IsShown()) @@ -249,6 +422,7 @@ void MainFrame::shutdown() m_settings_dialog->Destroy(); } +#endif // ENABLE_LAYOUT_NO_RESTART // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -307,9 +481,19 @@ void MainFrame::update_title() void MainFrame::init_tabpanel() { +#if ENABLE_LAYOUT_NO_RESTART + // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 + // with multiple high resolution displays connected. + m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); +#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); +#endif + m_tabpanel->Hide(); + m_settings_dialog.set_tabpanel(m_tabpanel); +#else m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld : - wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew : - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld; + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld; // From the very beginning the Print settings should be selected m_last_selected_tab = m_layout == slDlg ? 0 : 1; @@ -326,13 +510,14 @@ void MainFrame::init_tabpanel() m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); #endif } +#endif // ENABLE_LAYOUT_NO_RESTART m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { wxWindow* panel = m_tabpanel->GetCurrentPage(); Tab* tab = dynamic_cast(panel); // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology - if (panel == nullptr || (tab && ! tab->supports_printer_technology(m_plater->printer_technology()))) + if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology()))) return; auto& tabs_list = wxGetApp().tabs_list; @@ -346,6 +531,10 @@ void MainFrame::init_tabpanel() select_tab(0); // select Plater }); +#if ENABLE_LAYOUT_NO_RESTART + m_plater = new Plater(this, this); + m_plater->Hide(); +#else if (m_layout == slOld) { m_plater = new Plater(m_tabpanel, this); m_tabpanel->AddPage(m_plater, _L("Plater")); @@ -355,6 +544,7 @@ void MainFrame::init_tabpanel() if (m_layout == slNew) m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab } +#endif // ENABLE_LAYOUT_NO_RESTART wxGetApp().plater_ = m_plater; wxGetApp().obj_list()->create_popup_menus(); @@ -496,6 +686,18 @@ bool MainFrame::can_slice() const bool MainFrame::can_change_view() const { +#if ENABLE_LAYOUT_NO_RESTART + switch (m_layout) + { + default: { return false; } + case ESettingsLayout::New: { return m_plater->IsShown(); } + case ESettingsLayout::Dlg: { return true; } + case ESettingsLayout::Old: { + int page_id = m_tabpanel->GetSelection(); + return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; + } + } +#else if (m_layout == slNew) return m_plater->IsShown(); if (m_layout == slDlg) @@ -503,6 +705,7 @@ bool MainFrame::can_change_view() const // slOld layout mode int page_id = m_tabpanel->GetSelection(); return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; +#endif // ENABLE_LAYOUT_NO_RESTART } bool MainFrame::can_select() const @@ -530,16 +733,24 @@ bool MainFrame::can_reslice() const return (m_plater != nullptr) && !m_plater->model().objects.empty(); } -void MainFrame::on_dpi_changed(const wxRect &suggested_rect) +void MainFrame::on_dpi_changed(const wxRect& suggested_rect) { +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + wxGetApp().update_fonts(this); +#else wxGetApp().update_fonts(); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT this->SetFont(this->normal_font()); // update Plater wxGetApp().plater()->msw_rescale(); // update Tabs +#if ENABLE_LAYOUT_NO_RESTART + if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog +#else if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog +#endif // ENABLE_LAYOUT_NO_RESTART for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); @@ -549,7 +760,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) // Workarounds for correct Window rendering after rescale - /* Even if Window is maximized during moving, + /* Even if Window is maximized during moving, * first of all we should imitate Window resizing. So: * 1. cancel maximization, if it was set * 2. imitate resizing @@ -567,6 +778,11 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) this->SetSize(sz); this->Maximize(is_maximized); + +#if ENABLE_LAYOUT_NO_RESTART + if (m_layout == ESettingsLayout::Dlg) + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); +#endif // ENABLE_LAYOUT_NO_RESTART } void MainFrame::on_sys_color_changed() @@ -1075,10 +1291,10 @@ void MainFrame::quick_slice(const int qs) } // show processbar dialog - m_progress_dialog = new wxProgressDialog(_(L("Slicing")) + dots, - // TRN "Processing input_file_basename" - from_u8((boost::format(_utf8(L("Processing %s"))) % (input_file_basename + dots)).str()), - 100, this, 4); + m_progress_dialog = new wxProgressDialog(_L("Slicing") + dots, + // TRN "Processing input_file_basename" + from_u8((boost::format(_utf8(L("Processing %s"))) % (input_file_basename + dots)).str()), + 100, nullptr, wxPD_AUTO_HIDE); m_progress_dialog->Pulse(); { // my @warnings = (); @@ -1299,15 +1515,41 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { +#if ENABLE_LAYOUT_NO_RESTART + if (m_layout == ESettingsLayout::Dlg) { +#else if (m_layout == slDlg) { +#endif // ENABLE_LAYOUT_NO_RESTART if (tab==0) { +#if ENABLE_LAYOUT_NO_RESTART + if (m_settings_dialog.IsShown()) + this->SetFocus(); +#else if (m_settings_dialog->IsShown()) this->SetFocus(); +#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (m_plater->canvas3D()->is_search_pressed()) m_plater->SetFocus(); return; } +#if ENABLE_LAYOUT_NO_RESTART + // Show/Activate Settings Dialog +#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + if (m_settings_dialog.IsShown()) + m_settings_dialog.Hide(); + + m_tabpanel->Show(); + m_settings_dialog.Show(); +#else + if (m_settings_dialog.IsShown()) + m_settings_dialog.SetFocus(); + else { + m_tabpanel->Show(); + m_settings_dialog.Show(); + } +#endif +#else // Show/Activate Settings Dialog if (m_settings_dialog->IsShown()) #ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList @@ -1317,10 +1559,17 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) else #endif m_settings_dialog->Show(); +#endif // ENABLE_LAYOUT_NO_RESTART } +#if ENABLE_LAYOUT_NO_RESTART + else if (m_layout == ESettingsLayout::New) { + m_main_sizer->Show(m_plater, tab == 0); + m_main_sizer->Show(m_tabpanel, tab != 0); +#else else if (m_layout == slNew) { m_plater->Show(tab == 0); m_tabpanel->Show(tab != 0); +#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (tab == 0 && m_plater->canvas3D()->is_search_pressed()) @@ -1328,8 +1577,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) Layout(); } - // when tab == -1, it means we should to show the last selected tab + // when tab == -1, it means we should show the last selected tab +#if ENABLE_LAYOUT_NO_RESTART + m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); +#else m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab); +#endif // ENABLE_LAYOUT_NO_RESTART } // Set a camera direction, zoom to all objects. @@ -1440,14 +1693,12 @@ std::string MainFrame::get_dir_name(const wxString &full_name) const // ---------------------------------------------------------------------------- SettingsDialog::SettingsDialog(MainFrame* mainframe) -: DPIDialog(nullptr, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings"), wxDefaultPosition, wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxDIALOG_NO_PARENT, "settings_dialog"), +: DPIDialog(mainframe, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings"), wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { this->SetFont(wxGetApp().normal_font()); - - wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - this->SetBackgroundColour(bgr_clr); + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); // Load the icon either from the exe, or from the ico file. #if _WIN32 @@ -1460,6 +1711,7 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #endif // _WIN32 +#if !ENABLE_LAYOUT_NO_RESTART // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 // with multiple high resolution displays connected. m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); @@ -1484,10 +1736,45 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) } } }); +#endif // !ENABLE_LAYOUT_NO_RESTART + +#if ENABLE_LAYOUT_NO_RESTART + this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { + + auto key_up_handker = [this](wxKeyEvent& evt) { + if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) { + switch (evt.GetKeyCode()) { + case '1': { m_main_frame->select_tab(0); break; } + case '2': { m_main_frame->select_tab(1); break; } + case '3': { m_main_frame->select_tab(2); break; } + case '4': { m_main_frame->select_tab(3); break; } +#ifdef __APPLE__ + case 'f': +#else /* __APPLE__ */ + case WXK_CONTROL_F: +#endif /* __APPLE__ */ + case 'F': { m_main_frame->plater()->search(false); break; } + default:break; + } + } + }; + + if (evt.IsShown()) { + if (m_tabpanel != nullptr) + m_tabpanel->Bind(wxEVT_KEY_UP, key_up_handker); + } + else { + if (m_tabpanel != nullptr) + m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker); + } + }); +#endif // ENABLE_LAYOUT_NO_RESTART // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); +#if !ENABLE_LAYOUT_NO_RESTART sizer->Add(m_tabpanel, 1, wxEXPAND); +#endif // !ENABLE_LAYOUT_NO_RESTART sizer->SetSizeHints(this); SetSizer(sizer); Fit(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 3b64be9bcc..4514b8f50f 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -51,11 +51,15 @@ struct PresetTab { class SettingsDialog : public DPIDialog { wxNotebook* m_tabpanel { nullptr }; - MainFrame* m_main_frame {nullptr }; + MainFrame* m_main_frame { nullptr }; public: SettingsDialog(MainFrame* mainframe); ~SettingsDialog() {} +#if ENABLE_LAYOUT_NO_RESTART + void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; } +#else wxNotebook* get_tabpanel() { return m_tabpanel; } +#endif // ENABLE_LAYOUT_NO_RESTART protected: void on_dpi_changed(const wxRect& suggested_rect) override; @@ -72,6 +76,7 @@ class MainFrame : public DPIFrame wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now #endif wxMenuItem* m_menu_item_reslice_now { nullptr }; + wxSizer* m_main_sizer{ nullptr }; PrintHostQueueDialog *m_printhost_queue_dlg; @@ -114,11 +119,23 @@ class MainFrame : public DPIFrame wxFileHistory m_recent_projects; +#if ENABLE_LAYOUT_NO_RESTART + enum class ESettingsLayout + { + Unknown, + Old, + New, + Dlg, + }; + + ESettingsLayout m_layout{ ESettingsLayout::Unknown }; +#else enum SettingsLayout { slOld = 0, slNew, slDlg, } m_layout; +#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -128,6 +145,10 @@ public: MainFrame(); ~MainFrame() = default; +#if ENABLE_LAYOUT_NO_RESTART + void update_layout(); +#endif // ENABLE_LAYOUT_NO_RESTART + // Called when closing the application and when switching the application language. void shutdown(); @@ -169,7 +190,12 @@ public: Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; +#if ENABLE_LAYOUT_NO_RESTART + SettingsDialog m_settings_dialog; + wxWindow* m_plater_page{ nullptr }; +#else SettingsDialog* m_settings_dialog { nullptr }; +#endif // ENABLE_LAYOUT_NO_RESTART wxProgressDialog* m_progress_dialog { nullptr }; std::shared_ptr m_statusbar; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5673eece71..0d33debf06 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1929,9 +1929,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // collapse sidebar according to saved value bool is_collapsed = wxGetApp().app_config->get("collapsed_sidebar") == "1"; sidebar->collapse(is_collapsed); - // Update an enable of the collapse_toolbar: if sidebar is collapsed, then collapse_toolbar should be visible - if (is_collapsed) - wxGetApp().app_config->set("show_collapse_button", "1"); } Plater::priv::~priv() @@ -2055,17 +2052,17 @@ std::vector Plater::priv::load_files(const std::vector& input_ } const auto loading = _L("Loading") + dots; - wxProgressDialog dlg(loading, loading); + wxProgressDialog dlg(loading, "", 100, q, wxPD_AUTO_HIDE); dlg.Pulse(); auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); std::vector obj_idxs; - for (size_t i = 0; i < input_files.size(); i++) { + for (size_t i = 0; i < input_files.size(); ++i) { const auto &path = input_files[i]; const auto filename = path.filename(); - const auto dlg_info = format_wxstr(_L("Processing input file %s"), from_path(filename)) + "\n"; - dlg.Update(100 * i / input_files.size(), dlg_info); + const auto dlg_info = _L("Loading file") + ": " + from_path(filename); + dlg.Update(static_cast(100.0f * static_cast(i) / static_cast(input_files.size())), dlg_info); 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); @@ -3268,6 +3265,27 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); } + + if (evt.status.flags & (PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS | PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS)) { + // Update notification center with warnings of object_id and its warning_step. + ObjectID object_id = evt.status.warning_object_id; + int warning_step = evt.status.warning_step; + PrintStateBase::StateWithWarnings state; + if (evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) { + state = this->printer_technology == ptFFF ? + this->fff_print.step_state_with_warnings(static_cast(warning_step)) : + this->sla_print.step_state_with_warnings(static_cast(warning_step)); + } else if (this->printer_technology == ptFFF) { + const PrintObject *print_object = this->fff_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast(warning_step)); + } else { + const SLAPrintObject *print_object = this->sla_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast(warning_step)); + } + // Now process state.warnings. + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) @@ -4704,25 +4722,33 @@ void Plater::export_stl(bool extended, bool selection_only) ? Transform3d::Identity() : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + TriangleMesh inst_mesh; + if (has_pad_mesh) { TriangleMesh inst_pad_mesh = pad_mesh; inst_pad_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_pad_mesh); + inst_mesh.merge(inst_pad_mesh); } if (has_supports_mesh) { TriangleMesh inst_supports_mesh = supports_mesh; inst_supports_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_supports_mesh); + inst_mesh.merge(inst_supports_mesh); } TriangleMesh inst_object_mesh = object->get_mesh_to_print(); inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_object_mesh); + inst_mesh.merge(inst_object_mesh); + + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]); + + // merge instance with global mesh + mesh.merge(inst_mesh); if (one_inst_only) break; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 50abfb7e68..02e4a899d2 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -161,7 +161,7 @@ void PreferencesDialog::build() } }; - def.label = L("Show the button for the collapse sidebar"); + def.label = L("Show sidebar collapse/expand button"); def.type = coBool; def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); @@ -234,6 +234,7 @@ void PreferencesDialog::accept() } } +#if !ENABLE_LAYOUT_NO_RESTART if (m_settings_layout_changed) { // the dialog needs to be destroyed before the call to recreate_gui() // or sometimes the application crashes into wxDialogBase() destructor @@ -255,6 +256,7 @@ void PreferencesDialog::accept() return; } } +#endif // !ENABLE_LAYOUT_NO_RESTART for (std::map::iterator it = m_values.begin(); it != m_values.end(); ++it) app_config->set(it->first, it->second); @@ -351,9 +353,9 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { - wxString choices[] = { _L("Old regular layout with tab bar"), - _L("New layout without the tab bar on the platter"), - _L("Settings will be shown in non-modal dialog") }; + wxString choices[] = { _L("Old regular layout with the tab bar"), + _L("New layout without the tab bar on the plater"), + _L("Settings will be shown in the non-modal dialog") }; auto app_config = get_app_config(); int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 242e3d7256..2a2af5336b 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -617,8 +617,9 @@ void SearchDialog::update_list() for (const FoundOption& item : filters) search_list_model->Prepend(item.label); - // select first item - search_list->Select(search_list_model->GetItem(0)); + // select first item, if search_list + if (search_list_model->GetCount() > 0) + search_list->Select(search_list_model->GetItem(0)); prevent_list_events = false; } diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index a3683a84d3..86ff79aaaf 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -337,8 +337,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // Open a progress dialog. wxProgressDialog progress_dialog( - _(L("Model fixing")), - _(L("Exporting model...")), + _L("Model fixing"), + _L("Exporting model") + "...", 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 75a9c31372..c69e722af3 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -19,5 +19,9 @@ add_executable(${_TEST_NAME}_tests target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") +if (WIN32) + prusaslicer_copy_dlls(${_TEST_NAME}_tests) +endif() + # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS}) diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index ef05119ad9..ba318e4fd6 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1,16 +1,20 @@ #include #include -#include - #include #include #include #include + #include -#define BOOST_VORONOI_USE_GMP 1 -#include "boost/polygon/voronoi.hpp" +#include + +// #define VORONOI_DEBUG_OUT + +#ifdef VORONOI_DEBUG_OUT +#include +#endif using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; @@ -19,400 +23,6 @@ using namespace Slic3r; using VD = Geometry::VoronoiDiagram; -// #define VORONOI_DEBUG_OUT - -#ifdef VORONOI_DEBUG_OUT -#include -#endif - -#ifdef VORONOI_DEBUG_OUT -namespace boost { namespace polygon { - -// The following code for the visualization of the boost Voronoi diagram is based on: -// -// Boost.Polygon library voronoi_graphic_utils.hpp header file -// Copyright Andrii Sydorchuk 2010-2012. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -template -class voronoi_visual_utils { - public: - // Discretize parabolic Voronoi edge. - // Parabolic Voronoi edges are always formed by one point and one segment - // from the initial input set. - // - // Args: - // point: input point. - // segment: input segment. - // max_dist: maximum discretization distance. - // discretization: point discretization of the given Voronoi edge. - // - // Template arguments: - // InCT: coordinate type of the input geometries (usually integer). - // Point: point type, should model point concept. - // Segment: segment type, should model segment concept. - // - // Important: - // discretization should contain both edge endpoints initially. - template class Point, - template class Segment> - static - typename enable_if< - typename gtl_and< - typename gtl_if< - typename is_point_concept< - typename geometry_concept< Point >::type - >::type - >::type, - typename gtl_if< - typename is_segment_concept< - typename geometry_concept< Segment >::type - >::type - >::type - >::type, - void - >::type discretize( - const Point& point, - const Segment& segment, - const CT max_dist, - std::vector< Point >* discretization) { - // Apply the linear transformation to move start point of the segment to - // the point with coordinates (0, 0) and the direction of the segment to - // coincide the positive direction of the x-axis. - CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); - CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); - CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; - - // Compute x-coordinates of the endpoints of the edge - // in the transformed space. - CT projection_start = sqr_segment_length * - get_point_projection((*discretization)[0], segment); - CT projection_end = sqr_segment_length * - get_point_projection((*discretization)[1], segment); - - // Compute parabola parameters in the transformed space. - // Parabola has next representation: - // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). - CT point_vec_x = cast(x(point)) - cast(x(low(segment))); - CT point_vec_y = cast(y(point)) - cast(y(low(segment))); - CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; - CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; - - // Save the last point. - Point last_point = (*discretization)[1]; - discretization->pop_back(); - - // Use stack to avoid recursion. - std::stack point_stack; - point_stack.push(projection_end); - CT cur_x = projection_start; - CT cur_y = parabola_y(cur_x, rot_x, rot_y); - - // Adjust max_dist parameter in the transformed space. - const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; - while (!point_stack.empty()) { - CT new_x = point_stack.top(); - CT new_y = parabola_y(new_x, rot_x, rot_y); - - // Compute coordinates of the point of the parabola that is - // furthest from the current line segment. - CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; - CT mid_y = parabola_y(mid_x, rot_x, rot_y); - - // Compute maximum distance between the given parabolic arc - // and line segment that discretize it. - CT dist = (new_y - cur_y) * (mid_x - cur_x) - - (new_x - cur_x) * (mid_y - cur_y); - dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + - (new_x - cur_x) * (new_x - cur_x)); - if (dist <= max_dist_transformed) { - // Distance between parabola and line segment is less than max_dist. - point_stack.pop(); - CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / - sqr_segment_length + cast(x(low(segment))); - CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / - sqr_segment_length + cast(y(low(segment))); - discretization->push_back(Point(inter_x, inter_y)); - cur_x = new_x; - cur_y = new_y; - } else { - point_stack.push(mid_x); - } - } - - // Update last point. - discretization->back() = last_point; - } - - private: - // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). - static CT parabola_y(CT x, CT a, CT b) { - return ((x - a) * (x - a) + b * b) / (b + b); - } - - // Get normalized length of the distance between: - // 1) point projection onto the segment - // 2) start point of the segment - // Return this length divided by the segment length. This is made to avoid - // sqrt computation during transformation from the initial space to the - // transformed one and vice versa. The assumption is made that projection of - // the point lies between the start-point and endpoint of the segment. - template class Point, - template class Segment> - static - typename enable_if< - typename gtl_and< - typename gtl_if< - typename is_point_concept< - typename geometry_concept< Point >::type - >::type - >::type, - typename gtl_if< - typename is_segment_concept< - typename geometry_concept< Segment >::type - >::type - >::type - >::type, - CT - >::type get_point_projection( - const Point& point, const Segment& segment) { - CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); - CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); - CT point_vec_x = x(point) - cast(x(low(segment))); - CT point_vec_y = y(point) - cast(y(low(segment))); - CT sqr_segment_length = - segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; - CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; - return vec_dot / sqr_segment_length; - } - - template - static CT cast(const InCT& value) { - return static_cast(value); - } -}; - -} } // namespace boost::polygon - -// The following code for the visualization of the boost Voronoi diagram is based on: -// -// Boost.Polygon library voronoi_visualizer.cpp file -// Copyright Andrii Sydorchuk 2010-2012. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -namespace Voronoi { namespace Internal { - - typedef double coordinate_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; - typedef boost::polygon::voronoi_diagram VD; - typedef VD::cell_type cell_type; - typedef VD::cell_type::source_index_type source_index_type; - typedef VD::cell_type::source_category_type source_category_type; - typedef VD::edge_type edge_type; - typedef VD::cell_container_type cell_container_type; - typedef VD::cell_container_type vertex_container_type; - typedef VD::edge_container_type edge_container_type; - typedef VD::const_cell_iterator const_cell_iterator; - typedef VD::const_vertex_iterator const_vertex_iterator; - typedef VD::const_edge_iterator const_edge_iterator; - - static const std::size_t EXTERNAL_COLOR = 1; - - inline void color_exterior(const VD::edge_type* edge) - { - if (edge->color() == EXTERNAL_COLOR) - return; - edge->color(EXTERNAL_COLOR); - edge->twin()->color(EXTERNAL_COLOR); - const VD::vertex_type* v = edge->vertex1(); - if (v == NULL || !edge->is_primary()) - return; - v->color(EXTERNAL_COLOR); - const VD::edge_type* e = v->incident_edge(); - do { - color_exterior(e); - e = e->rot_next(); - } while (e != v->incident_edge()); - } - - inline point_type retrieve_point(const Points &points, const std::vector &segments, const cell_type& cell) - { - assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT || - cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); - return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ? - Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) : - (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? - low(segments[cell.source_index()]) : high(segments[cell.source_index()]); - } - - inline void clip_infinite_edge(const Points &points, const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) - { - const cell_type& cell1 = *edge.cell(); - const cell_type& cell2 = *edge.twin()->cell(); - point_type origin, direction; - // Infinite edges could not be created by two segment sites. - if (! cell1.contains_point() && ! cell2.contains_point()) { - printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n"); - return; - } - if (cell1.contains_point() && cell2.contains_point()) { - point_type p1 = retrieve_point(points, segments, cell1); - point_type p2 = retrieve_point(points, segments, cell2); - origin.x((p1.x() + p2.x()) * 0.5); - origin.y((p1.y() + p2.y()) * 0.5); - direction.x(p1.y() - p2.y()); - direction.y(p2.x() - p1.x()); - } else { - origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1); - segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; - coordinate_type dx = high(segment).x() - low(segment).x(); - coordinate_type dy = high(segment).y() - low(segment).y(); - if ((low(segment) == origin) ^ cell1.contains_point()) { - direction.x(dy); - direction.y(-dx); - } else { - direction.x(-dy); - direction.y(dx); - } - } - coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); - if (edge.vertex0() == NULL) { - clipped_edge->push_back(point_type( - origin.x() - direction.x() * koef, - origin.y() - direction.y() * koef)); - } else { - clipped_edge->push_back( - point_type(edge.vertex0()->x(), edge.vertex0()->y())); - } - if (edge.vertex1() == NULL) { - clipped_edge->push_back(point_type( - origin.x() + direction.x() * koef, - origin.y() + direction.y() * koef)); - } else { - clipped_edge->push_back( - point_type(edge.vertex1()->x(), edge.vertex1()->y())); - } - } - - inline void sample_curved_edge(const Points &points, const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) - { - point_type point = edge.cell()->contains_point() ? - retrieve_point(points, segments, *edge.cell()) : - retrieve_point(points, segments, *edge.twin()->cell()); - segment_type segment = edge.cell()->contains_point() ? - segments[edge.twin()->cell()->source_index()] : - segments[edge.cell()->source_index()]; - ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); - } - -} /* namespace Internal */ } // namespace Voronoi - -static inline void dump_voronoi_to_svg( - const char *path, - /* const */ VD &vd, - const Points &points, - const Lines &lines, - const Polygons &offset_curves = Polygons(), - const double scale = 0.7) // 0.2? -{ - const std::string inputSegmentPointColor = "lightseagreen"; - const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR); - const std::string inputSegmentColor = "lightseagreen"; - const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR); - - const std::string voronoiPointColor = "black"; - const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR); - const std::string voronoiLineColorPrimary = "black"; - const std::string voronoiLineColorSecondary = "green"; - const std::string voronoiArcColor = "red"; - const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); - - const std::string offsetCurveColor = "magenta"; - const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); - - const bool internalEdgesOnly = false; - const bool primaryEdgesOnly = false; - - BoundingBox bbox; - bbox.merge(get_extents(points)); - bbox.merge(get_extents(lines)); - bbox.min -= (0.01 * bbox.size().cast()).cast(); - bbox.max += (0.01 * bbox.size().cast()).cast(); - - ::Slic3r::SVG svg(path, bbox); - -// bbox.scale(1.2); - // For clipping of half-lines to some reasonable value. - // The line will then be clipped by the SVG viewer anyway. - const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); - // For the discretization of the Voronoi parabolic segments. - const double discretization_step = 0.05 * bbox_dim_max; - - // Make a copy of the input segments with the double type. - std::vector segments; - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) - segments.push_back(Voronoi::Internal::segment_type( - Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))), - Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1))))); - - // Color exterior edges. - for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) - if (!it->is_finite()) - Voronoi::Internal::color_exterior(&(*it)); - - // Draw the end points of the input polygon. - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { - svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); - svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); - } - // Draw the input polygon. - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) - svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth); - -#if 1 - // Draw voronoi vertices. - for (boost::polygon::voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) - if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) - svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); - - for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { - if (primaryEdgesOnly && !it->is_primary()) - continue; - if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) - continue; - std::vector samples; - std::string color = voronoiLineColorPrimary; - if (!it->is_finite()) { - Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples); - if (! it->is_primary()) - color = voronoiLineColorSecondary; - } else { - // Store both points of the segment into samples. sample_curved_edge will split the initial line - // until the discretization_step is reached. - samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); - samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); - if (it->is_curved()) { - Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step); - color = voronoiArcColor; - } else if (! it->is_primary()) - color = voronoiLineColorSecondary; - } - for (std::size_t i = 0; i + 1 < samples.size(); ++i) - svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); - } -#endif - - svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); - svg.Close(); -} -#endif - // https://svn.boost.org/trac10/ticket/12067 // This bug seems to be confirmed. // Vojtech supposes that there may be no Voronoi edges produced for @@ -1586,10 +1196,16 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), - vd, Points(), lines, Polygons(), 0.015); + vd, Points(), lines, Polygons(), Lines(), 0.015); #endif } +struct OffsetTest { + double distance; + size_t num_outer; + size_t num_inner; +}; + TEST_CASE("Voronoi offset", "[VoronoiOffset]") { Polygons poly_with_hole = { Polygon { @@ -1602,16 +1218,180 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]") } }; + double area = std::accumulate(poly_with_hole.begin(), poly_with_hole.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + VD vd; Lines lines = to_lines(poly_with_hole); construct_voronoi(lines.begin(), lines.end(), &vd); - Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 1, 1 }, + OffsetTest { scale_(0.4), 1, 1 }, + OffsetTest { scale_(0.5), 1, 1 }, + OffsetTest { scale_(0.505), 1, 2 }, + OffsetTest { scale_(0.51), 1, 2 }, + OffsetTest { scale_(0.52), 1, 1 }, + OffsetTest { scale_(0.53), 1, 1 }, + OffsetTest { scale_(0.54), 1, 1 }, + OffsetTest { scale_(0.55), 1, 0 } + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); #ifdef VORONOI_DEBUG_OUT - dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(), - vd, Points(), lines, offsetted_polygons); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); #endif - REQUIRE(offsetted_polygons.size() == 2); + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + } +} + +TEST_CASE("Voronoi offset 2", "[VoronoiOffset]") +{ + coord_t mm = coord_t(scale_(1.)); + Polygons poly = { + Polygon { + { 0, 0 }, + { 1, 0 }, + { 1, 1 }, + { 2, 1 }, + { 2, 0 }, + { 3, 0 }, + { 3, 2 }, + { 0, 2 } + }, + Polygon { + { 0, - 1 - 2 }, + { 3, - 1 - 2 }, + { 3, - 1 - 0 }, + { 2, - 1 - 0 }, + { 2, - 1 - 1 }, + { 1, - 1 - 1 }, + { 1, - 1 - 0 }, + { 0, - 1 - 0 } + }, + }; + for (Polygon &p : poly) + for (Point &pt : p.points) + pt *= mm; + + double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 2, 2 }, + OffsetTest { scale_(0.4), 2, 2 }, + OffsetTest { scale_(0.45), 2, 2 }, + OffsetTest { scale_(0.48), 2, 2 }, +//FIXME Exact intersections of an Offset curve with any Voronoi vertex are not handled correctly yet. +// OffsetTest { scale_(0.5), 2, 2 }, + OffsetTest { scale_(0.505), 2, 4 }, + OffsetTest { scale_(0.7), 2, 0 }, + OffsetTest { scale_(0.8), 1, 0 } + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + } +} + +TEST_CASE("Voronoi offset 3", "[VoronoiOffset]") +{ + coord_t mm = coord_t(scale_(1.)); + Polygons poly = { + Polygon { + { 0, 0 }, + { 2, 0 }, + { 2, 1 }, + { 3, 1 }, + { 3, 0 }, + { 5, 0 }, + { 5, 2 }, + { 4, 2 }, + { 4, 3 }, + { 1, 3 }, + { 1, 2 }, + { 0, 2 } + }, + Polygon { + { 0, -1 - 2 }, + { 1, -1 - 2 }, + { 1, -1 - 3 }, + { 4, -1 - 3 }, + { 4, -1 - 2 }, + { 5, -1 - 2 }, + { 5, -1 - 0 }, + { 3, -1 - 0 }, + { 3, -1 - 1 }, + { 2, -1 - 1 }, + { 2, -1 - 0 }, + { 0, -1 - 0 } + }, + }; + for (Polygon &p : poly) { + REQUIRE(p.area() > 0.); + for (Point &pt : p.points) + pt *= mm; + } + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 2, 2 }, + OffsetTest { scale_(0.4), 2, 2 }, + OffsetTest { scale_(0.49), 2, 2 }, +//FIXME this fails +// OffsetTest { scale_(0.5), 2, 2 }, + OffsetTest { scale_(0.51), 2, 2 }, + OffsetTest { scale_(0.56), 2, 2 }, + OffsetTest { scale_(0.6), 2, 2 }, + OffsetTest { scale_(0.7), 2, 2 }, + OffsetTest { scale_(0.8), 1, 6 }, + OffsetTest { scale_(0.9), 1, 6 }, + OffsetTest { scale_(0.99), 1, 6 }, +//FIXME this fails +// OffsetTest { scale_(1.0), 1, 6 }, + OffsetTest { scale_(1.01), 1, 0 }, + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + } } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index a59a199368..75d236a54c 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -185,6 +185,16 @@ if (MSVC) string(REPLACE "/" "\\" PROPS_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}") configure_file("../cmake/msvc/xs.wperl.props.in" "${CMAKE_BINARY_DIR}/xs.wperl.props" NEWLINE_STYLE CRLF) set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl.props") + + if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(_bits 64) + elseif ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + set(_bits 32) + endif () + add_custom_command(TARGET XS POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${TOP_LEVEL_PROJECT_DIR}/deps/GMP/gmp/lib/win${_bits}/libgmp-10.dll "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" + COMMENT "Installing gmp runtime into the local-lib directory ..." + VERBATIM) endif() # Installation diff --git a/xs/main.xs.in b/xs/main.xs.in index 3523d569ea..c10f432d83 100644 --- a/xs/main.xs.in +++ b/xs/main.xs.in @@ -5,7 +5,7 @@ // #include #ifdef __cplusplus -extern "C" { +/* extern "C" { */ #endif #include "EXTERN.h" #include "perl.h" @@ -14,7 +14,7 @@ extern "C" { #undef do_open #undef do_close #ifdef __cplusplus -} +/* } */ #endif #ifdef _WIN32 diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index f14e1262dc..2082dfb883 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -40,7 +40,7 @@ // #include #ifdef SLIC3RXS -extern "C" { +// extern "C" { #include "EXTERN.h" #include "perl.h" #include "XSUB.h" @@ -88,7 +88,7 @@ extern "C" { #undef Zero #undef Packet #undef _ -} +// } #endif #include diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index 5d6454e8a8..e44d169493 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -13,7 +13,7 @@ Pointfs arrange(size_t total_parts, Vec2d* part, coordf_t dist, BoundingBoxf* bb %code{% Pointfs points; if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) - CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", total_parts); + CONFESS("%zu parts won't fit in your print area!\n", total_parts); RETVAL = points; %};