diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 37483fc3e..6b06f8bab 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -111,7 +111,7 @@ public: void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; } void translate(const Vec3d &v) { this->min += v; this->max += v; } void offset(coordf_t delta); - BoundingBoxBase inflated(coordf_t delta) const throw() { BoundingBoxBase out(*this); out.offset(delta); return out; } + BoundingBox3Base inflated(coordf_t delta) const throw() { BoundingBox3Base out(*this); out.offset(delta); return out; } PointClass center() const; coordf_t max_size() const; diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp new file mode 100644 index 000000000..213c4468d --- /dev/null +++ b/src/libslic3r/BuildVolume.cpp @@ -0,0 +1,405 @@ +#include "BuildVolume.hpp" +#include "ClipperUtils.hpp" +#include "Geometry/ConvexHull.hpp" +#include "GCode/GCodeProcessor.hpp" +#include "Point.hpp" + +namespace Slic3r { + +BuildVolume::BuildVolume(const std::vector &bed_shape, const double max_print_height) : m_bed_shape(bed_shape), m_max_print_height(max_print_height) +{ + assert(max_print_height >= 0); + + m_polygon = Polygon::new_scale(bed_shape); + + // Calcuate various metrics of the input polygon. + m_convex_hull = Geometry::convex_hull(m_polygon.points); + m_bbox = get_extents(m_convex_hull); + m_area = m_polygon.area(); + + BoundingBoxf bboxf = get_extents(bed_shape); + m_bboxf = BoundingBoxf3{ to_3d(bboxf.min, 0.), to_3d(bboxf.max, max_print_height) }; + + if (bed_shape.size() >= 4 && std::abs((m_area - double(m_bbox.size().x()) * double(m_bbox.size().y()))) < sqr(SCALED_EPSILON)) { + // Square print bed, use the bounding box for collision detection. + m_type = Type::Rectangle; + m_circle.center = 0.5 * (m_bbox.min.cast() + m_bbox.max.cast()); + m_circle.radius = 0.5 * m_bbox.size().cast().norm(); + } else if (bed_shape.size() > 3) { + // Circle was discretized, formatted into text with limited accuracy, thus the circle was deformed. + // RANSAC is slightly more accurate than the iterative Taubin / Newton method with such an input. +// m_circle = Geometry::circle_taubin_newton(bed_shape); + m_circle = Geometry::circle_ransac(bed_shape); + bool is_circle = true; +#ifndef NDEBUG + // Measuring maximum absolute error of interpolating an input polygon with circle. + double max_error = 0; +#endif // NDEBUG + Vec2d prev = bed_shape.back(); + for (const Vec2d &p : bed_shape) { +#ifndef NDEBUG + max_error = std::max(max_error, std::abs((p - m_circle.center).norm() - m_circle.radius)); +#endif // NDEBUG + if (// Polygon vertices must lie very close the circle. + std::abs((p - m_circle.center).norm() - m_circle.radius) > 0.005 || + // Midpoints of polygon edges must not undercat more than 3mm. This corresponds to 72 edges per circle generated by BedShapePanel::update_shape(). + m_circle.radius - (0.5 * (prev + p) - m_circle.center).norm() > 3.) { + is_circle = false; + break; + } + prev = p; + } + if (is_circle) { + m_type = Type::Circle; + m_circle.center = scaled(m_circle.center); + m_circle.radius = scaled(m_circle.radius); + } + } + + if (bed_shape.size() >= 3 && m_type == Type::Invalid) { + // Circle check is not used for Convex / Custom shapes, fill it with something reasonable. + m_circle = Geometry::smallest_enclosing_circle_welzl(m_convex_hull.points); + m_type = (m_convex_hull.area() - m_area) < sqr(SCALED_EPSILON) ? Type::Convex : Type::Custom; + // Initialize the top / bottom decomposition for inside convex polygon check. Do it with two different epsilons applied. + auto convex_decomposition = [](const Polygon &in, double epsilon) { + Polygon src = expand(in, float(epsilon)).front(); + std::vector pts; + pts.reserve(src.size()); + for (const Point &pt : src.points) + pts.emplace_back(unscaled(pt.cast().eval())); + return Geometry::decompose_convex_polygon_top_bottom(pts); + }; + m_top_bottom_convex_hull_decomposition_scene = convex_decomposition(m_convex_hull, SceneEpsilon); + m_top_bottom_convex_hull_decomposition_bed = convex_decomposition(m_convex_hull, BedEpsilon); + } + + BOOST_LOG_TRIVIAL(debug) << "BuildVolume bed_shape clasified as: " << this->type_name(); +} + +#if 0 +// Tests intersections of projected triangles, not just their vertices against a bounding box. +// This test also correctly evaluates collision of a non-convex object with the bounding box. +// Not used, slower than simple bounding box collision check and nobody complained about the inaccuracy of the simple test. +static inline BuildVolume::ObjectState rectangle_test(const indexed_triangle_set &its, const Transform3f &trafo, const Vec2f min, const Vec2f max, const float max_z) +{ + bool inside = false; + bool outside = false; + + auto sign = [](const Vec3f& pt) -> char { return pt.z() > 0 ? 1 : pt.z() < 0 ? -1 : 0; }; + + // Returns true if both inside and outside are set, thus early exit. + auto test_intersection = [&inside, &outside, min, max, max_z](const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) -> bool { + // First test whether the triangle is completely inside or outside the bounding box. + Vec3f pmin = p1.cwiseMin(p2).cwiseMin(p3); + Vec3f pmax = p1.cwiseMax(p2).cwiseMax(p3); + bool tri_inside = false; + bool tri_outside = false; + if (pmax.x() < min.x() || pmin.x() > max.x() || pmax.y() < min.y() || pmin.y() > max.y()) { + // Separated by one of the rectangle sides. + tri_outside = true; + } else if (pmin.x() >= min.x() && pmax.x() <= max.x() && pmin.y() >= min.y() && pmax.y() <= max.y()) { + // Fully inside the rectangle. + tri_inside = true; + } else { + // Bounding boxes overlap. Test triangle sides against the bbox corners. + Vec2f v1(- p2.y() + p1.y(), p2.x() - p1.x()); + Vec2f v2(- p2.y() + p2.y(), p3.x() - p2.x()); + Vec2f v3(- p1.y() + p3.y(), p1.x() - p3.x()); + bool ccw = cross2(v1, v2) > 0; + for (const Vec2f &p : { Vec2f{ min.x(), min.y() }, Vec2f{ min.x(), max.y() }, Vec2f{ max.x(), min.y() }, Vec2f{ max.x(), max.y() } }) { + auto dot = v1.dot(p); + if (ccw ? dot >= 0 : dot <= 0) + tri_inside = true; + else + tri_outside = true; + } + } + inside |= tri_inside; + outside |= tri_outside; + return inside && outside; + }; + + // Edge crosses the z plane. Calculate intersection point with the plane. + auto clip_edge = [](const Vec3f &p1, const Vec3f &p2) -> Vec3f { + const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); + return { p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z }; + }; + + // Clip at (p1, p2), p3 must be on the clipping plane. + // Returns true if both inside and outside are set, thus early exit. + auto clip_and_test1 = [&test_intersection, &clip_edge](const Vec3f &p1, const Vec3f &p2, const Vec3f &p3, bool p1above) -> bool { + Vec3f pa = clip_edge(p1, p2); + return p1above ? test_intersection(p1, pa, p3) : test_intersection(pa, p2, p3); + }; + + // Clip at (p1, p2) and (p2, p3). + // Returns true if both inside and outside are set, thus early exit. + auto clip_and_test2 = [&test_intersection, &clip_edge](const Vec3f &p1, const Vec3f &p2, const Vec3f &p3, bool p2above) -> bool { + Vec3f pa = clip_edge(p1, p2); + Vec3f pb = clip_edge(p2, p3); + return p2above ? test_intersection(pa, p2, pb) : test_intersection(p1, pa, p3) || test_intersection(p3, pa, pb); + }; + + for (const stl_triangle_vertex_indices &tri : its.indices) { + const Vec3f pts[3] = { trafo * its.vertices[tri(0)], trafo * its.vertices[tri(1)], trafo * its.vertices[tri(2)] }; + char signs[3] = { sign(pts[0]), sign(pts[1]), sign(pts[2]) }; + bool clips[3] = { signs[0] * signs[1] == -1, signs[1] * signs[2] == -1, signs[2] * signs[0] == -1 }; + if (clips[0]) { + if (clips[1]) { + // Clipping at (pt0, pt1) and (pt1, pt2). + if (clip_and_test2(pts[0], pts[1], pts[2], signs[1] > 0)) + break; + } else if (clips[2]) { + // Clipping at (pt0, pt1) and (pt0, pt2). + if (clip_and_test2(pts[2], pts[0], pts[1], signs[0] > 0)) + break; + } else { + // Clipping at (pt0, pt1), pt2 must be on the clipping plane. + if (clip_and_test1(pts[0], pts[1], pts[2], signs[0] > 0)) + break; + } + } else if (clips[1]) { + if (clips[2]) { + // Clipping at (pt1, pt2) and (pt0, pt2). + if (clip_and_test2(pts[0], pts[1], pts[2], signs[1] > 0)) + break; + } else { + // Clipping at (pt1, pt2), pt0 must be on the clipping plane. + if (clip_and_test1(pts[1], pts[2], pts[0], signs[1] > 0)) + break; + } + } else if (clips[2]) { + // Clipping at (pt0, pt2), pt1 must be on the clipping plane. + if (clip_and_test1(pts[2], pts[0], pts[1], signs[2] > 0)) + break; + } else if (signs[0] >= 0 && signs[1] >= 0 && signs[2] >= 0) { + // The triangle is above or on the clipping plane. + if (test_intersection(pts[0], pts[1], pts[2])) + break; + } + } + return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside; +} +#endif + +// Trim the input transformed triangle mesh with print bed and test the remaining vertices with is_inside callback. +// Return inside / colliding / outside state. +template +BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, InsideFn is_inside) +{ + size_t num_inside = 0; + size_t num_above = 0; + bool inside = false; + bool outside = false; + static constexpr const auto world_min_z = float(-BuildVolume::SceneEpsilon); + + if (may_be_below_bed) + { + // Slower test, needs to clip the object edges with the print bed plane. + // 1) Allocate transformed vertices with their position with respect to print bed surface. + std::vector sides; + sides.reserve(its.vertices.size()); + + const auto sign = [](const stl_vertex& pt) { return pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0; }; + + for (const stl_vertex &v : its.vertices) { + const stl_vertex pt = trafo * v; + const int s = sign(pt); + sides.emplace_back(s); + if (s >= 0) { + // Vertex above or on print bed surface. Test whether it is inside the build volume. + ++ num_above; + if (is_inside(pt)) + ++ num_inside; + } + } + + // 2) Calculate intersections of triangle edges with the build surface. + inside = num_inside > 0; + outside = num_inside < num_above; + if (num_above < its.vertices.size() && ! (inside && outside)) { + // Not completely above the build surface and status may still change by testing edges intersecting the build platform. + for (const stl_triangle_vertex_indices &tri : its.indices) { + const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] }; + if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) { + // Some edge of this triangle intersects the build platform. Calculate the intersection. + int iprev = 2; + for (int iedge = 0; iedge < 3; ++ iedge) { + if (s[iprev] * s[iedge] == -1) { + // edge intersects the build surface. Calculate intersection point. + const stl_vertex p1 = trafo * its.vertices[tri(iprev)]; + const stl_vertex p2 = trafo * its.vertices[tri(iedge)]; + assert(sign(p1) == s[iprev]); + assert(sign(p2) == s[iedge]); + assert(p1.z() * p2.z() < 0); + // Edge crosses the z plane. Calculate intersection point with the plane. + const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); + (is_inside(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)) ? inside : outside) = true; + } + iprev = iedge; + } + if (inside && outside) + break; + } + } + } + } + else + { + // Much simpler and faster code, not clipping the object with the print bed. + assert(! may_be_below_bed); + num_above = its.vertices.size(); + for (const stl_vertex &v : its.vertices) { + const stl_vertex pt = trafo * v; + assert(pt.z() >= world_min_z); + if (is_inside(pt)) + ++ num_inside; + } + inside = num_inside > 0; + outside = num_inside < num_above; + } + + return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside; +} + +BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed) const +{ + switch (m_type) { + case Type::Rectangle: + { + BoundingBox3Base build_volume = this->bounding_volume().inflated(SceneEpsilon); + BoundingBox3Base build_volumef(build_volume.min.cast(), build_volume.max.cast()); + if (m_max_print_height == 0) + build_volume.max.z() = std::numeric_limits::max(); + // The following test correctly interprets intersection of a non-convex object with a rectangular build volume. + //return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z()); + //FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume. + return object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); }); + } + case Type::Circle: + { + Geometry::Circlef circle { unscaled(m_circle.center), unscaled(m_circle.radius + SceneEpsilon) }; + return m_max_print_height == 0 ? + object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) : + object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); }); + } + case Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case Type::Custom: + return m_max_print_height == 0 ? + object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }) : + object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast()); }); + case Type::Invalid: + default: + return ObjectState::Inside; + } +} + +BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3 &volume_bbox) const +{ + assert(m_type == Type::Rectangle); + BoundingBox3Base build_volume = this->bounding_volume().inflated(SceneEpsilon); + if (m_max_print_height == 0) + build_volume.max.z() = std::numeric_limits::max(); + return build_volume.contains(volume_bbox) ? ObjectState::Inside : build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside; +} + +bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const BoundingBoxf3 &paths_bbox) const +{ + auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) { + return move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.f && move.height != 0.f; + }; + static constexpr const double epsilon = BedEpsilon; + + switch (m_type) { + case Type::Rectangle: + { + BoundingBox3Base build_volume = this->bounding_volume().inflated(epsilon); + if (m_max_print_height == 0) + build_volume.max.z() = std::numeric_limits::max(); + return build_volume.contains(paths_bbox); + } + case Type::Circle: + { + const Vec2f c = unscaled(m_circle.center); + const float r = unscaled(m_circle.radius) + epsilon; + const float r2 = sqr(r); + return m_max_print_height == 0 ? + std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move) + { return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) : + std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move) + { return ! move_valid(move) || ((to_2d(move.position) - c).squaredNorm() <= r2 && move.position.z() <= z); }); + } + case Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case Type::Custom: + return m_max_print_height == 0 ? + std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move) + { return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast()); }) : + std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move) + { return ! move_valid(move) || (Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast()) && move.position.z() <= z); }); + default: + return true; + } +} + +template +inline bool all_inside_vertices_normals_interleaved(const std::vector &paths, Fn fn) +{ + for (auto it = paths.begin(); it != paths.end(); ) { + it += 3; + if (! fn({ *it, *(it + 1), *(it + 2) })) + return false; + it += 3; + } + return true; +} + +bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::vector &paths, const Eigen::AlignedBox &paths_bbox) const +{ + assert(paths.size() % 6 == 0); + static constexpr const double epsilon = BedEpsilon; + switch (m_type) { + case Type::Rectangle: + { + BoundingBox3Base build_volume = this->bounding_volume().inflated(epsilon); + if (m_max_print_height == 0) + build_volume.max.z() = std::numeric_limits::max(); + return build_volume.contains(paths_bbox.min().cast()) && build_volume.contains(paths_bbox.max().cast()); + } + case Type::Circle: + { + const Vec2f c = unscaled(m_circle.center); + const float r = unscaled(m_circle.radius) + float(epsilon); + const float r2 = sqr(r); + return m_max_print_height == 0 ? + all_inside_vertices_normals_interleaved(paths, [c, r2](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2; }) : + all_inside_vertices_normals_interleaved(paths, [c, r2, z = m_max_print_height + epsilon](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; }); + } + case Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case Type::Custom: + return m_max_print_height == 0 ? + all_inside_vertices_normals_interleaved(paths, [this](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast()); }) : + all_inside_vertices_normals_interleaved(paths, [this, z = m_max_print_height + epsilon](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast()) && p.z() <= z; }); + default: + return true; + } +} + +std::string_view BuildVolume::type_name(Type type) +{ + using namespace std::literals; + switch (type) { + case Type::Invalid: return "Invalid"sv; + case Type::Rectangle: return "Rectangle"sv; + case Type::Circle: return "Circle"sv; + case Type::Convex: return "Convex"sv; + case Type::Custom: return "Custom"sv; + } + // make visual studio happy + assert(false); + return {}; +} + +} // namespace Slic3r diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp new file mode 100644 index 000000000..39e994f00 --- /dev/null +++ b/src/libslic3r/BuildVolume.hpp @@ -0,0 +1,120 @@ +#ifndef slic3r_BuildVolume_hpp_ +#define slic3r_BuildVolume_hpp_ + +#include "Point.hpp" +#include "Geometry/Circle.hpp" + +#include + +namespace Slic3r { + +struct GCodeProcessorResult; + +// For collision detection of objects and G-code (extrusion paths) against the build volume. +class BuildVolume +{ +public: + enum class Type : unsigned char + { + // Not set yet or undefined. + Invalid, + // Rectangular print bed. Most common, cheap to work with. + Rectangle, + // Circular print bed. Common on detals, cheap to work with. + Circle, + // Convex print bed. Complex to process. + Convex, + // Some non convex shape. + Custom + }; + + // Initialized to empty, all zeros, Invalid. + BuildVolume() {} + // Initialize from PrintConfig::bed_shape and PrintConfig::max_print_height + BuildVolume(const std::vector &bed_shape, const double max_print_height); + + // Source data, unscaled coordinates. + const std::vector& bed_shape() const { return m_bed_shape; } + double max_print_height() const { return m_max_print_height; } + + // Derived data + Type type() const { return m_type; } + // Format the type for console output. + static std::string_view type_name(Type type); + std::string_view type_name() const { return type_name(m_type); } + bool valid() const { return m_type != Type::Invalid; } + // Same as bed_shape(), but scaled coordinates. + const Polygon& polygon() const { return m_polygon; } + // Bounding box of polygon(), scaled. + const BoundingBox& bounding_box() const { return m_bbox; } + // Bounding volume of bed_shape(), max_print_height(), unscaled. + const BoundingBoxf3& bounding_volume() const { return m_bboxf; } + BoundingBoxf bounding_volume2d() const { return { to_2d(m_bboxf.min), to_2d(m_bboxf.max) }; }; + + // Center of the print bed, unscaled. + Vec2d bed_center() const { return to_2d(m_bboxf.center()); } + // Convex hull of polygon(), scaled. + const Polygon& convex_hull() const { return m_convex_hull; } + // Smallest enclosing circle of polygon(), scaled. + const Geometry::Circled& circle() const { return m_circle; } + + enum class ObjectState : unsigned char + { + // Inside the build volume, thus printable. + Inside, + // Colliding with the build volume boundary, thus not printable and error is shown. + Colliding, + // Outside of the build volume means the object is ignored: Not printed and no error is shown. + Outside + }; + + // 1) Tests called on the plater. + // Using SceneEpsilon for all tests. + static constexpr const double SceneEpsilon = EPSILON; + // Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing. + // Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state() + // Using SceneEpsilon + ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed) const; + // Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example. + // Called for a rectangular bed: + ObjectState volume_state_bbox(const BoundingBoxf3 &volume_bbox) const; + + // 2) Test called on G-code paths. + // Using BedEpsilon for all tests. + static constexpr const double BedEpsilon = 3. * EPSILON; + // Called on final G-code paths. + //FIXME The test does not take the thickness of the extrudates into account! + bool all_paths_inside(const GCodeProcessorResult &paths, const BoundingBoxf3 &paths_bbox) const; + // Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices. + bool all_paths_inside_vertices_and_normals_interleaved(const std::vector &paths, const Eigen::AlignedBox &bbox) const; + +private: + // Source definition of the print bed geometry (PrintConfig::bed_shape) + std::vector m_bed_shape; + // Source definition of the print volume height (PrintConfig::max_print_height) + double m_max_print_height; + + // Derived values. + Type m_type { Type::Invalid }; + // Geometry of the print bed, scaled copy of m_bed_shape. + Polygon m_polygon; + // Scaled snug bounding box around m_polygon. + BoundingBox m_bbox; + // 3D bounding box around m_shape, m_max_print_height. + BoundingBoxf3 m_bboxf; + // Area of m_polygon, scaled. + double m_area { 0. }; + // Convex hull of m_polygon, scaled. + Polygon m_convex_hull; + // For collision detection against a convex build volume. Only filled in for m_type == Convex or Custom. + // Variant with SceneEpsilon applied. + std::pair, std::vector> m_top_bottom_convex_hull_decomposition_scene; + // Variant with BedEpsilon applied. + std::pair, std::vector> m_top_bottom_convex_hull_decomposition_bed; + // Smallest enclosing circle of m_polygon, scaled. + Geometry::Circled m_circle { Vec2d::Zero(), 0 }; +}; + +} // namespace Slic3r + +#endif // slic3r_BuildVolume_hpp_ diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5ad589289..e1a39b8e8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(libslic3r STATIC BridgeDetector.hpp Brim.cpp Brim.hpp + BuildVolume.cpp + BuildVolume.hpp clipper.cpp clipper.hpp ClipperUtils.cpp @@ -116,6 +118,8 @@ add_library(libslic3r STATIC Geometry.hpp Geometry/Circle.cpp Geometry/Circle.hpp + Geometry/ConvexHull.cpp + Geometry/ConvexHull.hpp Geometry/MedialAxis.cpp Geometry/MedialAxis.hpp Geometry/Voronoi.hpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 28fb09313..25454d500 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -542,6 +542,8 @@ static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, return to_polygons(clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } +Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) + { return _clipper(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index bbd91c0fd..d7027e0ec 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -331,6 +331,8 @@ Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons); // Aliases for the various offset(...) functions, conveying the purpose of the offset. +inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) + { assert(delta > 0); return offset(polygon, delta, joinType, miterLimit); } inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) { assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); } inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit) @@ -378,6 +380,7 @@ inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const flo Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); // Safety offset is applied to the clipping polygons only. +Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/ExPolygonCollection.cpp b/src/libslic3r/ExPolygonCollection.cpp index c33df0f29..1359c799a 100644 --- a/src/libslic3r/ExPolygonCollection.cpp +++ b/src/libslic3r/ExPolygonCollection.cpp @@ -1,5 +1,5 @@ #include "ExPolygonCollection.hpp" -#include "Geometry.hpp" +#include "Geometry/ConvexHull.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index da5a33acb..751e1e5ac 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4,7 +4,7 @@ #include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" -#include "Geometry.hpp" +#include "Geometry/ConvexHull.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTower.hpp" #include "ShortestPath.hpp" @@ -622,7 +622,7 @@ std::vector>> GCode::collec namespace DoExport { // static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) // { -// const GCodeProcessor::Result& result = processor.get_result(); +// const GCodeProcessorResult& result = processor.get_result(); // print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); // print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? // get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; @@ -630,7 +630,7 @@ namespace DoExport { static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) { - const GCodeProcessor::Result& result = processor.get_result(); + const GCodeProcessorResult& result = processor.get_result(); print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; @@ -723,7 +723,7 @@ namespace DoExport { } } // namespace DoExport -void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) +void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) { PROFILE_CLEAR(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 93a11821e..8dfe0fdce 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -143,7 +143,7 @@ public: // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). - void do_export(Print* print, const char* path, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); + void do_export(Print* print, const char* path, GCodeProcessorResult* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Vec2d& origin() const { return m_origin; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e146fb517..448ba89aa 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -343,7 +343,7 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } -void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) +void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) { FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; if (in.f == nullptr) @@ -636,7 +636,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st // updates moves' gcode ids which have been modified by the insertion of the M73 lines unsigned int curr_offset_id = 0; unsigned int total_offset = 0; - for (MoveVertex& move : moves) { + for (GCodeProcessorResult::MoveVertex& move : moves) { while (curr_offset_id < static_cast(offsets.size()) && offsets[curr_offset_id].first <= move.gcode_id) { total_offset += offsets[curr_offset_id].second; ++curr_offset_id; @@ -716,8 +716,8 @@ void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) } #if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeProcessor::Result::reset() { - moves = std::vector(); +void GCodeProcessorResult::reset() { + moves = std::vector(); bed_shape = Pointfs(); #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS max_print_height = 0.0f; @@ -731,7 +731,7 @@ void GCodeProcessor::Result::reset() { time = 0; } #else -void GCodeProcessor::Result::reset() { +void GCodeProcessorResult::reset() { moves.clear(); lines_ends.clear(); @@ -1256,7 +1256,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function filament; + std::string printer; + + void reset() { + print.clear(); + filament.clear(); + printer.clear(); + } + }; + + struct MoveVertex + { + unsigned int gcode_id{ 0 }; + EMoveType type{ EMoveType::Noop }; + ExtrusionRole extrusion_role{ erNone }; + unsigned char extruder_id{ 0 }; + unsigned char cp_color_id{ 0 }; + Vec3f position{ Vec3f::Zero() }; // mm + float delta_extruder{ 0.0f }; // mm + float feedrate{ 0.0f }; // mm/s + float width{ 0.0f }; // mm + float height{ 0.0f }; // mm + float mm3_per_mm{ 0.0f }; + float fan_speed{ 0.0f }; // percentage + float temperature{ 0.0f }; // Celsius degrees + float time{ 0.0f }; // s + + float volumetric_rate() const { return feedrate * mm3_per_mm; } + }; + + std::string filename; + unsigned int id; + std::vector moves; + // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. + std::vector lines_ends; + Pointfs bed_shape; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + float max_print_height; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + SettingsIds settings_ids; + size_t extruders_count; + std::vector extruder_colors; + std::vector filament_diameters; + std::vector filament_densities; + PrintEstimatedStatistics print_statistics; + std::vector custom_gcode_per_print_z; + +#if ENABLE_GCODE_VIEWER_STATISTICS + int64_t time{ 0 }; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + void reset(); + }; + + class GCodeProcessor { static const std::vector Reserved_Tags; @@ -190,26 +249,6 @@ namespace Slic3r { float time() const; }; - struct MoveVertex - { - unsigned int gcode_id{ 0 }; - EMoveType type{ EMoveType::Noop }; - ExtrusionRole extrusion_role{ erNone }; - unsigned char extruder_id{ 0 }; - unsigned char cp_color_id{ 0 }; - Vec3f position{ Vec3f::Zero() }; // mm - float delta_extruder{ 0.0f }; // mm - float feedrate{ 0.0f }; // mm/s - float width{ 0.0f }; // mm - float height{ 0.0f }; // mm - float mm3_per_mm{ 0.0f }; - float fan_speed{ 0.0f }; // percentage - float temperature{ 0.0f }; // Celsius degrees - float time{ 0.0f }; // s - - float volumetric_rate() const { return feedrate * mm3_per_mm; } - }; - private: struct TimeMachine { @@ -304,7 +343,7 @@ namespace Slic3r { // post process the file with the given filename to add remaining time lines M73 // and updates moves' gcode ids accordingly - void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends); + void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends); }; struct UsedFilaments // filaments per ColorChange @@ -331,43 +370,6 @@ namespace Slic3r { }; public: - struct Result - { - struct SettingsIds - { - std::string print; - std::vector filament; - std::string printer; - - void reset() { - print.clear(); - filament.clear(); - printer.clear(); - } - }; - std::string filename; - unsigned int id; - std::vector moves; - // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. - std::vector lines_ends; - Pointfs bed_shape; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - float max_print_height; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - SettingsIds settings_ids; - size_t extruders_count; - std::vector extruder_colors; - std::vector filament_diameters; - std::vector filament_densities; - PrintEstimatedStatistics print_statistics; - std::vector custom_gcode_per_print_z; - -#if ENABLE_GCODE_VIEWER_STATISTICS - int64_t time{ 0 }; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - void reset(); - }; - class SeamsDetector { bool m_active{ false }; @@ -394,12 +396,12 @@ namespace Slic3r { // custom gcode markes class OptionsZCorrector { - Result& m_result; + GCodeProcessorResult& m_result; std::optional m_move_id; std::optional m_custom_gcode_per_print_z_id; public: - explicit OptionsZCorrector(Result& result) : m_result(result) { + explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) { } void set() { @@ -413,7 +415,7 @@ namespace Slic3r { const Vec3f position = m_result.moves.back().position; - MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); + GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); move.position = position; move.height = height; m_result.moves.erase(m_result.moves.begin() + *m_move_id); @@ -562,7 +564,7 @@ namespace Slic3r { TimeProcessor m_time_processor; UsedFilaments m_used_filaments; - Result m_result; + GCodeProcessorResult m_result; static unsigned int s_result_id; #if ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -582,8 +584,8 @@ namespace Slic3r { void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void reset(); - const Result& get_result() const { return m_result; } - Result&& extract_result() { return std::move(m_result); } + const GCodeProcessorResult& get_result() const { return m_result; } + GCodeProcessorResult&& extract_result() { return std::move(m_result); } // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 4c24116e4..4466d0dcc 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -24,107 +24,8 @@ #define BOOST_NO_CXX17_HDR_STRING_VIEW #endif -#include - namespace Slic3r { namespace Geometry { -// This implementation is based on Andrew's monotone chain 2D convex hull algorithm -Polygon convex_hull(Points pts) -{ - std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a.x() < b.x() || (a.x() == b.x() && a.y() < b.y()); }); - pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a.x() == b.x() && a.y() == b.y(); }), pts.end()); - - Polygon hull; - int n = (int)pts.size(); - if (n >= 3) { - int k = 0; - hull.points.resize(2 * n); - // Build lower hull - for (int i = 0; i < n; ++ i) { - while (k >= 2 && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) - -- k; - hull[k ++] = pts[i]; - } - // Build upper hull - for (int i = n-2, t = k+1; i >= 0; i--) { - while (k >= t && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) - -- k; - hull[k ++] = pts[i]; - } - hull.points.resize(k); - assert(hull.points.front() == hull.points.back()); - hull.points.pop_back(); - } - return hull; -} - -Pointf3s convex_hull(Pointf3s points) -{ - assert(points.size() >= 3); - // sort input points - std::sort(points.begin(), points.end(), [](const Vec3d &a, const Vec3d &b){ return a.x() < b.x() || (a.x() == b.x() && a.y() < b.y()); }); - - int n = points.size(), k = 0; - Pointf3s hull; - - if (n >= 3) - { - hull.resize(2 * n); - - // Build lower hull - for (int i = 0; i < n; ++i) - { - Point p = Point::new_scale(points[i](0), points[i](1)); - while (k >= 2) - { - Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); - Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); - - if (p.ccw(k2, k1) <= 0) - --k; - else - break; - } - - hull[k++] = points[i]; - } - - // Build upper hull - for (int i = n - 2, t = k + 1; i >= 0; --i) - { - Point p = Point::new_scale(points[i](0), points[i](1)); - while (k >= t) - { - Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); - Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); - - if (p.ccw(k2, k1) <= 0) - --k; - else - break; - } - - hull[k++] = points[i]; - } - - hull.resize(k); - - assert(hull.front() == hull.back()); - hull.pop_back(); - } - - return hull; -} - -Polygon convex_hull(const Polygons &polygons) -{ - Points pp; - for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { - pp.insert(pp.end(), p->points.begin(), p->points.end()); - } - return convex_hull(std::move(pp)); -} - bool directions_parallel(double angle1, double angle2, double max_diff) { double diff = fabs(angle1 - angle2); @@ -761,205 +662,4 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) return (axis.z() < 0) ? -angle : angle; } -namespace rotcalip { - -using int256_t = boost::multiprecision::int256_t; -using int128_t = boost::multiprecision::int128_t; - -template -inline Scalar magnsq(const Point &p) -{ - return Scalar(p.x()) * p.x() + Scalar(p.y()) * p.y(); -} - -template -inline Scalar dot(const Point &a, const Point &b) -{ - return Scalar(a.x()) * b.x() + Scalar(a.y()) * b.y(); -} - -template -inline Scalar dotperp(const Point &a, const Point &b) -{ - return Scalar(a.x()) * b.y() - Scalar(a.y()) * b.x(); -} - -using boost::multiprecision::abs; - -// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle -// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0 -// if they are equal and 1 if alpha is greater than beta. Note that dir is -// reversed for beta, because it represents the opposite side of a caliper. -int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) { - int128_t dotA = dot(dir, dirA); - int128_t dotB = dot(-dir, dirB); - int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(abs(dotA)) * dotA; - int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(abs(dotB)) * dotB; - int256_t diff = dcosa - dcosb; - - return diff > 0? -1 : (diff < 0 ? 1 : 0); -} - -// A helper class to navigate on a polygon. Given a vertex index, one can -// get the edge belonging to that vertex, the coordinates of the vertex, the -// next and previous edges. Stuff that is needed in the rotating calipers algo. -class Idx -{ - size_t m_idx; - const Polygon *m_poly; -public: - explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {} - explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {} - - size_t idx() const { return m_idx; } - void set_idx(size_t i) { m_idx = i; } - size_t next() const { return (m_idx + 1) % m_poly->size(); } - size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); } - Point prev_dir() const { - return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()]; - } - - const Point &pt() const { return (*m_poly)[m_idx]; } - const Point dir() const { return (*m_poly)[next()] - pt(); } - const Point next_dir() const - { - return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()]; - } - const Polygon &poly() const { return *m_poly; } -}; - -enum class AntipodalVisitMode { Full, EdgesOnly }; - -// Visit all antipodal pairs starting from the initial ia, ib pair which -// has to be a valid antipodal pair (not checked). fn is called for every -// antipodal pair encountered including the initial one. -// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir) -// where i,j are the vertex indices of the antipodal pair and dir is the -// direction of the calipers touching the i vertex. -template -void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn) -{ - // Set current caliper direction to be the lower edge angle from X axis - int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir()); - Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia; - Idx *initial = current; - bool visitor_continue = true; - - size_t start = initial->idx(); - bool finished = false; - - while (visitor_continue && !finished) { - Point current_dir_a = current == &ia ? current->dir() : -current->dir(); - visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a); - - // Parallel edges encountered. An additional pair of antipodals - // can be yielded. - if constexpr (mode == AntipodalVisitMode::Full) - if (cmp == 0 && visitor_continue) { - visitor_continue = fn(current == &ia ? ia.idx() : ia.next(), - current == &ib ? ib.idx() : ib.next(), - current_dir_a); - } - - cmp = cmp_angles(current->dir(), current->next_dir(), other->dir()); - - current->inc(); - if (cmp > 0) { - std::swap(current, other); - } - - if (initial->idx() == start) finished = true; - } -} - -} // namespace rotcalip - -bool convex_polygons_intersect(const Polygon &A, const Polygon &B) -{ - using namespace rotcalip; - - // Establish starting antipodals as extremes in XY plane. Use the - // easily obtainable bounding boxes to check if A and B is disjoint - // and return false if the are. - struct BB - { - size_t xmin = 0, xmax = 0, ymin = 0, ymax = 0; - const Polygon &P; - static bool cmpy(const Point &l, const Point &u) - { - return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x()); - } - - BB(const Polygon &poly): P{poly} - { - for (size_t i = 0; i < P.size(); ++i) { - if (P[i] < P[xmin]) xmin = i; - if (P[xmax] < P[i]) xmax = i; - if (cmpy(P[i], P[ymin])) ymin = i; - if (cmpy(P[ymax], P[i])) ymax = i; - } - } - }; - - BB bA{A}, bB{B}; - BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}}; - BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}}; - -// if (!bbA.overlap(bbB)) -// return false; - - // Establish starting antipodals as extreme vertex pairs in X or Y direction - // which reside on different polygons. If no such pair is found, the two - // polygons are certainly not disjoint. - Idx imin{bA.xmin, A}, imax{bB.xmax, B}; - if (B[bB.xmin] < imin.pt()) imin = Idx{bB.xmin, B}; - if (imax.pt() < A[bA.xmax]) imax = Idx{bA.xmax, A}; - if (&imin.poly() == &imax.poly()) { - imin = Idx{bA.ymin, A}; - imax = Idx{bB.ymax, B}; - if (B[bB.ymin] < imin.pt()) imin = Idx{bB.ymin, B}; - if (imax.pt() < A[bA.ymax]) imax = Idx{bA.ymax, A}; - } - - if (&imin.poly() == &imax.poly()) - return true; - - bool found_divisor = false; - visit_antipodals( - imin, imax, - [&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) { - // std::cout << "A" << ia << " B" << ib << " dir " << - // dir.x() << " " << dir.y() << std::endl; - const Polygon &A = imin.poly(), &B = imax.poly(); - - Point ref_a = A[(ia + 2) % A.size()], ref_b = B[(ib + 2) % B.size()]; - - bool is_left_a = dotperp( dir, ref_a - A[ia]) > 0; - bool is_left_b = dotperp(-dir, ref_b - B[ib]) > 0; - - // If both reference points are on the left (or right) of their - // respective support lines and the opposite support line is to - // the right (or left), the divisor line is found. We only test - // the reference point, as by definition, if that is on one side, - // all the other points must be on the same side of a support - // line. If the support lines are collinear, the polygons must be - // on the same side of their respective support lines. - - auto d = dotperp(dir, B[ib] - A[ia]); - if (d == 0) { - // The caliper lines are collinear, not just parallel - found_divisor = (is_left_a && is_left_b) || (!is_left_a && !is_left_b); - } else if (d > 0) { // B is to the left of (A, A+1) - found_divisor = !is_left_a && !is_left_b; - } else { // B is to the right of (A, A+1) - found_divisor = is_left_a && is_left_b; - } - - return !found_divisor; - }); - - // Intersects if the divisor was not found - return !found_divisor; -} - }} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 179f062ca..769f1d6df 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -72,32 +72,6 @@ static inline bool is_ccw(const Polygon &poly) return o == ORIENTATION_CCW; } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -// returns true if the given polygons are identical -static inline bool are_approx(const Polygon& lhs, const Polygon& rhs) -{ - if (lhs.points.size() != rhs.points.size()) - return false; - - size_t rhs_id = 0; - while (rhs_id < rhs.points.size()) { - if (rhs.points[rhs_id].isApprox(lhs.points.front())) - break; - ++rhs_id; - } - - if (rhs_id == rhs.points.size()) - return false; - - for (size_t i = 0; i < lhs.points.size(); ++i) { - if (!lhs.points[i].isApprox(rhs.points[(i + rhs_id) % lhs.points.size()])) - return false; - } - - return true; -} -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res) { double denom = v1(0) * v2(1) - v2(0) * v1(1); @@ -313,10 +287,6 @@ bool liang_barsky_line_clipping( return liang_barsky_line_clipping(x0clip, x1clip, bbox); } -Pointf3s convex_hull(Pointf3s points); -Polygon convex_hull(Points points); -Polygon convex_hull(const Polygons &polygons); - bool directions_parallel(double angle1, double angle2, double max_diff = 0); #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS bool directions_perpendicular(double angle1, double angle2, double max_diff = 0); @@ -479,10 +449,6 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation) return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z()); } -// Returns true if the intersection of the two convex polygons A and B -// is not an empty set. -bool convex_polygons_intersect(const Polygon &A, const Polygon &B); - } } // namespace Slicer::Geometry #endif diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index bfa1602f4..4d7c38ccc 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -3,6 +3,7 @@ #include "../Polygon.hpp" #include +#include #include namespace Slic3r { namespace Geometry { @@ -94,4 +95,44 @@ Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, con return center + centroid; } +Circled circle_taubin_newton(const Vec2ds& input, size_t cycles) +{ + Circled out; + if (input.size() < 3) { + out = Circled::make_invalid(); + } else { + out.center = circle_center_taubin_newton(input, cycles); + out.radius = std::accumulate(input.begin(), input.end(), 0., [&out](double acc, const Vec2d& pt) { return (pt - out.center).norm() + acc; }); + out.radius /= double(input.size()); + } + return out; +} + +Circled circle_ransac(const Vec2ds& input, size_t iterations) +{ + if (input.size() < 3) + return Circled::make_invalid(); + + std::mt19937 rng; + std::vector samples; + Circled circle_best = Circled::make_invalid(); + double err_min = std::numeric_limits::max(); + for (size_t iter = 0; iter < iterations; ++ iter) { + samples.clear(); + std::sample(input.begin(), input.end(), std::back_inserter(samples), 3, rng); + Circled c; + c.center = Geometry::circle_center(samples[0], samples[1], samples[2], EPSILON); + c.radius = std::accumulate(input.begin(), input.end(), 0., [&c](double acc, const Vec2d& pt) { return (pt - c.center).norm() + acc; }); + c.radius /= double(input.size()); + double err = 0; + for (const Vec2d &pt : input) + err = std::max(err, std::abs((pt - c.center).norm() - c.radius)); + if (err < err_min) { + err_min = err; + circle_best = c; + } + } + return circle_best; +} + } } // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp index a8a993f8b..39973d916 100644 --- a/src/libslic3r/Geometry/Circle.hpp +++ b/src/libslic3r/Geometry/Circle.hpp @@ -7,14 +7,6 @@ namespace Slic3r { namespace Geometry { -/// Find the center of the circle corresponding to the vector of Points as an arc. -Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20); -inline Point circle_center_taubin_newton(const Points& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); } - -/// Find the center of the circle corresponding to the vector of Pointfs as an arc. -Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20); -inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); } - // https://en.wikipedia.org/wiki/Circumscribed_circle // Circumcenter coordinates, Cartesian coordinates template @@ -100,6 +92,18 @@ using Circled = Circle; using CircleSqf = CircleSq; using CircleSqd = CircleSq; +/// Find the center of the circle corresponding to the vector of Points as an arc. +Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20); +inline Point circle_center_taubin_newton(const Points& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); } + +/// Find the center of the circle corresponding to the vector of Pointfs as an arc. +Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_start, const Vec2ds::const_iterator& input_end, size_t cycles = 20); +inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20) { return circle_center_taubin_newton(input.cbegin(), input.cend(), cycles); } +Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20); + +// Find circle using RANSAC randomized algorithm. +Circled circle_ransac(const Vec2ds& input, size_t iterations = 20); + // Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon. template CircleSq smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon) diff --git a/src/libslic3r/Geometry/ConvexHull.cpp b/src/libslic3r/Geometry/ConvexHull.cpp new file mode 100644 index 000000000..80b417e61 --- /dev/null +++ b/src/libslic3r/Geometry/ConvexHull.cpp @@ -0,0 +1,398 @@ +#include "libslic3r.h" +#include "ConvexHull.hpp" + +#include + +namespace Slic3r { namespace Geometry { + +// This implementation is based on Andrew's monotone chain 2D convex hull algorithm +Polygon convex_hull(Points pts) +{ + std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a.x() < b.x() || (a.x() == b.x() && a.y() < b.y()); }); + pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a.x() == b.x() && a.y() == b.y(); }), pts.end()); + + Polygon hull; + int n = (int)pts.size(); + if (n >= 3) { + int k = 0; + hull.points.resize(2 * n); + // Build lower hull + for (int i = 0; i < n; ++ i) { + while (k >= 2 && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) + -- k; + hull[k ++] = pts[i]; + } + // Build upper hull + for (int i = n-2, t = k+1; i >= 0; i--) { + while (k >= t && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) + -- k; + hull[k ++] = pts[i]; + } + hull.points.resize(k); + assert(hull.points.front() == hull.points.back()); + hull.points.pop_back(); + } + return hull; +} + +Pointf3s convex_hull(Pointf3s points) +{ + assert(points.size() >= 3); + // sort input points + std::sort(points.begin(), points.end(), [](const Vec3d &a, const Vec3d &b){ return a.x() < b.x() || (a.x() == b.x() && a.y() < b.y()); }); + + int n = points.size(), k = 0; + Pointf3s hull; + + if (n >= 3) + { + hull.resize(2 * n); + + // Build lower hull + for (int i = 0; i < n; ++i) + { + Point p = Point::new_scale(points[i](0), points[i](1)); + while (k >= 2) + { + Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); + Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); + + if (p.ccw(k2, k1) <= 0) + --k; + else + break; + } + + hull[k++] = points[i]; + } + + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; --i) + { + Point p = Point::new_scale(points[i](0), points[i](1)); + while (k >= t) + { + Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1)); + Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1)); + + if (p.ccw(k2, k1) <= 0) + --k; + else + break; + } + + hull[k++] = points[i]; + } + + hull.resize(k); + + assert(hull.front() == hull.back()); + hull.pop_back(); + } + + return hull; +} + +Polygon convex_hull(const Polygons &polygons) +{ + Points pp; + for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { + pp.insert(pp.end(), p->points.begin(), p->points.end()); + } + return convex_hull(std::move(pp)); +} + + +namespace rotcalip { + +using int256_t = boost::multiprecision::int256_t; +using int128_t = boost::multiprecision::int128_t; + +template +inline Scalar magnsq(const Point &p) +{ + return Scalar(p.x()) * p.x() + Scalar(p.y()) * p.y(); +} + +template +inline Scalar dot(const Point &a, const Point &b) +{ + return Scalar(a.x()) * b.x() + Scalar(a.y()) * b.y(); +} + +template +inline Scalar dotperp(const Point &a, const Point &b) +{ + return Scalar(a.x()) * b.y() - Scalar(a.y()) * b.x(); +} + +using boost::multiprecision::abs; + +// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle +// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0 +// if they are equal and 1 if alpha is greater than beta. Note that dir is +// reversed for beta, because it represents the opposite side of a caliper. +int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) { + int128_t dotA = dot(dir, dirA); + int128_t dotB = dot(-dir, dirB); + int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(abs(dotA)) * dotA; + int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(abs(dotB)) * dotB; + int256_t diff = dcosa - dcosb; + + return diff > 0? -1 : (diff < 0 ? 1 : 0); +} + +// A helper class to navigate on a polygon. Given a vertex index, one can +// get the edge belonging to that vertex, the coordinates of the vertex, the +// next and previous edges. Stuff that is needed in the rotating calipers algo. +class Idx +{ + size_t m_idx; + const Polygon *m_poly; +public: + explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {} + explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {} + + size_t idx() const { return m_idx; } + void set_idx(size_t i) { m_idx = i; } + size_t next() const { return (m_idx + 1) % m_poly->size(); } + size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); } + Point prev_dir() const { + return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()]; + } + + const Point &pt() const { return (*m_poly)[m_idx]; } + const Point dir() const { return (*m_poly)[next()] - pt(); } + const Point next_dir() const + { + return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()]; + } + const Polygon &poly() const { return *m_poly; } +}; + +enum class AntipodalVisitMode { Full, EdgesOnly }; + +// Visit all antipodal pairs starting from the initial ia, ib pair which +// has to be a valid antipodal pair (not checked). fn is called for every +// antipodal pair encountered including the initial one. +// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir) +// where i,j are the vertex indices of the antipodal pair and dir is the +// direction of the calipers touching the i vertex. +template +void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn) +{ + // Set current caliper direction to be the lower edge angle from X axis + int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir()); + Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia; + Idx *initial = current; + bool visitor_continue = true; + + size_t start = initial->idx(); + bool finished = false; + + while (visitor_continue && !finished) { + Point current_dir_a = current == &ia ? current->dir() : -current->dir(); + visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a); + + // Parallel edges encountered. An additional pair of antipodals + // can be yielded. + if constexpr (mode == AntipodalVisitMode::Full) + if (cmp == 0 && visitor_continue) { + visitor_continue = fn(current == &ia ? ia.idx() : ia.next(), + current == &ib ? ib.idx() : ib.next(), + current_dir_a); + } + + cmp = cmp_angles(current->dir(), current->next_dir(), other->dir()); + + current->inc(); + if (cmp > 0) { + std::swap(current, other); + } + + if (initial->idx() == start) finished = true; + } +} + +} // namespace rotcalip + +bool convex_polygons_intersect(const Polygon &A, const Polygon &B) +{ + using namespace rotcalip; + + // Establish starting antipodals as extremes in XY plane. Use the + // easily obtainable bounding boxes to check if A and B is disjoint + // and return false if the are. + struct BB + { + size_t xmin = 0, xmax = 0, ymin = 0, ymax = 0; + const Polygon &P; + static bool cmpy(const Point &l, const Point &u) + { + return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x()); + } + + BB(const Polygon &poly): P{poly} + { + for (size_t i = 0; i < P.size(); ++i) { + if (P[i] < P[xmin]) xmin = i; + if (P[xmax] < P[i]) xmax = i; + if (cmpy(P[i], P[ymin])) ymin = i; + if (cmpy(P[ymax], P[i])) ymax = i; + } + } + }; + + BB bA{A}, bB{B}; + BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}}; + BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}}; + +// if (!bbA.overlap(bbB)) +// return false; + + // Establish starting antipodals as extreme vertex pairs in X or Y direction + // which reside on different polygons. If no such pair is found, the two + // polygons are certainly not disjoint. + Idx imin{bA.xmin, A}, imax{bB.xmax, B}; + if (B[bB.xmin] < imin.pt()) imin = Idx{bB.xmin, B}; + if (imax.pt() < A[bA.xmax]) imax = Idx{bA.xmax, A}; + if (&imin.poly() == &imax.poly()) { + imin = Idx{bA.ymin, A}; + imax = Idx{bB.ymax, B}; + if (B[bB.ymin] < imin.pt()) imin = Idx{bB.ymin, B}; + if (imax.pt() < A[bA.ymax]) imax = Idx{bA.ymax, A}; + } + + if (&imin.poly() == &imax.poly()) + return true; + + bool found_divisor = false; + visit_antipodals( + imin, imax, + [&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) { + // std::cout << "A" << ia << " B" << ib << " dir " << + // dir.x() << " " << dir.y() << std::endl; + const Polygon &A = imin.poly(), &B = imax.poly(); + + Point ref_a = A[(ia + 2) % A.size()], ref_b = B[(ib + 2) % B.size()]; + + bool is_left_a = dotperp( dir, ref_a - A[ia]) > 0; + bool is_left_b = dotperp(-dir, ref_b - B[ib]) > 0; + + // If both reference points are on the left (or right) of their + // respective support lines and the opposite support line is to + // the right (or left), the divisor line is found. We only test + // the reference point, as by definition, if that is on one side, + // all the other points must be on the same side of a support + // line. If the support lines are collinear, the polygons must be + // on the same side of their respective support lines. + + auto d = dotperp(dir, B[ib] - A[ia]); + if (d == 0) { + // The caliper lines are collinear, not just parallel + found_divisor = (is_left_a && is_left_b) || (!is_left_a && !is_left_b); + } else if (d > 0) { // B is to the left of (A, A+1) + found_divisor = !is_left_a && !is_left_b; + } else { // B is to the right of (A, A+1) + found_divisor = is_left_a && is_left_b; + } + + return !found_divisor; + }); + + // Intersects if the divisor was not found + return !found_divisor; +} + +// Decompose source convex hull points into a top / bottom chains with monotonically increasing x, +// creating an implicit trapezoidal decomposition of the source convex polygon. +// The source convex polygon has to be CCW oriented. O(n) time complexity. +std::pair, std::vector> decompose_convex_polygon_top_bottom(const std::vector &src) +{ + std::pair, std::vector> out; + std::vector &bottom = out.first; + std::vector &top = out.second; + + // Find the minimum point. + auto left_bottom = std::min_element(src.begin(), src.end(), [](const auto &l, const auto &r) { return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); }); + auto right_top = std::max_element(src.begin(), src.end(), [](const auto &l, const auto &r) { return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); }); + if (left_bottom != src.end() && left_bottom != right_top) { + // Produce the bottom and bottom chains. + if (left_bottom < right_top) { + bottom.assign(left_bottom, right_top + 1); + size_t cnt = (src.end() - right_top) + (left_bottom + 1 - src.begin()); + top.reserve(cnt); + top.assign(right_top, src.end()); + top.insert(top.end(), src.begin(), left_bottom + 1); + } else { + size_t cnt = (src.end() - left_bottom) + (right_top + 1 - src.begin()); + bottom.reserve(cnt); + bottom.assign(left_bottom, src.end()); + bottom.insert(bottom.end(), src.begin(), right_top + 1); + top.assign(right_top, left_bottom + 1); + } + // Remove strictly vertical segments at the end. + if (bottom.size() > 1) { + auto it = bottom.end(); + for (-- it; it != bottom.begin() && (it - 1)->x() == bottom.back().x(); -- it) ; + bottom.erase(it + 1, bottom.end()); + } + if (top.size() > 1) { + auto it = top.end(); + for (-- it; it != top.begin() && (it - 1)->x() == top.back().x(); -- it) ; + top.erase(it + 1, top.end()); + } + std::reverse(top.begin(), top.end()); + } + + if (top.size() < 2 || bottom.size() < 2) { + // invalid + top.clear(); + bottom.clear(); + } + return out; +} + +// Convex polygon check using a top / bottom chain decomposition with O(log n) time complexity. +bool inside_convex_polygon(const std::pair, std::vector> &top_bottom_decomposition, const Vec2d &pt) +{ + auto it_bottom = std::lower_bound(top_bottom_decomposition.first.begin(), top_bottom_decomposition.first.end(), pt, [](const auto &l, const auto &r){ return l.x() < r.x(); }); + auto it_top = std::lower_bound(top_bottom_decomposition.second.begin(), top_bottom_decomposition.second.end(), pt, [](const auto &l, const auto &r){ return l.x() < r.x(); }); + if (it_bottom == top_bottom_decomposition.first.end()) { + // Above max x. + assert(it_top == top_bottom_decomposition.second.end()); + return false; + } + if (it_bottom == top_bottom_decomposition.first.begin()) { + // Below or at min x. + if (pt.x() < it_bottom->x()) { + // Below min x. + assert(pt.x() < it_top->x()); + return false; + } + // At min x. + assert(pt.x() == it_bottom->x()); + assert(pt.x() == it_top->x()); + assert(it_bottom->y() <= pt.y() <= it_top->y()); + return pt.y() >= it_bottom->y() && pt.y() <= it_top->y(); + } + + // Trapezoid or a triangle. + assert(it_bottom != top_bottom_decomposition.first .begin() && it_bottom != top_bottom_decomposition.first .end()); + assert(it_top != top_bottom_decomposition.second.begin() && it_top != top_bottom_decomposition.second.end()); + assert(pt.x() <= it_bottom->x()); + assert(pt.x() <= it_top->x()); + auto it_top_prev = it_top - 1; + auto it_bottom_prev = it_bottom - 1; + assert(pt.x() >= it_top_prev->x()); + assert(pt.x() >= it_bottom_prev->x()); + double det = cross2(*it_bottom - *it_bottom_prev, pt - *it_bottom_prev); + if (det < 0) + return false; + det = cross2(*it_top - *it_top_prev, pt - *it_top_prev); + return det <= 0; +} + +} // namespace Geometry +} // namespace Slic3r + diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp new file mode 100644 index 000000000..03f00af6a --- /dev/null +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -0,0 +1,27 @@ +#ifndef slic3r_Geometry_ConvexHull_hpp_ +#define slic3r_Geometry_ConvexHull_hpp_ + +#include "../Polygon.hpp" + +namespace Slic3r { +namespace Geometry { + +Pointf3s convex_hull(Pointf3s points); +Polygon convex_hull(Points points); +Polygon convex_hull(const Polygons &polygons); + +// Returns true if the intersection of the two convex polygons A and B +// is not an empty set. +bool convex_polygons_intersect(const Polygon &A, const Polygon &B); + +// Decompose source convex hull points into top / bottom chains with monotonically increasing x, +// creating an implicit trapezoidal decomposition of the source convex polygon. +// The source convex polygon has to be CCW oriented. O(n) time complexity. +std::pair, std::vector> decompose_convex_polygon_top_bottom(const std::vector &src); + +// Convex polygon check using a top / bottom chain decomposition with O(log n) time complexity. +bool inside_convex_polygon(const std::pair, std::vector> &top_bottom_decomposition, const Vec2d &pt); + +} } // namespace Slicer::Geometry + +#endif diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index fec6e422c..b5d93205a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,8 +1,9 @@ #include "libslic3r.h" +#include "BuildVolume.hpp" #include "Exception.hpp" #include "Model.hpp" #include "ModelArrange.hpp" -#include "Geometry.hpp" +#include "Geometry/ConvexHull.hpp" #include "MTUtils.hpp" #include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" @@ -26,39 +27,6 @@ namespace Slic3r { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -// Using rotating callipers to check for collision of two convex polygons. Thus both printbed_shape and obj_hull_2d are convex polygons. -ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z) -{ - if (!Geometry::convex_polygons_intersect(printbed_shape, obj_hull_2d)) - return ModelInstancePVS_Fully_Outside; - - bool contained_xy = true; - for (const Point& p : obj_hull_2d) { - if (!printbed_shape.contains(p)) { - contained_xy = false; - break; - } - } - - const bool contained_z = -1e10 < obj_min_z && obj_max_z <= print_volume_height; - return (contained_xy && contained_z) ? ModelInstancePVS_Inside : ModelInstancePVS_Partly_Outside; -} - -/* -ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box) -{ - const Polygon box_hull_2d({ - { scale_(box.min.x()), scale_(box.min.y()) }, - { scale_(box.max.x()), scale_(box.min.y()) }, - { scale_(box.max.x()), scale_(box.max.y()) }, - { scale_(box.min.x()), scale_(box.max.y()) } - }); - return printbed_collision_state(printbed_shape, print_volume_height, box_hull_2d, box.min.z(), box.max.z()); -} -*/ -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Model& Model::assign_copy(const Model &rhs) { this->copy_id(rhs); @@ -363,24 +331,13 @@ BoundingBoxf3 Model::bounding_box() const return bb; } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -// printbed_shape is convex polygon -unsigned int Model::update_print_volume_state(const Polygon& printbed_shape, double print_volume_height) +unsigned int Model::update_print_volume_state(const BuildVolume &build_volume) { unsigned int num_printable = 0; for (ModelObject* model_object : this->objects) - num_printable += model_object->check_instances_print_volume_state(printbed_shape, print_volume_height); + num_printable += model_object->update_instances_print_volume_state(build_volume); return num_printable; } -#else -unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume) -{ - unsigned int num_printable = 0; - for (ModelObject *model_object : this->objects) - num_printable += model_object->check_instances_print_volume_state(print_volume); - return num_printable; -} -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS bool Model::center_instances_around_point(const Vec2d &point) { @@ -1572,9 +1529,7 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const return max_z + inst->get_offset(Z); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -// printbed_shape is convex polygon -unsigned int ModelObject::check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height) +unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume &build_volume) { unsigned int num_printable = 0; enum { @@ -1586,16 +1541,10 @@ unsigned int ModelObject::check_instances_print_volume_state(const Polygon& prin for (const ModelVolume* vol : this->volumes) if (vol->is_model_part()) { const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix(); - const BoundingBoxf3 bb = vol->mesh().transformed_bounding_box(matrix, 0.0); - if (!bb.defined) { - // this may happen if the part is fully below the printbed, leading to a crash in the following call to its_convex_hull_2d_above() - continue; - } - const Polygon volume_hull_2d = its_convex_hull_2d_above(vol->mesh().its, matrix.cast(), 0.0f); - ModelInstanceEPrintVolumeState state = printbed_collision_state(printbed_shape, print_volume_height, volume_hull_2d, bb.min.z(), bb.max.z()); - if (state == ModelInstancePVS_Inside) + BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */); + if (state == BuildVolume::ObjectState::Inside) inside_outside |= INSIDE; - else if (state == ModelInstancePVS_Fully_Outside) + else if (state == BuildVolume::ObjectState::Outside) inside_outside |= OUTSIDE; else inside_outside |= INSIDE | OUTSIDE; @@ -1608,35 +1557,6 @@ unsigned int ModelObject::check_instances_print_volume_state(const Polygon& prin } return num_printable; } -#else -unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) -{ - unsigned int num_printable = 0; - enum { - INSIDE = 1, - OUTSIDE = 2 - }; - for (ModelInstance *model_instance : this->instances) { - unsigned int inside_outside = 0; - for (const ModelVolume *vol : this->volumes) - if (vol->is_model_part()) { - BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->get_matrix() * vol->get_matrix()); - if (print_volume.contains(bb)) - inside_outside |= INSIDE; - else if (print_volume.intersects(bb)) - inside_outside |= INSIDE | OUTSIDE; - else - inside_outside |= OUTSIDE; - } - model_instance->print_volume_state = - (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstancePVS_Partly_Outside : - (inside_outside == INSIDE) ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside; - if (inside_outside == INSIDE) - ++ num_printable; - } - return num_printable; -} -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void ModelObject::print_info() const { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3e11336af..7a1cf206e 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -33,6 +33,7 @@ namespace cereal { namespace Slic3r { enum class ConversionType; +class BuildVolume; class Model; class ModelInstance; class ModelMaterial; @@ -366,13 +367,6 @@ public: double get_instance_min_z(size_t instance_idx) const; double get_instance_max_z(size_t instance_idx) const; - // Called by Print::validate() from the UI thread. -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - unsigned int check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height); -#else - unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - // Print object statistics to console. void print_info() const; @@ -505,6 +499,9 @@ private: sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } + + // Called by Print::validate() from the UI thread. + unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); }; enum class EnforcerBlockerType : int8_t { @@ -908,17 +905,6 @@ enum ModelInstanceEPrintVolumeState : unsigned char ModelInstanceNum_BedStates }; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -// return the state of the given object's volume (extrusion along z of obj_hull_2d from obj_min_z to obj_max_z) -// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height) -// Using rotating callipers to check for collision of two convex polygons. -ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z); -// return the state of the given box -// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height) -// Commented out, using rotating callipers is quite expensive for a bounding box test. -//ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - // A single instance of a ModelObject. // Knows the affine transformation of an object. class ModelInstance final : public ObjectBase @@ -1123,12 +1109,7 @@ public: BoundingBoxf3 bounding_box() const; // Set the print_volume_state of PrintObject::instances, // return total number of printable objects. -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - // printbed_shape is convex polygon - unsigned int update_print_volume_state(const Polygon& printbed_shape, double print_volume_height); -#else - unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + unsigned int update_print_volume_state(const BuildVolume &build_volume); // Returns true if any ModelObject was modified. bool center_instances_around_point(const Vec2d &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index fcea7e14e..01a89a8e5 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,6 +1,7 @@ #include "ModelArrange.hpp" #include +#include #include "MTUtils.hpp" namespace Slic3r { diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index b1f3a74bb..b2427d46d 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -201,6 +201,14 @@ BoundingBox get_extents(const std::vector &pts) return bbox; } +BoundingBoxf get_extents(const std::vector &pts) +{ + BoundingBoxf bbox; + for (const Vec2d &p : pts) + bbox.merge(p); + return bbox; +} + std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf) { return stm << pointf(0) << "," << pointf(1); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 499ab073d..8848279e7 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -16,6 +16,7 @@ namespace Slic3r { class BoundingBox; +class BoundingBoxf; class Line; class MultiPoint; class Point; @@ -133,6 +134,7 @@ public: Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } + static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } // This method allows you to assign Eigen expressions to MyVectorType template @@ -213,6 +215,7 @@ inline Point lerp(const Point &a, const Point &b, double t) BoundingBox get_extents(const Points &pts); BoundingBox get_extents(const std::vector &pts); +BoundingBoxf get_extents(const std::vector &pts); // Test for duplicate points in a vector of points. // The points are copied, sorted and checked for duplicates globally. diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 42a6cc209..c7bbe9706 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -334,6 +334,25 @@ extern std::vector get_extents_vector(const Polygons &polygons) return out; } +// Polygon must be valid (at least three points), collinear points and duplicate points removed. +bool polygon_is_convex(const Points &poly) +{ + if (poly.size() < 3) + return false; + + Point p0 = poly[poly.size() - 2]; + Point p1 = poly[poly.size() - 1]; + for (size_t i = 0; i < poly.size(); ++ i) { + Point p2 = poly[i]; + auto det = cross2((p1 - p0).cast(), (p2 - p1).cast()); + if (det < 0) + return false; + p0 = p1; + p1 = p2; + } + return true; +} + bool has_duplicate_points(const Polygons &polys) { #if 1 diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 29800a31d..7d34e3aae 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -84,6 +84,10 @@ BoundingBox get_extents_rotated(const Polygon &poly, double angle); BoundingBox get_extents_rotated(const Polygons &polygons, double angle); std::vector get_extents_vector(const Polygons &polygons); +// Polygon must be valid (at least three points), collinear points and duplicate points removed. +bool polygon_is_convex(const Points &poly); +inline bool polygon_is_convex(const Polygon &poly) { return polygon_is_convex(poly.points); } + // Test for duplicate points. The points are copied, sorted and checked for duplicates globally. inline bool has_duplicate_points(Polygon &&poly) { return has_duplicate_points(std::move(poly.points)); } inline bool has_duplicate_points(const Polygon &poly) { return has_duplicate_points(poly.points); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 03a3d1e57..086aeb2c4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -5,7 +5,7 @@ #include "ClipperUtils.hpp" #include "Extruder.hpp" #include "Flow.hpp" -#include "Geometry.hpp" +#include "Geometry/ConvexHull.hpp" #include "I18N.hpp" #include "ShortestPath.hpp" #include "SupportMaterial.hpp" @@ -850,7 +850,7 @@ void Print::process() // The export_gcode may die for various reasons (fails to process output_filename_format, // write error into the G-code, cannot execute post-processing scripts). // It is up to the caller to show an error message. -std::string Print::export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) +std::string Print::export_gcode(const std::string& path_template, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) { // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index ac986216e..e818cad0d 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -521,7 +521,7 @@ public: void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). - std::string export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); + std::string export_gcode(const std::string& path_template, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b33dd38cc..365de8fb4 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -4,6 +4,7 @@ #include "MeshSplitImpl.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include "Geometry/ConvexHull.hpp" #include "Point.hpp" #include "Execution/ExecutionTBB.hpp" #include "Execution/ExecutionSeq.hpp" @@ -436,27 +437,55 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c } #if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafo, double world_min_z) const +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafod, double world_min_z) const { - BoundingBoxf3 bbox; - const Transform3f ftrafo = trafo.cast(); - for (const stl_triangle_vertex_indices& tri : its.indices) { - const Vec3f pts[3] = { ftrafo * its.vertices[tri(0)], ftrafo * its.vertices[tri(1)], ftrafo * its.vertices[tri(2)] }; - int iprev = 2; - for (int iedge = 0; iedge < 3; ++iedge) { - const Vec3f& p1 = pts[iprev]; - const Vec3f& p2 = pts[iedge]; - if ((p1.z() < world_min_z && p2.z() > world_min_z) || (p2.z() < world_min_z && p1.z() > world_min_z)) { - // Edge crosses the z plane. Calculate intersection point with the plane. - const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); - bbox.merge(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z).cast()); - } - if (p2.z() >= world_min_z) - bbox.merge(p2.cast()); - iprev = iedge; + // 1) Allocate transformed vertices with their position with respect to print bed surface. + std::vector sides; + size_t num_above = 0; + Eigen::AlignedBox bbox; + Transform3f trafo = trafod.cast(); + sides.reserve(its.vertices.size()); + for (const stl_vertex &v : this->its.vertices) { + const stl_vertex pt = trafo * v; + const int sign = pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0; + sides.emplace_back(sign); + if (sign >= 0) { + // Vertex above or on print bed surface. Test whether it is inside the build volume. + ++ num_above; + bbox.extend(pt); } } - return bbox; + + // 2) Calculate intersections of triangle edges with the build surface. + if (num_above < its.vertices.size()) { + // Not completely above the build surface and status may still change by testing edges intersecting the build platform. + for (const stl_triangle_vertex_indices &tri : its.indices) { + const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] }; + if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) { + // Some edge of this triangle intersects the build platform. Calculate the intersection. + int iprev = 2; + for (int iedge = 0; iedge < 3; ++ iedge) { + if (s[iprev] * s[iedge] == -1) { + // edge intersects the build surface. Calculate intersection point. + const stl_vertex p1 = trafo * its.vertices[tri(iprev)]; + const stl_vertex p2 = trafo * its.vertices[tri(iedge)]; + // Edge crosses the z plane. Calculate intersection point with the plane. + const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); + bbox.extend(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)); + } + iprev = iedge; + } + } + } + } + + BoundingBoxf3 out; + if (! bbox.isEmpty()) { + out.min = bbox.min().cast(); + out.max = bbox.max().cast(); + out.defined = true; + }; + return out; } #endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index ee2fb2e69..9a2107911 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -137,7 +137,7 @@ void Bed3D::Axes::render() const glsafe(::glDisable(GL_DEPTH_TEST)); } -bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) +bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { auto check_texture = [](const std::string& texture) { boost::system::error_code ec; // so the exists call does not throw (e.g. after a permission problem) @@ -149,17 +149,13 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model, ec); }; - EType type; + Type type; std::string model; std::string texture; if (force_as_custom) -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - type = EType::Custom; -#else - type = Custom; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + type = Type::Custom; else { - auto [new_type, system_model, system_texture] = detect_type(shape); + auto [new_type, system_model, system_texture] = detect_type(bed_shape); type = new_type; model = system_model; texture = system_texture; @@ -177,29 +173,18 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c model_filename.clear(); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - EShapeType shape_type = detect_shape_type(shape); - if (m_shape == shape && m_type == type && m_shape_type == shape_type && m_texture_filename == texture_filename && m_model_filename == model_filename) -#else - if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename) -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + + if (m_build_volume.bed_shape() == bed_shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename) // No change, no need to update the UI. return false; - m_shape = shape; + m_type = type; + m_build_volume = BuildVolume { bed_shape, max_print_height }; m_texture_filename = texture_filename; m_model_filename = model_filename; - m_type = type; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - m_shape_type = shape_type; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_extended_bounding_box = this->calc_extended_bounding_box(); - calc_bounding_boxes(); - - ExPolygon poly; - for (const Vec2d& p : m_shape) { - poly.contour.append({ scale_(p(0)), scale_(p(1)) }); - } + ExPolygon poly{ Polygon::new_scale(bed_shape) }; calc_triangles(poly); @@ -208,13 +193,13 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c m_polygon = offset(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0]; - reset(); + this->release_VBOs(); m_texture.reset(); m_model.reset(); // Set the origin and size for rendering the coordinate system axes. m_axes.set_origin({ 0.0, 0.0, static_cast(GROUND_Z) }); - m_axes.set_stem_length(0.1f * static_cast(m_bounding_box.max_size())); + m_axes.set_stem_length(0.1f * static_cast(m_build_volume.bounding_volume().max_size())); // Let the calee to update the UI. return true; @@ -240,85 +225,6 @@ void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_fact render_internal(canvas, bottom, scale_factor, false, false, true); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -bool Bed3D::is_rectangle(const Pointfs& shape, Vec2d* min, Vec2d* max) -{ - const Lines lines = Polygon::new_scale(shape).lines(); - bool ret = lines.size() == 4 && lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3]) && lines[0].perpendicular_to(lines[1]); - if (ret) { - if (min != nullptr) { - *min = shape.front(); - for (const Vec2d& pt : shape) { - min->x() = std::min(min->x(), pt.x()); - min->y() = std::min(min->y(), pt.y()); - } - } - if (max != nullptr) { - *max = shape.front(); - for (const Vec2d& pt : shape) { - max->x() = std::max(max->x(), pt.x()); - max->y() = std::max(max->y(), pt.y()); - } - } - } - return ret; -} - -bool Bed3D::is_circle(const Pointfs& shape, Vec2d* center, double* radius) -{ - if (shape.size() < 3) - return false; - - // Analyze the array of points. - // Do they reside on a circle ? - const Vec2d box_center = Geometry::circle_center_taubin_newton(shape); - - std::vector vertex_distances; - double avg_dist = 0.0; - for (const Vec2d& pt : shape) { - double distance = (pt - box_center).norm(); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - double tolerance = avg_dist * 0.01; - - bool defined_value = true; - for (double el : vertex_distances) { - if (fabs(el - avg_dist) > tolerance) - defined_value = false; - break; - } - - if (center != nullptr) - *center = box_center; - - if (radius != nullptr) - *radius = avg_dist; - - return defined_value; -} - -bool Bed3D::is_convex(const Pointfs& shape) -{ - return Polygon::new_scale(shape).convex_points().size() == shape.size(); -} - -Bed3D::EShapeType Bed3D::detect_shape_type(const Pointfs& shape) -{ - if (shape.size() < 3) - return EShapeType::Invalid; - else if (is_rectangle(shape)) - return EShapeType::Rectangle; - else if (is_circle(shape)) - return EShapeType::Circle; - else - return EShapeType::Custom; -} -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking) { @@ -334,41 +240,31 @@ void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, switch (m_type) { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - case EType::System: { render_system(canvas, bottom, show_texture); break; } + case Type::System: { render_system(canvas, bottom, show_texture); break; } default: - case EType::Custom: { render_custom(canvas, bottom, show_texture, picking); break; } -#else - case System: { render_system(canvas, bottom, show_texture); break; } - default: - case Custom: { render_custom(canvas, bottom, show_texture, picking); break; } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + case Type::Custom: { render_custom(canvas, bottom, show_texture, picking); break; } } glsafe(::glDisable(GL_DEPTH_TEST)); } -void Bed3D::calc_bounding_boxes() const +// Calculate an extended bounding box from axes and current model for visualization purposes. +BoundingBoxf3 Bed3D::calc_extended_bounding_box() const { - BoundingBoxf3* bounding_box = const_cast(&m_bounding_box); - *bounding_box = BoundingBoxf3(); - for (const Vec2d& p : m_shape) { - bounding_box->merge({ p.x(), p.y(), 0.0 }); - } - - BoundingBoxf3* extended_bounding_box = const_cast(&m_extended_bounding_box); - *extended_bounding_box = m_bounding_box; - + BoundingBoxf3 out { m_build_volume.bounding_volume() }; + // Reset the build volume Z, we don't want to zoom to the top of the build volume if it is empty. + out.min.z() = 0; + out.max.z() = 0; // extend to contain axes - extended_bounding_box->merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); - extended_bounding_box->merge(extended_bounding_box->min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, extended_bounding_box->max(2))); - + out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); + out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max(2))); // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { model_bb.translate(m_model_offset); - extended_bounding_box->merge(model_bb); + out.merge(model_bb); } + return out; } void Bed3D::calc_triangles(const ExPolygon& poly) @@ -404,8 +300,9 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) BOOST_LOG_TRIVIAL(error) << "Unable to create bed grid lines\n"; } - -std::tuple Bed3D::detect_type(const Pointfs& shape) const +// Try to match the print bed shape with the shape of an active profile. If such a match exists, +// return the print bed model. +std::tuple Bed3D::detect_type(const Pointfs& shape) { auto bundle = wxGetApp().preset_bundle; if (bundle != nullptr) { @@ -416,11 +313,7 @@ std::tuple Bed3D::detect_type(const Poin std::string model_filename = PresetUtils::system_printer_bed_model(*curr); std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr); if (!model_filename.empty() && !texture_filename.empty()) -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - return { EType::System, model_filename, texture_filename }; -#else - return { System, model_filename, texture_filename }; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return { Type::System, model_filename, texture_filename }; } } @@ -428,16 +321,12 @@ std::tuple Bed3D::detect_type(const Poin } } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - return { EType::Custom, "", "" }; -#else - return { Custom, "", "" }; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return { Type::Custom, {}, {} }; } void Bed3D::render_axes() const { - if (!m_shape.empty()) + if (m_build_volume.valid()) m_axes.render(); } @@ -596,12 +485,10 @@ void Bed3D::render_model() const model->set_color(-1, DEFAULT_MODEL_COLOR); // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad - Vec3d shift = m_bounding_box.center(); - shift(2) = -0.03; - *const_cast(&m_model_offset) = shift; + *const_cast(&m_model_offset) = to_3d(m_build_volume.bounding_volume2d().center(), -0.03); // update extended bounding box - calc_bounding_boxes(); + const_cast(m_extended_bounding_box) = this->calc_extended_bounding_box(); } if (!model->get_filename().empty()) { @@ -673,7 +560,7 @@ void Bed3D::render_default(bool bottom, bool picking) const } } -void Bed3D::reset() +void Bed3D::release_VBOs() { if (m_vbo_id > 0) { glsafe(::glDeleteBuffers(1, &m_vbo_id)); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 07b9f1758..639dc6c16 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -5,6 +5,8 @@ #include "3DScene.hpp" #include "GLModel.hpp" +#include + #include #include @@ -62,41 +64,22 @@ class Bed3D }; public: -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - enum class EType : unsigned char + enum class Type : unsigned char { + // The print bed model and texture are available from some printer preset. System, + // The print bed model is unknown, thus it is rendered procedurally. Custom }; - enum class EShapeType : unsigned char - { - Rectangle, - Circle, - Custom, - Invalid - }; -#else - enum EType : unsigned char - { - System, - Custom, - Num_Types - }; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - private: -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - EType m_type{ EType::Custom }; - EShapeType m_shape_type{ EShapeType::Invalid }; -#else - EType m_type{ Custom }; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Pointfs m_shape; + BuildVolume m_build_volume; + Type m_type{ Type::Custom }; std::string m_texture_filename; std::string m_model_filename; - BoundingBoxf3 m_bounding_box; + // Print volume bounding box exteded with axes and model. BoundingBoxf3 m_extended_bounding_box; + // Slightly expanded print bed polygon, for collision detection. Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; @@ -112,42 +95,39 @@ private: public: Bed3D() = default; - ~Bed3D() { reset(); } + ~Bed3D() { release_VBOs(); } - EType get_type() const { return m_type; } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - EShapeType get_shape_type() const { return m_shape_type; } - bool is_custom() const { return m_type == EType::Custom; } -#else - bool is_custom() const { return m_type == Custom; } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - - const Pointfs& get_shape() const { return m_shape; } + // Update print bed model from configuration. // Return true if the bed shape changed, so the calee will update the UI. - bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); + //FIXME if the build volume max print height is updated, this function still returns zero + // as this class does not use it, thus there is no need to update the UI. + bool set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); - const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } + // Build volume geometry for various collision detection tasks. + const BuildVolume& build_volume() const { return m_build_volume; } + // Was the model provided, or was it generated procedurally? + Type get_type() const { return m_type; } + // Was the model generated procedurally? + bool is_custom() const { return m_type == Type::Custom; } + + // Bounding box around the print bed, axes and model, for rendering. + const BoundingBoxf3& extended_bounding_box() const { return m_extended_bounding_box; } + + // Check against an expanded 2d bounding box. + //FIXME shall one check against the real build volume? bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D& canvas, bool bottom, float scale_factor, - bool show_axes, bool show_texture); - + void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture); void render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - static bool is_rectangle(const Pointfs& shape, Vec2d* min = nullptr, Vec2d* max = nullptr); - static bool is_circle(const Pointfs& shape, Vec2d* center = nullptr, double* radius = nullptr); - static bool is_convex(const Pointfs& shape); - static EShapeType detect_shape_type(const Pointfs& shape); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - private: - void calc_bounding_boxes() const; + // Calculate an extended bounding box from axes and current model for visualization purposes. + BoundingBoxf3 calc_extended_bounding_box() const; void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); - std::tuple detect_type(const Pointfs& shape) const; + static std::tuple detect_type(const Pointfs& shape); void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking); void render_axes() const; @@ -156,7 +136,7 @@ private: void render_model() const; void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture, bool picking) const; void render_default(bool bottom, bool picking) const; - void reset(); + void release_VBOs(); }; } // GUI diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6bceaec09..f377edafa 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -11,10 +11,8 @@ #include "GUI_App.hpp" #include "Plater.hpp" #include "BitmapCache.hpp" -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -#include "3DBed.hpp" -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "libslic3r/BuildVolume.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/Geometry.hpp" @@ -617,22 +615,6 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -void GLVolume::calc_convex_hull_3d() -{ - const std::vector &src = this->indexed_vertex_array.vertices_and_normals_interleaved; - std::vector pts; - assert(src.size() % 6 == 0); - pts.reserve(src.size() / 6); - for (auto it = src.begin(); it != src.end(); ) { - it += 3; - pts.push_back({ *it, *(it + 1), *(it + 2) }); - it += 3; - } - this->set_convex_hull(TriangleMesh(its_convex_hull(pts))); -} -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -959,136 +941,51 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_BLEND)); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state, bool as_toolpaths) const -#else -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state) const { - if (config == nullptr) - return false; - - const ConfigOptionPoints* opt = dynamic_cast(config->option("bed_shape")); - if (opt == nullptr) - return false; - -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); - const float bed_height = config->opt_float("max_print_height"); - const BoundingBox bed_box_2D = get_extents(bed_poly); - BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), -1e10 }, - { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), bed_height }); - - auto check_against_rectangular_bed = [&print_volume](GLVolume& volume, ModelInstanceEPrintVolumeState& state) { - const BoundingBoxf3* const bb = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box(); - volume.is_outside = !print_volume.contains(*bb); - if (volume.printable) { - if (state == ModelInstancePVS_Inside && volume.is_outside) - state = ModelInstancePVS_Fully_Outside; - if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && print_volume.intersects(*bb)) - state = ModelInstancePVS_Partly_Outside; - } - }; - - auto check_against_circular_bed = [bed_height](GLVolume& volume, ModelInstanceEPrintVolumeState& state, const Vec2d& center, double radius) { - const TriangleMesh* mesh = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull(); - const double sq_radius = sqr(radius); - size_t outside_count = 0; - size_t valid_count = 0; - for (const Vec3f& v : mesh->its.vertices) { - const Vec3f world_v = volume.world_matrix().cast() * v; - if (0.0f <= world_v.z()) { - ++valid_count; - if (sq_radius < sqr(world_v.x() - center.x()) + sqr(world_v.y() - center.y()) || bed_height < world_v.z()) - ++outside_count; - } - } - volume.is_outside = outside_count > 0; - if (volume.printable) { - if (state == ModelInstancePVS_Inside && volume.is_outside) - state = ModelInstancePVS_Fully_Outside; - if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && outside_count < valid_count) - state = ModelInstancePVS_Partly_Outside; - } - }; - - auto check_against_convex_bed = [&bed_poly, bed_height](GLVolume& volume, ModelInstanceEPrintVolumeState& state) { - const TriangleMesh* mesh = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull(); - const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast(), 0.0f); - const BoundingBoxf3* const bb = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box(); - // Using rotating callipers to check for collision of two convex polygons. - ModelInstanceEPrintVolumeState volume_state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb->min.z(), bb->max.z()); - bool contained = (volume_state == ModelInstancePVS_Inside); - bool intersects = (volume_state == ModelInstancePVS_Partly_Outside); - - volume.is_outside = !contained; - if (volume.printable) { - if (state == ModelInstancePVS_Inside && volume.is_outside) - state = ModelInstancePVS_Fully_Outside; - - if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && intersects) - state = ModelInstancePVS_Partly_Outside; - } - }; -#else - const BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0 }, - { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), config->opt_float("max_print_height") }); - // Allow the objects to protrude below the print bed - print_volume.min.z() = -1e10; - print_volume.min.x() -= BedEpsilon; - print_volume.min.y() -= BedEpsilon; - print_volume.max.x() += BedEpsilon; - print_volume.max.y() += BedEpsilon; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + const Model& model = GUI::wxGetApp().plater()->model(); + // Volume is partially below the print bed, thus a pre-calculated convex hull cannot be used. + auto volume_sinking = [](GLVolume& volume) -> bool + { return volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1; }; + // Cached bounding box of a volume above the print bed. + auto volume_bbox = [volume_sinking](GLVolume& volume) -> BoundingBoxf3 + { return volume_sinking(volume) ? volume.transformed_non_sinking_bounding_box() : volume.transformed_convex_hull_bounding_box(); }; + // Cached 3D convex hull of a volume above the print bed. + auto volume_convex_mesh = [volume_sinking, &model](GLVolume& volume) -> const TriangleMesh& + { return volume_sinking(volume) ? model.objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); }; ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside; bool contained_min_one = false; - enum class BedShape { Rectangle, Circle, Convex, NonConvex }; - Vec2d center; - double radius; - BedShape bed_shape = - GUI::Bed3D::is_rectangle(opt->values) ? BedShape::Rectangle : - GUI::Bed3D::is_circle(opt->values, ¢er, &radius) ? BedShape::Circle : - GUI::Bed3D::is_convex(opt->values) ? BedShape::Convex : BedShape::NonConvex; - - for (GLVolume* volume : this->volumes) { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (as_toolpaths && !volume->is_extrusion_path) - continue; - else if (!as_toolpaths && (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0)))) - continue; - - switch (bed_shape) { - case BedShape::Rectangle: check_against_rectangular_bed(*volume, overall_state); break; - case BedShape::Circle: check_against_circular_bed(*volume, overall_state, center, radius); break; - case BedShape::Convex: check_against_convex_bed(*volume, overall_state); break; - default: break; + for (GLVolume* volume : this->volumes) + if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) { + BuildVolume::ObjectState state; + switch (build_volume.type()) { + case BuildVolume::Type::Rectangle: + //FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects. + state = build_volume.volume_state_bbox(volume_bbox(*volume)); + break; + case BuildVolume::Type::Circle: + case BuildVolume::Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case BuildVolume::Type::Custom: + state = build_volume.object_state(volume_convex_mesh(*volume).its, volume->world_matrix().cast(), volume_sinking(*volume)); + break; + default: + // Ignore, don't produce any collision. + state = BuildVolume::ObjectState::Inside; + break; + } + volume->is_outside = state != BuildVolume::ObjectState::Inside; + if (volume->printable) { + if (overall_state == ModelInstancePVS_Inside && volume->is_outside) + overall_state = ModelInstancePVS_Fully_Outside; + if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && state == BuildVolume::ObjectState::Colliding) + overall_state = ModelInstancePVS_Partly_Outside; + contained_min_one |= !volume->is_outside; + } } - contained_min_one |= !volume->is_outside; -#else - if (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0))) - continue; - - const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); - bool contained = print_volume.contains(bb); - - volume->is_outside = !contained; - if (!volume->printable) - continue; - - contained_min_one |= contained; - - if (overall_state == ModelInstancePVS_Inside && volume->is_outside) - overall_state = ModelInstancePVS_Fully_Outside; - - if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) - overall_state = ModelInstancePVS_Partly_Outside; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - } - if (out_state != nullptr) *out_state = overall_state; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index abf66394d..6d82e3bb7 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -31,6 +31,7 @@ namespace Slic3r { class SLAPrintObject; enum SLAPrintObjectStep : unsigned int; +class BuildVolume; class DynamicPrintConfig; class ExtrusionPath; class ExtrusionMultiPath; @@ -281,10 +282,8 @@ private: std::shared_ptr m_convex_hull; // Bounding box of this volume, in unscaled coordinates. std::optional m_transformed_convex_hull_bounding_box; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // Bounding box of the non sinking part of this volume, in unscaled coordinates. std::optional m_transformed_non_sinking_bounding_box; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS class SinkingContours { @@ -475,12 +474,10 @@ public: BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; // caching variant const BoundingBoxf3& transformed_convex_hull_bounding_box() const; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // non-caching variant BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; // caching variant const BoundingBoxf3& transformed_non_sinking_bounding_box() const; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // convex hull const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } @@ -493,15 +490,11 @@ public: void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); m_transformed_convex_hull_bounding_box.reset(); m_transformed_non_sinking_bounding_box.reset(); } -#else - void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); m_transformed_convex_hull_bounding_box.reset(); } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS bool is_sla_support() const; bool is_sla_pad() const; @@ -518,12 +511,6 @@ public: // Return an estimate of the memory held by GPU vertex buffers. size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } - -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - // calculates the 3D convex hull from indexed_vertex_array.vertices_and_normals_interleaved - // must be called before calling indexed_vertex_array.finalize_geometry(); - void calc_convex_hull_3d(); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS }; typedef std::vector GLVolumePtrs; @@ -540,7 +527,6 @@ public: All }; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS struct PrintVolume { // see: Bed3D::EShapeType @@ -554,16 +540,9 @@ public: // [0] = min z, [1] = max z std::array zs; }; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS private: -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS PrintVolume m_print_volume; -#else - // min and max vertex of the print box volume - float m_print_box_min[3]; - float m_print_box_max[3]; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // z range for clipping in shaders float m_z_range[2]; @@ -635,14 +614,7 @@ public: bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } -#else - void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) { - m_print_box_min[0] = min_x; m_print_box_min[1] = min_y; m_print_box_min[2] = min_z; - m_print_box_max[0] = max_x; m_print_box_max[1] = max_y; m_print_box_max[2] = max_z; - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; } @@ -657,11 +629,7 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state, bool as_toolpaths = false) const; -#else - bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); @@ -699,8 +667,6 @@ struct _3DScene static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); }; -static constexpr float BedEpsilon = 3.f * float(EPSILON); - } #endif diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index f87a58fd6..5fba237e3 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -86,7 +86,7 @@ public: void set_fff_print(Print *print) { m_fff_print = print; } void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } - void set_gcode_result(GCodeProcessor::Result* result) { m_gcode_result = result; } + void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; } // The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished // and the background processing will transition into G-code export. @@ -216,7 +216,7 @@ private: Print *m_fff_print = nullptr; SLAPrint *m_sla_print = nullptr; // Data structure, to which the G-code export writes its annotations. - GCodeProcessor::Result *m_gcode_result = nullptr; + GCodeProcessorResult *m_gcode_result = nullptr; // Callback function, used to write thumbnails into gcode. ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; SL1Archive m_sla_archive; diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 0b8e31e13..2d46a5228 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -22,98 +22,7 @@ namespace GUI { BedShape::BedShape(const ConfigOptionPoints& points) { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (points.size() < 3) { - m_type = Bed3D::EShapeType::Invalid; - return; - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - - // is this a rectangle ? -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Vec2d min; - Vec2d max; - if (Bed3D::is_rectangle(points.values, &min, &max)) { - m_type = Bed3D::EShapeType::Rectangle; - m_rectSize = max - min; - m_rectOrigin = -min; - return; - } -#else - Polygon polygon = Polygon::new_scale(points.values); - if (points.size() == 4) { - auto lines = polygon.lines(); - if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { - // okay, it's a rectangle - // find origin - coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points.values[0](0); - y_max = y_min = points.values[0](1); - for (auto pt : points.values) - { - x_min = std::min(x_min, pt(0)); - x_max = std::max(x_max, pt(0)); - y_min = std::min(y_min, pt(1)); - y_max = std::max(y_max, pt(1)); - } - - m_type = Type::Rectangular; - m_rectSize = Vec2d(x_max - x_min, y_max - y_min); - m_rectOrigin = Vec2d(-x_min, -y_min); - - return; - } - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - - // is this a circle ? -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Vec2d center; - double radius; - if (Bed3D::is_circle(points.values, ¢er, &radius)) { - m_type = Bed3D::EShapeType::Circle; - m_diameter = 2.0 * radius; - return; - } - - // This is a custom bed shape, use the polygon provided. - m_type = Bed3D::EShapeType::Custom; -#else - { - // Analyze the array of points.Do they reside on a circle ? - auto center = polygon.bounding_box().center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt : polygon.points) - { - double distance = (pt - center).cast().norm(); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - bool defined_value = true; - for (auto el : vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - defined_value = false; - break; - } - if (defined_value) { - // all vertices are equidistant to center - m_type = Type::Circular; - m_diameter = unscale(avg_dist * 2); - - return; - } - } - - if (points.size() < 3) - return; - - // This is a custom bed shape, use the polygon provided. - m_type = Type::Custom; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_build_volume = { points.values, 0. }; } static std::string get_option_label(BedShape::Parameter param) @@ -122,119 +31,101 @@ static std::string get_option_label(BedShape::Parameter param) case BedShape::Parameter::RectSize : return L("Size"); case BedShape::Parameter::RectOrigin: return L("Origin"); case BedShape::Parameter::Diameter : return L("Diameter"); - default: return ""; + default: assert(false); return {}; } } void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param) { ConfigOptionDef def; - - if (param == Parameter::RectSize) { + t_config_option_key key; + switch (param) { + case Parameter::RectSize: def.type = coPoints; def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); def.min = 0; def.max = 1200; def.label = get_option_label(param); def.tooltip = L("Size in X and Y of the rectangular plate."); - - Option option(def, "rect_size"); - optgroup->append_single_option_line(option); - } - else if (param == Parameter::RectOrigin) { + key = "rect_size"; + break; + case Parameter::RectOrigin: def.type = coPoints; def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); def.min = -600; def.max = 600; def.label = get_option_label(param); def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); - - Option option(def, "rect_origin"); - optgroup->append_single_option_line(option); - } - else if (param == Parameter::Diameter) { + key = "rect_origin"; + break; + case Parameter::Diameter: def.type = coFloat; def.set_default_value(new ConfigOptionFloat(200)); def.sidetext = L("mm"); def.label = get_option_label(param); def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); - - Option option(def, "diameter"); - optgroup->append_single_option_line(option); + key = "diameter"; + break; + default: + assert(false); } + + optgroup->append_single_option_line({ def, std::move(key) }); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -wxString BedShape::get_name(Bed3D::EShapeType type) +wxString BedShape::get_name(PageType type) { switch (type) { - case Bed3D::EShapeType::Rectangle: { return _L("Rectangular"); } - case Bed3D::EShapeType::Circle: { return _L("Circular"); } - case Bed3D::EShapeType::Custom: { return _L("Custom"); } - case Bed3D::EShapeType::Invalid: - default: return _L("Invalid"); + case PageType::Rectangle: return _L("Rectangular"); + case PageType::Circle: return _L("Circular"); + case PageType::Custom: return _L("Custom"); } + // make visual studio happy + assert(false); + return {}; } -#else -wxString BedShape::get_name(Type type) -{ - switch (type) { - case Type::Rectangular: return _L("Rectangular"); - case Type::Circular: return _L("Circular"); - case Type::Custom: return _L("Custom"); - case Type::Invalid: - default: return _L("Invalid"); - } -} -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -size_t BedShape::get_type() +BedShape::PageType BedShape::get_page_type() { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - return static_cast(m_type == Bed3D::EShapeType::Invalid ? Bed3D::EShapeType::Rectangle : m_type); -#else - return static_cast(m_type == Type::Invalid ? Type::Rectangular : m_type); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + switch (m_build_volume.type()) { + case BuildVolume::Type::Rectangle: + case BuildVolume::Type::Invalid: return PageType::Rectangle; + case BuildVolume::Type::Circle: return PageType::Circle; + case BuildVolume::Type::Convex: + case BuildVolume::Type::Custom: return PageType::Custom; + } + // make visual studio happy + assert(false); + return PageType::Rectangle; } wxString BedShape::get_full_name_with_params() { - wxString out = _L("Shape") + ": " + get_name(m_type); - -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (m_type == Bed3D::EShapeType::Rectangle) { -#else - if (m_type == Type::Rectangular) { -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]"; - out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]"; + wxString out = _L("Shape") + ": " + get_name(this->get_page_type()); + switch (m_build_volume.type()) { + case BuildVolume::Type::Circle: + out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(2. * unscaled(m_build_volume.circle().radius)) + "]"; + break; + default: + // rectangle, convex, concave... + out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(to_2d(m_build_volume.bounding_volume().size())).serialize() + "]"; + out += "\n" + _(get_option_label(Parameter::RectOrigin)) + ": [" + ConfigOptionPoint(to_2d(m_build_volume.bounding_volume().min)).serialize() + "]"; + break; } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - else if (m_type == Bed3D::EShapeType::Circle) -#else - else if (m_type == Type::Circular) -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]"; - return out; } void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup) { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (m_type == Bed3D::EShapeType::Rectangle || m_type == Bed3D::EShapeType::Invalid) { -#else - if (m_type == Type::Rectangular || m_type == Type::Invalid) { -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize }); - optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin }); + switch (m_build_volume.type()) { + case BuildVolume::Type::Circle: + optgroup->set_value("diameter", double_to_string(2. * unscaled(m_build_volume.circle().radius))); + break; + default: + // rectangle, convex, concave... + optgroup->set_value("rect_size" , new ConfigOptionPoints{ to_2d(m_build_volume.bounding_volume().size()) }); + optgroup->set_value("rect_origin" , new ConfigOptionPoints{ to_2d(m_build_volume.bounding_volume().min) }); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - else if (m_type == Bed3D::EShapeType::Circle) -#else - else if (m_type == Type::Circular) -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - optgroup->set_value("diameter", double_to_string(m_diameter)); } void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) @@ -295,28 +186,16 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf sbsizer->Add(m_shape_options_book); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - auto optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Rectangle)); -#else - auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Rectangle)); BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); activate_options_page(optgroup); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Circle)); -#else - optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Circle)); BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); activate_options_page(optgroup); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Custom)); -#else - optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Custom)); Line line{ "", "" }; line.full_width = 1; @@ -538,8 +417,8 @@ void BedShapePanel::set_shape(const ConfigOptionPoints& points) { BedShape shape(points); - m_shape_options_book->SetSelection(shape.get_type()); - shape.apply_optgroup_values(m_optgroups[shape.get_type()]); + m_shape_options_book->SetSelection(int(shape.get_page_type())); + shape.apply_optgroup_values(m_optgroups[int(shape.get_page_type())]); // Copy the polygon to the canvas, make a copy of the array, if custom shape is selected if (shape.is_custom()) @@ -562,17 +441,9 @@ void BedShapePanel::update_shape() auto page_idx = m_shape_options_book->GetSelection(); auto opt_group = m_optgroups[page_idx]; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Bed3D::EShapeType page_type = static_cast(page_idx); -#else - BedShape::Type page_type = static_cast(page_idx); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (page_type == Bed3D::EShapeType::Rectangle) { -#else - if (page_type == BedShape::Type::Rectangular) { -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + switch (static_cast(page_idx)) { + case BedShape::PageType::Rectangle: + { Vec2d rect_size(Vec2d::Zero()); Vec2d rect_origin(Vec2d::Zero()); @@ -602,12 +473,10 @@ void BedShapePanel::update_shape() Vec2d(x1, y0), Vec2d(x1, y1), Vec2d(x0, y1) }; + break; } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - else if (page_type == Bed3D::EShapeType::Circle) { -#else - else if (page_type == BedShape::Type::Circular) { -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + case BedShape::PageType::Circle: + { double diameter; try { diameter = boost::any_cast(opt_group->get_value("diameter")); } catch (const std::exception & /* e */) { return; } @@ -615,6 +484,7 @@ void BedShapePanel::update_shape() if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; + // Don't change this value without adjusting BuildVolume constructor detecting circle diameter! auto edges = 72; std::vector points; for (int i = 1; i <= edges; ++i) { @@ -622,13 +492,12 @@ void BedShapePanel::update_shape() points.push_back(Vec2d(r*cos(angle), r*sin(angle))); } m_shape = points; + break; } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - else if (page_type == Bed3D::EShapeType::Custom) -#else - else if (page_type == BedShape::Type::Custom) -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + case BedShape::PageType::Custom: m_shape = m_loaded_shape; + break; + } update_preview(); } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index af84ffb95..032aa2880 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -5,11 +5,10 @@ #include "GUI_Utils.hpp" #include "2DBed.hpp" -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -#include "3DBed.hpp" -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #include "I18N.hpp" +#include + #include #include @@ -22,14 +21,11 @@ using ConfigOptionsGroupShp = std::shared_ptr; struct BedShape { -#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - enum class Type { - Rectangular = 0, - Circular, - Custom, - Invalid + enum class PageType { + Rectangle, + Circle, + Custom }; -#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS enum class Parameter { RectSize, @@ -39,34 +35,18 @@ struct BedShape BedShape(const ConfigOptionPoints& points); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - bool is_custom() { return m_type == Bed3D::EShapeType::Custom; } -#else - bool is_custom() { return m_type == Type::Custom; } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool is_custom() { return m_build_volume.type() == BuildVolume::Type::Convex || m_build_volume.type() == BuildVolume::Type::Custom; } static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - static wxString get_name(Bed3D::EShapeType type); -#else - static wxString get_name(Type type); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + static wxString get_name(PageType type); - // convert Type to size_t - size_t get_type(); + PageType get_page_type(); wxString get_full_name_with_params(); void apply_optgroup_values(ConfigOptionsGroupShp optgroup); private: -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Bed3D::EShapeType m_type{ Bed3D::EShapeType::Invalid }; -#else - Type m_type {Type::Invalid}; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Vec2d m_rectSize {200, 200}; - Vec2d m_rectOrigin {0, 0}; - double m_diameter {0}; + BuildVolume m_build_volume; }; class BedShapePanel : public wxPanel diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1d4551f2e..6463cc180 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "GCodeViewer.hpp" +#include "libslic3r/BuildVolume.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" @@ -20,9 +21,6 @@ #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "GUI_ObjectManipulation.hpp" -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -#include "3DBed.hpp" -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #include @@ -123,7 +121,7 @@ void GCodeViewer::IBuffer::reset() count = 0; } -bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const +bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const { auto matches_percent = [](float value1, float value2, float max_percent) { return std::abs(value2 - value1) / value1 <= max_percent; @@ -174,7 +172,7 @@ void GCodeViewer::TBuffer::reset() #endif // ENABLE_SEAMS_USING_MODELS } -void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) +void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths @@ -665,7 +663,7 @@ void GCodeViewer::init() } #endif // ENABLE_SEAMS_USING_MODELS -void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) +void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized) { // avoid processing if called with the same gcode_result if (m_last_result_id == gcode_result.id) @@ -737,7 +735,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& { min.x(), max.y() } }; } - wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); + wxGetApp().plater()->set_bed_shape(bed_shape, gcode_result.max_print_height, texture, model, gcode_result.bed_shape.empty()); } m_print_statistics = gcode_result.print_statistics; @@ -750,7 +748,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& } } -void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) +void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) { #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); @@ -779,7 +777,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: if (i == 0) continue; - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; switch (curr.type) { @@ -1210,7 +1208,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fclose(fp); } -void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) +void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) { // max index buffer size, in bytes static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024; @@ -1232,23 +1230,23 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) }; // format data into the buffers to be rendered as points - auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { + auto add_vertices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { vertices.push_back(curr.position.x()); vertices.push_back(curr.position.y()); vertices.push_back(curr.position.z()); }; - auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + auto add_indices_as_point = [](const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { buffer.add_path(curr, ibuffer_id, indices.size(), move_id); indices.push_back(static_cast(indices.size())); }; // format data into the buffers to be rendered as lines - auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { + auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) { // x component of the normal to the current segment (the normal is parallel to the XY plane) const float normal_x = (curr.position - prev.position).normalized().y(); - auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { + auto add_vertex = [&vertices, normal_x](const GCodeProcessorResult::MoveVertex& vertex) { // add position vertices.push_back(vertex.position.x()); vertices.push_back(vertex.position.y()); @@ -1262,7 +1260,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // add current vertex add_vertex(curr); }; - auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { // add starting index @@ -1283,7 +1281,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) }; // format data into the buffers to be rendered as solid - auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { + auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { // append position vertices.push_back(position.x()); @@ -1340,7 +1338,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; }; - auto add_indices_as_solid = [&](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, const GCodeProcessor::MoveVertex* next, + auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { static Vec3f prev_dir; static Vec3f prev_up; @@ -1482,7 +1480,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_SEAMS_USING_MODELS // format data into the buffers to be rendered as instanced model - auto add_model_instance = [](const GCodeProcessor::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { // append position instances.push_back(curr.position.x()); instances.push_back(curr.position.y()); @@ -1498,7 +1496,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_SEAMS_USING_BATCHED_MODELS // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessor::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { const double width = static_cast(1.5f * curr.width); const double height = static_cast(1.5f * curr.height); @@ -1542,7 +1540,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex); + m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex); m_statistics.results_time = gcode_result.time; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1561,7 +1559,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) wxBusyCursor busy; // extract approximate paths bounding box from result - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { if (wxGetApp().is_gcode_viewer()) // for the gcode viewer we need to take in account all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); @@ -1575,57 +1573,18 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (wxGetApp().is_editor()) { - const Bed3D::EShapeType bed_type = wxGetApp().plater()->get_bed().get_shape_type(); - if (bed_type == Bed3D::EShapeType::Rectangle) { - BoundingBoxf3 print_volume = wxGetApp().plater()->get_bed().get_bounding_box(false); - print_volume.min.z() = -1e10; - print_volume.max.z() = m_max_print_height; - print_volume.min -= Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast(); - print_volume.max += Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast(); - m_contained_in_bed = print_volume.contains(m_paths_bounding_box); - } - else if (bed_type == Bed3D::EShapeType::Circle) { - Vec2d center; - double radius; - Bed3D::is_circle(wxGetApp().plater()->get_bed().get_shape(), ¢er, &radius); - const double sq_radius = sqr(radius); - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { - if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { - if (sq_radius < (Vec2d(move.position.x(), move.position.y()) - center).squaredNorm()) { - m_contained_in_bed = false; - break; - } - } - } - } - else if (bed_type == Bed3D::EShapeType::Custom) { - const Pointfs& shape = wxGetApp().plater()->get_bed().get_shape(); - if (Bed3D::is_convex(shape)) { - const Polygon poly = Polygon::new_scale(shape); - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { - if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { - if (!poly.contains(Point::new_scale(Vec2d(move.position.x(), move.position.y())))) { - m_contained_in_bed = false; - break; - } - } - } - } - } - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (wxGetApp().is_editor()) + m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box); #if ENABLE_FIX_SEAMS_SYNCH m_sequential_view.gcode_ids.clear(); for (size_t i = 0; i < gcode_result.moves.size(); ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; + const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; if (move.type != EMoveType::Seam) m_sequential_view.gcode_ids.push_back(move.gcode_id); } #else - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) { m_sequential_view.gcode_ids.push_back(move.gcode_id); } #endif // ENABLE_FIX_SEAMS_SYNCH @@ -1648,7 +1607,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // toolpaths data -> extract vertices from result for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; #if ENABLE_FIX_SEAMS_SYNCH if (curr.type == EMoveType::Seam) { ++seams_count; @@ -1662,7 +1621,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; // update progress dialog ++progress_count; @@ -2066,7 +2025,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #endif // ENABLE_FIX_SEAMS_SYNCH for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i]; #if ENABLE_FIX_SEAMS_SYNCH if (curr.type == EMoveType::Seam) ++seams_count; @@ -2078,8 +2037,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessor::MoveVertex* next = nullptr; + const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessorResult::MoveVertex* next = nullptr; if (i < m_moves_count - 1) next = &gcode_result.moves[i + 1]; @@ -2286,7 +2245,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) seams_count = 0; #endif // ENABLE_FIX_SEAMS_SYNCH for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; + const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i]; #if ENABLE_FIX_SEAMS_SYNCH if (move.type == EMoveType::Seam) ++seams_count; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 66fcba2bc..85151ceb8 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -233,7 +233,7 @@ class GCodeViewer unsigned char cp_color_id{ 0 }; std::vector sub_paths; - bool matches(const GCodeProcessor::MoveVertex& move) const; + bool matches(const GCodeProcessorResult::MoveVertex& move) const; size_t vertices_count() const { return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; } @@ -251,7 +251,7 @@ class GCodeViewer return -1; } } - void add_sub_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { + void add_sub_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { Endpoint endpoint = { b_id, i_id, s_id, move.position }; sub_paths.push_back({ endpoint , endpoint }); } @@ -361,7 +361,7 @@ class GCodeViewer // b_id index of buffer contained in this->indices // i_id index of first index contained in this->indices[b_id] // s_id index of first vertex contained in this->vertices - void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); + void add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); unsigned int max_vertices_per_segment() const { switch (render_primitive_type) @@ -802,7 +802,7 @@ private: Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS std::array m_detected_point_sizes = { 0.0f, 0.0f }; - GCodeProcessor::Result::SettingsIds m_settings_ids; + GCodeProcessorResult::SettingsIds m_settings_ids; std::array m_sequential_range_caps; std::vector m_custom_gcode_per_print_z; @@ -820,9 +820,9 @@ public: #endif // ENABLE_SEAMS_USING_MODELS // extract rendering data from the given parameters - void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); + void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized); // recalculate ranges in dependence of what is visible and sets tool/print colors - void refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); + void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); void refresh_render_paths(); void update_shells_color_by_extruder(const DynamicPrintConfig* config); @@ -870,7 +870,7 @@ public: size_t get_extruders_count() { return m_extruders_count; } private: - void load_toolpaths(const GCodeProcessor::Result& gcode_result); + void load_toolpaths(const GCodeProcessorResult& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; void render_toolpaths(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f349a0354..452173ae2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3,16 +3,18 @@ #include +#include "libslic3r/BuildVolume.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PresetBundle.hpp" +#include "slic3r/GUI/3DBed.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" #include "slic3r/GUI/GLShader.hpp" @@ -20,7 +22,6 @@ #include "slic3r/GUI/Tab.hpp" #include "slic3r/GUI/GUI_Preview.hpp" #include "slic3r/GUI/OpenGLManager.hpp" -#include "slic3r/GUI/3DBed.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -957,9 +958,10 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const return m_process->current_printer_technology(); } -GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) : m_canvas(canvas) , m_context(nullptr) + , m_bed(bed) #if ENABLE_RETINA_GL , m_retina_helper(nullptr) #endif @@ -1115,18 +1117,10 @@ void GLCanvas3D::reset_volumes() _set_warning_notification(EWarning::ObjectOutside, false); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool as_toolpaths) const -#else ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS { ModelInstanceEPrintVolumeState state; -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - m_volumes.check_outside_state(m_config, &state, as_toolpaths); -#else - m_volumes.check_outside_state(m_config, &state); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_volumes.check_outside_state(m_bed.build_volume(), &state); return state; } @@ -1250,13 +1244,11 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const BoundingBoxf3 GLCanvas3D::scene_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(wxGetApp().plater()->get_bed().get_bounding_box(true)); - if (m_config != nullptr) { - double h = m_config->opt_float("max_print_height"); - bb.min(2) = std::min(bb.min(2), -h); - bb.max(2) = std::max(bb.max(2), h); - } - + bb.merge(m_bed.extended_bounding_box()); + double h = m_bed.build_volume().max_print_height(); + //FIXME why -h? + bb.min.z() = std::min(bb.min.z(), -h); + bb.max.z() = std::max(bb.max.z(), h); return bb; } @@ -1362,7 +1354,7 @@ void GLCanvas3D::allow_multisample(bool allow) void GLCanvas3D::zoom_to_bed() { - _zoom_to_box(wxGetApp().plater()->get_bed().get_bounding_box(false)); + _zoom_to_box(m_bed.build_volume().bounding_volume()); } void GLCanvas3D::zoom_to_volumes() @@ -1423,7 +1415,7 @@ void GLCanvas3D::render() m_gcode_viewer.init(); #endif // ENABLE_SEAMS_USING_MODELS - if (wxGetApp().plater()->get_bed().get_shape().empty()) { + if (! m_bed.build_volume().valid()) { // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); return; @@ -2057,7 +2049,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { ModelInstanceEPrintVolumeState state; - const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); + const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state); const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); @@ -2109,7 +2101,7 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old.finalize_geometry(gl_initialized); } -void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) +void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); @@ -2138,10 +2130,6 @@ void GLCanvas3D::load_sla_preview() // Release OpenGL data before generating new data. reset_volumes(); _load_sla_shells(); -#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_box(float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, 0.0f, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon, (float)m_config->opt_float("max_print_height")); -#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS _update_sla_shells_outside_state(); _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); } @@ -2158,20 +2146,12 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c // Release OpenGL data before generating new data. this->reset_volumes(); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - bool requires_convex_hulls = wxGetApp().plater()->get_bed().get_shape_type() != Bed3D::EShapeType::Rectangle; - _load_print_toolpaths(requires_convex_hulls); - _load_wipe_tower_toolpaths(str_tool_colors, requires_convex_hulls); + const BuildVolume &build_volume = m_bed.build_volume(); + _load_print_toolpaths(build_volume); + _load_wipe_tower_toolpaths(build_volume, str_tool_colors); for (const PrintObject* object : print->objects()) - _load_print_object_toolpaths(*object, str_tool_colors, color_print_values, requires_convex_hulls); -#else - _load_print_toolpaths(); - _load_wipe_tower_toolpaths(str_tool_colors); - for (const PrintObject* object : print->objects()) - _load_print_object_toolpaths(*object, str_tool_colors, color_print_values); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values); - _update_toolpath_volumes_outside_state(); _set_warning_notification_if_needed(EWarning::ToolpathOutside); } @@ -3770,7 +3750,7 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const { - return factor * wxGetApp().plater()->get_bed().get_bounding_box(false).max_size(); + return factor * m_bed.build_volume().bounding_volume().max_size(); } void GLCanvas3D::set_cursor(ECursorType type) @@ -4161,7 +4141,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const } else // This happens for empty projects - volumes_box = wxGetApp().plater()->get_bed().get_bounding_box(true); + volumes_box = m_bed.extended_bounding_box(); #endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED Camera camera; @@ -4178,7 +4158,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const // extends the near and far z of the frustrum to avoid the bed being clipped // box in eye space - BoundingBoxf3 t_bed_box = wxGetApp().plater()->get_bed().get_bounding_box(true).transformed(camera.get_view_matrix()); + BoundingBoxf3 t_bed_box = m_bed.extended_bounding_box().transformed(camera.get_view_matrix()); near_z = -t_bed_box.max.z(); far_z = -t_bed_box.min.z(); } @@ -4861,7 +4841,7 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); } - bb.merge(wxGetApp().plater()->get_bed().get_bounding_box(include_bed_model)); + bb.merge(include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume()); if (!m_main_toolbar.is_enabled()) bb.merge(m_gcode_viewer.get_max_bounding_box()); @@ -5035,25 +5015,6 @@ void GLCanvas3D::_rectangular_selection_picking_pass() _update_volumes_hover_state(); } -#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) -{ - // tolerance to avoid false detection at bed edges - const double tolerance_x = 0.05; - const double tolerance_y = 0.05; - - BoundingBoxf3 ret; - const ConfigOptionPoints* opt = dynamic_cast(config.option("bed_shape")); - if (opt != nullptr) { - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - ret = BoundingBoxf3(Vec3d(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Vec3d(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, config.opt_float("max_print_height"))); - // Allow the objects to protrude below the print bed - ret.min(2) = -1e10; - } - return ret; -} -#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - void GLCanvas3D::_render_background() const { bool use_error_color = false; @@ -5064,15 +5025,7 @@ void GLCanvas3D::_render_background() const if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); else -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); -#else - { - const BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); - use_error_color &= (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) ? !test_volume.contains(paths_volume) : false; - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } glsafe(::glPushMatrix()); @@ -5123,7 +5076,7 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes) && m_gizmos.get_current_type() != GLGizmosManager::Seam && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); - wxGetApp().plater()->get_bed().render(*this, bottom, scale_factor, show_axes, show_texture); + m_bed.render(*this, bottom, scale_factor, show_axes, show_texture); } void GLCanvas3D::_render_bed_for_picking(bool bottom) @@ -5133,7 +5086,7 @@ void GLCanvas3D::_render_bed_for_picking(bool bottom) scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - wxGetApp().plater()->get_bed().render_for_picking(*this, bottom, scale_factor); + m_bed.render_for_picking(*this, bottom, scale_factor); } void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) @@ -5148,55 +5101,35 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) if (m_picking_enabled) { // Update the layer editing selection to the first object selected, update the current object maximum Z. m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (m_config != nullptr) { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - Bed3D::EShapeType type = wxGetApp().plater()->get_bed().get_shape_type(); - switch (type) - { - case Bed3D::EShapeType::Circle: { - Vec2d center; - double radius; - if (Bed3D::is_circle(wxGetApp().plater()->get_bed().get_shape(), ¢er, &radius)) { - m_volumes.set_print_volume({ static_cast(type), - { float(center.x()), float(center.y()), float(radius) + BedEpsilon, 0.0f }, - { 0.0f, float(m_config->opt_float("max_print_height")) } }); - } + if (const BuildVolume &build_volume = m_bed.build_volume(); build_volume.valid()) { + switch (build_volume.type()) { + case BuildVolume::Type::Rectangle: { + const BoundingBox3Base bed_bb = build_volume.bounding_volume().inflated(BuildVolume::SceneEpsilon); + m_volumes.set_print_volume({ 0, // circle + { float(bed_bb.min.x()), float(bed_bb.min.y()), float(bed_bb.max.x()), float(bed_bb.max.y()) }, + { 0.0f, float(build_volume.max_print_height()) } }); break; } - case Bed3D::EShapeType::Rectangle: { - const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_volume({ static_cast(type), - { float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon }, - { 0.0f, float(m_config->opt_float("max_print_height")) } }); + case BuildVolume::Type::Circle: { + m_volumes.set_print_volume({ 1, // rectangle + { unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y()), unscaled(build_volume.circle().radius + BuildVolume::SceneEpsilon), 0.0f }, + { 0.0f, float(build_volume.max_print_height() + BuildVolume::SceneEpsilon) } }); break; } default: - case Bed3D::EShapeType::Custom: { + case BuildVolume::Type::Custom: { m_volumes.set_print_volume({ static_cast(type), { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f } }); } } -#else - const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min.x() - BedEpsilon, (float)bed_bb.min.y() - BedEpsilon, 0.0f, (float)bed_bb.max.x() + BedEpsilon, (float)bed_bb.max.y() + BedEpsilon, (float)m_config->opt_float("max_print_height")); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (m_requires_check_outside_state) { - m_volumes.check_outside_state(m_config, nullptr); + m_volumes.check_outside_state(build_volume, nullptr); m_requires_check_outside_state = false; } -#else - m_volumes.check_outside_state(m_config, nullptr); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } -#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } -#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (m_use_clipping_planes) m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); @@ -5206,11 +5139,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS GLShaderProgram* shader = wxGetApp().get_shader("gouraud_mod"); -#else - GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (shader != nullptr) { shader->start_using(); @@ -5832,11 +5761,7 @@ void GLCanvas3D::_stop_timer() m_timer.Stop(); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -void GLCanvas3D::_load_print_toolpaths(bool generate_convex_hulls) -#else -void GLCanvas3D::_load_print_toolpaths() -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume) { const Print *print = this->fff_print(); if (print == nullptr) @@ -5889,18 +5814,11 @@ void GLCanvas3D::_load_print_toolpaths() reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); } } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (generate_convex_hulls) - volume->calc_convex_hull_3d(); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array.vertices_and_normals_interleaved, volume->indexed_vertex_array.bounding_box()); volume->indexed_vertex_array.finalize_geometry(m_initialized); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values, bool generate_convex_hulls) -#else -void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values) -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector& str_tool_colors, const std::vector& color_print_values) { std::vector> tool_colors = _parse_colors(str_tool_colors); @@ -6187,26 +6105,16 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; - if (generate_convex_hulls) - v->calc_convex_hull_3d(); + v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); v->indexed_vertex_array.finalize_geometry(m_initialized); } -#else - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors, bool generate_convex_hulls) -#else -void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector& str_tool_colors) { const Print *print = this->fff_print(); if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) @@ -6357,17 +6265,11 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { GLVolume* v = m_volumes.volumes[i]; - if (generate_convex_hulls) - v->calc_convex_hull_3d(); + v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array.vertices_and_normals_interleaved, v->indexed_vertex_array.bounding_box()); v->indexed_vertex_array.finalize_geometry(m_initialized); } -#else - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6427,28 +6329,9 @@ void GLCanvas3D::_load_sla_shells() update_volumes_colors_by_extruder(); } -void GLCanvas3D::_update_toolpath_volumes_outside_state() -{ -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - check_volumes_outside_state(true); -#else - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - for (GLVolume* volume : m_volumes.volumes) { - volume->is_outside = (test_volume.radius() > 0.0 && volume->is_extrusion_path) ? !test_volume.contains(volume->bounding_box()) : false; - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -} - void GLCanvas3D::_update_sla_shells_outside_state() { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS check_volumes_outside_state(); -#else - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - for (GLVolume* volume : m_volumes.volumes) { - volume->is_outside = (test_volume.radius() > 0.0 && volume->shader_outside_printer_detection_enabled) ? !test_volume.contains(volume->transformed_convex_hull_bounding_box()) : false; - } -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 202036029..ffcc4a6a0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -39,6 +39,7 @@ class wxGLContext; namespace Slic3r { class BackgroundSlicingProcess; +class BuildVolume; struct ThumbnailData; struct ThumbnailsParams; class ModelObject; @@ -50,6 +51,8 @@ namespace CustomGCode { struct Item; } namespace GUI { +class Bed3D; + #if ENABLE_RETINA_GL class RetinaHelper; #endif @@ -446,6 +449,7 @@ public: private: wxGLCanvas* m_canvas; wxGLContext* m_context; + Bed3D &m_bed; #if ENABLE_RETINA_GL std::unique_ptr m_retina_helper; #endif @@ -600,7 +604,7 @@ private: m_gizmo_highlighter; public: - explicit GLCanvas3D(wxGLCanvas* canvas); + explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed); ~GLCanvas3D(); bool is_initialized() const { return m_initialized; } @@ -621,11 +625,7 @@ public: unsigned int get_volumes_count() const; const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - ModelInstanceEPrintVolumeState check_volumes_outside_state(bool as_toolpaths = false) const; -#else ModelInstanceEPrintVolumeState check_volumes_outside_state() const; -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_SEAMS_USING_MODELS void init_gcode_viewer() { m_gcode_viewer.init(); } @@ -736,7 +736,7 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); - void load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); + void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); void refresh_gcode_preview_render_paths(); void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } @@ -955,33 +955,19 @@ private: void _start_timer(); void _stop_timer(); -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // Create 3D thick extrusion lines for a skirt and brim. - // Adds a new Slic3r::GUI::3DScene::Volume to volumes. - void _load_print_toolpaths(bool generate_convex_hulls = false); + // Adds a new Slic3r::GUI::3DScene::Volume to volumes, updates collision with the build_volume. + void _load_print_toolpaths(const BuildVolume &build_volume); // Create 3D thick extrusion lines for object forming extrusions. // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, - // one for perimeters, one for infill and one for supports. - void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, - const std::vector& color_print_values, bool generate_convex_hulls = false); - // Create 3D thick extrusion lines for wipe tower extrusions - void _load_wipe_tower_toolpaths(const std::vector& str_tool_colors, bool generate_convex_hulls = false); -#else - // Create 3D thick extrusion lines for a skirt and brim. - // Adds a new Slic3r::GUI::3DScene::Volume to volumes. - void _load_print_toolpaths(); - // Create 3D thick extrusion lines for object forming extrusions. - // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, - // one for perimeters, one for infill and one for supports. - void _load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, - const std::vector& color_print_values); - // Create 3D thick extrusion lines for wipe tower extrusions - void _load_wipe_tower_toolpaths(const std::vector& str_tool_colors); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // one for perimeters, one for infill and one for supports, updates collision with the build_volume. + void _load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume &build_volume, + const std::vector& str_tool_colors, const std::vector& color_print_values); + // Create 3D thick extrusion lines for wipe tower extrusions, updates collision with the build_volume. + void _load_wipe_tower_toolpaths(const BuildVolume &build_volume, const std::vector& str_tool_colors); // Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished. void _load_sla_shells(); - void _update_toolpath_volumes_outside_state(); void _update_sla_shells_outside_state(); void _set_warning_notification_if_needed(EWarning warning); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e044056e6..4571b7346 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2652,6 +2652,11 @@ Plater* GUI_App::plater() return plater_; } +const Plater* GUI_App::plater() const +{ + return plater_; +} + Model& GUI_App::model() { return plater_->model(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 1d281cafe..95ac8c025 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -281,6 +281,7 @@ public: ObjectList* obj_list(); ObjectLayers* obj_layers(); Plater* plater(); + const Plater* plater() const; Model& model(); NotificationManager * notification_manager(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9b1ee1cbf..69c855872 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1758,12 +1758,9 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name new_object->invalidate_bounding_box(); new_object->translate(-bb.center()); - if (center) { - const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); - new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation.z())); - } else { - new_object->instances[0]->set_offset(bb.center()); - } + new_object->instances[0]->set_offset(center ? + to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) : + bb.center()); new_object->ensure_on_bed(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index d4aa918b4..f1607bafa 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -37,11 +37,11 @@ namespace Slic3r { namespace GUI { -View3D::View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) +View3D::View3D(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) : m_canvas_widget(nullptr) , m_canvas(nullptr) { - init(parent, model, config, process); + init(parent, bed, model, config, process); } View3D::~View3D() @@ -50,7 +50,7 @@ View3D::~View3D() delete m_canvas_widget; } -bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) +bool View3D::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; @@ -59,7 +59,7 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba if (m_canvas_widget == nullptr) return false; - m_canvas = new GLCanvas3D(m_canvas_widget); + m_canvas = new GLCanvas3D(m_canvas_widget, bed); m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget)); m_canvas->allow_multisample(OpenGLManager::can_multisample()); @@ -169,18 +169,18 @@ void View3D::render() } Preview::Preview( - wxWindow* parent, Model* model, DynamicPrintConfig* config, - BackgroundSlicingProcess* process, GCodeProcessor::Result* gcode_result, std::function schedule_background_process_func) + wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodeProcessorResult* gcode_result, std::function schedule_background_process_func) : m_config(config) , m_process(process) , m_gcode_result(gcode_result) , m_schedule_background_process(schedule_background_process_func) { - if (init(parent, model)) + if (init(parent, bed, model)) load_print(); } -bool Preview::init(wxWindow* parent, Model* model) +bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; @@ -196,7 +196,7 @@ bool Preview::init(wxWindow* parent, Model* model) if (m_canvas_widget == nullptr) return false; - m_canvas = new GLCanvas3D(m_canvas_widget); + m_canvas = new GLCanvas3D(m_canvas_widget, bed); m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget)); m_canvas->allow_multisample(OpenGLManager::can_multisample()); m_canvas->set_config(m_config); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 97ced0a1e..42246aa18 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -44,7 +44,7 @@ class View3D : public wxPanel GLCanvas3D* m_canvas; public: - View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + View3D(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); virtual ~View3D(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } @@ -70,7 +70,7 @@ public: void render(); private: - bool init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + bool init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); }; class Preview : public wxPanel @@ -93,7 +93,7 @@ class Preview : public wxPanel DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; - GCodeProcessor::Result* m_gcode_result; + GCodeProcessorResult* m_gcode_result; #ifdef __linux__ // We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955. @@ -129,8 +129,8 @@ public: Legend }; - Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, - GCodeProcessor::Result* gcode_result, std::function schedule_background_process = []() {}); + Preview(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, + GCodeProcessorResult* gcode_result, std::function schedule_background_process = []() {}); virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } @@ -161,7 +161,7 @@ public: void hide_layers_slider(); private: - bool init(wxWindow* parent, Model* model); + bool init(wxWindow* parent, Bed3D& bed, Model* model); void bind_event_handlers(); void unbind_event_handlers(); diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 2aba75fba..1191e5c2e 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -26,10 +26,10 @@ #include "3DScene.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" -#include "3DBed.hpp" #include "MsgDialog.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" +#include "libslic3r/BuildVolume.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Format/OBJ.hpp" @@ -270,9 +270,7 @@ static void generate_thumbnail_from_model(const std::string& filename) model.objects[0]->center_around_origin(false); model.objects[0]->ensure_on_bed(false); - const Vec3d bed_center_3d = wxGetApp().plater()->get_bed().get_bounding_box(false).center(); - const Vec2d bed_center_2d = { bed_center_3d.x(), bed_center_3d.y()}; - model.center_instances_around_point(bed_center_2d); + model.center_instances_around_point(to_2d(wxGetApp().plater()->build_volume().bounding_volume().center())); GLVolumeCollection volumes; volumes.volumes.push_back(new GLVolume()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 9e30202bd..9034d78d8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -3,6 +3,7 @@ #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Model.hpp" #include diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index f63cf5585..2771f9d27 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -1,5 +1,6 @@ #include "ArrangeJob.hpp" +#include "libslic3r/BuildVolume.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/Model.hpp" @@ -263,7 +264,7 @@ get_wipe_tower_arrangepoly(const Plater &plater) } double bed_stride(const Plater *plater) { - double bedwidth = plater->bed_shape_bb().size().x(); + double bedwidth = plater->build_volume().bounding_volume().size().x(); return scaled((1. + LOGICAL_BED_GAP) * bedwidth); } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index c2344274a..6002e1d52 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -486,7 +486,7 @@ void MainFrame::update_layout() case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); - m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); + m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, 0, {}, {}, true); m_plater->get_collapse_toolbar().set_enabled(false); m_plater->collapse_sidebar(true); m_plater->Show(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 04ccbb183..9d8fdc74c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1554,7 +1554,7 @@ struct Plater::priv Slic3r::SLAPrint sla_print; Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; - Slic3r::GCodeProcessor::Result gcode_result; + Slic3r::GCodeProcessorResult gcode_result; // GUI elements wxSizer* panel_sizer{ nullptr }; @@ -1717,8 +1717,6 @@ struct Plater::priv void update_main_toolbar_tooltips(); // std::shared_ptr statusbar(); std::string get_config(const std::string &key) const; - BoundingBoxf bed_shape_bb() const; - BoundingBox scaled_bed_shape_bb() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false); std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); @@ -1842,7 +1840,7 @@ struct Plater::priv // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); + void set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); bool can_delete() const; bool can_delete_all() const; @@ -1956,8 +1954,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) sla_print.set_status_callback(statuscb); this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); - view3D = new View3D(q, &model, config, &background_process); - preview = new Preview(q, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); }); + view3D = new View3D(q, bed, &model, config, &background_process); + preview = new Preview(q, bed, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); }); #ifdef __APPLE__ // set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size @@ -2172,13 +2170,8 @@ void Plater::priv::update(unsigned int flags) { // the following line, when enabled, causes flickering on NVIDIA graphics cards // wxWindowUpdateLocker freeze_guard(q); - if (get_config("autocenter") == "1") { - // auto *bed_shape_opt = config->opt("bed_shape"); - // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); - // const BoundingBox bed_shape_bb = bed_shape.bounding_box(); - const Vec2d& bed_center = bed_shape_bb().center(); - model.center_instances_around_point(bed_center); - } + if (get_config("autocenter") == "1") + model.center_instances_around_point(this->bed.build_volume().bed_center()); unsigned int update_status = 0; const bool force_background_processing_restart = this->printer_technology == ptSLA || (flags & (unsigned int)UpdateParams::FORCE_BACKGROUND_PROCESSING_UPDATE); @@ -2281,19 +2274,6 @@ std::string Plater::priv::get_config(const std::string &key) const return wxGetApp().app_config->get(key); } -BoundingBoxf Plater::priv::bed_shape_bb() const -{ - BoundingBox bb = scaled_bed_shape_bb(); - return BoundingBoxf(unscale(bb.min), unscale(bb.max)); -} - -BoundingBox Plater::priv::scaled_bed_shape_bb() const -{ - const auto *bed_shape_opt = config->opt("bed_shape"); - const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); - return bed_shape.bounding_box(); -} - std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/) { if (input_files.empty()) { return std::vector(); } @@ -2564,7 +2544,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (one_by_one) { if (type_3mf && !is_project_file) - model.center_instances_around_point(bed_shape_bb().center()); + model.center_instances_around_point(this->bed.build_volume().bed_center()); auto loaded_idxs = load_model_objects(model.objects, is_project_file); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } else { @@ -2623,8 +2603,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) { - const BoundingBoxf bed_shape = bed_shape_bb(); - const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0) - 2.0 * Vec3d::Ones(); + const Vec3d bed_size = Slic3r::to_3d(this->bed.build_volume().bounding_volume2d().size(), 1.0) - 2.0 * Vec3d::Ones(); #ifndef AUTOPLACEMENT_ON_LOAD // bool need_arrange = false; @@ -2652,7 +2631,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode // add a default instance and center object around origin object->center_around_origin(); // also aligns object to Z = 0 ModelInstance* instance = object->add_instance(); - instance->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -object->origin_translation(2))); + instance->set_offset(Slic3r::to_3d(this->bed.build_volume().bed_center(), -object->origin_translation(2))); #endif /* AUTOPLACEMENT_ON_LOAD */ } @@ -2989,7 +2968,7 @@ void Plater::find_new_position(const ModelInstancePtrs &instances) if (auto wt = get_wipe_tower_arrangepoly(*this)) fixed.emplace_back(*wt); - arrangement::arrange(movable, fixed, get_bed_shape(*config()), arr_params); + arrangement::arrange(movable, fixed, this->build_volume().polygon(), arr_params); for (auto & m : movable) m.apply(); @@ -3057,22 +3036,9 @@ void Plater::priv::schedule_background_process() void Plater::priv::update_print_volume_state() { -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - const ConfigOptionPoints* opt = dynamic_cast(this->config->option("bed_shape")); - const Polygon bed_poly_convex = offset(Geometry::convex_hull(Polygon::new_scale(opt->values).points), static_cast(scale_(BedEpsilon))).front(); - const float bed_height = this->config->opt_float("max_print_height"); - this->q->model().update_print_volume_state(bed_poly_convex, bed_height); -#else - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt("bed_shape")->values)); - BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height")))); - // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. - print_volume.offset(BedEpsilon); - print_volume.min(2) = -1e10; - this->q->model().update_print_volume_state(print_volume); -#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + this->q->model().update_print_volume_state(this->bed.build_volume()); } - void Plater::priv::process_validation_warning(const std::string& warning) const { if (warning.empty()) @@ -4588,9 +4554,9 @@ bool Plater::priv::can_reload_from_disk() const return !paths.empty(); } -void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) +void Plater::priv::set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { - bool new_shape = bed.set_shape(shape, custom_texture, custom_model, force_as_custom); + bool new_shape = bed.set_shape(shape, max_print_height, custom_texture, custom_model, force_as_custom); if (new_shape) { if (view3D) view3D->bed_shape_changed(); if (preview) preview->bed_shape_changed(); @@ -6278,13 +6244,14 @@ void Plater::on_config_change(const DynamicPrintConfig &config) void Plater::set_bed_shape() const { set_bed_shape(p->config->option("bed_shape")->values, + p->config->option("max_print_height")->value, p->config->option("bed_custom_texture")->value, p->config->option("bed_custom_model")->value); } -void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const +void Plater::set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const { - p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom); + p->set_bed_shape(shape, max_print_height, custom_texture, custom_model, force_as_custom); } void Plater::force_filament_colors_update() @@ -6339,7 +6306,7 @@ void Plater::on_activate() } // Get vector of extruder colors considering filament color, if extruder color is undefined. -std::vector Plater::get_extruder_colors_from_plater_config(const GCodeProcessor::Result* const result) const +std::vector Plater::get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result) const { if (wxGetApp().is_gcode_viewer() && result != nullptr) return result->extruder_colors; @@ -6365,7 +6332,7 @@ std::vector Plater::get_extruder_colors_from_plater_config(const GC /* Get vector of colors used for rendering of a Preview scene in "Color print" mode * It consists of extruder colors and colors, saved in model.custom_gcode_per_print_z */ -std::vector Plater::get_colors_for_color_print(const GCodeProcessor::Result* const result) const +std::vector Plater::get_colors_for_color_print(const GCodeProcessorResult* const result) const { std::vector colors = get_extruder_colors_from_plater_config(result); colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); @@ -6431,11 +6398,6 @@ GLCanvas3D* Plater::get_current_canvas3D() return p->get_current_canvas3D(); } -BoundingBoxf Plater::bed_shape_bb() const -{ - return p->bed_shape_bb(); -} - void Plater::arrange() { p->m_ui_jobs.arrange(); @@ -6725,14 +6687,9 @@ unsigned int Plater::get_environment_texture_id() const } #endif // ENABLE_ENVIRONMENT_MAP -const Bed3D& Plater::get_bed() const +const BuildVolume& Plater::build_volume() const { - return p->bed; -} - -Bed3D& Plater::get_bed() -{ - return p->bed; + return p->bed.build_volume(); } const GLToolbar& Plater::get_view_toolbar() const diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 8bc683635..4c9bfc763 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -23,6 +23,7 @@ class wxString; namespace Slic3r { +class BuildVolume; class Model; class ModelObject; enum class ModelObjectCutAttribute : int; @@ -53,7 +54,6 @@ class GLCanvas3D; class Mouse3DController; class NotificationManager; struct Camera; -class Bed3D; class GLToolbar; class PlaterPresetComboBox; @@ -265,8 +265,8 @@ public: void force_print_bed_update(); // On activating the parent window. void on_activate(); - std::vector get_extruder_colors_from_plater_config(const GCodeProcessor::Result* const result = nullptr) const; - std::vector get_colors_for_color_print(const GCodeProcessor::Result* const result = nullptr) const; + std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; void update_menus(); void show_action_buttons(const bool is_ready_to_slice) const; @@ -282,7 +282,6 @@ public: GLCanvas3D* canvas3D(); const GLCanvas3D * canvas3D() const; GLCanvas3D* get_current_canvas3D(); - BoundingBoxf bed_shape_bb() const; void arrange(); void find_new_position(const ModelInstancePtrs &instances); @@ -339,8 +338,7 @@ public: unsigned int get_environment_texture_id() const; #endif // ENABLE_ENVIRONMENT_MAP - const Bed3D& get_bed() const; - Bed3D& get_bed(); + const BuildVolume& build_volume() const; const GLToolbar& get_view_toolbar() const; GLToolbar& get_view_toolbar(); @@ -359,7 +357,7 @@ public: Mouse3DController& get_mouse3d_controller(); void set_bed_shape() const; - void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; + void set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; NotificationManager * get_notification_manager(); const NotificationManager * get_notification_manager() const; diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 6bec7af68..21f9a9663 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Line.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ShortestPath.hpp" @@ -119,82 +120,28 @@ SCENARIO("Intersections of line segments", "[Geometry]"){ } } -/* -Tests for unused methods still written in perl -{ - my $polygon = Slic3r::Polygon->new( - [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], - [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], - [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], - [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], - [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], - [285273900, 461246400], [254081000, 515273900], - ); - - # this points belongs to $polyline - # note: it's actually a vertex, while we should better check an intermediate point - my $point = Slic3r::Point->new(104577600, 327748400); - - local $Slic3r::Geometry::epsilon = 1E-5; - is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp, - [ [107014700, 340000000], [104577600, 327748400] ], - 'polygon_segment_having_point'; -} -{ - auto point = Point(736310778.185108, 5017423926.8924); - auto line = Line(Point((long int) 627484000, (long int) 3695776000), Point((long int) 750000000, (long int)3720147000)); - //is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -// Possible to delete -{ - //my $p1 = [10, 10]; - //my $p2 = [10, 20]; - //my $p3 = [10, 30]; - //my $p4 = [20, 20]; - //my $p5 = [0, 20]; - - THEN("Points in a line give the correct angles"){ - //is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points'; - //is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; +SCENARIO("polygon_is_convex works") { + GIVEN("A square of dimension 10") { + WHEN("Polygon is convex clockwise") { + Polygon cw_square { { {0, 0}, {0,10}, {10,10}, {10,0} } }; + THEN("it is not convex") { + REQUIRE(! polygon_is_convex(cw_square)); + } } - THEN("Left turns give the correct angle"){ - //is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points'; - //is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points'; - } - THEN("Right turns give the correct angle"){ - //is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points'; - //is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points'; - } - //my $p1 = [30, 30]; - //my $p2 = [20, 20]; - //my $p3 = [10, 10]; - //my $p4 = [30, 10]; - - //is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; - //is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points'; - //is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points'; -} - -SCENARIO("polygon_is_convex works"){ - GIVEN("A square of dimension 10"){ - //my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ]; - THEN("It is not convex clockwise"){ - //is polygon_is_convex($cw_square), 0, 'cw square is not convex'; - } - THEN("It is convex counter-clockwise"){ - //is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex'; + WHEN("Polygon is convex counter-clockwise") { + Polygon ccw_square { { {0, 0}, {10,0}, {10,10}, {0,10} } }; + THEN("it is convex") { + REQUIRE(polygon_is_convex(ccw_square)); + } } - } - GIVEN("A concave polygon"){ - //my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ]; - THEN("It is concave"){ - //is polygon_is_convex($convex1), 0, 'concave polygon'; + GIVEN("A concave polygon") { + Polygon concave = { {0,0}, {10,0}, {10,10}, {0,10}, {0,6}, {4,6}, {4,4}, {0,4} }; + THEN("It is not convex") { + REQUIRE(! polygon_is_convex(concave)); } } -}*/ - +} TEST_CASE("Creating a polyline generates the obvious lines", "[Geometry]"){ Slic3r::Polyline polyline; diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index e44d16949..e59e8793b 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/ShortestPath.hpp" %}