diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index d6aedf2..03605eb 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -3199,8 +3199,8 @@ inline OutRec* ParseFirstLeft(OutRec* FirstLeft) FirstLeft = FirstLeft->FirstLeft; return FirstLeft; } - //------------------------------------------------------------------------------ + // This is potentially very expensive! O(n^3)! void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp new file mode 100644 index 0000000..4b41a62 --- /dev/null +++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp @@ -0,0 +1,574 @@ +#include +#include +#include + +#include "clipper/clipper_z.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/ClipperZUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/libslic3r.h" + +#include "LineSegmentation.hpp" + +namespace Slic3r::Algorithm::LineSegmentation { + +const constexpr coord_t POINT_IS_ON_LINE_THRESHOLD_SQR = Slic3r::sqr(scaled(EPSILON)); + +struct ZAttributes +{ + bool is_clip_point = false; + bool is_new_point = false; + uint32_t point_index = 0; + + ZAttributes() = default; + + explicit ZAttributes(const uint32_t clipper_coord) : + is_clip_point((clipper_coord >> 31) & 0x1), is_new_point((clipper_coord >> 30) & 0x1), point_index(clipper_coord & 0x3FFFFFFF) {} + + explicit ZAttributes(const ClipperLib_Z::IntPoint &clipper_pt) : ZAttributes(clipper_pt.z()) {} + + ZAttributes(const bool is_clip_point, const bool is_new_point, const uint32_t point_index) : + is_clip_point(is_clip_point), is_new_point(is_new_point), point_index(point_index) + { + assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!"); + } + + // Encode the structure to uint32_t. + constexpr uint32_t encode() const + { + assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!"); + return (this->is_clip_point << 31) | (this->is_new_point << 30) | (this->point_index & 0x3FFFFFFF); + } + + // Decode the uint32_t to the structure. + static ZAttributes decode(const uint32_t clipper_coord) + { + return { bool((clipper_coord >> 31) & 0x1), bool((clipper_coord >> 30) & 0x1), clipper_coord & 0x3FFFFFFF }; + } + + static ZAttributes decode(const ClipperLib_Z::IntPoint &clipper_pt) { return ZAttributes::decode(clipper_pt.z()); } +}; + +struct LineRegionRange +{ + size_t begin_idx; // Index of the line on which the region begins. + double begin_t; // Scalar position on the begin_idx line in which the region begins. The value is from range <0., 1.>. + size_t end_idx; // Index of the line on which the region ends. + double end_t; // Scalar position on the end_idx line in which the region ends. The value is from range <0., 1.>. + size_t clip_idx; // Index of clipping ExPolygons to identified which ExPolygons group contains this line. + + LineRegionRange(size_t begin_idx, double begin_t, size_t end_idx, double end_t, size_t clip_idx) + : begin_idx(begin_idx), begin_t(begin_t), end_idx(end_idx), end_t(end_t), clip_idx(clip_idx) {} + + // Check if 'other' overlaps with this LineRegionRange. + bool is_overlap(const LineRegionRange &other) const + { + if (this->end_idx < other.begin_idx || this->begin_idx > other.end_idx) { + return false; + } else if (this->end_idx == other.begin_idx && this->end_t <= other.begin_t) { + return false; + } else if (this->begin_idx == other.end_idx && this->begin_t >= other.end_t) { + return false; + } + + return true; + } + + // Check if 'inner' is whole inside this LineRegionRange. + bool is_inside(const LineRegionRange &inner) const + { + if (!this->is_overlap(inner)) { + return false; + } + + const bool starts_after = (this->begin_idx < inner.begin_idx) || (this->begin_idx == inner.begin_idx && this->begin_t <= inner.begin_t); + const bool ends_before = (this->end_idx > inner.end_idx) || (this->end_idx == inner.end_idx && this->end_t >= inner.end_t); + + return starts_after && ends_before; + } + + bool is_zero_length() const { return this->begin_idx == this->end_idx && this->begin_t == this->end_t; } + + bool operator<(const LineRegionRange &rhs) const + { + return this->begin_idx < rhs.begin_idx || (this->begin_idx == rhs.begin_idx && this->begin_t < rhs.begin_t); + } +}; + +using LineRegionRanges = std::vector; + +inline Point make_point(const ClipperLib_Z::IntPoint &clipper_pt) { return { clipper_pt.x(), clipper_pt.y() }; } + +inline ClipperLib_Z::Paths to_clip_zpaths(const ExPolygons &clips) { return ClipperZUtils::expolygons_to_zpaths_with_same_z(clips, coord_t(ZAttributes(true, false, 0).encode())); } + +static ClipperLib_Z::Path subject_to_zpath(const Points &subject, const bool is_closed) +{ + ZAttributes z_attributes(false, false, 0); + + ClipperLib_Z::Path out; + if (!subject.empty()) { + out.reserve((subject.size() + is_closed) ? 1 : 0); + for (const Point &p : subject) { + out.emplace_back(p.x(), p.y(), z_attributes.encode()); + ++z_attributes.point_index; + } + + if (is_closed) { + // If it is closed, then duplicate the first point at the end to make a closed path open. + out.emplace_back(subject.front().x(), subject.front().y(), z_attributes.encode()); + } + } + + return out; +} + +static ClipperLib_Z::Path subject_to_zpath(const Arachne::ExtrusionLine &subject) +{ + // Closed Arachne::ExtrusionLine already has duplicated the last point. + ZAttributes z_attributes(false, false, 0); + + ClipperLib_Z::Path out; + if (!subject.empty()) { + out.reserve(subject.size()); + for (const Arachne::ExtrusionJunction &junction : subject) { + out.emplace_back(junction.p.x(), junction.p.y(), z_attributes.encode()); + ++z_attributes.point_index; + } + } + + return out; +} + +static ClipperLib_Z::Path subject_to_zpath(const Polyline &subject) { return subject_to_zpath(subject.points, false); } + +[[maybe_unused]] static ClipperLib_Z::Path subject_to_zpath(const Polygon &subject) { return subject_to_zpath(subject.points, true); } + +struct ProjectionInfo +{ + double projected_t; + double distance_sqr; +}; + +static ProjectionInfo project_point_on_line(const Point &line_from_pt, const Point &line_to_pt, const Point &query_pt) +{ + const Vec2d line_vec = (line_to_pt - line_from_pt).template cast(); + const Vec2d query_vec = (query_pt - line_from_pt).template cast(); + const double line_length_sqr = line_vec.squaredNorm(); + + if (line_length_sqr <= 0.) { + return { std::numeric_limits::max(), std::numeric_limits::max() }; + } + + const double projected_t = query_vec.dot(line_vec); + const double projected_t_normalized = std::clamp(projected_t / line_length_sqr, 0., 1.); + // Projected point have to line on the line. + if (projected_t < 0. || projected_t > line_length_sqr) { + return { projected_t_normalized, std::numeric_limits::max() }; + } + + const Vec2d projected_vec = projected_t_normalized * line_vec; + const double distance_sqr = (projected_vec - query_vec).squaredNorm(); + + return { projected_t_normalized, distance_sqr }; +} + +static int32_t find_closest_line_to_point(const ClipperLib_Z::Path &subject, const ClipperLib_Z::IntPoint &query) +{ + auto it_min = subject.end(); + double distance_sqr_min = std::numeric_limits::max(); + + const Point query_pt = make_point(query); + Point prev_pt = make_point(subject.front()); + for (auto it_curr = std::next(subject.begin()); it_curr != subject.end(); ++it_curr) { + const Point curr_pt = make_point(*it_curr); + + const double distance_sqr = project_point_on_line(prev_pt, curr_pt, query_pt).distance_sqr; + if (distance_sqr <= POINT_IS_ON_LINE_THRESHOLD_SQR) { + return int32_t(std::distance(subject.begin(), std::prev(it_curr))); + } + + if (distance_sqr < distance_sqr_min) { + distance_sqr_min = distance_sqr; + it_min = std::prev(it_curr); + } + + prev_pt = curr_pt; + } + + if (it_min != subject.end()) { + return int32_t(std::distance(subject.begin(), it_min)); + } + + return -1; +} + +std::optional create_line_region_range(ClipperLib_Z::Path &&intersection, const ClipperLib_Z::Path &subject, const size_t region_idx) +{ + if (intersection.size() < 2) { + return std::nullopt; + } + + auto need_reverse = [&subject](const ClipperLib_Z::Path &intersection) -> bool { + for (size_t curr_idx = 1; curr_idx < intersection.size(); ++curr_idx) { + ZAttributes prev_z(intersection[curr_idx - 1]); + ZAttributes curr_z(intersection[curr_idx]); + + if (!prev_z.is_clip_point && !curr_z.is_clip_point) { + if (prev_z.point_index > curr_z.point_index) { + return true; + } else if (curr_z.point_index == prev_z.point_index) { + assert(curr_z.point_index < subject.size()); + const Point subject_pt = make_point(subject[curr_z.point_index]); + const Point prev_pt = make_point(intersection[curr_idx - 1]); + const Point curr_pt = make_point(intersection[curr_idx]); + + const double prev_dist = (prev_pt - subject_pt).cast().squaredNorm(); + const double curr_dist = (curr_pt - subject_pt).cast().squaredNorm(); + if (prev_dist > curr_dist) { + return true; + } + } + } + } + + return false; + }; + + for (ClipperLib_Z::IntPoint &clipper_pt : intersection) { + const ZAttributes clipper_pt_z(clipper_pt); + if (!clipper_pt_z.is_clip_point) { + continue; + } + + // FIXME @hejllukas: We could save searing for the source line in some cases using other intersection points, + // but in reality, the clip point will be inside the intersection in very rare cases. + if (int32_t subject_line_idx = find_closest_line_to_point(subject, clipper_pt); subject_line_idx != -1) { + clipper_pt.z() = coord_t(ZAttributes(false, true, subject_line_idx).encode()); + } + + assert(!ZAttributes(clipper_pt).is_clip_point); + if (ZAttributes(clipper_pt).is_clip_point) { + return std::nullopt; + } + } + + // Ensure that indices of source input are ordered in increasing order. + if (need_reverse(intersection)) { + std::reverse(intersection.begin(), intersection.end()); + } + + ZAttributes begin_z(intersection.front()); + ZAttributes end_z(intersection.back()); + + assert(begin_z.point_index <= subject.size() && end_z.point_index <= subject.size()); + const size_t begin_idx = begin_z.point_index; + const size_t end_idx = end_z.point_index; + const double begin_t = begin_z.is_new_point ? project_point_on_line(make_point(subject[begin_idx]), make_point(subject[begin_idx + 1]), make_point(intersection.front())).projected_t : 0.; + const double end_t = end_z.is_new_point ? project_point_on_line(make_point(subject[end_idx]), make_point(subject[end_idx + 1]), make_point(intersection.back())).projected_t : 0.; + + if (begin_t == std::numeric_limits::max() || end_t == std::numeric_limits::max()) { + return std::nullopt; + } + + return LineRegionRange{ begin_idx, begin_t, end_idx, end_t, region_idx }; +} + +LineRegionRanges intersection_with_region(const ClipperLib_Z::Path &subject, const ClipperLib_Z::Paths &clips, const size_t region_config_idx) +{ + ClipperLib_Z::Clipper clipper; + clipper.PreserveCollinear(true); // Especially with Arachne, we don't want to remove collinear edges. + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, + const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, + ClipperLib_Z::IntPoint &new_pt) { + const ZAttributes e1bot_z(e1bot), e1top_z(e1top), e2bot_z(e2bot), e2top_z(e2top); + + assert(e1bot_z.is_clip_point == e1top_z.is_clip_point); + assert(e2bot_z.is_clip_point == e2top_z.is_clip_point); + + if (!e1bot_z.is_clip_point && !e1top_z.is_clip_point) { + assert(e1bot_z.point_index + 1 == e1top_z.point_index || e1bot_z.point_index == e1top_z.point_index + 1); + new_pt.z() = coord_t(ZAttributes(false, true, std::min(e1bot_z.point_index, e1top_z.point_index)).encode()); + } else if (!e2bot_z.is_clip_point && !e2top_z.is_clip_point) { + assert(e2bot_z.point_index + 1 == e2top_z.point_index || e2bot_z.point_index == e2top_z.point_index + 1); + new_pt.z() = coord_t(ZAttributes(false, true, std::min(e2bot_z.point_index, e2top_z.point_index)).encode()); + } else { + assert(false && "At least one of the conditions above has to be met."); + } + }); + + clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); + clipper.AddPaths(clips, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::Paths intersections; + { + ClipperLib_Z::PolyTree clipped_polytree; + clipper.Execute(ClipperLib_Z::ctIntersection, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), intersections); + } + + LineRegionRanges line_region_ranges; + line_region_ranges.reserve(intersections.size()); + for (ClipperLib_Z::Path &intersection : intersections) { + if (std::optional region_range = create_line_region_range(std::move(intersection), subject, region_config_idx); region_range.has_value()) { + line_region_ranges.emplace_back(*region_range); + } + } + + return line_region_ranges; +} + +LineRegionRanges create_continues_line_region_ranges(LineRegionRanges &&line_region_ranges, const size_t default_clip_idx, const size_t total_lines_cnt) +{ + if (line_region_ranges.empty()) { + return line_region_ranges; + } + + std::sort(line_region_ranges.begin(), line_region_ranges.end()); + + // Resolve overlapping regions if it happens, but it should never happen. + for (size_t region_range_idx = 1; region_range_idx < line_region_ranges.size(); ++region_range_idx) { + LineRegionRange &prev_range = line_region_ranges[region_range_idx - 1]; + LineRegionRange &curr_range = line_region_ranges[region_range_idx]; + + assert(!prev_range.is_overlap(curr_range)); + if (prev_range.is_inside(curr_range)) { + // Make the previous range zero length to remove it later. + curr_range = prev_range; + prev_range.begin_idx = curr_range.begin_idx; + prev_range.begin_t = curr_range.begin_t; + prev_range.end_idx = curr_range.begin_idx; + prev_range.end_t = curr_range.begin_t; + } else if (prev_range.is_overlap(curr_range)) { + curr_range.begin_idx = prev_range.end_idx; + curr_range.begin_t = prev_range.end_t; + } + } + + // Fill all gaps between regions with the default region. + LineRegionRanges line_region_ranges_out; + size_t prev_line_idx = 0.; + double prev_t = 0.; + for (const LineRegionRange &curr_line_region : line_region_ranges) { + if (curr_line_region.is_zero_length()) { + continue; + } + + assert(prev_line_idx < curr_line_region.begin_idx || (prev_line_idx == curr_line_region.begin_idx && prev_t <= curr_line_region.begin_t)); + + // Fill the gap if it is necessary. + if (prev_line_idx != curr_line_region.begin_idx || prev_t != curr_line_region.begin_t) { + line_region_ranges_out.emplace_back(prev_line_idx, prev_t, curr_line_region.begin_idx, curr_line_region.begin_t, default_clip_idx); + } + + // Add the current region. + line_region_ranges_out.emplace_back(curr_line_region); + prev_line_idx = curr_line_region.end_idx; + prev_t = curr_line_region.end_t; + } + + // Fill the last remaining gap if it exists. + const size_t last_line_idx = total_lines_cnt - 1; + if ((prev_line_idx == last_line_idx && prev_t == 1.) || ((prev_line_idx == total_lines_cnt && prev_t == 0.))) { + // There is no gap at the end. + return line_region_ranges_out; + } + + // Fill the last remaining gap. + line_region_ranges_out.emplace_back(prev_line_idx, prev_t, last_line_idx, 1., default_clip_idx); + + return line_region_ranges_out; +} + +LineRegionRanges subject_segmentation(const ClipperLib_Z::Path &subject, const std::vector &expolygons_clips, const size_t default_clip_idx = 0) +{ + LineRegionRanges line_region_ranges; + for (const ExPolygons &expolygons_clip : expolygons_clips) { + const size_t expolygons_clip_idx = &expolygons_clip - expolygons_clips.data(); + const ClipperLib_Z::Paths clips = to_clip_zpaths(expolygons_clip); + Slic3r::append(line_region_ranges, intersection_with_region(subject, clips, expolygons_clip_idx + default_clip_idx + 1)); + } + + return create_continues_line_region_ranges(std::move(line_region_ranges), default_clip_idx, subject.size() - 1); +} + +PolylineSegment create_polyline_segment(const LineRegionRange &line_region_range, const Polyline &subject) +{ + Polyline polyline_out; + if (line_region_range.begin_t == 0.) { + polyline_out.points.emplace_back(subject[line_region_range.begin_idx]); + } else { + assert(line_region_range.begin_idx <= subject.size()); + Point interpolated_start_pt = lerp(subject[line_region_range.begin_idx], subject[line_region_range.begin_idx + 1], line_region_range.begin_t); + polyline_out.points.emplace_back(interpolated_start_pt); + } + + for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) { + polyline_out.points.emplace_back(subject[line_idx]); + } + + if (line_region_range.end_t == 0.) { + polyline_out.points.emplace_back(subject[line_region_range.end_idx]); + } else if (line_region_range.end_t == 1.) { + assert(line_region_range.end_idx <= subject.size()); + polyline_out.points.emplace_back(subject[line_region_range.end_idx + 1]); + } else { + assert(line_region_range.end_idx <= subject.size()); + Point interpolated_end_pt = lerp(subject[line_region_range.end_idx], subject[line_region_range.end_idx + 1], line_region_range.end_t); + polyline_out.points.emplace_back(interpolated_end_pt); + } + + return { polyline_out, line_region_range.clip_idx }; +} + +PolylineSegments create_polyline_segments(const LineRegionRanges &line_region_ranges, const Polyline &subject) +{ + PolylineSegments polyline_segments; + polyline_segments.reserve(line_region_ranges.size()); + for (const LineRegionRange ®ion_range : line_region_ranges) { + polyline_segments.emplace_back(create_polyline_segment(region_range, subject)); + } + + return polyline_segments; +} + +ExtrusionSegment create_extrusion_segment(const LineRegionRange &line_region_range, const Arachne::ExtrusionLine &subject) +{ + // When we call this function, we split ExtrusionLine into at least two segments, so none of those segments are closed. + Arachne::ExtrusionLine extrusion_out(subject.inset_idx, subject.is_odd); + if (line_region_range.begin_t == 0.) { + extrusion_out.junctions.emplace_back(subject[line_region_range.begin_idx]); + } else { + assert(line_region_range.begin_idx <= subject.size()); + const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.begin_idx]; + const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.begin_idx + 1]; + + const Point interpolated_start_pt = lerp(junction_from.p, junction_to.p, line_region_range.begin_t); + const coord_t interpolated_start_w = lerp(junction_from.w, junction_to.w, line_region_range.begin_t); + + assert(junction_from.perimeter_index == junction_to.perimeter_index); + extrusion_out.junctions.emplace_back(interpolated_start_pt, interpolated_start_w, junction_from.perimeter_index); + } + + for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) { + extrusion_out.junctions.emplace_back(subject[line_idx]); + } + + if (line_region_range.end_t == 0.) { + extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx]); + } else if (line_region_range.end_t == 1.) { + assert(line_region_range.end_idx <= subject.size()); + extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx + 1]); + } else { + assert(line_region_range.end_idx <= subject.size()); + const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.end_idx]; + const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.end_idx + 1]; + + const Point interpolated_end_pt = lerp(junction_from.p, junction_to.p, line_region_range.end_t); + const coord_t interpolated_end_w = lerp(junction_from.w, junction_to.w, line_region_range.end_t); + + assert(junction_from.perimeter_index == junction_to.perimeter_index); + extrusion_out.junctions.emplace_back(interpolated_end_pt, interpolated_end_w, junction_from.perimeter_index); + } + + return { extrusion_out, line_region_range.clip_idx }; +} + +ExtrusionSegments create_extrusion_segments(const LineRegionRanges &line_region_ranges, const Arachne::ExtrusionLine &subject) +{ + ExtrusionSegments extrusion_segments; + extrusion_segments.reserve(line_region_ranges.size()); + for (const LineRegionRange ®ion_range : line_region_ranges) { + extrusion_segments.emplace_back(create_extrusion_segment(region_range, subject)); + } + + return extrusion_segments; +} + +PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx); + if (line_region_ranges.empty()) { + return { PolylineSegment{subject, default_clip_idx} }; + } else if (line_region_ranges.size() == 1) { + return { PolylineSegment{subject, line_region_ranges.front().clip_idx} }; + } + + return create_polyline_segments(line_region_ranges, subject); +} + +PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + return polyline_segmentation(to_polyline(subject), expolygons_clips, default_clip_idx); +} + +ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &expolygons_clips, const size_t default_clip_idx) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx); + if (line_region_ranges.empty()) { + return { ExtrusionSegment{subject, default_clip_idx} }; + } else if (line_region_ranges.size() == 1) { + return { ExtrusionSegment{subject, line_region_ranges.front().clip_idx} }; + } + + return create_extrusion_segments(line_region_ranges, subject); +} + +inline std::vector to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips) +{ + std::vector expolygons_clips; + expolygons_clips.reserve(perimeter_regions_clips.size()); + for (const PerimeterRegion &perimeter_region_clip : perimeter_regions_clips) { + expolygons_clips.emplace_back(perimeter_region_clip.expolygons); + } + + return expolygons_clips; +} + +PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips)); + if (line_region_ranges.empty()) { + return { PolylineRegionSegment{subject, base_config} }; + } else if (line_region_ranges.size() == 1) { + return { PolylineRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} }; + } + + PolylineRegionSegments segments_out; + for (PolylineSegment &segment : create_polyline_segments(line_region_ranges, subject)) { + const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config(); + segments_out.emplace_back(std::move(segment.polyline), config); + } + + return segments_out; +} + +PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + return polyline_segmentation(to_polyline(subject), base_config, perimeter_regions_clips); +} + +ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips) +{ + const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips)); + if (line_region_ranges.empty()) { + return { ExtrusionRegionSegment{subject, base_config} }; + } else if (line_region_ranges.size() == 1) { + return { ExtrusionRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} }; + } + + ExtrusionRegionSegments segments_out; + for (ExtrusionSegment &segment : create_extrusion_segments(line_region_ranges, subject)) { + const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config(); + segments_out.emplace_back(std::move(segment.extrusion), config); + } + + return segments_out; +} + +} // namespace Slic3r::Algorithm::LineSegmentation diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp new file mode 100644 index 0000000..5e53e0a --- /dev/null +++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp @@ -0,0 +1,69 @@ +#ifndef libslic3r_LineSegmentation_hpp_ +#define libslic3r_LineSegmentation_hpp_ + +#include + +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" + +namespace Slic3r { +class ExPolygon; +class Polyline; +class Polygon; +class PrintRegionConfig; + +struct PerimeterRegion; + +using ExPolygons = std::vector; +using PerimeterRegions = std::vector; +} // namespace Slic3r + +namespace Slic3r::Arachne { +struct ExtrusionLine; +} + +namespace Slic3r::Algorithm::LineSegmentation { + +struct PolylineSegment +{ + Polyline polyline; + size_t clip_idx; +}; + +struct PolylineRegionSegment +{ + Polyline polyline; + const PrintRegionConfig &config; + + PolylineRegionSegment(const Polyline &polyline, const PrintRegionConfig &config) : polyline(polyline), config(config) {} +}; + +struct ExtrusionSegment +{ + Arachne::ExtrusionLine extrusion; + size_t clip_idx; +}; + +struct ExtrusionRegionSegment +{ + Arachne::ExtrusionLine extrusion; + const PrintRegionConfig &config; + + ExtrusionRegionSegment(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &config) : extrusion(extrusion), config(config) {} +}; + +using PolylineSegments = std::vector; +using ExtrusionSegments = std::vector; +using PolylineRegionSegments = std::vector; +using ExtrusionRegionSegments = std::vector; + +PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); +PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); +ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0); + +PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); +PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); +ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips); + +} // namespace Slic3r::Algorithm::LineSegmentation + +#endif // libslic3r_LineSegmentation_hpp_ diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index fe43852..0a23227 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -52,9 +52,6 @@ void AppConfig::reset() void AppConfig::set_defaults() { if (m_mode == EAppMode::Editor) { - // Reset the empty fields to defaults. - if (get("autocenter").empty()) - set("autocenter", "0"); // Disable background processing by default as it is not stable. if (get("background_processing").empty()) set("background_processing", "0"); diff --git a/src/libslic3r/Arachne/PerimeterOrder.hpp b/src/libslic3r/Arachne/PerimeterOrder.hpp index 20d8a3d..f8469d9 100644 --- a/src/libslic3r/Arachne/PerimeterOrder.hpp +++ b/src/libslic3r/Arachne/PerimeterOrder.hpp @@ -32,9 +32,6 @@ struct PerimeterExtrusion size_t depth = std::numeric_limits::max(); PerimeterExtrusion *nearest_external_perimeter = nullptr; - // Should this extrusion be fuzzyfied during path generation? - bool fuzzify = false; - // Returns if ExtrusionLine is a contour or a hole. bool is_contour() const { return extrusion.is_contour(); } diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp index 49f721b..d0139b0 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp @@ -55,7 +55,8 @@ inline const Point& make_point(const ExtrusionJunction& ej) return ej.p; } -using LineJunctions = std::vector; //; //; } #endif // UTILS_EXTRUSION_JUNCTION_H diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index c597ac2..16dfa9c 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -23,6 +23,8 @@ namespace Slic3r::Arachne ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {} +ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd, const bool is_closed) : inset_idx(inset_idx), is_odd(is_odd), is_closed(is_closed) {} + int64_t ExtrusionLine::getLength() const { if (junctions.empty()) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index 32bbc96..fb61760 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -81,7 +81,8 @@ struct ExtrusionLine */ std::vector junctions; - ExtrusionLine(const size_t inset_idx, const bool is_odd); + ExtrusionLine(size_t inset_idx, bool is_odd); + ExtrusionLine(size_t inset_idx, bool is_odd, bool is_closed); ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {} ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {} diff --git a/src/libslic3r/Arrange/Arrange.hpp b/src/libslic3r/Arrange/Arrange.hpp deleted file mode 100644 index bd5c921..0000000 --- a/src/libslic3r/Arrange/Arrange.hpp +++ /dev/null @@ -1,269 +0,0 @@ - -#ifndef ARRANGE2_HPP -#define ARRANGE2_HPP - -#include "Scene.hpp" -#include "Items/MutableItemTraits.hpp" -#include "Core/NFP/NFPArrangeItemTraits.hpp" - -#include "libslic3r/MinAreaBoundingBox.hpp" - -namespace Slic3r { namespace arr2 { - -template class Arranger -{ -public: - class Ctl : public ArrangeTaskCtl { - public: - virtual void on_packed(ArrItem &item) {}; - }; - - virtual ~Arranger() = default; - - virtual void arrange(std::vector &items, - const std::vector &fixed, - const ExtendedBed &bed, - Ctl &ctl) = 0; - - void arrange(std::vector &items, - const std::vector &fixed, - const ExtendedBed &bed, - ArrangeTaskCtl &ctl); - - void arrange(std::vector &items, - const std::vector &fixed, - const ExtendedBed &bed, - Ctl &&ctl) - { - arrange(items, fixed, bed, ctl); - } - - void arrange(std::vector &items, - const std::vector &fixed, - const ExtendedBed &bed, - ArrangeTaskCtl &&ctl) - { - arrange(items, fixed, bed, ctl); - } - - static std::unique_ptr create(const ArrangeSettingsView &settings); -}; - -template using ArrangerCtl = typename Arranger::Ctl; - -template -class DefaultArrangerCtl : public Arranger::Ctl { - ArrangeTaskCtl *taskctl = nullptr; - -public: - DefaultArrangerCtl() = default; - - explicit DefaultArrangerCtl(ArrangeTaskCtl &ctl) : taskctl{&ctl} {} - - void update_status(int st) override - { - if (taskctl) - taskctl->update_status(st); - } - - bool was_canceled() const override - { - if (taskctl) - return taskctl->was_canceled(); - - return false; - } -}; - -template -void Arranger::arrange(std::vector &items, - const std::vector &fixed, - const ExtendedBed &bed, - ArrangeTaskCtl &ctl) -{ - arrange(items, fixed, bed, DefaultArrangerCtl{ctl}); -} - -class EmptyItemOutlineError: public std::exception { - static constexpr const char *Msg = "No outline can be derived for object"; - -public: - const char* what() const noexcept override { return Msg; } -}; - -template class ArrangeableToItemConverter -{ -public: - virtual ~ArrangeableToItemConverter() = default; - - // May throw EmptyItemOutlineError - virtual ArrItem convert(const Arrangeable &arrbl, coord_t offs = 0) const = 0; - - // Returns the extent of simplification that the converter utilizes when - // creating arrange items. Zero shall mean no simplification at all. - virtual coord_t simplification_tolerance() const { return 0; } - - static std::unique_ptr create( - ArrangeSettingsView::GeometryHandling geometry_handling, - coord_t safety_d); - - static std::unique_ptr create( - const Scene &sc) - { - return create(sc.settings().get_geometry_handling(), - scaled(sc.settings().get_distance_from_objects())); - } -}; - -template> -class AnyWritableDataStore: public AnyWritable -{ - DStore &dstore; - -public: - AnyWritableDataStore(DStore &store): dstore{store} {} - - void write(std::string_view key, std::any d) override - { - set_data(dstore, std::string{key}, std::move(d)); - } -}; - -template -class BasicItemConverter : public ArrangeableToItemConverter -{ - coord_t m_safety_d; - coord_t m_simplify_tol; - -public: - BasicItemConverter(coord_t safety_d = 0, coord_t simpl_tol = 0) - : m_safety_d{safety_d}, m_simplify_tol{simpl_tol} - {} - - coord_t safety_dist() const noexcept { return m_safety_d; } - - coord_t simplification_tolerance() const override - { - return m_simplify_tol; - } -}; - -template -class ConvexItemConverter : public BasicItemConverter -{ -public: - using BasicItemConverter::BasicItemConverter; - - ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override; -}; - -template -class AdvancedItemConverter : public BasicItemConverter -{ -protected: - virtual ArrItem get_arritem(const Arrangeable &arrbl, coord_t eps) const; - -public: - using BasicItemConverter::BasicItemConverter; - - ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override; -}; - -template -class BalancedItemConverter : public AdvancedItemConverter -{ -protected: - ArrItem get_arritem(const Arrangeable &arrbl, coord_t offs) const override; - -public: - using AdvancedItemConverter::AdvancedItemConverter; -}; - -template struct ImbueableItemTraits_ -{ - static constexpr const char *Key = "object_id"; - - static void imbue_id(ArrItem &itm, const ObjectID &id) - { - set_arbitrary_data(itm, Key, id); - } - - static std::optional retrieve_id(const ArrItem &itm) - { - std::optional ret; - auto idptr = get_data(itm, Key); - if (idptr) - ret = *idptr; - - return ret; - } -}; - -template -using ImbueableItemTraits = ImbueableItemTraits_>; - -template -void imbue_id(ArrItem &itm, const ObjectID &id) -{ - ImbueableItemTraits::imbue_id(itm, id); -} - -template -std::optional retrieve_id(const ArrItem &itm) -{ - return ImbueableItemTraits::retrieve_id(itm); -} - -template -bool apply_arrangeitem(const ArrItem &itm, ArrangeableModel &mdl) -{ - bool ret = false; - - if (auto id = retrieve_id(itm)) { - mdl.visit_arrangeable(*id, [&itm, &ret](Arrangeable &arrbl) { - if ((ret = arrbl.assign_bed(get_bed_index(itm)))) - arrbl.transform(unscaled(get_translation(itm)), get_rotation(itm)); - }); - } - - return ret; -} - -template -double get_min_area_bounding_box_rotation(const ArrItem &itm) -{ - return MinAreaBoundigBox{envelope_convex_hull(itm), - MinAreaBoundigBox::pcConvex} - .angle_to_X(); -} - -template -double get_fit_into_bed_rotation(const ArrItem &itm, const RectangleBed &bed) -{ - double ret = 0.; - - auto bbsz = envelope_bounding_box(itm).size(); - auto binbb = bounding_box(bed); - auto binbbsz = binbb.size(); - - if (bbsz.x() >= binbbsz.x() || bbsz.y() >= binbbsz.y()) - ret = fit_into_box_rotation(envelope_convex_hull(itm), binbb); - - return ret; -} - -template -auto get_corrected_bed(const ExtendedBed &bed, - const ArrangeableToItemConverter &converter) -{ - auto bedcpy = bed; - visit_bed([tol = -converter.simplification_tolerance()](auto &rawbed) { - rawbed = offset(rawbed, tol); - }, bedcpy); - - return bedcpy; -} - -}} // namespace Slic3r::arr2 - -#endif // ARRANGE2_HPP diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp deleted file mode 100644 index c89bfa1..0000000 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ /dev/null @@ -1,498 +0,0 @@ - -#ifndef ARRANGEIMPL_HPP -#define ARRANGEIMPL_HPP - -#include -#include - -#include "Arrange.hpp" - -#include "Core/ArrangeBase.hpp" -#include "Core/ArrangeFirstFit.hpp" -#include "Core/NFP/PackStrategyNFP.hpp" -#include "Core/NFP/Kernels/TMArrangeKernel.hpp" -#include "Core/NFP/Kernels/GravityKernel.hpp" -#include "Core/NFP/RectangleOverfitPackingStrategy.hpp" -#include "Core/Beds.hpp" - -#include "Items/MutableItemTraits.hpp" - -#include "SegmentedRectangleBed.hpp" - -#include "libslic3r/Execution/ExecutionTBB.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" - -#ifndef NDEBUG -#include "Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp" -#endif - -namespace Slic3r { namespace arr2 { - -// arrange overload for SegmentedRectangleBed which is exactly what is used -// by XL printers. -template -void arrange(SelectionStrategy &&selstrategy, - PackStrategy &&packingstrategy, - const Range &items, - const Range &fixed, - const SegmentedRectangleBed &bed) -{ - // Dispatch: - arrange(std::forward(selstrategy), - std::forward(packingstrategy), items, fixed, - RectangleBed{bed.bb}, SelStrategyTag{}); - - std::vector bed_indices = get_bed_indices(items, fixed); - std::map pilebb; - std::map bed_occupied; - - for (auto &itm : items) { - auto bedidx = get_bed_index(itm); - if (bedidx >= 0) { - pilebb[bedidx].merge(fixed_bounding_box(itm)); - if (is_wipe_tower(itm)) - bed_occupied[bedidx] = true; - } - } - - for (auto &fxitm : fixed) { - auto bedidx = get_bed_index(fxitm); - if (bedidx >= 0) - bed_occupied[bedidx] = true; - } - - auto bedbb = bounding_box(bed); - auto piecesz = unscaled(bedbb).size(); - piecesz.x() /= bed.segments_x(); - piecesz.y() /= bed.segments_y(); - - using Pivots = RectPivots; - - Pivots pivot = bed.alignment(); - - for (int bedidx : bed_indices) { - if (auto occup_it = bed_occupied.find(bedidx); - occup_it != bed_occupied.end() && occup_it->second) - continue; - - BoundingBox bb; - auto pilesz = unscaled(pilebb[bedidx]).size(); - bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x()); - bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y()); - - switch (pivot) { - case Pivots::BottomLeft: - bb.translate(bedbb.min - bb.min); - break; - case Pivots::TopRight: - bb.translate(bedbb.max - bb.max); - break; - case Pivots::BottomRight: { - Point bedref{bedbb.max.x(), bedbb.min.y()}; - Point bbref {bb.max.x(), bb.min.y()}; - bb.translate(bedref - bbref); - break; - } - case Pivots::TopLeft: { - Point bedref{bedbb.min.x(), bedbb.max.y()}; - Point bbref {bb.min.x(), bb.max.y()}; - bb.translate(bedref - bbref); - break; - } - case Pivots::Center: { - bb.translate(bedbb.center() - bb.center()); - break; - } - default: - ; - } - - Vec2crd d = bb.center() - pilebb[bedidx].center(); - - auto pilebbx = pilebb[bedidx]; - pilebbx.translate(d); - - Point corr{0, 0}; - corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x()) - -std::max(0, pilebbx.max.x() - bedbb.max.x()); - corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y()) - -std::max(0, pilebbx.max.y() - bedbb.max.y()); - - d += corr; - - for (auto &itm : items) - if (get_bed_index(itm) == static_cast(bedidx) && !is_wipe_tower(itm)) - translate(itm, d); - } -} - - -using VariantKernel = - boost::variant; - -template<> struct KernelTraits_ { - template - static double placement_fitness(const VariantKernel &kernel, - const ArrItem &itm, - const Vec2crd &transl) - { - double ret = NaNd; - boost::apply_visitor( - [&](auto &k) { ret = k.placement_fitness(itm, transl); }, kernel); - - return ret; - } - - template - static bool on_start_packing(VariantKernel &kernel, - ArrItem &itm, - const Bed &bed, - const Ctx &packing_context, - const Range &remaining_items) - { - bool ret = false; - - boost::apply_visitor([&](auto &k) { - ret = k.on_start_packing(itm, bed, packing_context, remaining_items); - }, kernel); - - return ret; - } - - template - static bool on_item_packed(VariantKernel &kernel, ArrItem &itm) - { - bool ret = false; - boost::apply_visitor([&](auto &k) { ret = k.on_item_packed(itm); }, - kernel); - - return ret; - } -}; - -template -struct firstfit::ItemArrangedVisitor> { - template - static void on_arranged(ArrItem &itm, - const Bed &bed, - const Range &packed, - const Range &remaining) - { - using OnArrangeCb = std::function &)>; - - auto cb = get_data(itm, "on_arranged"); - - if (cb) { - (*cb)(itm); - } - } -}; - -inline RectPivots xlpivots_to_rect_pivots(ArrangeSettingsView::XLPivots xlpivot) -{ - if (xlpivot == arr2::ArrangeSettingsView::xlpRandom) { - // means it should be random - std::random_device rd{}; - std::mt19937 rng(rd()); - std::uniform_int_distribution - dist(0, arr2::ArrangeSettingsView::xlpRandom - 1); - xlpivot = static_cast(dist(rng)); - } - - RectPivots rectpivot = RectPivots::Center; - - switch(xlpivot) { - case arr2::ArrangeSettingsView::xlpCenter: rectpivot = RectPivots::Center; break; - case arr2::ArrangeSettingsView::xlpFrontLeft: rectpivot = RectPivots::BottomLeft; break; - case arr2::ArrangeSettingsView::xlpFrontRight: rectpivot = RectPivots::BottomRight; break; - case arr2::ArrangeSettingsView::xlpRearLeft: rectpivot = RectPivots::TopLeft; break; - case arr2::ArrangeSettingsView::xlpRearRight: rectpivot = RectPivots::TopRight; break; - default: - ; - } - - return rectpivot; -} - -template -void fill_rotations(const Range &items, - const Bed &bed, - const ArrangeSettingsView &settings) -{ - if (!settings.is_rotation_enabled()) - return; - - for (auto &itm : items) { - if (is_wipe_tower(itm)) // Rotating the wipe tower is currently problematic - continue; - - // Use the minimum bounding box rotation as a starting point. - auto minbbr = get_min_area_bounding_box_rotation(itm); - std::vector rotations = - {minbbr, - minbbr + PI / 4., minbbr + PI / 2., - minbbr + PI, minbbr + 3 * PI / 4.}; - - // Add the original rotation of the item if minbbr - // is not already the original rotation (zero) - if (std::abs(minbbr) > 0.) - rotations.emplace_back(0.); - - // Also try to find the rotation that fits the item - // into a rectangular bed, given that it cannot fit, - // and there exists a rotation which can fit. - if constexpr (std::is_convertible_v) { - double fitbrot = get_fit_into_bed_rotation(itm, bed); - if (std::abs(fitbrot) > 0.) - rotations.emplace_back(fitbrot); - } - - set_allowed_rotations(itm, rotations); - } -} - -// An arranger put together to fulfill all the requirements of QIDISlicer based -// on the supplied ArrangeSettings -template -class DefaultArranger: public Arranger { - ArrangeSettings m_settings; - - static constexpr auto Accuracy = 1.; - - template - void arrange_( - const Range &items, - const Range &fixed, - const Bed &bed, - ArrangerCtl &ctl) - { - auto cmpfn = [](const auto &itm1, const auto &itm2) { - int pa = get_priority(itm1); - int pb = get_priority(itm2); - - return pa == pb ? area(envelope_convex_hull(itm1)) > area(envelope_convex_hull(itm2)) : - pa > pb; - }; - - auto on_arranged = [&ctl](auto &itm, auto &bed, auto &ctx, auto &rem) { - ctl.update_status(rem.size()); - - ctl.on_packed(itm); - - firstfit::DefaultOnArrangedFn{}(itm, bed, ctx, rem); - }; - - auto stop_cond = [&ctl] { return ctl.was_canceled(); }; - - firstfit::SelectionStrategy sel{cmpfn, on_arranged, stop_cond}; - - constexpr auto ep = ex_tbb; - - VariantKernel basekernel; - switch (m_settings.get_arrange_strategy()) { - default: - [[fallthrough]]; - case ArrangeSettingsView::asAuto: - if constexpr (std::is_convertible_v){ - basekernel = GravityKernel{}; - } else { - basekernel = TMArrangeKernel{items.size(), area(bed)}; - } - break; - case ArrangeSettingsView::asPullToCenter: - basekernel = GravityKernel{}; - break; - } - -#ifndef NDEBUG - SVGDebugOutputKernelWrapper kernel{bounding_box(bed), basekernel}; -#else - auto & kernel = basekernel; -#endif - - fill_rotations(items, bed, m_settings); - - bool with_wipe_tower = std::any_of(items.begin(), items.end(), - [](auto &itm) { - return is_wipe_tower(itm); - }); - - // With rectange bed, and no fixed items, let's use an infinite bed - // with RectangleOverfitKernelWrapper. It produces better results than - // a pure RectangleBed with inner-fit polygon calculation. - if (!with_wipe_tower && - m_settings.get_arrange_strategy() == ArrangeSettingsView::asAuto && - IsRectangular) { - PackStrategyNFP base_strategy{std::move(kernel), ep, Accuracy, stop_cond}; - - RectangleOverfitPackingStrategy final_strategy{std::move(base_strategy)}; - - arr2::arrange(sel, final_strategy, items, fixed, bed); - } else { - PackStrategyNFP ps{std::move(kernel), ep, Accuracy, stop_cond}; - - arr2::arrange(sel, ps, items, fixed, bed); - } - } - -public: - explicit DefaultArranger(const ArrangeSettingsView &settings) - { - m_settings.set_from(settings); - } - - void arrange( - std::vector &items, - const std::vector &fixed, - const ExtendedBed &bed, - ArrangerCtl &ctl) override - { - visit_bed([this, &items, &fixed, &ctl](auto rawbed) { - - if constexpr (IsSegmentedBed) - rawbed.pivot = xlpivots_to_rect_pivots( - m_settings.get_xl_alignment()); - - arrange_(range(items), crange(fixed), rawbed, ctl); - }, bed); - } -}; - -template -std::unique_ptr> Arranger::create( - const ArrangeSettingsView &settings) -{ - // Currently all that is needed is handled by DefaultArranger - return std::make_unique>(settings); -} - -template -ArrItem ConvexItemConverter::convert(const Arrangeable &arrbl, - coord_t offs) const -{ - auto bed_index = arrbl.get_bed_index(); - Polygon outline = arrbl.convex_outline(); - - if (outline.empty()) - throw EmptyItemOutlineError{}; - - Polygon envelope = arrbl.convex_envelope(); - - coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); - - if (infl != 0) { - outline = Geometry::convex_hull(offset(outline, infl)); - if (! envelope.empty()) - envelope = Geometry::convex_hull(offset(envelope, infl)); - } - - ArrItem ret; - set_convex_shape(ret, outline); - if (! envelope.empty()) - set_convex_envelope(ret, envelope); - - set_bed_index(ret, bed_index); - set_priority(ret, arrbl.priority()); - - imbue_id(ret, arrbl.id()); - if constexpr (IsWritableDataStore) - arrbl.imbue_data(AnyWritableDataStore{ret}); - - return ret; -} - -template -ArrItem AdvancedItemConverter::convert(const Arrangeable &arrbl, - coord_t offs) const -{ - auto bed_index = arrbl.get_bed_index(); - ArrItem ret = get_arritem(arrbl, offs); - - set_bed_index(ret, bed_index); - set_priority(ret, arrbl.priority()); - imbue_id(ret, arrbl.id()); - if constexpr (IsWritableDataStore) - arrbl.imbue_data(AnyWritableDataStore{ret}); - - return ret; -} - -template -ArrItem AdvancedItemConverter::get_arritem(const Arrangeable &arrbl, - coord_t offs) const -{ - coord_t infl = offs + coord_t(std::ceil(this->safety_dist() / 2.)); - - auto outline = arrbl.full_outline(); - - if (outline.empty()) - throw EmptyItemOutlineError{}; - - auto envelope = arrbl.full_envelope(); - - if (infl != 0) { - outline = offset_ex(outline, infl); - if (! envelope.empty()) - envelope = offset_ex(envelope, infl); - } - - auto simpl_tol = static_cast(this->simplification_tolerance()); - - if (simpl_tol > 0.) - { - outline = expolygons_simplify(outline, simpl_tol); - if (!envelope.empty()) - envelope = expolygons_simplify(envelope, simpl_tol); - } - - ArrItem ret; - set_shape(ret, outline); - if (! envelope.empty()) - set_envelope(ret, envelope); - - return ret; -} - -template -ArrItem BalancedItemConverter::get_arritem(const Arrangeable &arrbl, - coord_t offs) const -{ - ArrItem ret = AdvancedItemConverter::get_arritem(arrbl, offs); - set_convex_envelope(ret, envelope_convex_hull(ret)); - - return ret; -} - -template -std::unique_ptr> -ArrangeableToItemConverter::create( - ArrangeSettingsView::GeometryHandling gh, - coord_t safety_d) -{ - std::unique_ptr> ret; - - constexpr coord_t SimplifyTol = scaled(.2); - - switch(gh) { - case arr2::ArrangeSettingsView::ghConvex: - ret = std::make_unique>(safety_d); - break; - case arr2::ArrangeSettingsView::ghBalanced: - ret = std::make_unique>(safety_d, SimplifyTol); - break; - case arr2::ArrangeSettingsView::ghAdvanced: - ret = std::make_unique>(safety_d, SimplifyTol); - break; - default: - ; - } - - return ret; -} - -}} // namespace Slic3r::arr2 - -#endif // ARRANGEIMPL_HPP diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp deleted file mode 100644 index 18f4fee..0000000 --- a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp +++ /dev/null @@ -1,190 +0,0 @@ - -#include "ArrangeSettingsDb_AppCfg.hpp" - -#include "LocalesUtils.hpp" -#include "libslic3r/AppConfig.hpp" -#include "libslic3r/Arrange/ArrangeSettingsView.hpp" - -namespace Slic3r { - -ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg(AppConfig *appcfg) : m_appcfg{appcfg} -{ - sync(); -} - -void ArrangeSettingsDb_AppCfg::sync() -{ - m_settings_fff.postfix = "_fff"; - m_settings_fff_seq.postfix = "_fff_seq_print"; - m_settings_sla.postfix = "_sla"; - - std::string dist_fff_str = - m_appcfg->get("arrange", "min_object_distance_fff"); - - std::string dist_bed_fff_str = - m_appcfg->get("arrange", "min_bed_distance_fff"); - - std::string dist_fff_seq_print_str = - m_appcfg->get("arrange", "min_object_distance_fff_seq_print"); - - std::string dist_bed_fff_seq_print_str = - m_appcfg->get("arrange", "min_bed_distance_fff_seq_print"); - - std::string dist_sla_str = - m_appcfg->get("arrange", "min_object_distance_sla"); - - std::string dist_bed_sla_str = - m_appcfg->get("arrange", "min_bed_distance_sla"); - - std::string en_rot_fff_str = - m_appcfg->get("arrange", "enable_rotation_fff"); - - std::string en_rot_fff_seqp_str = - m_appcfg->get("arrange", "enable_rotation_fff_seq_print"); - - std::string en_rot_sla_str = - m_appcfg->get("arrange", "enable_rotation_sla"); - - std::string alignment_xl_str = - m_appcfg->get("arrange", "alignment_xl"); - - std::string geom_handling_str = - m_appcfg->get("arrange", "geometry_handling"); - - std::string strategy_str = - m_appcfg->get("arrange", "arrange_strategy"); - - if (!dist_fff_str.empty()) - m_settings_fff.vals.d_obj = string_to_float_decimal_point(dist_fff_str); - else - m_settings_fff.vals.d_obj = m_settings_fff.defaults.d_obj; - - if (!dist_bed_fff_str.empty()) - m_settings_fff.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_str); - else - m_settings_fff.vals.d_bed = m_settings_fff.defaults.d_bed; - - if (!dist_fff_seq_print_str.empty()) - m_settings_fff_seq.vals.d_obj = string_to_float_decimal_point(dist_fff_seq_print_str); - else - m_settings_fff_seq.vals.d_obj = m_settings_fff_seq.defaults.d_obj; - - if (!dist_bed_fff_seq_print_str.empty()) - m_settings_fff_seq.vals.d_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str); - else - m_settings_fff_seq.vals.d_bed = m_settings_fff_seq.defaults.d_bed; - - if (!dist_sla_str.empty()) - m_settings_sla.vals.d_obj = string_to_float_decimal_point(dist_sla_str); - else - m_settings_sla.vals.d_obj = m_settings_sla.defaults.d_obj; - - if (!dist_bed_sla_str.empty()) - m_settings_sla.vals.d_bed = string_to_float_decimal_point(dist_bed_sla_str); - else - m_settings_sla.vals.d_bed = m_settings_sla.defaults.d_bed; - - if (!en_rot_fff_str.empty()) - m_settings_fff.vals.rotations = (en_rot_fff_str == "1" || en_rot_fff_str == "yes"); - - if (!en_rot_fff_seqp_str.empty()) - m_settings_fff_seq.vals.rotations = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes"); - else - m_settings_fff_seq.vals.rotations = m_settings_fff_seq.defaults.rotations; - - if (!en_rot_sla_str.empty()) - m_settings_sla.vals.rotations = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); - else - m_settings_sla.vals.rotations = m_settings_sla.defaults.rotations; - - // Override default alignment and save/load it to a temporary slot "alignment_xl" - auto arr_alignment = ArrangeSettingsView::to_xl_pivots(alignment_xl_str) - .value_or(m_settings_fff.defaults.xl_align); - - m_settings_sla.vals.xl_align = arr_alignment ; - m_settings_fff.vals.xl_align = arr_alignment ; - m_settings_fff_seq.vals.xl_align = arr_alignment ; - - auto geom_handl = ArrangeSettingsView::to_geometry_handling(geom_handling_str) - .value_or(m_settings_fff.defaults.geom_handling); - - m_settings_sla.vals.geom_handling = geom_handl; - m_settings_fff.vals.geom_handling = geom_handl; - m_settings_fff_seq.vals.geom_handling = geom_handl; - - auto arr_strategy = ArrangeSettingsView::to_arrange_strategy(strategy_str) - .value_or(m_settings_fff.defaults.arr_strategy); - - m_settings_sla.vals.arr_strategy = arr_strategy; - m_settings_fff.vals.arr_strategy = arr_strategy; - m_settings_fff_seq.vals.arr_strategy = arr_strategy; -} - -void ArrangeSettingsDb_AppCfg::distance_from_obj_range(float &min, - float &max) const -{ - min = get_slot(this).dobj_range.minval; - max = get_slot(this).dobj_range.maxval; -} - -void ArrangeSettingsDb_AppCfg::distance_from_bed_range(float &min, - float &max) const -{ - min = get_slot(this).dbed_range.minval; - max = get_slot(this).dbed_range.maxval; -} - -arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_objects(float v) -{ - Slot &slot = get_slot(this); - slot.vals.d_obj = v; - m_appcfg->set("arrange", "min_object_distance" + slot.postfix, - float_to_string_decimal_point(v)); - - return *this; -} - -arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_distance_from_bed(float v) -{ - Slot &slot = get_slot(this); - slot.vals.d_bed = v; - m_appcfg->set("arrange", "min_bed_distance" + slot.postfix, - float_to_string_decimal_point(v)); - - return *this; -} - -arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_rotation_enabled(bool v) -{ - Slot &slot = get_slot(this); - slot.vals.rotations = v; - m_appcfg->set("arrange", "enable_rotation" + slot.postfix, v ? "1" : "0"); - - return *this; -} - -arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_xl_alignment(XLPivots v) -{ - m_settings_fff.vals.xl_align = v; - m_appcfg->set("arrange", "alignment_xl", std::string{get_label(v)}); - - return *this; -} - -arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_geometry_handling(GeometryHandling v) -{ - m_settings_fff.vals.geom_handling = v; - m_appcfg->set("arrange", "geometry_handling", std::string{get_label(v)}); - - return *this; -} - -arr2::ArrangeSettingsDb& ArrangeSettingsDb_AppCfg::set_arrange_strategy(ArrangeStrategy v) -{ - m_settings_fff.vals.arr_strategy = v; - m_appcfg->set("arrange", "arrange_strategy", std::string{get_label(v)}); - - return *this; -} - -} // namespace Slic3r diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp deleted file mode 100644 index 3637152..0000000 --- a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp +++ /dev/null @@ -1,97 +0,0 @@ - -#ifndef ARRANGESETTINGSDB_APPCFG_HPP -#define ARRANGESETTINGSDB_APPCFG_HPP - -#include - -#include "ArrangeSettingsView.hpp" -#include "libslic3r/AppConfig.hpp" -#include "libslic3r/PrintConfig.hpp" - -namespace Slic3r { -class AppConfig; - -class ArrangeSettingsDb_AppCfg: public arr2::ArrangeSettingsDb -{ -public: - enum Slots { slotFFF, slotFFFSeqPrint, slotSLA }; - -private: - AppConfig *m_appcfg; - Slots m_current_slot = slotFFF; - - struct FloatRange { float minval = 0.f, maxval = 100.f; }; - struct Slot - { - Values vals; - Values defaults; - FloatRange dobj_range, dbed_range; - std::string postfix; - }; - - // Settings and their defaults are stored separately for fff, - // sla and fff sequential mode - Slot m_settings_fff, m_settings_fff_seq, m_settings_sla; - - template - static auto & get_slot(Self *self, Slots slot) { - switch(slot) { - case slotFFF: return self->m_settings_fff; - case slotFFFSeqPrint: return self->m_settings_fff_seq; - case slotSLA: return self->m_settings_sla; - } - - return self->m_settings_fff; - } - - template static auto &get_slot(Self *self) - { - return get_slot(self, self->m_current_slot); - } - - template - static auto& get_ref(Self *self) { return get_slot(self).vals; } - -public: - explicit ArrangeSettingsDb_AppCfg(AppConfig *appcfg); - - void sync(); - - float get_distance_from_objects() const override { return get_ref(this).d_obj; } - float get_distance_from_bed() const override { return get_ref(this).d_bed; } - bool is_rotation_enabled() const override { return get_ref(this).rotations; } - - XLPivots get_xl_alignment() const override { return m_settings_fff.vals.xl_align; } - GeometryHandling get_geometry_handling() const override { return m_settings_fff.vals.geom_handling; } - ArrangeStrategy get_arrange_strategy() const override { return m_settings_fff.vals.arr_strategy; } - - void distance_from_obj_range(float &min, float &max) const override; - void distance_from_bed_range(float &min, float &max) const override; - - ArrangeSettingsDb& set_distance_from_objects(float v) override; - ArrangeSettingsDb& set_distance_from_bed(float v) override; - ArrangeSettingsDb& set_rotation_enabled(bool v) override; - - ArrangeSettingsDb& set_xl_alignment(XLPivots v) override; - ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) override; - ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) override; - - Values get_defaults() const override { return get_slot(this).defaults; } - - void set_active_slot(Slots slot) noexcept { m_current_slot = slot; } - void set_distance_from_obj_range(Slots slot, float min, float max) - { - get_slot(this, slot).dobj_range = FloatRange{min, max}; - } - - void set_distance_from_bed_range(Slots slot, float min, float max) - { - get_slot(this, slot).dbed_range = FloatRange{min, max}; - } - - Values &get_defaults(Slots slot) { return get_slot(this, slot).defaults; } -}; - -} // namespace Slic3r - -#endif // ARRANGESETTINGSDB_APPCFG_HPP diff --git a/src/libslic3r/Arrange/ArrangeSettingsView.hpp b/src/libslic3r/Arrange/ArrangeSettingsView.hpp deleted file mode 100644 index 3f30633..0000000 --- a/src/libslic3r/Arrange/ArrangeSettingsView.hpp +++ /dev/null @@ -1,235 +0,0 @@ - -#ifndef ARRANGESETTINGSVIEW_HPP -#define ARRANGESETTINGSVIEW_HPP - -#include -#include - -#include "libslic3r/StaticMap.hpp" - -namespace Slic3r { namespace arr2 { - -using namespace std::string_view_literals; - -class ArrangeSettingsView -{ -public: - enum GeometryHandling { ghConvex, ghBalanced, ghAdvanced, ghCount }; - enum ArrangeStrategy { asAuto, asPullToCenter, asCount }; - enum XLPivots { - xlpCenter, - xlpRearLeft, - xlpFrontLeft, - xlpFrontRight, - xlpRearRight, - xlpRandom, - xlpCount - }; - - virtual ~ArrangeSettingsView() = default; - - virtual float get_distance_from_objects() const = 0; - virtual float get_distance_from_bed() const = 0; - virtual bool is_rotation_enabled() const = 0; - - virtual XLPivots get_xl_alignment() const = 0; - virtual GeometryHandling get_geometry_handling() const = 0; - virtual ArrangeStrategy get_arrange_strategy() const = 0; - - static constexpr std::string_view get_label(GeometryHandling v) - { - constexpr auto STR = std::array{ - "0"sv, // convex - "1"sv, // balanced - "2"sv, // advanced - "-1"sv, // undefined - }; - - return STR[v]; - } - - static constexpr std::string_view get_label(ArrangeStrategy v) - { - constexpr auto STR = std::array{ - "0"sv, // auto - "1"sv, // pulltocenter - "-1"sv, // undefined - }; - - return STR[v]; - } - - static constexpr std::string_view get_label(XLPivots v) - { - constexpr auto STR = std::array{ - "0"sv, // center - "1"sv, // rearleft - "2"sv, // frontleft - "3"sv, // frontright - "4"sv, // rearright - "5"sv, // random - "-1"sv, // undefined - }; - - return STR[v]; - } - -private: - - template - using EnumMap = StaticMap; - - template - static constexpr std::optional get_enumval(std::string_view str, - const EnumMap &emap) - { - std::optional ret; - - if (auto v = query(emap, str); v.has_value()) { - ret = *v; - } - - return ret; - } - -public: - - static constexpr std::optional to_geometry_handling(std::string_view str) - { - return get_enumval(str, GeometryHandlingLabels); - } - - static constexpr std::optional to_arrange_strategy(std::string_view str) - { - return get_enumval(str, ArrangeStrategyLabels); - } - - static constexpr std::optional to_xl_pivots(std::string_view str) - { - return get_enumval(str, XLPivotsLabels); - } - -private: - - static constexpr const auto GeometryHandlingLabels = make_staticmap({ - {"convex"sv, ghConvex}, - {"balanced"sv, ghBalanced}, - {"advanced"sv, ghAdvanced}, - - {"0"sv, ghConvex}, - {"1"sv, ghBalanced}, - {"2"sv, ghAdvanced}, - }); - - static constexpr const auto ArrangeStrategyLabels = make_staticmap({ - {"auto"sv, asAuto}, - {"pulltocenter"sv, asPullToCenter}, - - {"0"sv, asAuto}, - {"1"sv, asPullToCenter} - }); - - static constexpr const auto XLPivotsLabels = make_staticmap({ - {"center"sv, xlpCenter }, - {"rearleft"sv, xlpRearLeft }, - {"frontleft"sv, xlpFrontLeft }, - {"frontright"sv, xlpFrontRight }, - {"rearright"sv, xlpRearRight }, - {"random"sv, xlpRandom }, - - {"0"sv, xlpCenter }, - {"1"sv, xlpRearLeft }, - {"2"sv, xlpFrontLeft }, - {"3"sv, xlpFrontRight }, - {"4"sv, xlpRearRight }, - {"5"sv, xlpRandom } - }); -}; - -class ArrangeSettingsDb: public ArrangeSettingsView -{ -public: - - virtual void distance_from_obj_range(float &min, float &max) const = 0; - virtual void distance_from_bed_range(float &min, float &max) const = 0; - - virtual ArrangeSettingsDb& set_distance_from_objects(float v) = 0; - virtual ArrangeSettingsDb& set_distance_from_bed(float v) = 0; - virtual ArrangeSettingsDb& set_rotation_enabled(bool v) = 0; - - virtual ArrangeSettingsDb& set_xl_alignment(XLPivots v) = 0; - virtual ArrangeSettingsDb& set_geometry_handling(GeometryHandling v) = 0; - virtual ArrangeSettingsDb& set_arrange_strategy(ArrangeStrategy v) = 0; - - struct Values { - float d_obj = 6.f, d_bed = 0.f; - bool rotations = false; - XLPivots xl_align = XLPivots::xlpFrontLeft; - GeometryHandling geom_handling = GeometryHandling::ghConvex; - ArrangeStrategy arr_strategy = ArrangeStrategy::asAuto; - - Values() = default; - Values(const ArrangeSettingsView &sv) - { - d_bed = sv.get_distance_from_bed(); - d_obj = sv.get_distance_from_objects(); - arr_strategy = sv.get_arrange_strategy(); - geom_handling = sv.get_geometry_handling(); - rotations = sv.is_rotation_enabled(); - xl_align = sv.get_xl_alignment(); - } - }; - - virtual Values get_defaults() const { return {}; } - - ArrangeSettingsDb& set_from(const ArrangeSettingsView &sv) - { - set_distance_from_bed(sv.get_distance_from_bed()); - set_distance_from_objects(sv.get_distance_from_objects()); - set_arrange_strategy(sv.get_arrange_strategy()); - set_geometry_handling(sv.get_geometry_handling()); - set_rotation_enabled(sv.is_rotation_enabled()); - set_xl_alignment(sv.get_xl_alignment()); - - return *this; - } -}; - -class ArrangeSettings: public Slic3r::arr2::ArrangeSettingsDb -{ - ArrangeSettingsDb::Values m_v = {}; - -public: - explicit ArrangeSettings( - const ArrangeSettingsDb::Values &v = {}) - : m_v{v} - {} - - explicit ArrangeSettings(const ArrangeSettingsView &v) - : m_v{v} - {} - - float get_distance_from_objects() const override { return m_v.d_obj; } - float get_distance_from_bed() const override { return m_v.d_bed; } - bool is_rotation_enabled() const override { return m_v.rotations; } - XLPivots get_xl_alignment() const override { return m_v.xl_align; } - GeometryHandling get_geometry_handling() const override { return m_v.geom_handling; } - ArrangeStrategy get_arrange_strategy() const override { return m_v.arr_strategy; } - - void distance_from_obj_range(float &min, float &max) const override { min = 0.f; max = 100.f; } - void distance_from_bed_range(float &min, float &max) const override { min = 0.f; max = 100.f; } - - ArrangeSettings& set_distance_from_objects(float v) override { m_v.d_obj = v; return *this; } - ArrangeSettings& set_distance_from_bed(float v) override { m_v.d_bed = v; return *this; } - ArrangeSettings& set_rotation_enabled(bool v) override { m_v.rotations = v; return *this; } - ArrangeSettings& set_xl_alignment(XLPivots v) override { m_v.xl_align = v; return *this; } - ArrangeSettings& set_geometry_handling(GeometryHandling v) override { m_v.geom_handling = v; return *this; } - ArrangeSettings& set_arrange_strategy(ArrangeStrategy v) override { m_v.arr_strategy = v; return *this; } - - auto & values() const { return m_v; } - auto & values() { return m_v; } -}; - -}} // namespace Slic3r::arr2 - -#endif // ARRANGESETTINGSVIEW_HPP diff --git a/src/libslic3r/Arrange/Core/ArrangeBase.hpp b/src/libslic3r/Arrange/Core/ArrangeBase.hpp deleted file mode 100644 index 5759e67..0000000 --- a/src/libslic3r/Arrange/Core/ArrangeBase.hpp +++ /dev/null @@ -1,295 +0,0 @@ - -#ifndef ARRANGEBASE_HPP -#define ARRANGEBASE_HPP - -#include -#include - -#include "ArrangeItemTraits.hpp" -#include "PackingContext.hpp" - -#include "libslic3r/Point.hpp" - -namespace Slic3r { namespace arr2 { - -namespace detail_is_const_it { - -template -struct IsConstIt_ { static constexpr bool value = false; }; - -template -using iterator_category_t = typename std::iterator_traits::iterator_category; - -template -using iterator_reference_t = typename std::iterator_traits::reference; - -template -struct IsConstIt_ >> > -{ - static constexpr bool value = - std::is_const_v>>; -}; - -} // namespace detail_is_const_it - -template -static constexpr bool IsConstIterator = detail_is_const_it::IsConstIt_::value; - -template -constexpr bool is_const_iterator(const It &it) noexcept { return IsConstIterator; } - -// The pack() function will use tag dispatching, based on the given strategy -// object that is used as its first argument. - -// This tag is derived for a packing strategy as default, and will be used -// to cast a compile error. -struct UnimplementedPacking {}; - -// PackStrategyTag_ needs to be specialized for any valid packing strategy class -template struct PackStrategyTag_ { - using Tag = UnimplementedPacking; -}; - -// Helper metafunc to derive packing strategy tag from a strategy object. -template -using PackStrategyTag = - typename PackStrategyTag_>::Tag; - - -template struct PackStrategyTraits_ { - template using Context = DefaultPackingContext; - - template - static Context create_context(PackStrategy &ps, - const Bed &bed, - int bed_index) - { - return {}; - } -}; - -template using PackStrategyTraits = PackStrategyTraits_>; - -template -using PackStrategyContext = - typename PackStrategyTraits::template Context>; - -template -PackStrategyContext create_context(PackStrategy &&ps, - const Bed &bed, - int bed_index) -{ - return PackStrategyTraits::template create_context< - StripCVRef>(ps, bed, bed_index); -} - -// Function to pack one item into a bed. -// strategy parameter holds clue to what packing strategy to use. This function -// needs to be overloaded for the strategy tag belonging to the given -// strategy. -// 'bed' parameter is the type of bed into which the new item should be packed. -// See beds.hpp for valid bed classes. -// 'item' parameter is the item to be packed. After succesful arrangement -// (see return value) the item will have it's translation and rotation -// set correctly. If the function returns false, the translation and -// rotation of the input item might be changed to arbitrary values. -// 'fixed_items' paramter holds a range of ArrItem type objects that are already -// on the bed and need to be avoided by the newly packed item. -// 'remaining_items' is a range of ArrItem type objects that are intended to be -// packed in the future. This information can be leveradged by -// the packing strategy to make more intelligent placement -// decisions for the input item. -template -bool pack(Strategy &&strategy, - const Bed &bed, - ArrItem &item, - const PackStrategyContext &context, - const Range &remaining_items) -{ - static_assert(IsConstIterator, "Remaining item iterator is not const!"); - - // Dispatch: - return pack(std::forward(strategy), bed, item, context, - remaining_items, PackStrategyTag{}); -} - -// Overload without fixed items: -template -bool pack(Strategy &&strategy, const Bed &bed, ArrItem &item) -{ - std::vector dummy; - auto context = create_context(strategy, bed, PhysicalBedId); - return pack(std::forward(strategy), bed, item, context, - crange(dummy)); -} - -// Overload when strategy is unkown, yields compile error: -template -bool pack(Strategy &&strategy, - const Bed &bed, - ArrItem &item, - const PackStrategyContext &context, - const Range &remaining_items, - const UnimplementedPacking &) -{ - static_assert(always_false::value, - "Packing unimplemented for this placement strategy"); - - return false; -} - -// Helper function to remove unpackable items from the input container. -template -void remove_unpackable_items(PackStrategy &&ps, - Container &c, - const Bed &bed, - const StopCond &stopcond) -{ - // Safety test: try to pack each item into an empty bed. If it fails - // then it should be removed from the list - auto it = c.begin(); - while (it != c.end() && !stopcond()) { - StripCVRef &itm = *it; - auto cpy{itm}; - - if (!pack(ps, bed, cpy)) { - set_bed_index(itm, Unarranged); - it = c.erase(it); - } else - it++; - } -} - -// arrange() function will use tag dispatching based on the selection strategy -// given as its first argument. - -// This tag is derived for a selection strategy as default, and will be used -// to cast a compile error. -struct UnimplementedSelection {}; - -// SelStrategyTag_ needs to be specialized for any valid selection strategy class -template struct SelStrategyTag_ { - using Tag = UnimplementedSelection; -}; - -// Helper metafunc to derive the selection strategy tag from a strategy object. -template -using SelStrategyTag = typename SelStrategyTag_>::Tag; - -// Main function to start the arrangement. Takes a selection and a packing -// strategy object as the first two parameters. An implementation -// (function overload) must exist for this function that takes the coresponding -// selection strategy tag belonging to the given selstrategy argument. -// -// items parameter is a range of arrange items to arrange. -// fixed parameter is a range of arrange items that have fixed position and will -// not move during the arrangement but need to be avoided by the -// moving items. -// bed parameter is the type of bed into which the items need to fit. -template -void arrange(SelectionStrategy &&selstrategy, - PackStrategy &&packingstrategy, - const Range &items, - const Range &fixed, - const TBed &bed) -{ - static_assert(IsConstIterator, "Fixed item iterator is not const!"); - - // Dispatch: - arrange(std::forward(selstrategy), - std::forward(packingstrategy), items, fixed, bed, - SelStrategyTag{}); -} - -template -void arrange(SelectionStrategy &&selstrategy, - PackStrategy &&packingstrategy, - const Range &items, - const TBed &bed) -{ - std::vector::value_type> dummy; - arrange(std::forward(selstrategy), - std::forward(packingstrategy), items, crange(dummy), - bed); -} - -// Overload for unimplemented selection strategy, yields compile error: -template -void arrange(SelectionStrategy &&selstrategy, - PackStrategy &&packingstrategy, - const Range &items, - const Range &fixed, - const TBed &bed, - const UnimplementedSelection &) -{ - static_assert(always_false::value, - "Arrange unimplemented for this selection strategy"); -} - -template -std::vector get_bed_indices(const Range &items) -{ - auto bed_indices = reserve_vector(items.size()); - - for (auto &itm : items) - bed_indices.emplace_back(get_bed_index(itm)); - - std::sort(bed_indices.begin(), bed_indices.end()); - auto endit = std::unique(bed_indices.begin(), bed_indices.end()); - - bed_indices.erase(endit, bed_indices.end()); - - return bed_indices; -} - -template -std::vector get_bed_indices(const Range &items, const Range &fixed) -{ - std::vector ret; - - auto iitems = get_bed_indices(items); - auto ifixed = get_bed_indices(fixed); - ret.reserve(std::max(iitems.size(), ifixed.size())); - std::set_union(iitems.begin(), iitems.end(), - ifixed.begin(), ifixed.end(), - std::back_inserter(ret)); - - return ret; -} - -template -size_t get_bed_count(const Range &items) -{ - return get_bed_indices(items).size(); -} - -template int get_max_bed_index(const Range &items) -{ - auto it = std::max_element(items.begin(), - items.end(), - [](auto &i1, auto &i2) { - return get_bed_index(i1) < get_bed_index(i2); - }); - - int ret = Unarranged; - if (it != items.end()) - ret = get_bed_index(*it); - - return ret; -} - -struct DefaultStopCondition { - constexpr bool operator()() const noexcept { return false; } -}; - -}} // namespace Slic3r::arr2 - -#endif // ARRANGEBASE_HPP diff --git a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp b/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp deleted file mode 100644 index b57230c..0000000 --- a/src/libslic3r/Arrange/Core/ArrangeFirstFit.hpp +++ /dev/null @@ -1,166 +0,0 @@ - -#ifndef ARRANGEFIRSTFIT_HPP -#define ARRANGEFIRSTFIT_HPP - -#include -#include - -#include - -namespace Slic3r { namespace arr2 { namespace firstfit { - -struct SelectionTag {}; - -// Can be specialized by Items -template -struct ItemArrangedVisitor { - template - static void on_arranged(ArrItem &itm, - const Bed &bed, - const Range &packed_items, - const Range &remaining_items) - {} -}; - -// Use the the visitor baked into the ArrItem type by default -struct DefaultOnArrangedFn { - template - void operator()(ArrItem &itm, - const Bed &bed, - const Range &packed, - const Range &remaining) - { - ItemArrangedVisitor>::on_arranged(itm, bed, packed, - remaining); - } -}; - -struct DefaultItemCompareFn { - template - bool operator() (const ArrItem &ia, const ArrItem &ib) - { - return get_priority(ia) > get_priority(ib); - } -}; - -template -struct SelectionStrategy -{ - CompareFn cmpfn; - OnArrangedFn on_arranged_fn; - StopCondition cancel_fn; - - SelectionStrategy(CompareFn cmp = {}, - OnArrangedFn on_arranged = {}, - StopCondition stopcond = {}) - : cmpfn{cmp}, - on_arranged_fn{std::move(on_arranged)}, - cancel_fn{std::move(stopcond)} - {} -}; - -} // namespace firstfit - -template struct SelStrategyTag_> { - using Tag = firstfit::SelectionTag; -}; - -template -void arrange( - SelStrategy &&sel, - PackStrategy &&ps, - const Range &items, - const Range &fixed, - const TBed &bed, - const firstfit::SelectionTag &) -{ - using ArrItem = typename std::iterator_traits::value_type; - using ArrItemRef = std::reference_wrapper; - - auto sorted_items = reserve_vector(items.size()); - - for (auto &itm : items) { - set_bed_index(itm, Unarranged); - sorted_items.emplace_back(itm); - } - - using Context = PackStrategyContext; - - std::map bed_contexts; - auto get_or_init_context = [&ps, &bed, &bed_contexts](int bedidx) -> Context& { - auto ctx_it = bed_contexts.find(bedidx); - if (ctx_it == bed_contexts.end()) { - auto res = bed_contexts.emplace( - bedidx, create_context(ps, bed, bedidx)); - - assert(res.second); - - ctx_it = res.first; - } - - return ctx_it->second; - }; - - for (auto &itm : fixed) { - auto bedidx = get_bed_index(itm); - if (bedidx >= 0) { - Context &ctx = get_or_init_context(bedidx); - add_fixed_item(ctx, itm); - } - } - - if constexpr (!std::is_null_pointer_v) { - std::stable_sort(sorted_items.begin(), sorted_items.end(), sel.cmpfn); - } - - auto is_cancelled = [&sel]() { - return sel.cancel_fn(); - }; - - remove_unpackable_items(ps, sorted_items, bed, [&is_cancelled]() { - return is_cancelled(); - }); - - auto it = sorted_items.begin(); - - using SConstIt = typename std::vector::const_iterator; - - while (it != sorted_items.end() && !is_cancelled()) { - bool was_packed = false; - int bedidx = 0; - while (!was_packed && !is_cancelled()) { - for (; !was_packed && !is_cancelled(); bedidx++) { - set_bed_index(*it, bedidx); - - auto remaining = Range{std::next(static_cast(it)), - sorted_items.cend()}; - - Context &ctx = get_or_init_context(bedidx); - - was_packed = pack(ps, bed, *it, ctx, remaining); - - if(was_packed) { - add_packed_item(ctx, *it); - - auto packed_range = Range{sorted_items.cbegin(), - static_cast(it)}; - - sel.on_arranged_fn(*it, bed, packed_range, remaining); - } else { - set_bed_index(*it, Unarranged); - } - } - } - ++it; - } -} - -}} // namespace Slic3r::arr2 - -#endif // ARRANGEFIRSTFIT_HPP diff --git a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp deleted file mode 100644 index 4903c0e..0000000 --- a/src/libslic3r/Arrange/Core/ArrangeItemTraits.hpp +++ /dev/null @@ -1,114 +0,0 @@ - -#ifndef ARRANGE_ITEM_TRAITS_HPP -#define ARRANGE_ITEM_TRAITS_HPP - -#include - -namespace Slic3r { namespace arr2 { - -// A logical bed representing an object not being arranged. Either the arrange -// has not yet successfully run on this ArrangePolygon or it could not fit the -// object due to overly large size or invalid geometry. -const constexpr int Unarranged = -1; - -const constexpr int PhysicalBedId = 0; - -// Basic interface of an arrange item. This struct can be specialized for any -// type that is arrangeable. -template struct ArrangeItemTraits_ { - static Vec2crd get_translation(const ArrItem &ap) - { - return ap.get_translation(); - } - - static double get_rotation(const ArrItem &ap) - { - return ap.get_rotation(); - } - - static int get_bed_index(const ArrItem &ap) { return ap.get_bed_index(); } - - static int get_priority(const ArrItem &ap) { return ap.get_priority(); } - - // Setters: - - static void set_translation(ArrItem &ap, const Vec2crd &v) - { - ap.set_translation(v); - } - - static void set_rotation(ArrItem &ap, double v) { ap.set_rotation(v); } - - static void set_bed_index(ArrItem &ap, int v) { ap.set_bed_index(v); } -}; - -template using ArrangeItemTraits = ArrangeItemTraits_>; - -// Getters: - -template Vec2crd get_translation(const T &itm) -{ - return ArrangeItemTraits::get_translation(itm); -} - -template double get_rotation(const T &itm) -{ - return ArrangeItemTraits::get_rotation(itm); -} - -template int get_bed_index(const T &itm) -{ - return ArrangeItemTraits::get_bed_index(itm); -} - -template int get_priority(const T &itm) -{ - return ArrangeItemTraits::get_priority(itm); -} - -// Setters: - -template void set_translation(T &itm, const Vec2crd &v) -{ - ArrangeItemTraits::set_translation(itm, v); -} - -template void set_rotation(T &itm, double v) -{ - ArrangeItemTraits::set_rotation(itm, v); -} - -template void set_bed_index(T &itm, int v) -{ - ArrangeItemTraits::set_bed_index(itm, v); -} - -// Helper functions for arrange items -template bool is_arranged(const ArrItem &ap) -{ - return get_bed_index(ap) > Unarranged; -} - -template bool is_fixed(const ArrItem &ap) -{ - return get_bed_index(ap) >= PhysicalBedId; -} - -template bool is_on_physical_bed(const ArrItem &ap) -{ - return get_bed_index(ap) == PhysicalBedId; -} - -template void translate(ArrItem &ap, const Vec2crd &t) -{ - set_translation(ap, get_translation(ap) + t); -} - -template void rotate(ArrItem &ap, double rads) -{ - set_rotation(ap, get_rotation(ap) + rads); -} - -}} // namespace Slic3r::arr2 - -#endif // ARRANGE_ITEM_HPP diff --git a/src/libslic3r/Arrange/Core/Beds.cpp b/src/libslic3r/Arrange/Core/Beds.cpp deleted file mode 100644 index 50bf3e2..0000000 --- a/src/libslic3r/Arrange/Core/Beds.cpp +++ /dev/null @@ -1,136 +0,0 @@ - -#include "Beds.hpp" - -#include - -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Point.hpp" - -namespace Slic3r { namespace arr2 { - -BoundingBox bounding_box(const InfiniteBed &bed) -{ - BoundingBox ret; - using C = coord_t; - - // It is important for Mx and My to be strictly less than half of the - // range of type C. width(), height() and area() will not overflow this way. - C Mx = C((std::numeric_limits::lowest() + 2 * bed.center.x()) / 4.01); - C My = C((std::numeric_limits::lowest() + 2 * bed.center.y()) / 4.01); - - ret.max = bed.center - Point{Mx, My}; - ret.min = bed.center + Point{Mx, My}; - - return ret; -} - -Polygon to_rectangle(const BoundingBox &bb) -{ - Polygon ret; - ret.points = { - bb.min, - Point{bb.max.x(), bb.min.y()}, - bb.max, - Point{bb.min.x(), bb.max.y()} - }; - - return ret; -} - -Polygon approximate_circle_with_polygon(const arr2::CircleBed &bed, int nedges) -{ - Polygon ret; - - double angle_incr = (2 * M_PI) / nedges; // Angle increment for each edge - double angle = 0; // Starting angle - - // Loop to generate vertices for each edge - for (int i = 0; i < nedges; i++) { - // Calculate coordinates of the vertices using trigonometry - auto x = bed.center().x() + static_cast(bed.radius() * std::cos(angle)); - auto y = bed.center().y() + static_cast(bed.radius() * std::sin(angle)); - - // Add vertex to the vector - ret.points.emplace_back(x, y); - - // Update the angle for the next iteration - angle += angle_incr; - } - - return ret; -} - -inline coord_t width(const BoundingBox &box) -{ - return box.max.x() - box.min.x(); -} -inline coord_t height(const BoundingBox &box) -{ - return box.max.y() - box.min.y(); -} -inline double poly_area(const Points &pts) -{ - return std::abs(Polygon::area(pts)); -} -inline double distance_to(const Point &p1, const Point &p2) -{ - double dx = p2.x() - p1.x(); - double dy = p2.y() - p1.y(); - return std::sqrt(dx * dx + dy * dy); -} - -static CircleBed to_circle(const Point ¢er, const Points &points) -{ - std::vector vertex_distances; - double avg_dist = 0; - - for (const Point &pt : points) { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - CircleBed ret(center, avg_dist); - for (auto el : vertex_distances) { - if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = {}; - break; - } - } - - return ret; -} - -template auto call_with_bed(const Points &bed, Fn &&fn) -{ - if (bed.empty()) - return fn(InfiniteBed{}); - else if (bed.size() == 1) - return fn(InfiniteBed{bed.front()}); - else { - auto bb = BoundingBox(bed); - CircleBed circ = to_circle(bb.center(), bed); - auto parea = poly_area(bed); - - if ((1.0 - parea / area(bb)) < 1e-3) { - return fn(RectangleBed{bb}); - } else if (!std::isnan(circ.radius()) && (1.0 - parea / area(circ)) < 1e-2) - return fn(circ); - else - return fn(IrregularBed{{ExPolygon(bed)}}); - } -} - -ArrangeBed to_arrange_bed(const Points &bedpts) -{ - ArrangeBed ret; - - call_with_bed(bedpts, [&](const auto &bed) { ret = bed; }); - - return ret; -} - -}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Core/Beds.hpp b/src/libslic3r/Arrange/Core/Beds.hpp deleted file mode 100644 index 3dfd18f..0000000 --- a/src/libslic3r/Arrange/Core/Beds.hpp +++ /dev/null @@ -1,203 +0,0 @@ - -#ifndef BEDS_HPP -#define BEDS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libslic3r/Polygon.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { namespace arr2 { - -// Bed types to be used with arrangement. Most generic bed is a simple polygon -// with holes, but other special bed types are also valid, like a bed without -// boundaries, or a special case of a rectangular or circular bed which leaves -// a lot of room for optimizations. - -// Representing an unbounded bed. -struct InfiniteBed { - Point center; - explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} -}; - -BoundingBox bounding_box(const InfiniteBed &bed); - -inline InfiniteBed offset(const InfiniteBed &bed, coord_t) { return bed; } - -struct RectangleBed { - BoundingBox bb; - - explicit RectangleBed(const BoundingBox &bedbb) : bb{bedbb} {} - explicit RectangleBed(coord_t w, coord_t h, Point c = {0, 0}): - bb{{c.x() - w / 2, c.y() - h / 2}, {c.x() + w / 2, c.y() + h / 2}} - {} - - coord_t width() const { return bb.size().x(); } - coord_t height() const { return bb.size().y(); } -}; - -inline BoundingBox bounding_box(const RectangleBed &bed) { return bed.bb; } -inline RectangleBed offset(RectangleBed bed, coord_t v) -{ - bed.bb.offset(v); - return bed; -} - -Polygon to_rectangle(const BoundingBox &bb); - -inline Polygon to_rectangle(const RectangleBed &bed) -{ - return to_rectangle(bed.bb); -} - -class CircleBed { - Point m_center; - double m_radius; - -public: - CircleBed(): m_center(0, 0), m_radius(NaNd) {} - explicit CircleBed(const Point& c, double r) - : m_center(c) - , m_radius(r) - {} - - double radius() const { return m_radius; } - const Point& center() const { return m_center; } -}; - -// Function to approximate a circle with a convex polygon -Polygon approximate_circle_with_polygon(const CircleBed &bed, int nedges = 24); - -inline BoundingBox bounding_box(const CircleBed &bed) -{ - auto r = static_cast(std::round(bed.radius())); - Point R{r, r}; - - return {bed.center() - R, bed.center() + R}; -} -inline CircleBed offset(const CircleBed &bed, coord_t v) -{ - return CircleBed{bed.center(), bed.radius() + v}; -} - -struct IrregularBed { ExPolygons poly; }; -inline BoundingBox bounding_box(const IrregularBed &bed) -{ - return get_extents(bed.poly); -} - -inline IrregularBed offset(IrregularBed bed, coord_t v) -{ - bed.poly = offset_ex(bed.poly, v); - return bed; -} - -using ArrangeBed = - boost::variant; - -inline BoundingBox bounding_box(const ArrangeBed &bed) -{ - BoundingBox ret; - auto visitor = [&ret](const auto &b) { ret = bounding_box(b); }; - boost::apply_visitor(visitor, bed); - - return ret; -} - -inline ArrangeBed offset(ArrangeBed bed, coord_t v) -{ - auto visitor = [v](auto &b) { b = offset(b, v); }; - boost::apply_visitor(visitor, bed); - - return bed; -} - -inline double area(const BoundingBox &bb) -{ - auto bbsz = bb.size(); - return double(bbsz.x()) * bbsz.y(); -} - -inline double area(const RectangleBed &bed) -{ - auto bbsz = bed.bb.size(); - return double(bbsz.x()) * bbsz.y(); -} - -inline double area(const InfiniteBed &bed) -{ - return std::numeric_limits::infinity(); -} - -inline double area(const IrregularBed &bed) -{ - return std::accumulate(bed.poly.begin(), bed.poly.end(), 0., - [](double s, auto &p) { return s + p.area(); }); -} - -inline double area(const CircleBed &bed) -{ - return bed.radius() * bed.radius() * PI; -} - -inline double area(const ArrangeBed &bed) -{ - double ret = 0.; - auto visitor = [&ret](auto &b) { ret = area(b); }; - boost::apply_visitor(visitor, bed); - - return ret; -} - -inline ExPolygons to_expolygons(const InfiniteBed &bed) -{ - return {ExPolygon{to_rectangle(RectangleBed{scaled(1000.), scaled(1000.)})}}; -} - -inline ExPolygons to_expolygons(const RectangleBed &bed) -{ - return {ExPolygon{to_rectangle(bed)}}; -} - -inline ExPolygons to_expolygons(const CircleBed &bed) -{ - return {ExPolygon{approximate_circle_with_polygon(bed)}}; -} - -inline ExPolygons to_expolygons(const IrregularBed &bed) { return bed.poly; } - -inline ExPolygons to_expolygons(const ArrangeBed &bed) -{ - ExPolygons ret; - auto visitor = [&ret](const auto &b) { ret = to_expolygons(b); }; - boost::apply_visitor(visitor, bed); - - return ret; -} - -ArrangeBed to_arrange_bed(const Points &bedpts); - -template struct IsRectangular_ : public std::false_type {}; -template<> struct IsRectangular_: public std::true_type {}; -template<> struct IsRectangular_: public std::true_type {}; - -template static constexpr bool IsRectangular = IsRectangular_::value; - -} // namespace arr2 - -inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; } -inline const BoundingBox &bounding_box(const BoundingBox &bb) { return bb; } -inline BoundingBox bounding_box(const Polygon &p) { return get_extents(p); } - -} // namespace Slic3r - -#endif // BEDS_HPP diff --git a/src/libslic3r/Arrange/Core/DataStoreTraits.hpp b/src/libslic3r/Arrange/Core/DataStoreTraits.hpp deleted file mode 100644 index 5797d7d..0000000 --- a/src/libslic3r/Arrange/Core/DataStoreTraits.hpp +++ /dev/null @@ -1,79 +0,0 @@ - -#ifndef DATASTORETRAITS_HPP -#define DATASTORETRAITS_HPP - -#include - -#include "libslic3r/libslic3r.h" - -namespace Slic3r { namespace arr2 { - -// Some items can be containers of arbitrary data stored under string keys. -template struct DataStoreTraits_ -{ - static constexpr bool Implemented = false; - - template static const T *get(const ArrItem &, const std::string &key) - { - return nullptr; - } - - // Same as above just not const. - template static T *get(ArrItem &, const std::string &key) - { - return nullptr; - } - - static bool has_key(const ArrItem &itm, const std::string &key) - { - return false; - } -}; - -template struct WritableDataStoreTraits_ -{ - static constexpr bool Implemented = false; - - template static void set(ArrItem &, const std::string &key, T &&data) - { - } -}; - -template using DataStoreTraits = DataStoreTraits_>; -template constexpr bool IsDataStore = DataStoreTraits>::Implemented; -template using DataStoreOnly = std::enable_if_t, TT>; - -template -const T *get_data(const ArrItem &itm, const std::string &key) -{ - return DataStoreTraits::template get(itm, key); -} - -template -bool has_key(const ArrItem &itm, const std::string &key) -{ - return DataStoreTraits::has_key(itm, key); -} - -template -T *get_data(ArrItem &itm, const std::string &key) -{ - return DataStoreTraits::template get(itm, key); -} - -template using WritableDataStoreTraits = WritableDataStoreTraits_>; -template constexpr bool IsWritableDataStore = WritableDataStoreTraits>::Implemented; -template using WritableDataStoreOnly = std::enable_if_t, TT>; - -template -void set_data(ArrItem &itm, const std::string &key, T &&data) -{ - WritableDataStoreTraits::template set(itm, key, std::forward(data)); -} - -template constexpr bool IsReadWritableDataStore = IsDataStore && IsWritableDataStore; -template using ReadWritableDataStoreOnly = std::enable_if_t, TT>; - -}} // namespace Slic3r::arr2 - -#endif // DATASTORETRAITS_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp b/src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp deleted file mode 100644 index d8afd93..0000000 --- a/src/libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp +++ /dev/null @@ -1,111 +0,0 @@ - -#ifndef CIRCULAR_EDGEITERATOR_HPP -#define CIRCULAR_EDGEITERATOR_HPP - -#include -#include - -namespace Slic3r { - -// Circular iterator over a polygon yielding individual edges as Line objects -// if flip_lines is true, the orientation of each line is flipped (not the -// direction of traversal) -template -class CircularEdgeIterator_ { - const Polygon *m_poly = nullptr; - size_t m_i = 0; - size_t m_c = 0; // counting how many times the iterator has circled over - -public: - - // i: vertex position of first line's starting vertex - // poly: target polygon - CircularEdgeIterator_(size_t i, const Polygon &poly) - : m_poly{&poly} - , m_i{!poly.empty() ? i % poly.size() : 0} - , m_c{!poly.empty() ? i / poly.size() : 0} - {} - - explicit CircularEdgeIterator_ (const Polygon &poly) - : CircularEdgeIterator_(0, poly) {} - - using iterator_category = std::forward_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = Line; - using pointer = Line*; - using reference = Line&; - - CircularEdgeIterator_ & operator++() - { - assert (m_poly); - ++m_i; - if (m_i == m_poly->size()) { // faster than modulo (?) - m_i = 0; - ++m_c; - } - - return *this; - } - - CircularEdgeIterator_ operator++(int) - { - auto cpy = *this; ++(*this); return cpy; - } - - Line operator*() const - { - size_t nx = m_i == m_poly->size() - 1 ? 0 : m_i + 1; - Line ret; - if constexpr (flip_lines) - ret = Line((*m_poly)[nx], (*m_poly)[m_i]); - else - ret = Line((*m_poly)[m_i], (*m_poly)[nx]); - - return ret; - } - - Line operator->() const { return *(*this); } - - bool operator==(const CircularEdgeIterator_& other) const - { - return m_i == other.m_i && m_c == other.m_c; - } - - bool operator!=(const CircularEdgeIterator_& other) const - { - return !(*this == other); - } - - CircularEdgeIterator_& operator +=(size_t dist) - { - m_i = (m_i + dist) % m_poly->size(); - m_c = (m_i + (m_c * m_poly->size()) + dist) / m_poly->size(); - - return *this; - } - - CircularEdgeIterator_ operator +(size_t dist) - { - auto cpy = *this; - cpy += dist; - - return cpy; - } -}; - -using CircularEdgeIterator = CircularEdgeIterator_<>; -using CircularReverseEdgeIterator = CircularEdgeIterator_; - -inline Range line_range(const Polygon &poly) -{ - return Range{CircularEdgeIterator{0, poly}, CircularEdgeIterator{poly.size(), poly}}; -} - -inline Range line_range_flp(const Polygon &poly) -{ - return Range{CircularReverseEdgeIterator{0, poly}, CircularReverseEdgeIterator{poly.size(), poly}}; -} - -} // namespace Slic3r - -#endif // CIRCULAR_EDGEITERATOR_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp b/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp deleted file mode 100644 index 2b4b102..0000000 --- a/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp +++ /dev/null @@ -1,105 +0,0 @@ - -#include "EdgeCache.hpp" - -#include - -#include "CircularEdgeIterator.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Line.hpp" - -namespace Slic3r { namespace arr2 { - -void EdgeCache::create_cache(const ExPolygon &sh) -{ - m_contour.distances.reserve(sh.contour.size()); - m_holes.reserve(sh.holes.size()); - - m_contour.poly = &sh.contour; - - fill_distances(sh.contour, m_contour.distances); - - for (const Polygon &hole : sh.holes) { - auto &hc = m_holes.emplace_back(); - hc.poly = &hole; - fill_distances(hole, hc.distances); - } -} - -Vec2crd EdgeCache::coords(const ContourCache &cache, double distance) const -{ - assert(cache.poly); - return arr2::coords(*cache.poly, cache.distances, distance); -} - -void EdgeCache::sample_contour(double accuracy, std::vector &samples) -{ - const auto N = m_contour.distances.size(); - const auto S = stride(N, accuracy); - - if (N == 0 || S == 0) - return; - - samples.reserve(N / S + 1); - for(size_t i = 0; i < N; i += S) { - samples.emplace_back( - ContourLocation{0, m_contour.distances[i] / m_contour.distances.back()}); - } - - for (size_t hidx = 1; hidx <= m_holes.size(); ++hidx) { - auto& hc = m_holes[hidx - 1]; - - const auto NH = hc.distances.size(); - const auto SH = stride(NH, accuracy); - - if (NH == 0 || SH == 0) - continue; - - samples.reserve(samples.size() + NH / SH + 1); - for (size_t i = 0; i < NH; i += SH) { - samples.emplace_back( - ContourLocation{hidx, hc.distances[i] / hc.distances.back()}); - } - } -} - -Vec2crd coords(const Polygon &poly, const std::vector &distances, double distance) -{ - assert(poly.size() > 1 && distance >= .0 && distance <= 1.0); - - // distance is from 0.0 to 1.0, we scale it up to the full length of - // the circumference - double d = distance * distances.back(); - - // Magic: we find the right edge in log time - auto it = std::lower_bound(distances.begin(), distances.end(), d); - - assert(it != distances.end()); - - auto idx = it - distances.begin(); // get the index of the edge - auto &pts = poly.points; - auto edge = idx == long(pts.size() - 1) ? Line(pts.back(), pts.front()) : - Line(pts[idx], pts[idx + 1]); - - // Get the remaining distance on the target edge - auto ed = d - (idx > 0 ? *std::prev(it) : 0 ); - - double t = ed / edge.length(); - Vec2d n {double(edge.b.x()) - edge.a.x(), double(edge.b.y()) - edge.a.y()}; - Vec2crd ret = (edge.a.cast() + t * n).cast(); - - return ret; -} - -void fill_distances(const Polygon &poly, std::vector &distances) -{ - distances.reserve(poly.size()); - - double dist = 0.; - auto lrange = line_range(poly); - for (const Line l : lrange) { - dist += l.length(); - distances.emplace_back(dist); - } -} - -}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp b/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp deleted file mode 100644 index df7e1bd..0000000 --- a/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp +++ /dev/null @@ -1,81 +0,0 @@ - -#ifndef EDGECACHE_HPP -#define EDGECACHE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libslic3r/Point.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { namespace arr2 { - -// Position on the circumference of an ExPolygon. -// countour_id: 0th is contour, 1..N are holes -// dist: position given as a floating point number within <0., 1.> -struct ContourLocation { size_t contour_id; double dist; }; - -void fill_distances(const Polygon &poly, std::vector &distances); - -Vec2crd coords(const Polygon &poly, const std::vector& distances, double distance); - -// A class for getting a point on the circumference of the polygon (in log time) -// -// This is a transformation of the provided polygon to be able to pinpoint -// locations on the circumference. The optimizer will pass a floating point -// value e.g. within <0,1> and we have to transform this value quickly into a -// coordinate on the circumference. By definition 0 should yield the first -// vertex and 1.0 would be the last (which should coincide with first). -// -// We also have to make this work for the holes of the captured polygon. -class EdgeCache { - struct ContourCache { - const Polygon *poly; - std::vector distances; - } m_contour; - - std::vector m_holes; - - void create_cache(const ExPolygon& sh); - - Vec2crd coords(const ContourCache& cache, double distance) const; - -public: - - explicit EdgeCache(const ExPolygon *sh) - { - create_cache(*sh); - } - - // Given coeff for accuracy <0., 1.>, return the number of vertices to skip - // when fetching corners. - static inline size_t stride(const size_t N, double accuracy) - { - size_t n = std::max(size_t{1}, N); - return static_cast( - std::round(N / std::pow(n, std::pow(accuracy, 1./3.))) - ); - } - - void sample_contour(double accuracy, std::vector &samples); - - Vec2crd coords(const ContourLocation &loc) const - { - assert(loc.contour_id <= m_holes.size()); - - return loc.contour_id > 0 ? - coords(m_holes[loc.contour_id - 1], loc.dist) : - coords(m_contour, loc.dist); - } -}; - -}} // namespace Slic3r::arr2 - -#endif // EDGECACHE_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp deleted file mode 100644 index 1c8b644..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/CompactifyKernel.hpp +++ /dev/null @@ -1,62 +0,0 @@ - -#ifndef COMPACTIFYKERNEL_HPP -#define COMPACTIFYKERNEL_HPP - -#include - -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -#include -#include - -#include "KernelUtils.hpp" - -namespace Slic3r { namespace arr2 { - -struct CompactifyKernel { - ExPolygons merged_pile; - - template - double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const - { - auto pile = merged_pile; - - ExPolygons itm_tr = to_expolygons(envelope_outline(itm)); - for (auto &p : itm_tr) - p.translate(transl); - - append(pile, std::move(itm_tr)); - - pile = union_ex(pile); - - Polygon chull = Geometry::convex_hull(pile); - - return -(chull.area()); - } - - template - bool on_start_packing(ArrItem &itm, - const Bed &bed, - const Context &packing_context, - const Range & /*remaining_items*/) - { - bool ret = find_initial_position(itm, bounding_box(bed).center(), bed, - packing_context); - - merged_pile.clear(); - for (const auto &gitm : all_items_range(packing_context)) { - append(merged_pile, to_expolygons(fixed_outline(gitm))); - } - merged_pile = union_ex(merged_pile); - - return ret; - } - - template - bool on_item_packed(ArrItem &itm) { return true; } -}; - -}} // namespace Slic3r::arr2 - -#endif // COMPACTIFYKERNEL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp deleted file mode 100644 index 875b228..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp +++ /dev/null @@ -1,61 +0,0 @@ - -#ifndef GRAVITYKERNEL_HPP -#define GRAVITYKERNEL_HPP - -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -#include "KernelUtils.hpp" - -namespace Slic3r { namespace arr2 { - -struct GravityKernel { - std::optional sink; - std::optional item_sink; - Vec2d active_sink; - - GravityKernel(Vec2crd gravity_center) : - sink{gravity_center}, active_sink{unscaled(gravity_center)} {} - - GravityKernel() = default; - - template - double placement_fitness(const ArrItem &itm, const Vec2crd &transl) const - { - Vec2d center = unscaled(envelope_centroid(itm)); - - center += unscaled(transl); - - return - (center - active_sink).squaredNorm(); - } - - template - bool on_start_packing(ArrItem &itm, - const Bed &bed, - const Ctx &packing_context, - const Range & /*remaining_items*/) - { - bool ret = false; - - item_sink = get_gravity_sink(itm); - - if (!sink) { - sink = bounding_box(bed).center(); - } - - if (item_sink) - active_sink = unscaled(*item_sink); - else - active_sink = unscaled(*sink); - - ret = find_initial_position(itm, scaled(active_sink), bed, packing_context); - - return ret; - } - - template bool on_item_packed(ArrItem &itm) { return true; } -}; - -}} // namespace Slic3r::arr2 - -#endif // GRAVITYKERNEL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp deleted file mode 100644 index 6245756..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/KernelTraits.hpp +++ /dev/null @@ -1,58 +0,0 @@ - -#ifndef KERNELTRAITS_HPP -#define KERNELTRAITS_HPP - -#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" - -namespace Slic3r { namespace arr2 { - -// An arrangement kernel that specifies the object function to the arrangement -// optimizer and additional callback functions to be able to track the state -// of the arranged pile during arrangement. -template struct KernelTraits_ -{ - // Has to return a score value marking the quality of the arrangement. The - // higher this value is, the better a particular placement of the item is. - // parameter transl is the translation needed for the item to be moved to - // the candidate position. - // To discard the item, return NaN as score for every translation. - template - static double placement_fitness(const Kernel &k, - const ArrItem &itm, - const Vec2crd &transl) - { - return k.placement_fitness(itm, transl); - } - - // Called whenever a new item is about to be processed by the optimizer. - // The current state of the arrangement can be saved by the kernel: the - // already placed items and the remaining items that need to fit into a - // particular bed. - // Returns true if the item is can be packed immediately, false if it - // should be processed further. This way, a kernel have the power to - // choose an initial position for the item that is not on the NFP. - template - static bool on_start_packing(Kernel &k, - ArrItem &itm, - const Bed &bed, - const Ctx &packing_context, - const Range &remaining_items) - { - return k.on_start_packing(itm, bed, packing_context, remaining_items); - } - - // Called when an item has been succesfully packed. itm should have the - // final translation and rotation already set. - // Can return false to discard the item after the optimization. - template - static bool on_item_packed(Kernel &k, ArrItem &itm) - { - return k.on_item_packed(itm); - } -}; - -template using KernelTraits = KernelTraits_>; - -}} // namespace Slic3r::arr2 - -#endif // KERNELTRAITS_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp deleted file mode 100644 index a06bc75..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/KernelUtils.hpp +++ /dev/null @@ -1,77 +0,0 @@ - -#ifndef ARRANGEKERNELUTILS_HPP -#define ARRANGEKERNELUTILS_HPP - -#include - -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" -#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" - -namespace Slic3r { namespace arr2 { - -template -bool find_initial_position(Itm &itm, - const Vec2crd &sink, - const Bed &bed, - const Context &packing_context) -{ - bool ret = false; - - if constexpr (std::is_convertible_v || - std::is_convertible_v || - std::is_convertible_v) - { - if (all_items_range(packing_context).empty()) { - auto rotations = allowed_rotations(itm); - set_rotation(itm, 0.); - auto chull = envelope_convex_hull(itm); - - for (double rot : rotations) { - auto chullcpy = chull; - chullcpy.rotate(rot); - auto bbitm = bounding_box(chullcpy); - - Vec2crd cb = sink; - Vec2crd ci = bbitm.center(); - - Vec2crd d = cb - ci; - bbitm.translate(d); - - if (bounding_box(bed).contains(bbitm)) { - rotate(itm, rot); - translate(itm, d); - ret = true; - break; - } - } - } - } - - return ret; -} - -template std::optional get_gravity_sink(const ArrItem &itm) -{ - constexpr const char * SinkKey = "sink"; - - std::optional ret; - - auto ptr = get_data(itm, SinkKey); - - if (ptr) - ret = *ptr; - - return ret; -} - -template bool is_wipe_tower(const ArrItem &itm) -{ - constexpr const char * Key = "is_wipe_tower"; - - return has_key(itm, Key); -} - -}} // namespace Slic3r::arr2 - -#endif // ARRANGEKERNELUTILS_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp deleted file mode 100644 index b5df073..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp +++ /dev/null @@ -1,95 +0,0 @@ - -#ifndef RECTANGLEOVERFITKERNELWRAPPER_HPP -#define RECTANGLEOVERFITKERNELWRAPPER_HPP - -#include "KernelTraits.hpp" - -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -namespace Slic3r { namespace arr2 { - -// This is a kernel wrapper that will apply a penality to the object function -// if the result cannot fit into the given rectangular bounds. This can be used -// to arrange into rectangular boundaries without calculating the IFP of the -// rectangle bed. Note that after the arrangement, what is garanteed is that -// the resulting pile will fit into the rectangular boundaries, but it will not -// be within the given rectangle. The items need to be moved afterwards manually. -// Use RectangeOverfitPackingStrategy to automate this post process step. -template -struct RectangleOverfitKernelWrapper { - Kernel &k; - BoundingBox binbb; - BoundingBox pilebb; - - RectangleOverfitKernelWrapper(Kernel &kern, const BoundingBox &limits) - : k{kern} - , binbb{limits} - {} - - double overfit(const BoundingBox &itmbb) const - { - auto fullbb = pilebb; - fullbb.merge(itmbb); - auto fullbbsz = fullbb.size(); - auto binbbsz = binbb.size(); - - auto wdiff = fullbbsz.x() - binbbsz.x() - SCALED_EPSILON; - auto hdiff = fullbbsz.y() - binbbsz.y() - SCALED_EPSILON; - double miss = .0; - if (wdiff > 0) - miss += double(wdiff); - if (hdiff > 0) - miss += double(hdiff); - - miss = miss > 0? miss : 0; - - return miss; - } - - template - double placement_fitness(const ArrItem &item, const Vec2crd &transl) const - { - double score = KernelTraits::placement_fitness(k, item, transl); - - auto itmbb = envelope_bounding_box(item); - itmbb.translate(transl); - double miss = overfit(itmbb); - score -= miss * miss; - - return score; - } - - template - bool on_start_packing(ArrItem &itm, - const Bed &bed, - const Ctx &packing_context, - const Range &remaining_items) - { - pilebb = BoundingBox{}; - - for (auto &fitm : all_items_range(packing_context)) - pilebb.merge(fixed_bounding_box(fitm)); - - return KernelTraits::on_start_packing(k, itm, RectangleBed{binbb}, - packing_context, - remaining_items); - } - - template - bool on_item_packed(ArrItem &itm) - { - bool ret = KernelTraits::on_item_packed(k, itm); - - double miss = overfit(envelope_bounding_box(itm)); - - if (miss > 0.) - ret = false; - - return ret; - } -}; - -}} // namespace Slic3r::arr2 - -#endif // RECTANGLEOVERFITKERNELWRAPPER_H diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp deleted file mode 100644 index 153c7ba..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp +++ /dev/null @@ -1,97 +0,0 @@ - -#ifndef SVGDEBUGOUTPUTKERNELWRAPPER_HPP -#define SVGDEBUGOUTPUTKERNELWRAPPER_HPP - -#include - -#include "KernelTraits.hpp" - -#include "libslic3r/Arrange/Core/PackingContext.hpp" -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -#include - -namespace Slic3r { namespace arr2 { - -template -struct SVGDebugOutputKernelWrapper { - Kernel &k; - std::unique_ptr svg; - BoundingBox drawbounds; - - template - SVGDebugOutputKernelWrapper(const BoundingBox &bounds, Kernel &kern) - : k{kern}, drawbounds{bounds} - {} - - template - bool on_start_packing(ArrItem &itm, - const Bed &bed, - const Context &packing_context, - const Range &rem) - { - using namespace Slic3r; - - bool ret = KernelTraits::on_start_packing(k, itm, bed, - packing_context, - rem); - - if (arr2::get_bed_index(itm) < 0) - return ret; - - svg.reset(); - auto bounds = drawbounds; - auto fixed = all_items_range(packing_context); - svg = std::make_unique(std::string("arrange_bed") + - std::to_string( - arr2::get_bed_index(itm)) + - "_" + std::to_string(fixed.size()) + - ".svg", - bounds, 0, false); - - svg->draw(ExPolygon{arr2::to_rectangle(drawbounds)}, "blue", .2f); - - auto nfp = calculate_nfp(itm, packing_context, bed); - svg->draw_outline(nfp); - svg->draw(nfp, "green", 0.2f); - - for (const auto &fixeditm : fixed) { - ExPolygons fixeditm_outline = to_expolygons(fixed_outline(fixeditm)); - svg->draw_outline(fixeditm_outline); - svg->draw(fixeditm_outline, "yellow", 0.5f); - } - - return ret; - } - - template - double placement_fitness(const ArrItem &item, const Vec2crd &transl) const - { - return KernelTraits::placement_fitness(k, item, transl); - } - - template - bool on_item_packed(ArrItem &itm) - { - using namespace Slic3r; - using namespace Slic3r::arr2; - - bool ret = KernelTraits::on_item_packed(k, itm); - - if (svg) { - ExPolygons itm_outline = to_expolygons(fixed_outline(itm)); - - svg->draw_outline(itm_outline); - svg->draw(itm_outline, "grey"); - - svg->Close(); - } - - return ret; - } -}; - -}} // namespace Slic3r::arr2 - -#endif // SVGDEBUGOUTPUTKERNELWRAPPER_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp deleted file mode 100644 index a74c8a9..0000000 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp +++ /dev/null @@ -1,246 +0,0 @@ - -#ifndef TMARRANGEKERNEL_HPP -#define TMARRANGEKERNEL_HPP - -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -#include "KernelUtils.hpp" - -#include -#include - -namespace Slic3r { namespace arr2 { - -// Summon the spatial indexing facilities from boost -namespace bgi = boost::geometry::index; -using SpatElement = std::pair; -using SpatIndex = bgi::rtree >; - -class TMArrangeKernel { - SpatIndex m_rtree; // spatial index for the normal (bigger) objects - SpatIndex m_smallsrtree; // spatial index for only the smaller items - BoundingBox m_pilebb; - double m_bin_area = NaNd; - double m_norm; - size_t m_rem_cnt = 0; - size_t m_item_cnt = 0; - - - struct ItemStats { double area = 0.; BoundingBox bb; }; - std::vector m_itemstats; - - // A coefficient used in separating bigger items and smaller items. - static constexpr double BigItemTreshold = 0.02; - - template ArithmeticOnly norm(T val) const - { - return double(val) / m_norm; - } - - // Treat big items (compared to the print bed) differently - bool is_big(double a) const { return a / m_bin_area > BigItemTreshold; } - -protected: - std::optional sink; - std::optional item_sink; - Point active_sink; - - const BoundingBox & pilebb() const { return m_pilebb; } - -public: - TMArrangeKernel() = default; - TMArrangeKernel(Vec2crd gravity_center, size_t itm_cnt, double bedarea = NaNd) - : m_bin_area(bedarea) - , m_item_cnt{itm_cnt} - , sink{gravity_center} - {} - - TMArrangeKernel(size_t itm_cnt, double bedarea = NaNd) - : m_bin_area(bedarea), m_item_cnt{itm_cnt} - {} - - template - double placement_fitness(const ArrItem &item, const Vec2crd &transl) const - { - // Candidate item bounding box - auto ibb = envelope_bounding_box(item); - ibb.translate(transl); - auto itmcntr = envelope_centroid(item); - itmcntr += transl; - - // Calculate the full bounding box of the pile with the candidate item - auto fullbb = m_pilebb; - fullbb.merge(ibb); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - BoundingBox bigbb; - if(m_rtree.empty()) { - bigbb = fullbb; - } - else { - auto boostbb = m_rtree.bounds(); - boost::geometry::convert(boostbb, bigbb); - } - - // Will hold the resulting score - double score = 0; - - // Distinction of cases for the arrangement scene - enum e_cases { - // This branch is for big items in a mixed (big and small) scene - // OR for all items in a small-only scene. - BIG_ITEM, - - // For small items in a mixed scene. - SMALL_ITEM, - - WIPE_TOWER, - } compute_case; - - bool is_wt = is_wipe_tower(item); - bool bigitems = is_big(envelope_area(item)) || m_rtree.empty(); - if (is_wt) - compute_case = WIPE_TOWER; - else if (bigitems) - compute_case = BIG_ITEM; - else - compute_case = SMALL_ITEM; - - switch (compute_case) { - case WIPE_TOWER: { - score = (unscaled(itmcntr) - unscaled(active_sink)).squaredNorm(); - break; - } - case BIG_ITEM: { - const Point& minc = ibb.min; // bottom left corner - const Point& maxc = ibb.max; // top right corner - - // top left and bottom right corners - Point top_left{minc.x(), maxc.y()}; - Point bottom_right{maxc.x(), minc.y()}; - - // The smallest distance from the arranged pile center: - double dist = norm((itmcntr - m_pilebb.center()).template cast().norm()); - - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item - // aligned with its neighbors. We will check the alignment - // with all neighbors and return the score for the best - // alignment. So it is enough for the candidate to be - // aligned with only one item. - auto alignment_score = 1.; - - auto query = bgi::intersects(ibb); - auto& index = is_big(envelope_area(item)) ? m_rtree : m_smallsrtree; - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(index.size()); - - index.query(query, std::back_inserter(result)); - - // now get the score for the best alignment - for(auto& e : result) { - auto idx = e.second; - const ItemStats& p = m_itemstats[idx]; - auto parea = p.area; - if(std::abs(1.0 - parea / fixed_area(item)) < 1e-6) { - auto bb = p.bb; - bb.merge(ibb); - auto bbarea = area(bb); - auto ascore = 1.0 - (area(fixed_bounding_box(item)) + area(p.bb)) / bbarea; - - if(ascore < alignment_score) - alignment_score = ascore; - } - } - - double R = double(m_rem_cnt) / (m_item_cnt); - R = std::pow(R, 1./3.); - - // The final mix of the score is the balance between the - // distance from the full pile center, the pack density and - // the alignment with the neighbors - - // Let the density matter more when fewer objects remain - score = 0.6 * dist + 0.1 * alignment_score + (1.0 - R) * (0.3 * dist) + R * 0.3 * alignment_score; - - break; - } - case SMALL_ITEM: { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = norm((itmcntr - bigbb.center()).template cast().norm()); - break; - } - } - - return -score; - } - - template - bool on_start_packing(ArrItem &itm, - const Bed &bed, - const Context &packing_context, - const Range &remaining_items) - { - item_sink = get_gravity_sink(itm); - - if (!sink) { - sink = bounding_box(bed).center(); - } - - if (item_sink) - active_sink = *item_sink; - else - active_sink = *sink; - - auto fixed = all_items_range(packing_context); - - bool ret = find_initial_position(itm, active_sink, bed, packing_context); - - m_rem_cnt = remaining_items.size(); - - if (m_item_cnt == 0) - m_item_cnt = m_rem_cnt + fixed.size() + 1; - - if (std::isnan(m_bin_area)) { - auto sz = bounding_box(bed).size(); - - m_bin_area = scaled(unscaled(sz.x()) * unscaled(sz.y())); - } - - m_norm = std::sqrt(m_bin_area); - - m_itemstats.clear(); - m_itemstats.reserve(fixed.size()); - m_rtree.clear(); - m_smallsrtree.clear(); - m_pilebb = {active_sink, active_sink}; - unsigned idx = 0; - for (auto &fixitem : fixed) { - auto fixitmbb = fixed_bounding_box(fixitem); - m_itemstats.emplace_back(ItemStats{fixed_area(fixitem), fixitmbb}); - m_pilebb.merge(fixitmbb); - - if(is_big(fixed_area(fixitem))) - m_rtree.insert({fixitmbb, idx}); - - m_smallsrtree.insert({fixitmbb, idx}); - idx++; - } - - return ret; - } - - template - bool on_item_packed(ArrItem &itm) { return true; } -}; - -}} // namespace Slic3r::arr2 - -#endif // TMARRANGEKERNEL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.cpp b/src/libslic3r/Arrange/Core/NFP/NFP.cpp deleted file mode 100644 index b1d16b7..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFP.cpp +++ /dev/null @@ -1,434 +0,0 @@ - -#ifndef NFP_CPP -#define NFP_CPP - -#include "NFP.hpp" - -#include "CircularEdgeIterator.hpp" -#include "NFPConcave_Tesselate.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Line.hpp" -#include "libslic3r/libslic3r.h" - -#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) -namespace Slic3r { using LargeInt = __int128; } -#else -#include - -namespace Slic3r { using LargeInt = boost::multiprecision::int128_t; } -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Slic3r { - -static bool line_cmp(const Line& e1, const Line& e2) -{ - using Ratio = boost::rational; - - const Vec<2, int64_t> ax(1, 0); // Unit vector for the X axis - - Vec<2, int64_t> p1 = (e1.b - e1.a).cast(); - Vec<2, int64_t> p2 = (e2.b - e2.a).cast(); - - // Quadrant mapping array. The quadrant of a vector can be determined - // from the dot product of the vector and its perpendicular pair - // with the unit vector X axis. The products will carry the values - // lcos = dot(p, ax) = l * cos(phi) and - // lsin = -dotperp(p, ax) = l * sin(phi) where - // l is the length of vector p. From the signs of these values we can - // construct an index which has the sign of lcos as MSB and the - // sign of lsin as LSB. This index can be used to retrieve the actual - // quadrant where vector p resides using the following map: - // (+ is 0, - is 1) - // cos | sin | decimal | quadrant - // + | + | 0 | 0 - // + | - | 1 | 3 - // - | + | 2 | 1 - // - | - | 3 | 2 - std::array quadrants {0, 3, 1, 2 }; - - std::array q {0, 0}; // Quadrant indices for p1 and p2 - - using TDots = std::array; - TDots lcos { p1.dot(ax), p2.dot(ax) }; - TDots lsin { -dotperp(p1, ax), -dotperp(p2, ax) }; - - // Construct the quadrant indices for p1 and p2 - for(size_t i = 0; i < 2; ++i) { - if (lcos[i] == 0) - q[i] = lsin[i] > 0 ? 1 : 3; - else if (lsin[i] == 0) - q[i] = lcos[i] > 0 ? 0 : 2; - else - q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; - } - - if (q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant - auto lsq1 = p1.squaredNorm(); // squared magnitudes, avoid sqrt - auto lsq2 = p2.squaredNorm(); // squared magnitudes, avoid sqrt - - // We will actually compare l^2 * cos^2(phi) which saturates the - // cos function. But with the quadrant info we can get the sign back - int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; - - // If Ratio is an actual rational type, there is no precision loss - auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; - auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; - - return q[0] < 2 ? pcos1 > pcos2 : pcos1 < pcos2; - } - - // If in different quadrants, compare the quadrant indices only. - return q[0] < q[1]; -} - -static inline bool vsort(const Vec2crd& v1, const Vec2crd& v2) -{ - return v1.y() == v2.y() ? v1.x() < v2.x() : v1.y() < v2.y(); -} - -ExPolygons ifp_convex(const arr2::RectangleBed &obed, const Polygon &convexpoly) -{ - ExPolygon ret; - - auto sbox = bounding_box(convexpoly); - auto sboxsize = sbox.size(); - coord_t sheight = sboxsize.y(); - coord_t swidth = sboxsize.x(); - Point sliding_top = reference_vertex(convexpoly); - auto leftOffset = sliding_top.x() - sbox.min.x(); - auto rightOffset = sliding_top.x() - sbox.max.x(); - coord_t topOffset = 0; - auto bottomOffset = sheight; - - auto bedbb = obed.bb; -// bedbb.offset(1); - auto bedsz = bedbb.size(); - auto boxWidth = bedsz.x(); - auto boxHeight = bedsz.y(); - - auto bedMinx = bedbb.min.x(); - auto bedMiny = bedbb.min.y(); - auto bedMaxx = bedbb.max.x(); - auto bedMaxy = bedbb.max.y(); - - Polygon innerNfp{ Point{bedMinx + leftOffset, bedMaxy + topOffset}, - Point{bedMaxx + rightOffset, bedMaxy + topOffset}, - Point{bedMaxx + rightOffset, bedMiny + bottomOffset}, - Point{bedMinx + leftOffset, bedMiny + bottomOffset}, - Point{bedMinx + leftOffset, bedMaxy + topOffset} }; - - if (sheight <= boxHeight && swidth <= boxWidth) - ret.contour = std::move(innerNfp); - - return {ret}; -} - -Polygon ifp_convex_convex(const Polygon &fixed, const Polygon &movable) -{ - auto subnfps = reserve_polygons(fixed.size()); - - // For each edge of the bed polygon, determine the nfp of convexpoly and - // the zero area polygon formed by the edge. The union of all these sub-nfps - // will contain a hole that is the actual ifp. - auto lrange = line_range(fixed); - for (const Line l : lrange) { // Older mac compilers generate warnging if line_range is called in-place - Polygon fixed = {l.a, l.b}; - subnfps.emplace_back(nfp_convex_convex_legacy(fixed, movable)); - } - - // Do the union and then keep only the holes (should be only one or zero, if - // the convexpoly cannot fit into the bed) - Polygons ifp = union_(subnfps); - Polygon ret; - - // find the first hole - auto it = std::find_if(ifp.begin(), ifp.end(), [](const Polygon &subifp){ - return subifp.is_clockwise(); - }); - - if (it != ifp.end()) { - ret = std::move(*it); - std::reverse(ret.begin(), ret.end()); - } - - return ret; -} - -ExPolygons ifp_convex(const arr2::CircleBed &bed, const Polygon &convexpoly) -{ - Polygon circle = approximate_circle_with_polygon(bed); - - return {ExPolygon{ifp_convex_convex(circle, convexpoly)}}; -} - -ExPolygons ifp_convex(const arr2::IrregularBed &bed, const Polygon &convexpoly) -{ - auto bb = get_extents(bed.poly); - bb.offset(scaled(1.)); - - Polygon rect = arr2::to_rectangle(bb); - - ExPolygons blueprint = diff_ex(rect, bed.poly); - Polygons ifp; - for (const ExPolygon &part : blueprint) { - Polygons triangles = Slic3r::convex_decomposition_tess(part); - for (const Polygon &tr : triangles) { - Polygon subifp = nfp_convex_convex_legacy(tr, convexpoly); - ifp.emplace_back(std::move(subifp)); - } - } - - ifp = union_(ifp); - - Polygons ret; - - std::copy_if(ifp.begin(), ifp.end(), std::back_inserter(ret), - [](const Polygon &p) { return p.is_clockwise(); }); - - for (Polygon &p : ret) - std::reverse(p.begin(), p.end()); - - return to_expolygons(ret); -} - -Vec2crd reference_vertex(const Polygon &poly) -{ - Vec2crd ret{std::numeric_limits::min(), - std::numeric_limits::min()}; - - auto it = std::max_element(poly.points.begin(), poly.points.end(), vsort); - if (it != poly.points.end()) - ret = std::max(ret, static_cast(*it), vsort); - - return ret; -} - -Vec2crd reference_vertex(const ExPolygon &expoly) -{ - return reference_vertex(expoly.contour); -} - -Vec2crd reference_vertex(const Polygons &outline) -{ - Vec2crd ret{std::numeric_limits::min(), - std::numeric_limits::min()}; - - for (const Polygon &poly : outline) - ret = std::max(ret, reference_vertex(poly), vsort); - - return ret; -} - -Vec2crd reference_vertex(const ExPolygons &outline) -{ - Vec2crd ret{std::numeric_limits::min(), - std::numeric_limits::min()}; - - for (const ExPolygon &expoly : outline) - ret = std::max(ret, reference_vertex(expoly), vsort); - - return ret; -} - -Vec2crd min_vertex(const Polygon &poly) -{ - Vec2crd ret{std::numeric_limits::max(), - std::numeric_limits::max()}; - - auto it = std::min_element(poly.points.begin(), poly.points.end(), vsort); - if (it != poly.points.end()) - ret = std::min(ret, static_cast(*it), vsort); - - return ret; -} - -// Find the vertex corresponding to the edge with minimum angle to X axis. -// Only usable with CircularEdgeIterator<> template. -template It find_min_anglex_edge(It from) -{ - bool found = false; - auto it = from; - while (!found ) { - found = !line_cmp(*it, *std::next(it)); - ++it; - } - - return it; -} - -// Only usable if both fixed and movable polygon is convex. In that case, -// their edges are already sorted by angle to X axis, only the starting -// (lowest X axis) edge needs to be found first. -void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &poly) -{ - if (fixed.empty() || movable.empty()) - return; - - // Clear poly and adjust its capacity. Nothing happens if poly is - // already sufficiently large and and empty. - poly.clear(); - poly.points.reserve(fixed.size() + movable.size()); - - // Find starting positions on the fixed and moving polygons - auto it_fx = find_min_anglex_edge(CircularEdgeIterator{fixed}); - auto it_mv = find_min_anglex_edge(CircularReverseEdgeIterator{movable}); - - // End positions are at the same vertex after completing one full circle - auto end_fx = it_fx + fixed.size(); - auto end_mv = it_mv + movable.size(); - - // Pos zero is just fine as starting point: - poly.points.emplace_back(0, 0); - - // Output iterator adapter for std::merge - struct OutItAdaptor { - using value_type [[maybe_unused]] = Line; - using difference_type [[maybe_unused]] = std::ptrdiff_t; - using pointer [[maybe_unused]] = Line*; - using reference [[maybe_unused]] = Line& ; - using iterator_category [[maybe_unused]] = std::output_iterator_tag; - - Polygon *outpoly; - OutItAdaptor(Polygon &out) : outpoly{&out} {} - - OutItAdaptor &operator *() { return *this; } - void operator=(const Line &l) - { - // Yielding l.b, offsetted so that l.a touches the last vertex in - // in outpoly - outpoly->points.emplace_back(l.b + outpoly->back() - l.a); - } - - OutItAdaptor& operator++() { return *this; }; - }; - - // Use std algo to merge the edges from the two polygons - std::merge(it_fx, end_fx, it_mv, end_mv, OutItAdaptor{poly}, line_cmp); -} - -Polygon nfp_convex_convex(const Polygon &fixed, const Polygon &movable) -{ - Polygon ret; - nfp_convex_convex(fixed, movable, ret); - - return ret; -} - -static void buildPolygon(const std::vector& edgelist, - Polygon& rpoly, - Point& top_nfp) -{ - auto& rsh = rpoly.points; - - rsh.reserve(2 * edgelist.size()); - - // Add the two vertices from the first edge into the final polygon. - rsh.emplace_back(edgelist.front().a); - rsh.emplace_back(edgelist.front().b); - - // Sorting function for the nfp reference vertex search - - // the reference (rightmost top) vertex so far - top_nfp = *std::max_element(std::cbegin(rsh), std::cend(rsh), vsort); - - auto tmp = std::next(std::begin(rsh)); - - // Construct final nfp by placing each edge to the end of the previous - for(auto eit = std::next(edgelist.begin()); eit != edgelist.end(); ++eit) { - auto d = *tmp - eit->a; - Vec2crd p = eit->b + d; - - rsh.emplace_back(p); - - // Set the new reference vertex - if (vsort(top_nfp, p)) - top_nfp = p; - - tmp = std::next(tmp); - } -} - -Polygon nfp_convex_convex_legacy(const Polygon &fixed, const Polygon &movable) -{ - assert (!fixed.empty()); - assert (!movable.empty()); - - Polygon rsh; // Final nfp placeholder - Point max_nfp; - std::vector edgelist; - - auto cap = fixed.points.size() + movable.points.size(); - - // Reserve the needed memory - edgelist.reserve(cap); - rsh.points.reserve(cap); - - auto add_edge = [&edgelist](const Point &v1, const Point &v2) { - Line e{v1, v2}; - if ((e.b - e.a).cast().squaredNorm() > 0) - edgelist.emplace_back(e); - }; - - Point max_fixed = fixed.points.front(); - { // place all edges from fixed into edgelist - auto first = std::cbegin(fixed); - auto next = std::next(first); - - while(next != std::cend(fixed)) { - add_edge(*(first), *(next)); - max_fixed = std::max(max_fixed, *first, vsort); - - ++first; ++next; - } - - add_edge(*std::crbegin(fixed), *std::cbegin(fixed)); - max_fixed = std::max(max_fixed, *std::crbegin(fixed), vsort); - } - - Point max_movable = movable.points.front(); - Point min_movable = movable.points.front(); - { // place all edges from movable into edgelist - auto first = std::cbegin(movable); - auto next = std::next(first); - - while(next != std::cend(movable)) { - add_edge(*(next), *(first)); - min_movable = std::min(min_movable, *first, vsort); - max_movable = std::max(max_movable, *first, vsort); - - ++first; ++next; - } - - add_edge(*std::cbegin(movable), *std::crbegin(movable)); - min_movable = std::min(min_movable, *std::crbegin(movable), vsort); - max_movable = std::max(max_movable, *std::crbegin(movable), vsort); - } - - std::sort(edgelist.begin(), edgelist.end(), line_cmp); - - buildPolygon(edgelist, rsh, max_nfp); - - auto dtouch = max_fixed - min_movable; - auto top_other = max_movable + dtouch; - auto dnfp = top_other - max_nfp; - rsh.translate(dnfp); - - return rsh; -} - -} // namespace Slic3r - -#endif // NFP_CPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.hpp b/src/libslic3r/Arrange/Core/NFP/NFP.hpp deleted file mode 100644 index eabe684..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFP.hpp +++ /dev/null @@ -1,57 +0,0 @@ - -#ifndef NFP_HPP -#define NFP_HPP - -#include -#include -#include -#include -#include - -#include "libslic3r/Point.hpp" -#include "libslic3r/Polygon.hpp" - -namespace Slic3r { - -template -Unit dotperp(const Vec<2, T> &a, const Vec<2, T> &b) -{ - return Unit(a.x()) * Unit(b.y()) - Unit(a.y()) * Unit(b.x()); -} - -// Convex-Convex nfp in linear time (fixed.size() + movable.size()), -// no memory allocations (if out param is used). -// FIXME: Currently broken for very sharp triangles. -Polygon nfp_convex_convex(const Polygon &fixed, const Polygon &movable); -void nfp_convex_convex(const Polygon &fixed, const Polygon &movable, Polygon &out); -Polygon nfp_convex_convex_legacy(const Polygon &fixed, const Polygon &movable); - -Polygon ifp_convex_convex(const Polygon &fixed, const Polygon &movable); - -ExPolygons ifp_convex(const arr2::RectangleBed &bed, const Polygon &convexpoly); -ExPolygons ifp_convex(const arr2::CircleBed &bed, const Polygon &convexpoly); -ExPolygons ifp_convex(const arr2::IrregularBed &bed, const Polygon &convexpoly); -inline ExPolygons ifp_convex(const arr2::InfiniteBed &bed, const Polygon &convexpoly) -{ - return {}; -} - -inline ExPolygons ifp_convex(const arr2::ArrangeBed &bed, const Polygon &convexpoly) -{ - ExPolygons ret; - auto visitor = [&ret, &convexpoly](const auto &b) { ret = ifp_convex(b, convexpoly); }; - boost::apply_visitor(visitor, bed); - - return ret; -} - -Vec2crd reference_vertex(const Polygon &outline); -Vec2crd reference_vertex(const ExPolygon &outline); -Vec2crd reference_vertex(const Polygons &outline); -Vec2crd reference_vertex(const ExPolygons &outline); - -Vec2crd min_vertex(const Polygon &outline); - -} // namespace Slic3r - -#endif // NFP_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp b/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp deleted file mode 100644 index 41f98a1..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp +++ /dev/null @@ -1,197 +0,0 @@ - -#ifndef NFPARRANGEITEMTRAITS_HPP -#define NFPARRANGEITEMTRAITS_HPP - -#include - -#include "libslic3r/Arrange/Core/ArrangeBase.hpp" - -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/BoundingBox.hpp" - -namespace Slic3r { namespace arr2 { - -// Additional methods that an ArrangeItem object has to implement in order -// to be usable with PackStrategyNFP. -template struct NFPArrangeItemTraits_ -{ - template - static ExPolygons calculate_nfp(const ArrItem &item, - const Context &packing_context, - const Bed &bed, - StopCond stop_condition = {}) - { - static_assert(always_false::value, - "NFP unimplemented for this item type."); - return {}; - } - - static Vec2crd reference_vertex(const ArrItem &item) - { - return item.reference_vertex(); - } - - static BoundingBox envelope_bounding_box(const ArrItem &itm) - { - return itm.envelope_bounding_box(); - } - - static BoundingBox fixed_bounding_box(const ArrItem &itm) - { - return itm.fixed_bounding_box(); - } - - static const Polygons & envelope_outline(const ArrItem &itm) - { - return itm.envelope_outline(); - } - - static const Polygons & fixed_outline(const ArrItem &itm) - { - return itm.fixed_outline(); - } - - static const Polygon & envelope_convex_hull(const ArrItem &itm) - { - return itm.envelope_convex_hull(); - } - - static const Polygon & fixed_convex_hull(const ArrItem &itm) - { - return itm.fixed_convex_hull(); - } - - static double envelope_area(const ArrItem &itm) - { - return itm.envelope_area(); - } - - static double fixed_area(const ArrItem &itm) - { - return itm.fixed_area(); - } - - static auto allowed_rotations(const ArrItem &) - { - return std::array{0.}; - } - - static Vec2crd fixed_centroid(const ArrItem &itm) - { - return fixed_bounding_box(itm).center(); - } - - static Vec2crd envelope_centroid(const ArrItem &itm) - { - return envelope_bounding_box(itm).center(); - } -}; - -template -using NFPArrangeItemTraits = NFPArrangeItemTraits_>; - -template -ExPolygons calculate_nfp(const ArrItem &itm, - const Context &context, - const Bed &bed, - StopCond stopcond = {}) -{ - return NFPArrangeItemTraits::calculate_nfp(itm, context, bed, - std::move(stopcond)); -} - -template Vec2crd reference_vertex(const ArrItem &itm) -{ - return NFPArrangeItemTraits::reference_vertex(itm); -} - -template BoundingBox envelope_bounding_box(const ArrItem &itm) -{ - return NFPArrangeItemTraits::envelope_bounding_box(itm); -} - -template BoundingBox fixed_bounding_box(const ArrItem &itm) -{ - return NFPArrangeItemTraits::fixed_bounding_box(itm); -} - -template decltype(auto) envelope_convex_hull(const ArrItem &itm) -{ - return NFPArrangeItemTraits::envelope_convex_hull(itm); -} - -template decltype(auto) fixed_convex_hull(const ArrItem &itm) -{ - return NFPArrangeItemTraits::fixed_convex_hull(itm); -} - -template decltype(auto) envelope_outline(const ArrItem &itm) -{ - return NFPArrangeItemTraits::envelope_outline(itm); -} - -template decltype(auto) fixed_outline(const ArrItem &itm) -{ - return NFPArrangeItemTraits::fixed_outline(itm); -} - -template double envelope_area(const ArrItem &itm) -{ - return NFPArrangeItemTraits::envelope_area(itm); -} - -template double fixed_area(const ArrItem &itm) -{ - return NFPArrangeItemTraits::fixed_area(itm); -} - -template Vec2crd fixed_centroid(const ArrItem &itm) -{ - return NFPArrangeItemTraits::fixed_centroid(itm); -} - -template Vec2crd envelope_centroid(const ArrItem &itm) -{ - return NFPArrangeItemTraits::envelope_centroid(itm); -} - -template -auto allowed_rotations(const ArrItem &itm) -{ - return NFPArrangeItemTraits::allowed_rotations(itm); -} - -template -BoundingBox bounding_box(const Range &itms) noexcept -{ - auto pilebb = - std::accumulate(itms.begin(), itms.end(), BoundingBox{}, - [](BoundingBox bb, const auto &itm) { - bb.merge(fixed_bounding_box(itm)); - return bb; - }); - - return pilebb; -} - -template -BoundingBox bounding_box_on_bedidx(const Range &itms, int bed_index) noexcept -{ - auto pilebb = - std::accumulate(itms.begin(), itms.end(), BoundingBox{}, - [bed_index](BoundingBox bb, const auto &itm) { - if (bed_index == get_bed_index(itm)) - bb.merge(fixed_bounding_box(itm)); - - return bb; - }); - - return pilebb; -} - -}} // namespace Slic3r::arr2 - -#endif // ARRANGEITEMTRAITSNFP_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp deleted file mode 100644 index e438115..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp +++ /dev/null @@ -1,118 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "NFP.hpp" -#include "NFPConcave_CGAL.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { - -using K = CGAL::Exact_predicates_inexact_constructions_kernel; -using Partition_traits_2 = CGAL::Partition_traits_2::type >; -using Point_2 = Partition_traits_2::Point_2; -using Polygon_2 = Partition_traits_2::Polygon_2; // a polygon of indices - -ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable) -{ - Polygons fixed_decomp = convex_decomposition_cgal(fixed); - Polygons movable_decomp = convex_decomposition_cgal(movable); - - auto refs_mv = reserve_vector(movable_decomp.size()); - - for (const Polygon &p : movable_decomp) - refs_mv.emplace_back(reference_vertex(p)); - - auto nfps = reserve_polygons(fixed_decomp.size() *movable_decomp.size()); - - Vec2crd ref_whole = reference_vertex(movable); - for (const Polygon &fixed_part : fixed_decomp) { - size_t mvi = 0; - for (const Polygon &movable_part : movable_decomp) { - Polygon subnfp = nfp_convex_convex(fixed_part, movable_part); - const Vec2crd &ref_mp = refs_mv[mvi]; - auto d = ref_whole - ref_mp; - subnfp.translate(d); - nfps.emplace_back(subnfp); - mvi++; - } - } - - return union_ex(nfps); -} - -// TODO: holes -Polygons convex_decomposition_cgal(const ExPolygon &expoly) -{ - CGAL::Polygon_vertical_decomposition_2 decomp; - - CGAL::Polygon_2 contour; - for (auto &p : expoly.contour.points) - contour.push_back({unscaled(p.x()), unscaled(p.y())}); - - CGAL::Polygon_with_holes_2 cgalpoly{contour}; - for (const Polygon &h : expoly.holes) { - CGAL::Polygon_2 hole; - for (auto &p : h.points) - hole.push_back({unscaled(p.x()), unscaled(p.y())}); - - cgalpoly.add_hole(hole); - } - - std::vector> out; - decomp(cgalpoly, std::back_inserter(out)); - - Polygons ret; - for (auto &pwh : out) { - Polygon poly; - for (auto &p : pwh) - poly.points.emplace_back(scaled(p.x()), scaled(p.y())); - ret.emplace_back(std::move(poly)); - } - - return ret; //convex_decomposition_cgal(expoly.contour); -} - -Polygons convex_decomposition_cgal(const Polygon &poly) -{ - auto pts = reserve_vector(poly.size()); - - for (const Point &p : poly.points) - pts.emplace_back(unscaled(p.x()), unscaled(p.y())); - - Partition_traits_2 traits(CGAL::make_property_map(pts)); - - Polygon_2 polyidx; - for (size_t i = 0; i < pts.size(); ++i) - polyidx.push_back(i); - - std::vector outp; - - CGAL::optimal_convex_partition_2(polyidx.vertices_begin(), - polyidx.vertices_end(), - std::back_inserter(outp), - traits); - - Polygons ret; - for (const Polygon_2& poly : outp){ - Polygon r; - for(Point_2 p : poly.container()) - r.points.emplace_back(scaled(pts[p].x()), scaled(pts[p].y())); - - ret.emplace_back(std::move(r)); - } - - return ret; -} - -} // namespace Slic3r diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp deleted file mode 100644 index 0d8bc53..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp +++ /dev/null @@ -1,17 +0,0 @@ - -#ifndef NFPCONCAVE_CGAL_HPP -#define NFPCONCAVE_CGAL_HPP - -#include - -#include "libslic3r/Polygon.hpp" - -namespace Slic3r { - -Polygons convex_decomposition_cgal(const Polygon &expoly); -Polygons convex_decomposition_cgal(const ExPolygon &expoly); -ExPolygons nfp_concave_concave_cgal(const ExPolygon &fixed, const ExPolygon &movable); - -} // namespace Slic3r - -#endif // NFPCONCAVE_CGAL_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp deleted file mode 100644 index 9b2c959..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp +++ /dev/null @@ -1,78 +0,0 @@ - -#include "NFPConcave_Tesselate.hpp" - -#include -#include -#include -#include -#include -#include - -#include "NFP.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { - -Polygons convex_decomposition_tess(const Polygon &expoly) -{ - return convex_decomposition_tess(ExPolygon{expoly}); -} - -Polygons convex_decomposition_tess(const ExPolygon &expoly) -{ - std::vector tr = Slic3r::triangulate_expolygon_2d(expoly); - - auto ret = Slic3r::reserve_polygons(tr.size() / 3); - for (size_t i = 0; i < tr.size(); i += 3) { - ret.emplace_back( - Polygon{scaled(tr[i]), scaled(tr[i + 1]), scaled(tr[i + 2])}); - } - - return ret; -} - -Polygons convex_decomposition_tess(const ExPolygons &expolys) -{ - constexpr size_t AvgTriangleCountGuess = 50; - - auto ret = reserve_polygons(AvgTriangleCountGuess * expolys.size()); - for (const ExPolygon &expoly : expolys) { - Polygons convparts = convex_decomposition_tess(expoly); - std::move(convparts.begin(), convparts.end(), std::back_inserter(ret)); - } - - return ret; -} - -ExPolygons nfp_concave_concave_tess(const ExPolygon &fixed, - const ExPolygon &movable) -{ - Polygons fixed_decomp = convex_decomposition_tess(fixed); - Polygons movable_decomp = convex_decomposition_tess(movable); - - auto refs_mv = reserve_vector(movable_decomp.size()); - - for (const Polygon &p : movable_decomp) - refs_mv.emplace_back(reference_vertex(p)); - - auto nfps = reserve_polygons(fixed_decomp.size() * movable_decomp.size()); - - Vec2crd ref_whole = reference_vertex(movable); - for (const Polygon &fixed_part : fixed_decomp) { - size_t mvi = 0; - for (const Polygon &movable_part : movable_decomp) { - Polygon subnfp = nfp_convex_convex(fixed_part, movable_part); - const Vec2crd &ref_mp = refs_mv[mvi]; - auto d = ref_whole - ref_mp; - subnfp.translate(d); - nfps.emplace_back(subnfp); - mvi++; - } - } - - return union_ex(nfps); -} - -} // namespace Slic3r diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp deleted file mode 100644 index ea12806..0000000 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp +++ /dev/null @@ -1,18 +0,0 @@ - -#ifndef NFPCONCAVE_TESSELATE_HPP -#define NFPCONCAVE_TESSELATE_HPP - -#include - -#include "libslic3r/Polygon.hpp" - -namespace Slic3r { - -Polygons convex_decomposition_tess(const Polygon &expoly); -Polygons convex_decomposition_tess(const ExPolygon &expoly); -Polygons convex_decomposition_tess(const ExPolygons &expolys); -ExPolygons nfp_concave_concave_tess(const ExPolygon &fixed, const ExPolygon &movable); - -} // namespace Slic3r - -#endif // NFPCONCAVE_TESSELATE_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp b/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp deleted file mode 100644 index 86bd6cc..0000000 --- a/src/libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp +++ /dev/null @@ -1,286 +0,0 @@ - -#ifndef PACKSTRATEGYNFP_HPP -#define PACKSTRATEGYNFP_HPP - -#include "libslic3r/Arrange/Core/ArrangeBase.hpp" - -#include "EdgeCache.hpp" -#include "Kernels/KernelTraits.hpp" - -#include "NFPArrangeItemTraits.hpp" - -#include "libslic3r/Optimize/NLoptOptimizer.hpp" -#include "libslic3r/Execution/ExecutionSeq.hpp" - -namespace Slic3r { namespace arr2 { - -struct NFPPackingTag{}; - -struct DummyArrangeKernel -{ - template - double placement_fitness(const ArrItem &itm, const Vec2crd &dest_pos) const - { - return NaNd; - } - - template - bool on_start_packing(ArrItem &itm, - const Bed &bed, - const Context &packing_context, - const Range &remaining_items) - { - return true; - } - - template bool on_item_packed(ArrItem &itm) { return true; } -}; - -template using OptAlg = typename Strategy::OptAlg; - -template -struct PackStrategyNFP { - using OptAlg = OptMethod; - - ArrangeKernel kernel; - ExecPolicy ep; - double accuracy = 1.; - opt::Optimizer solver; - StopCond stop_condition; - - PackStrategyNFP(opt::Optimizer slv, - ArrangeKernel k = {}, - ExecPolicy execpolicy = {}, - double accur = 1., - StopCond stop_cond = {}) - : kernel{std::move(k)}, - ep{std::move(execpolicy)}, - accuracy{accur}, - solver{std::move(slv)}, - stop_condition{std::move(stop_cond)} - {} - - PackStrategyNFP(ArrangeKernel k = {}, - ExecPolicy execpolicy = {}, - double accur = 1., - StopCond stop_cond = {}) - : PackStrategyNFP{opt::Optimizer{}, std::move(k), - std::move(execpolicy), accur, std::move(stop_cond)} - { - // Defaults for AlgNLoptSubplex - auto iters = static_cast(std::floor(1000 * accuracy)); - auto optparams = - opt::StopCriteria{}.max_iterations(iters).rel_score_diff( - 1e-20) /*.abs_score_diff(1e-20)*/; - - solver.set_criteria(optparams); - } -}; - -template -struct PackStrategyTag_> -{ - using Tag = NFPPackingTag; -}; - - -template -double pick_best_spot_on_nfp_verts_only(ArrItem &item, - const ExPolygons &nfp, - const Bed &bed, - const PStrategy &strategy) -{ - using KernelT = KernelTraits; - - auto score = -std::numeric_limits::infinity(); - Vec2crd orig_tr = get_translation(item); - Vec2crd translation{0, 0}; - - auto eval_fitness = [&score, &strategy, &item, &translation, - &orig_tr](const Vec2crd &p) { - set_translation(item, orig_tr); - Vec2crd ref_v = reference_vertex(item); - Vec2crd tr = p - ref_v; - double fitness = KernelT::placement_fitness(strategy.kernel, item, tr); - if (fitness > score) { - score = fitness; - translation = tr; - } - }; - - for (const ExPolygon &expoly : nfp) { - for (const Point &p : expoly.contour) { - eval_fitness(p); - } - - for (const Polygon &h : expoly.holes) - for (const Point &p : h.points) - eval_fitness(p); - } - - set_translation(item, orig_tr + translation); - - return score; -} - -struct CornerResult -{ - size_t contour_id; - opt::Result<1> oresult; -}; - -template -double pick_best_spot_on_nfp(ArrItem &item, - const ExPolygons &nfp, - const Bed &bed, - const PackStrategyNFP &strategy) -{ - auto &ex_policy = strategy.ep; - using KernelT = KernelTraits; - - auto score = -std::numeric_limits::infinity(); - Vec2crd orig_tr = get_translation(item); - Vec2crd translation{0, 0}; - Vec2crd ref_v = reference_vertex(item); - - auto edge_caches = reserve_vector(nfp.size()); - auto sample_sets = reserve_vector>( - nfp.size()); - - for (const ExPolygon &expoly : nfp) { - edge_caches.emplace_back(EdgeCache{&expoly}); - edge_caches.back().sample_contour(strategy.accuracy, - sample_sets.emplace_back()); - } - - auto nthreads = execution::max_concurrency(ex_policy); - - std::vector gresults(edge_caches.size()); - - auto resultcmp = [](auto &a, auto &b) { - return a.oresult.score < b.oresult.score; - }; - - execution::for_each( - ex_policy, size_t(0), edge_caches.size(), - [&](size_t edge_cache_idx) { - auto &ec_contour = edge_caches[edge_cache_idx]; - auto &corners = sample_sets[edge_cache_idx]; - std::vector results(corners.size()); - - auto cornerfn = [&](size_t i) { - ContourLocation cr = corners[i]; - auto objfn = [&](opt::Input<1> &in) { - Vec2crd p = ec_contour.coords(ContourLocation{cr.contour_id, in[0]}); - Vec2crd tr = p - ref_v; - - return KernelT::placement_fitness(strategy.kernel, item, tr); - }; - - // Assuming that solver is a lightweight object - auto solver = strategy.solver; - solver.to_max(); - auto oresult = solver.optimize(objfn, - opt::initvals({cr.dist}), - opt::bounds({{0., 1.}})); - - results[i] = CornerResult{cr.contour_id, oresult}; - }; - - execution::for_each(ex_policy, size_t(0), results.size(), - cornerfn, nthreads); - - auto it = std::max_element(results.begin(), results.end(), - resultcmp); - - if (it != results.end()) - gresults[edge_cache_idx] = *it; - }, - nthreads); - - auto it = std::max_element(gresults.begin(), gresults.end(), resultcmp); - if (it != gresults.end()) { - score = it->oresult.score; - size_t path_id = std::distance(gresults.begin(), it); - size_t contour_id = it->contour_id; - double dist = it->oresult.optimum[0]; - - Vec2crd pos = edge_caches[path_id].coords(ContourLocation{contour_id, dist}); - Vec2crd tr = pos - ref_v; - - set_translation(item, orig_tr + tr); - } - - return score; -} - -template -bool pack(Strategy &strategy, - const Bed &bed, - ArrItem &item, - const PackStrategyContext &packing_context, - const Range &remaining_items, - const NFPPackingTag &) -{ - using KernelT = KernelTraits; - - // The kernel might pack the item immediately - bool packed = KernelT::on_start_packing(strategy.kernel, item, bed, - packing_context, remaining_items); - - double orig_rot = get_rotation(item); - double final_rot = 0.; - double final_score = -std::numeric_limits::infinity(); - Vec2crd orig_tr = get_translation(item); - Vec2crd final_tr = orig_tr; - - bool cancelled = strategy.stop_condition(); - const auto & rotations = allowed_rotations(item); - - // Check all rotations but only if item is not already packed - for (auto rot_it = rotations.begin(); - !cancelled && !packed && rot_it != rotations.end(); ++rot_it) { - - double rot = *rot_it; - - set_rotation(item, orig_rot + rot); - set_translation(item, orig_tr); - - auto nfp = calculate_nfp(item, packing_context, bed, - strategy.stop_condition); - double score = NaNd; - if (!nfp.empty()) { - score = pick_best_spot_on_nfp(item, nfp, bed, strategy); - - cancelled = strategy.stop_condition(); - if (score > final_score) { - final_score = score; - final_rot = rot; - final_tr = get_translation(item); - } - } - } - - // If the score is not valid, and the item is not already packed, or - // the packing was cancelled asynchronously by stop condition, then - // discard the packing - bool is_score_valid = !std::isnan(final_score) && !std::isinf(final_score); - packed = !cancelled && (packed || is_score_valid); - - if (packed) { - set_translation(item, final_tr); - set_rotation(item, orig_rot + final_rot); - - // Finally, consult the kernel if the packing is sane - packed = KernelT::on_item_packed(strategy.kernel, item); - } - - return packed; -} - -}} // namespace Slic3r::arr2 - -#endif // PACKSTRATEGYNFP_HPP diff --git a/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp b/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp deleted file mode 100644 index df92d37..0000000 --- a/src/libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp +++ /dev/null @@ -1,142 +0,0 @@ - -#ifndef RECTANGLEOVERFITPACKINGSTRATEGY_HPP -#define RECTANGLEOVERFITPACKINGSTRATEGY_HPP - -#include "Kernels/RectangleOverfitKernelWrapper.hpp" - -#include "libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -namespace Slic3r { namespace arr2 { - -using PostAlignmentFn = std::function; - -struct CenterAlignmentFn { - Vec2crd operator() (const BoundingBox &bedbb, - const BoundingBox &pilebb) - { - return bedbb.center() - pilebb.center(); - } -}; - -template -struct RectangleOverfitPackingContext : public DefaultPackingContext -{ - BoundingBox limits; - int bed_index; - PostAlignmentFn post_alignment_fn; - - explicit RectangleOverfitPackingContext(const BoundingBox limits, - int bedidx, - PostAlignmentFn alignfn = CenterAlignmentFn{}) - : limits{limits}, bed_index{bedidx}, post_alignment_fn{alignfn} - {} - - void align_pile() - { - // Here, the post alignment can be safely done. No throwing - // functions are called! - if (fixed_items_range(*this).empty()) { - auto itms = packed_items_range(*this); - auto pilebb = bounding_box(itms); - - for (auto &itm : itms) { - translate(itm, post_alignment_fn(limits, pilebb)); - } - } - } - - ~RectangleOverfitPackingContext() { align_pile(); } -}; - -// With rectange bed, and no fixed items, an infinite bed with -// RectangleOverfitKernelWrapper can produce better results than a pure -// RectangleBed with inner-fit polygon calculation. -template -struct RectangleOverfitPackingStrategy { - PackStrategyNFP base_strategy; - - PostAlignmentFn post_alignment_fn = CenterAlignmentFn{}; - - template - using Context = RectangleOverfitPackingContext; - - RectangleOverfitPackingStrategy(PackStrategyNFP s, - PostAlignmentFn post_align_fn) - : base_strategy{std::move(s)}, post_alignment_fn{post_align_fn} - {} - - RectangleOverfitPackingStrategy(PackStrategyNFP s) - : base_strategy{std::move(s)} - {} -}; - -struct RectangleOverfitPackingStrategyTag {}; - -template -struct PackStrategyTag_> { - using Tag = RectangleOverfitPackingStrategyTag; -}; - -template -struct PackStrategyTraits_> { - template - using Context = typename RectangleOverfitPackingStrategy< - Args...>::template Context>; - - template - static Context create_context( - RectangleOverfitPackingStrategy &ps, - const Bed &bed, - int bed_index) - { - return Context{bounding_box(bed), bed_index, - ps.post_alignment_fn}; - } -}; - -template -struct PackingContextTraits_> - : public PackingContextTraits_> -{ - static void add_packed_item(RectangleOverfitPackingContext &ctx, ArrItem &itm) - { - ctx.add_packed_item(itm); - - // to prevent coords going out of range - ctx.align_pile(); - } -}; - -template -bool pack(Strategy &strategy, - const Bed &bed, - ArrItem &item, - const PackStrategyContext &packing_context, - const Range &remaining_items, - const RectangleOverfitPackingStrategyTag &) -{ - bool ret = false; - - if (fixed_items_range(packing_context).empty()) { - auto &base = strategy.base_strategy; - PackStrategyNFP modded_strategy{ - base.solver, - RectangleOverfitKernelWrapper{base.kernel, packing_context.limits}, - base.ep, base.accuracy}; - - ret = pack(modded_strategy, - InfiniteBed{packing_context.limits.center()}, item, - packing_context, remaining_items, NFPPackingTag{}); - } else { - ret = pack(strategy.base_strategy, bed, item, packing_context, - remaining_items, NFPPackingTag{}); - } - - return ret; -} - -}} // namespace Slic3r::arr2 - -#endif // RECTANGLEOVERFITPACKINGSTRATEGY_HPP diff --git a/src/libslic3r/Arrange/Core/PackingContext.hpp b/src/libslic3r/Arrange/Core/PackingContext.hpp deleted file mode 100644 index 0a9eef9..0000000 --- a/src/libslic3r/Arrange/Core/PackingContext.hpp +++ /dev/null @@ -1,125 +0,0 @@ - -#ifndef PACKINGCONTEXT_HPP -#define PACKINGCONTEXT_HPP - -#include "ArrangeItemTraits.hpp" - -namespace Slic3r { namespace arr2 { - -template -struct PackingContextTraits_ { - template - static void add_fixed_item(Ctx &ctx, const ArrItem &itm) - { - ctx.add_fixed_item(itm); - } - - template - static void add_packed_item(Ctx &ctx, ArrItem &itm) - { - ctx.add_packed_item(itm); - } - - // returns a range of all packed items in the context ctx - static auto all_items_range(const Ctx &ctx) - { - return ctx.all_items_range(); - } - - static auto fixed_items_range(const Ctx &ctx) - { - return ctx.fixed_items_range(); - } - - static auto packed_items_range(const Ctx &ctx) - { - return ctx.packed_items_range(); - } - - static auto packed_items_range(Ctx &ctx) - { - return ctx.packed_items_range(); - } -}; - -template -void add_fixed_item(Ctx &ctx, const ArrItem &itm) -{ - PackingContextTraits_>::add_fixed_item(ctx, itm); -} - -template -void add_packed_item(Ctx &ctx, ArrItem &itm) -{ - PackingContextTraits_>::add_packed_item(ctx, itm); -} - -template -auto all_items_range(const Ctx &ctx) -{ - return PackingContextTraits_>::all_items_range(ctx); -} - -template -auto fixed_items_range(const Ctx &ctx) -{ - return PackingContextTraits_>::fixed_items_range(ctx); -} - -template -auto packed_items_range(Ctx &&ctx) -{ - return PackingContextTraits_>::packed_items_range(ctx); -} - -template -class DefaultPackingContext { - using ArrItemRaw = StripCVRef; - std::vector> m_fixed; - std::vector> m_packed; - std::vector> m_items; - -public: - DefaultPackingContext() = default; - - template - explicit DefaultPackingContext(const Range &fixed_items) - { - std::copy(fixed_items.begin(), fixed_items.end(), std::back_inserter(m_fixed)); - std::copy(fixed_items.begin(), fixed_items.end(), std::back_inserter(m_items)); - } - - auto all_items_range() const noexcept { return crange(m_items); } - auto fixed_items_range() const noexcept { return crange(m_fixed); } - auto packed_items_range() const noexcept { return crange(m_packed); } - auto packed_items_range() noexcept { return range(m_packed); } - - void add_fixed_item(const ArrItem &itm) - { - m_fixed.emplace_back(itm); - m_items.emplace_back(itm); - } - - void add_packed_item(ArrItem &itm) - { - m_packed.emplace_back(itm); - m_items.emplace_back(itm); - } -}; - -template -auto default_context(const Range &items) -{ - using ArrItem = StripCVRef::value_type>; - return DefaultPackingContext{items}; -} - -template -auto default_context(const Cont &container) -{ - return DefaultPackingContext{crange(container)}; -} - -}} // namespace Slic3r::arr2 - -#endif // PACKINGCONTEXT_HPP diff --git a/src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp b/src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp deleted file mode 100644 index 810f6a4..0000000 --- a/src/libslic3r/Arrange/Items/ArbitraryDataStore.hpp +++ /dev/null @@ -1,92 +0,0 @@ - -#ifndef ARBITRARYDATASTORE_HPP -#define ARBITRARYDATASTORE_HPP - -#include -#include -#include - -#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" - -namespace Slic3r { namespace arr2 { - -// An associative container able to store and retrieve any data type. -// Based on std::any -class ArbitraryDataStore { - std::map m_data; - -public: - template void add(const std::string &key, T &&data) - { - m_data[key] = std::any{std::forward(data)}; - } - - void add(const std::string &key, std::any &&data) - { - m_data[key] = std::move(data); - } - - // Return nullptr if the key does not exist or the stored data has a - // type other then T. Otherwise returns a pointer to the stored data. - template const T *get(const std::string &key) const - { - auto it = m_data.find(key); - return it != m_data.end() ? std::any_cast(&(it->second)) : - nullptr; - } - - // Same as above just not const. - template T *get(const std::string &key) - { - auto it = m_data.find(key); - return it != m_data.end() ? std::any_cast(&(it->second)) : nullptr; - } - - bool has_key(const std::string &key) const - { - auto it = m_data.find(key); - return it != m_data.end(); - } -}; - -// Some items can be containers of arbitrary data stored under string keys. -template<> struct DataStoreTraits_ -{ - static constexpr bool Implemented = true; - - template - static const T *get(const ArbitraryDataStore &s, const std::string &key) - { - return s.get(key); - } - - // Same as above just not const. - template - static T *get(ArbitraryDataStore &s, const std::string &key) - { - return s.get(key); - } - - template - static bool has_key(ArbitraryDataStore &s, const std::string &key) - { - return s.has_key(key); - } -}; - -template<> struct WritableDataStoreTraits_ -{ - static constexpr bool Implemented = true; - - template - static void set(ArbitraryDataStore &store, - const std::string &key, - T &&data) - { - store.add(key, std::forward(data)); - } -}; - -}} // namespace Slic3r::arr2 - -#endif // ARBITRARYDATASTORE_HPP diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.cpp b/src/libslic3r/Arrange/Items/ArrangeItem.cpp deleted file mode 100644 index 043aada..0000000 --- a/src/libslic3r/Arrange/Items/ArrangeItem.cpp +++ /dev/null @@ -1,206 +0,0 @@ - -#include "ArrangeItem.hpp" - -#include - -#include "libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp" -#include "libslic3r/Arrange/ArrangeImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Geometry/ConvexHull.hpp" - -namespace Slic3r { namespace arr2 { - -const Polygons &DecomposedShape::transformed_outline() const -{ - constexpr auto sc = scaled(1.) * scaled(1.); - - if (!m_transformed_outline_valid) { - m_transformed_outline = contours(); - for (Polygon &poly : m_transformed_outline) { - poly.rotate(rotation()); - poly.translate(translation()); - } - - m_area = std::accumulate(m_transformed_outline.begin(), - m_transformed_outline.end(), 0., - [sc](double s, const auto &p) { - return s + p.area() / sc; - }); - - m_convex_hull = Geometry::convex_hull(m_transformed_outline); - m_bounding_box = get_extents(m_convex_hull); - - m_transformed_outline_valid = true; - } - - return m_transformed_outline; -} - -const Polygon &DecomposedShape::convex_hull() const -{ - if (!m_transformed_outline_valid) - transformed_outline(); - - return m_convex_hull; -} - -const BoundingBox &DecomposedShape::bounding_box() const -{ - if (!m_transformed_outline_valid) - transformed_outline(); - - return m_bounding_box; -} - -const Vec2crd &DecomposedShape::reference_vertex() const -{ - if (!m_reference_vertex_valid) { - m_reference_vertex = Slic3r::reference_vertex(transformed_outline()); - m_refs.clear(); - m_mins.clear(); - m_refs.reserve(m_transformed_outline.size()); - m_mins.reserve(m_transformed_outline.size()); - for (auto &poly : m_transformed_outline) { - m_refs.emplace_back(Slic3r::reference_vertex(poly)); - m_mins.emplace_back(Slic3r::min_vertex(poly)); - } - m_reference_vertex_valid = true; - } - - return m_reference_vertex; -} - -const Vec2crd &DecomposedShape::reference_vertex(size_t i) const -{ - if (!m_reference_vertex_valid) { - reference_vertex(); - } - - return m_refs[i]; -} - -const Vec2crd &DecomposedShape::min_vertex(size_t idx) const -{ - if (!m_reference_vertex_valid) { - reference_vertex(); - } - - return m_mins[idx]; -} - -Vec2crd DecomposedShape::centroid() const -{ - constexpr double area_sc = scaled(1.) * scaled(1.); - - if (!m_centroid_valid) { - double total_area = 0.0; - Vec2d cntr = Vec2d::Zero(); - - for (const Polygon& poly : transformed_outline()) { - double parea = poly.area() / area_sc; - Vec2d pcntr = unscaled(poly.centroid()); - total_area += parea; - cntr += pcntr * parea; - } - - cntr /= total_area; - m_centroid = scaled(cntr); - m_centroid_valid = true; - } - - return m_centroid; -} - -DecomposedShape decompose(const ExPolygons &shape) -{ - return DecomposedShape{convex_decomposition_tess(shape)}; -} - -DecomposedShape decompose(const Polygon &shape) -{ - Polygons convex_shapes; - - bool is_convex = polygon_is_convex(shape); - if (is_convex) { - convex_shapes.emplace_back(shape); - } else { - convex_shapes = convex_decomposition_tess(shape); - } - - return DecomposedShape{std::move(convex_shapes)}; -} - -ArrangeItem::ArrangeItem(const ExPolygons &shape) - : m_shape{decompose(shape)}, m_envelope{&m_shape} -{} - -ArrangeItem::ArrangeItem(Polygon shape) - : m_shape{decompose(shape)}, m_envelope{&m_shape} -{} - -ArrangeItem::ArrangeItem(const ArrangeItem &other) -{ - this->operator= (other); -} - -ArrangeItem::ArrangeItem(ArrangeItem &&other) noexcept -{ - this->operator=(std::move(other)); -} - -ArrangeItem &ArrangeItem::operator=(const ArrangeItem &other) -{ - m_shape = other.m_shape; - m_datastore = other.m_datastore; - m_bed_idx = other.m_bed_idx; - m_priority = other.m_priority; - - if (other.m_envelope.get() == &other.m_shape) - m_envelope = &m_shape; - else - m_envelope = std::make_unique(other.envelope()); - - return *this; -} - -void ArrangeItem::set_shape(DecomposedShape shape) -{ - m_shape = std::move(shape); - m_envelope = &m_shape; -} - -void ArrangeItem::set_envelope(DecomposedShape envelope) -{ - m_envelope = std::make_unique(std::move(envelope)); - - // Initial synch of transformations of envelope and shape. - // They need to be in synch all the time - m_envelope->translation(m_shape.translation()); - m_envelope->rotation(m_shape.rotation()); -} - -ArrangeItem &ArrangeItem::operator=(ArrangeItem &&other) noexcept -{ - m_shape = std::move(other.m_shape); - m_datastore = std::move(other.m_datastore); - m_bed_idx = other.m_bed_idx; - m_priority = other.m_priority; - - if (other.m_envelope.get() == &other.m_shape) - m_envelope = &m_shape; - else - m_envelope = std::move(other.m_envelope); - - return *this; -} - -template struct ImbueableItemTraits_; -template class ArrangeableToItemConverter; -template struct ArrangeTask; -template struct FillBedTask; -template struct MultiplySelectionTask; -template class Arranger; - -}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.hpp b/src/libslic3r/Arrange/Items/ArrangeItem.hpp deleted file mode 100644 index b16bb09..0000000 --- a/src/libslic3r/Arrange/Items/ArrangeItem.hpp +++ /dev/null @@ -1,494 +0,0 @@ - -#ifndef ARRANGEITEM_HPP -#define ARRANGEITEM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/AnyPtr.hpp" -#include "libslic3r/Arrange/Core/PackingContext.hpp" -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/NFP/NFP.hpp" -#include "libslic3r/Arrange/Items/MutableItemTraits.hpp" -#include "libslic3r/Arrange/Arrange.hpp" -#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" -#include "libslic3r/Arrange/Tasks/FillBedTask.hpp" -#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp" -#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp" -#include "libslic3r/Arrange/Core/ArrangeBase.hpp" -#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { namespace arr2 { -struct InfiniteBed; - -inline bool check_polygons_are_convex(const Polygons &pp) { - return std::all_of(pp.begin(), pp.end(), [](const Polygon &p) { - return polygon_is_convex(p); - }); -} - -// A class that stores a set of polygons that are garanteed to be all convex. -// They collectively represent a decomposition of a more complex shape into -// its convex part. Note that this class only stores the result of the decomp, -// does not do the job itself. In debug mode, an explicit check is done for -// each component to be convex. -// -// Additionally class stores a translation vector and a rotation angle for the -// stored polygon, plus additional privitives that are all cached cached after -// appying a the transformations. The caching is not thread safe! -class DecomposedShape -{ - Polygons m_shape; - - Vec2crd m_translation{0, 0}; // The translation of the poly - double m_rotation{0.0}; // The rotation of the poly in radians - - mutable Polygons m_transformed_outline; - mutable bool m_transformed_outline_valid = false; - - mutable Point m_reference_vertex; - mutable std::vector m_refs; - mutable std::vector m_mins; - mutable bool m_reference_vertex_valid = false; - - mutable Point m_centroid; - mutable bool m_centroid_valid = false; - - mutable Polygon m_convex_hull; - mutable BoundingBox m_bounding_box; - mutable double m_area = 0; - -public: - DecomposedShape() = default; - - explicit DecomposedShape(Polygon sh) - { - m_shape.emplace_back(std::move(sh)); - assert(check_polygons_are_convex(m_shape)); - } - - explicit DecomposedShape(std::initializer_list pts) - : DecomposedShape(Polygon{pts}) - {} - - explicit DecomposedShape(Polygons sh) : m_shape{std::move(sh)} - { - assert(check_polygons_are_convex(m_shape)); - } - - const Polygons &contours() const { return m_shape; } - - const Vec2crd &translation() const { return m_translation; } - double rotation() const { return m_rotation; } - - void translation(const Vec2crd &v) - { - m_translation = v; - m_transformed_outline_valid = false; - m_reference_vertex_valid = false; - m_centroid_valid = false; - } - - void rotation(double v) - { - m_rotation = v; - m_transformed_outline_valid = false; - m_reference_vertex_valid = false; - m_centroid_valid = false; - } - - const Polygons &transformed_outline() const; - const Polygon &convex_hull() const; - const BoundingBox &bounding_box() const; - - // The cached reference vertex in the context of NFP creation. Always - // refers to the leftmost upper vertex. - const Vec2crd &reference_vertex() const; - const Vec2crd &reference_vertex(size_t idx) const; - - // Also for NFP calculations, the rightmost lowest vertex of the shape. - const Vec2crd &min_vertex(size_t idx) const; - - double area_unscaled() const - { - // update cache - transformed_outline(); - - return m_area; - } - - Vec2crd centroid() const; -}; - -DecomposedShape decompose(const ExPolygons &polys); -DecomposedShape decompose(const Polygon &p); - -class ArrangeItem -{ -private: - DecomposedShape m_shape; // Shape of item when it's not moving - AnyPtr m_envelope; // Possibly different shape when packed - - ArbitraryDataStore m_datastore; - - int m_bed_idx{Unarranged}; // To which logical bed does this item belong - int m_priority{0}; // For sorting - -public: - ArrangeItem() = default; - - explicit ArrangeItem(DecomposedShape shape) - : m_shape(std::move(shape)), m_envelope{&m_shape} - {} - - explicit ArrangeItem(DecomposedShape shape, DecomposedShape envelope) - : m_shape(std::move(shape)) - , m_envelope{std::make_unique(std::move(envelope))} - {} - - explicit ArrangeItem(const ExPolygons &shape); - explicit ArrangeItem(Polygon shape); - explicit ArrangeItem(std::initializer_list pts) - : ArrangeItem(Polygon{pts}) - {} - - ArrangeItem(const ArrangeItem &); - ArrangeItem(ArrangeItem &&) noexcept; - ArrangeItem & operator=(const ArrangeItem &); - ArrangeItem & operator=(ArrangeItem &&) noexcept; - - int bed_idx() const { return m_bed_idx; } - int priority() const { return m_priority; } - - void bed_idx(int v) { m_bed_idx = v; } - void priority(int v) { m_priority = v; } - - const ArbitraryDataStore &datastore() const { return m_datastore; } - ArbitraryDataStore &datastore() { return m_datastore; } - - const DecomposedShape & shape() const { return m_shape; } - void set_shape(DecomposedShape shape); - - const DecomposedShape & envelope() const { return *m_envelope; } - void set_envelope(DecomposedShape envelope); - - const Vec2crd &translation() const { return m_shape.translation(); } - double rotation() const { return m_shape.rotation(); } - - void translation(const Vec2crd &v) - { - m_shape.translation(v); - m_envelope->translation(v); - } - - void rotation(double v) - { - m_shape.rotation(v); - m_envelope->rotation(v); - } - - void update_caches() const - { - m_shape.reference_vertex(); - m_envelope->reference_vertex(); - m_shape.centroid(); - m_envelope->centroid(); - } -}; - -template<> struct ArrangeItemTraits_ -{ - static const Vec2crd &get_translation(const ArrangeItem &itm) - { - return itm.translation(); - } - - static double get_rotation(const ArrangeItem &itm) - { - return itm.rotation(); - } - - static int get_bed_index(const ArrangeItem &itm) - { - return itm.bed_idx(); - } - - static int get_priority(const ArrangeItem &itm) - { - return itm.priority(); - } - - // Setters: - - static void set_translation(ArrangeItem &itm, const Vec2crd &v) - { - itm.translation(v); - } - - static void set_rotation(ArrangeItem &itm, double v) - { - itm.rotation(v); - } - - static void set_bed_index(ArrangeItem &itm, int v) - { - itm.bed_idx(v); - } -}; - -// Some items can be containers of arbitrary data stored under string keys. -template<> struct DataStoreTraits_ -{ - static constexpr bool Implemented = true; - - template - static const T *get(const ArrangeItem &itm, const std::string &key) - { - return itm.datastore().get(key); - } - - // Same as above just not const. - template - static T *get(ArrangeItem &itm, const std::string &key) - { - return itm.datastore().get(key); - } - - static bool has_key(const ArrangeItem &itm, const std::string &key) - { - return itm.datastore().has_key(key); - } -}; - -template<> struct WritableDataStoreTraits_ -{ - static constexpr bool Implemented = true; - - template - static void set(ArrangeItem &itm, - const std::string &key, - T &&data) - { - itm.datastore().add(key, std::forward(data)); - } -}; - -template -static Polygons calculate_nfp_unnormalized(const ArrangeItem &item, - const Range &fixed_items, - StopCond &&stop_cond = {}) -{ - size_t cap = 0; - - for (const ArrangeItem &fixitem : fixed_items) { - const Polygons &outlines = fixitem.shape().transformed_outline(); - cap += outlines.size(); - } - - const Polygons &item_outlines = item.envelope().transformed_outline(); - - auto nfps = reserve_polygons(cap * item_outlines.size()); - - Vec2crd ref_whole = item.envelope().reference_vertex(); - Polygon subnfp; - - for (const ArrangeItem &fixed : fixed_items) { - // fixed_polys should already be a set of strictly convex polygons, - // as ArrangeItem stores convex-decomposed polygons - const Polygons & fixed_polys = fixed.shape().transformed_outline(); - - for (const Polygon &fixed_poly : fixed_polys) { - Point max_fixed = Slic3r::reference_vertex(fixed_poly); - for (size_t mi = 0; mi < item_outlines.size(); ++mi) { - const Polygon &movable = item_outlines[mi]; - const Vec2crd &mref = item.envelope().reference_vertex(mi); - subnfp = nfp_convex_convex_legacy(fixed_poly, movable); - - Vec2crd min_movable = item.envelope().min_vertex(mi); - - Vec2crd dtouch = max_fixed - min_movable; - Vec2crd top_other = mref + dtouch; - Vec2crd max_nfp = Slic3r::reference_vertex(subnfp); - auto dnfp = top_other - max_nfp; - - auto d = ref_whole - mref + dnfp; - subnfp.translate(d); - nfps.emplace_back(subnfp); - } - - if (stop_cond()) - break; - - nfps = union_(nfps); - } - - if (stop_cond()) { - nfps.clear(); - break; - } - } - - return nfps; -} - -template<> struct NFPArrangeItemTraits_ { - template - static ExPolygons calculate_nfp(const ArrangeItem &item, - const Context &packing_context, - const Bed &bed, - StopCond &&stopcond) - { - auto static_items = all_items_range(packing_context); - Polygons nfps = arr2::calculate_nfp_unnormalized(item, static_items, stopcond); - - ExPolygons nfp_ex; - - if (!stopcond()) { - if constexpr (!std::is_convertible_v) { - ExPolygons ifpbed = ifp_convex(bed, item.envelope().convex_hull()); - nfp_ex = diff_ex(ifpbed, nfps); - } else { - nfp_ex = union_ex(nfps); - } - } - - item.update_caches(); - - return nfp_ex; - } - - static const Vec2crd& reference_vertex(const ArrangeItem &item) - { - return item.envelope().reference_vertex(); - } - - static BoundingBox envelope_bounding_box(const ArrangeItem &itm) - { - return itm.envelope().bounding_box(); - } - - static BoundingBox fixed_bounding_box(const ArrangeItem &itm) - { - return itm.shape().bounding_box(); - } - - static double envelope_area(const ArrangeItem &itm) - { - return itm.envelope().area_unscaled() * scaled(1.) * - scaled(1.); - } - - static double fixed_area(const ArrangeItem &itm) - { - return itm.shape().area_unscaled() * scaled(1.) * - scaled(1.); - } - - static const Polygons & envelope_outline(const ArrangeItem &itm) - { - return itm.envelope().transformed_outline(); - } - - static const Polygons & fixed_outline(const ArrangeItem &itm) - { - return itm.shape().transformed_outline(); - } - - static const Polygon & envelope_convex_hull(const ArrangeItem &itm) - { - return itm.envelope().convex_hull(); - } - - static const Polygon & fixed_convex_hull(const ArrangeItem &itm) - { - return itm.shape().convex_hull(); - } - - static const std::vector& allowed_rotations(const ArrangeItem &itm) - { - static const std::vector ret_zero = {0.}; - - const std::vector * ret_ptr = &ret_zero; - - auto rots = get_data>(itm, "rotations"); - if (rots) { - ret_ptr = rots; - } - - return *ret_ptr; - } - - static Vec2crd fixed_centroid(const ArrangeItem &itm) - { - return itm.shape().centroid(); - } - - static Vec2crd envelope_centroid(const ArrangeItem &itm) - { - return itm.envelope().centroid(); - } -}; - -template<> struct IsMutableItem_: public std::true_type {}; - -template<> -struct MutableItemTraits_ { - - static void set_priority(ArrangeItem &itm, int p) { itm.priority(p); } - static void set_convex_shape(ArrangeItem &itm, const Polygon &shape) - { - itm.set_shape(DecomposedShape{shape}); - } - static void set_shape(ArrangeItem &itm, const ExPolygons &shape) - { - itm.set_shape(decompose(shape)); - } - static void set_convex_envelope(ArrangeItem &itm, const Polygon &envelope) - { - itm.set_envelope(DecomposedShape{envelope}); - } - static void set_envelope(ArrangeItem &itm, const ExPolygons &envelope) - { - itm.set_envelope(decompose(envelope)); - } - - template - static void set_arbitrary_data(ArrangeItem &itm, const std::string &key, T &&data) - { - set_data(itm, key, std::forward(data)); - } - - static void set_allowed_rotations(ArrangeItem &itm, const std::vector &rotations) - { - set_data(itm, "rotations", rotations); - } -}; - -extern template struct ImbueableItemTraits_; -extern template class ArrangeableToItemConverter; -extern template struct ArrangeTask; -extern template struct FillBedTask; -extern template struct MultiplySelectionTask; -extern template class Arranger; - -}} // namespace Slic3r::arr2 - -#endif // ARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Items/MutableItemTraits.hpp b/src/libslic3r/Arrange/Items/MutableItemTraits.hpp deleted file mode 100644 index 7c58748..0000000 --- a/src/libslic3r/Arrange/Items/MutableItemTraits.hpp +++ /dev/null @@ -1,137 +0,0 @@ - -#ifndef MutableItemTraits_HPP -#define MutableItemTraits_HPP - -#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" - -#include "libslic3r/ExPolygon.hpp" - -namespace Slic3r { namespace arr2 { - -template struct IsMutableItem_ : public std::false_type -{}; - -// Using this interface to set up any arrange item. Provides default -// implementation but it needs to be explicitly switched on with -// IsMutableItem_ or completely reimplement a specialization. -template struct MutableItemTraits_ -{ - static_assert(IsMutableItem_::value, "Not a Writable item type!"); - - static void set_priority(Itm &itm, int p) { itm.set_priority(p); } - - static void set_convex_shape(Itm &itm, const Polygon &shape) - { - itm.set_convex_shape(shape); - } - - static void set_shape(Itm &itm, const ExPolygons &shape) - { - itm.set_shape(shape); - } - - static void set_convex_envelope(Itm &itm, const Polygon &envelope) - { - itm.set_convex_envelope(envelope); - } - - static void set_envelope(Itm &itm, const ExPolygons &envelope) - { - itm.set_envelope(envelope); - } - - template - static void set_arbitrary_data(Itm &itm, const std::string &key, T &&data) - { - if constexpr (IsWritableDataStore) - set_data(itm, key, std::forward(data)); - } - - static void set_allowed_rotations(Itm &itm, - const std::vector &rotations) - { - itm.set_allowed_rotations(rotations); - } -}; - -template -using MutableItemTraits = MutableItemTraits_>; - -template constexpr bool IsMutableItem = IsMutableItem_::value; -template -using MutableItemOnly = std::enable_if_t, TT>; - -template void set_priority(Itm &itm, int p) -{ - MutableItemTraits::set_priority(itm, p); -} - -template void set_convex_shape(Itm &itm, const Polygon &shape) -{ - MutableItemTraits::set_convex_shape(itm, shape); -} - -template void set_shape(Itm &itm, const ExPolygons &shape) -{ - MutableItemTraits::set_shape(itm, shape); -} - -template -void set_convex_envelope(Itm &itm, const Polygon &envelope) -{ - MutableItemTraits::set_convex_envelope(itm, envelope); -} - -template void set_envelope(Itm &itm, const ExPolygons &envelope) -{ - MutableItemTraits::set_envelope(itm, envelope); -} - -template -void set_arbitrary_data(Itm &itm, const std::string &key, T &&data) -{ - MutableItemTraits::set_arbitrary_data(itm, key, std::forward(data)); -} - -template -void set_allowed_rotations(Itm &itm, const std::vector &rotations) -{ - MutableItemTraits::set_allowed_rotations(itm, rotations); -} - -template int raise_priority(ArrItem &itm) -{ - int ret = get_priority(itm) + 1; - set_priority(itm, ret); - - return ret; -} - -template int reduce_priority(ArrItem &itm) -{ - int ret = get_priority(itm) - 1; - set_priority(itm, ret); - - return ret; -} - -template int lowest_priority(const Range &item_range) -{ - auto minp_it = std::min_element(item_range.begin(), - item_range.end(), - [](auto &itm1, auto &itm2) { - return get_priority(itm1) < - get_priority(itm2); - }); - - int min_priority = 0; - if (minp_it != item_range.end()) - min_priority = get_priority(*minp_it); - - return min_priority; -} - -}} // namespace Slic3r::arr2 - -#endif // MutableItemTraits_HPP diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp deleted file mode 100644 index ac3ebf7..0000000 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp +++ /dev/null @@ -1,25 +0,0 @@ - -#include "SimpleArrangeItem.hpp" -#include "libslic3r/Arrange/ArrangeImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep -#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep - -namespace Slic3r { namespace arr2 { - -Polygon SimpleArrangeItem::outline() const -{ - Polygon ret = shape(); - ret.rotate(m_rotation); - ret.translate(m_translation); - - return ret; -} - -template class ArrangeableToItemConverter; -template struct ArrangeTask; -template struct FillBedTask; -template struct MultiplySelectionTask; -template class Arranger; - -}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp deleted file mode 100644 index 7861a97..0000000 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ /dev/null @@ -1,227 +0,0 @@ - -#ifndef SIMPLEARRANGEITEM_HPP -#define SIMPLEARRANGEITEM_HPP - -#include -#include -#include -#include -#include - -#include "libslic3r/Arrange/Core/PackingContext.hpp" -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" -#include "libslic3r/Arrange/Core/NFP/NFP.hpp" -#include "libslic3r/Arrange/Arrange.hpp" -#include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" -#include "libslic3r/Arrange/Tasks/FillBedTask.hpp" -#include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "MutableItemTraits.hpp" -#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/ObjectID.hpp" -#include "libslic3r/Point.hpp" - -namespace Slic3r { namespace arr2 { -struct InfiniteBed; - -class SimpleArrangeItem { - Polygon m_shape; - - Vec2crd m_translation = Vec2crd::Zero(); - double m_rotation = 0.; - int m_priority = 0; - int m_bed_idx = Unarranged; - - std::vector m_allowed_rotations = {0.}; - ObjectID m_obj_id; - -public: - explicit SimpleArrangeItem(Polygon chull = {}): m_shape{std::move(chull)} {} - - void set_shape(Polygon chull) { m_shape = std::move(chull); } - - const Vec2crd& get_translation() const noexcept { return m_translation; } - double get_rotation() const noexcept { return m_rotation; } - int get_priority() const noexcept { return m_priority; } - int get_bed_index() const noexcept { return m_bed_idx; } - - void set_translation(const Vec2crd &v) { m_translation = v; } - void set_rotation(double v) noexcept { m_rotation = v; } - void set_priority(int v) noexcept { m_priority = v; } - void set_bed_index(int v) noexcept { m_bed_idx = v; } - - const Polygon &shape() const { return m_shape; } - Polygon outline() const; - - const auto &allowed_rotations() const noexcept - { - return m_allowed_rotations; - } - - void set_allowed_rotations(std::vector rots) - { - m_allowed_rotations = std::move(rots); - } - - void set_object_id(const ObjectID &id) noexcept { m_obj_id = id; } - const ObjectID & get_object_id() const noexcept { return m_obj_id; } -}; - -template<> struct NFPArrangeItemTraits_ -{ - template - static ExPolygons calculate_nfp(const SimpleArrangeItem &item, - const Context &packing_context, - const Bed &bed, - StopCond &&stop_cond) - { - auto fixed_items = all_items_range(packing_context); - auto nfps = reserve_polygons(fixed_items.size()); - for (const SimpleArrangeItem &fixed_part : fixed_items) { - Polygon subnfp = nfp_convex_convex_legacy(fixed_part.outline(), - item.outline()); - nfps.emplace_back(subnfp); - - - if (stop_cond()) { - nfps.clear(); - break; - } - } - - ExPolygons nfp_ex; - if (!stop_cond()) { - if constexpr (!std::is_convertible_v) { - ExPolygons ifpbed = ifp_convex(bed, item.outline()); - nfp_ex = diff_ex(ifpbed, nfps); - } else { - nfp_ex = union_ex(nfps); - } - } - - return nfp_ex; - } - - static Vec2crd reference_vertex(const SimpleArrangeItem &item) - { - return Slic3r::reference_vertex(item.outline()); - } - - static BoundingBox envelope_bounding_box(const SimpleArrangeItem &itm) - { - return get_extents(itm.outline()); - } - - static BoundingBox fixed_bounding_box(const SimpleArrangeItem &itm) - { - return get_extents(itm.outline()); - } - - static Polygons envelope_outline(const SimpleArrangeItem &itm) - { - return {itm.outline()}; - } - - static Polygons fixed_outline(const SimpleArrangeItem &itm) - { - return {itm.outline()}; - } - - static Polygon envelope_convex_hull(const SimpleArrangeItem &itm) - { - return Geometry::convex_hull(itm.outline()); - } - - static Polygon fixed_convex_hull(const SimpleArrangeItem &itm) - { - return Geometry::convex_hull(itm.outline()); - } - - static double envelope_area(const SimpleArrangeItem &itm) - { - return itm.shape().area(); - } - - static double fixed_area(const SimpleArrangeItem &itm) - { - return itm.shape().area(); - } - - static const auto& allowed_rotations(const SimpleArrangeItem &itm) noexcept - { - return itm.allowed_rotations(); - } - - static Vec2crd fixed_centroid(const SimpleArrangeItem &itm) noexcept - { - return itm.outline().centroid(); - } - - static Vec2crd envelope_centroid(const SimpleArrangeItem &itm) noexcept - { - return itm.outline().centroid(); - } -}; - -template<> struct IsMutableItem_: public std::true_type {}; - -template<> -struct MutableItemTraits_ { - - static void set_priority(SimpleArrangeItem &itm, int p) { itm.set_priority(p); } - static void set_convex_shape(SimpleArrangeItem &itm, const Polygon &shape) - { - itm.set_shape(shape); - } - static void set_shape(SimpleArrangeItem &itm, const ExPolygons &shape) - { - itm.set_shape(Geometry::convex_hull(shape)); - } - static void set_convex_envelope(SimpleArrangeItem &itm, const Polygon &envelope) - { - itm.set_shape(envelope); - } - static void set_envelope(SimpleArrangeItem &itm, const ExPolygons &envelope) - { - itm.set_shape(Geometry::convex_hull(envelope)); - } - - template - static void set_data(SimpleArrangeItem &itm, const std::string &key, T &&data) - {} - - static void set_allowed_rotations(SimpleArrangeItem &itm, const std::vector &rotations) - { - itm.set_allowed_rotations(rotations); - } -}; - -template<> struct ImbueableItemTraits_ -{ - static void imbue_id(SimpleArrangeItem &itm, const ObjectID &id) - { - itm.set_object_id(id); - } - - static std::optional retrieve_id(const SimpleArrangeItem &itm) - { - std::optional ret; - if (itm.get_object_id().valid()) - ret = itm.get_object_id(); - - return ret; - } -}; - -extern template class ArrangeableToItemConverter; -extern template struct ArrangeTask; -extern template struct FillBedTask; -extern template struct MultiplySelectionTask; -extern template class Arranger; - -}} // namespace Slic3r::arr2 - -#endif // SIMPLEARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp b/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp deleted file mode 100644 index 58db30a..0000000 --- a/src/libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp +++ /dev/null @@ -1,80 +0,0 @@ - -#ifndef TRAFOONLYARRANGEITEM_HPP -#define TRAFOONLYARRANGEITEM_HPP - -#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" - -#include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp" -#include "libslic3r/Arrange/Items/MutableItemTraits.hpp" - -namespace Slic3r { namespace arr2 { - -class TrafoOnlyArrangeItem { - int m_bed_idx = Unarranged; - int m_priority = 0; - Vec2crd m_translation = Vec2crd::Zero(); - double m_rotation = 0.; - - ArbitraryDataStore m_datastore; - -public: - TrafoOnlyArrangeItem() = default; - - template - explicit TrafoOnlyArrangeItem(const ArrItm &other) - : m_bed_idx{arr2::get_bed_index(other)}, - m_priority{arr2::get_priority(other)}, - m_translation(arr2::get_translation(other)), - m_rotation{arr2::get_rotation(other)} - {} - - const Vec2crd& get_translation() const noexcept { return m_translation; } - double get_rotation() const noexcept { return m_rotation; } - int get_bed_index() const noexcept { return m_bed_idx; } - int get_priority() const noexcept { return m_priority; } - - const ArbitraryDataStore &datastore() const noexcept { return m_datastore; } - ArbitraryDataStore &datastore() { return m_datastore; } -}; - -template<> struct DataStoreTraits_ -{ - static constexpr bool Implemented = true; - - template - static const T *get(const TrafoOnlyArrangeItem &itm, const std::string &key) - { - return itm.datastore().get(key); - } - - template - static T *get(TrafoOnlyArrangeItem &itm, const std::string &key) - { - return itm.datastore().get(key); - } - - static bool has_key(const TrafoOnlyArrangeItem &itm, const std::string &key) - { - return itm.datastore().has_key(key); - } -}; - -template<> struct IsMutableItem_: public std::true_type {}; - -template<> struct WritableDataStoreTraits_ -{ - static constexpr bool Implemented = true; - - template - static void set(TrafoOnlyArrangeItem &itm, - const std::string &key, - T &&data) - { - set_data(itm.datastore(), key, std::forward(data)); - } -}; - -} // namespace arr2 -} // namespace Slic3r - -#endif // TRAFOONLYARRANGEITEM_HPP diff --git a/src/libslic3r/Arrange/Scene.cpp b/src/libslic3r/Arrange/Scene.cpp deleted file mode 100644 index 26b7c4d..0000000 --- a/src/libslic3r/Arrange/Scene.cpp +++ /dev/null @@ -1,65 +0,0 @@ - -#include "Scene.hpp" - -#include "Items/ArrangeItem.hpp" - -#include "Tasks/ArrangeTask.hpp" -#include "Tasks/FillBedTask.hpp" - -namespace Slic3r { namespace arr2 { - -std::vector Scene::selected_ids() const -{ - auto items = reserve_vector(model().arrangeable_count()); - - model().for_each_arrangeable([ &items](auto &arrbl) mutable { - if (arrbl.is_selected()) - items.emplace_back(arrbl.id()); - }); - - return items; -} - -using DefaultArrangeItem = ArrangeItem; - -std::unique_ptr ArrangeTaskBase::create(Tasks task_type, const Scene &sc) -{ - std::unique_ptr ret; - switch(task_type) { - case Tasks::Arrange: - ret = ArrangeTask::create(sc); - break; - case Tasks::FillBed: - ret = FillBedTask::create(sc); - break; - default: - ; - } - - return ret; -} - -std::set selected_geometry_ids(const Scene &sc) -{ - std::set result; - - std::vector selected_ids = sc.selected_ids(); - for (const ObjectID &id : selected_ids) { - sc.model().visit_arrangeable(id, [&result](const Arrangeable &arrbl) { - auto id = arrbl.geometry_id(); - if (id.valid()) - result.insert(arrbl.geometry_id()); - }); - } - - return result; -} - -bool arrange(Scene &scene, ArrangeTaskCtl &ctl) -{ - auto task = ArrangeTaskBase::create(Tasks::Arrange, scene); - auto result = task->process(ctl); - return result->apply_on(scene.model()); -} - -}} // namespace Slic3r::arr2 diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp deleted file mode 100644 index 1e3fd99..0000000 --- a/src/libslic3r/Arrange/Scene.hpp +++ /dev/null @@ -1,429 +0,0 @@ - -#ifndef ARR2_SCENE_HPP -#define ARR2_SCENE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libslic3r/ObjectID.hpp" -#include "libslic3r/AnyPtr.hpp" -#include "libslic3r/Arrange/ArrangeSettingsView.hpp" -#include "libslic3r/Arrange/SegmentedRectangleBed.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { namespace arr2 { - -// This module contains all the necessary high level interfaces for -// arrangement. No dependency on the rest of libslic3r is intoduced here. (No -// Model, ModelObject, etc...) except for ObjectID. - - -// An interface that allows to store arbitrary data (std::any) under a specific -// key in an object implementing the interface. This is later used to pass -// arbitrary parameters from any arrangeable object down to the arrangement core. -class AnyWritable -{ -public: - virtual ~AnyWritable() = default; - - virtual void write(std::string_view key, std::any d) = 0; -}; - -// The interface that captures the objects which are actually moved around. -// Implementations must provide means to extract the 2D outline that is used -// by the arrangement core. -class Arrangeable -{ -public: - virtual ~Arrangeable() = default; - - // ID is implementation specific, must uniquely identify an Arrangeable - // object. - virtual ObjectID id() const = 0; - - // This is different than id(), and identifies an underlying group into - // which the Arrangeable belongs. Can be used to group arrangeables sharing - // the same outline. - virtual ObjectID geometry_id() const = 0; - - // Outline extraction can be a demanding operation, so there is a separate - // method the extract the full outline of an object and the convex hull only - // It will depend on the arrangement config to choose which one is called. - // convex_outline might be considerably faster than calling full_outline() - // and then calculating the convex hull from that. - virtual ExPolygons full_outline() const = 0; - virtual Polygon convex_outline() const = 0; - - // Envelope is the boundary that an arrangeble object might have which - // is used when the object is being placed or moved around. Once it is - // placed, the outline (convex or full) will be used to determine the - // boundaries instead of the envelope. This concept can be used to - // implement arranging objects with support structures that can overlap, - // but never touch the actual object. In this case, full envelope would - // return the silhouette of the object with supports (pad, brim, etc...) and - // outline would be the actual object boundary. - virtual ExPolygons full_envelope() const { return {}; } - virtual Polygon convex_envelope() const { return {}; } - - // Write the transformations determined by the arrangement into the object - virtual void transform(const Vec2d &transl, double rot) = 0; - - // An arrangeable can be printable or unprintable, they should not be on - // the same bed. (See arrange tasks) - virtual bool is_printable() const { return true; } - - // An arrangeable can be selected or not, this will determine if treated - // as static objects or movable ones. - virtual bool is_selected() const { return true; } - - // Determines the order in which the objects are arranged. Higher priority - // objects are arranged first. - virtual int priority() const { return 0; } - - // Any implementation specific properties can be passed to the arrangement - // core by overriding this method. This implies that the specific Arranger - // will be able to interpret these properties. An example usage is to mark - // special objects (like a wipe tower) - virtual void imbue_data(AnyWritable &datastore) const {} - - // for convinience to pass an AnyWritable created in the same expression - // as the method call - void imbue_data(AnyWritable &&datastore) const { imbue_data(datastore); } - - // An Arrangeable might reside on a logical bed instead of the real one - // in case that the arrangement can not fit it onto the real bed. Handling - // of logical beds is also implementation specific and are specified with - // the next two methods: - - // Returns the bed index on which the given Arrangeable is sitting. - virtual int get_bed_index() const = 0; - - // Assign the Arrangeable to the given bed index. Note that this - // method can return false, indicating that the given bed is not available - // to be occupied. - virtual bool assign_bed(int bed_idx) = 0; -}; - -// Arrangeable objects are provided by an ArrangeableModel which is also able to -// create new arrangeables given a prototype id to copy. -class ArrangeableModel -{ -public: - virtual ~ArrangeableModel() = default; - - // Visit all arrangeable in this model and call the provided visitor - virtual void for_each_arrangeable(std::function) = 0; - virtual void for_each_arrangeable(std::function) const = 0; - - // Visit a specific arrangeable identified by it's id - virtual void visit_arrangeable(const ObjectID &id, std::function) const = 0; - virtual void visit_arrangeable(const ObjectID &id, std::function) = 0; - - // Add a new arrangeable which is a copy of the one matching prototype_id - // Return the new object id or an invalid id if the new object was not - // created. - virtual ObjectID add_arrangeable(const ObjectID &prototype_id) = 0; - - size_t arrangeable_count() const - { - size_t cnt = 0; - for_each_arrangeable([&cnt](auto &) { ++cnt; }); - - return cnt; - } -}; - -// The special bed type used by XL printers -using XLBed = SegmentedRectangleBed, - std::integral_constant>; - -// ExtendedBed is a variant type holding all bed types supported by the -// arrange core and the additional XLBed - -template struct ExtendedBed_ -{ - using Type = - boost::variant; -}; - -template struct ExtendedBed_> -{ - using Type = boost::variant; -}; - -using ExtendedBed = typename ExtendedBed_::Type; - -template void visit_bed(BedFn &&fn, const ExtendedBed &bed) -{ - boost::apply_visitor(fn, bed); -} - -template void visit_bed(BedFn &&fn, ExtendedBed &bed) -{ - boost::apply_visitor(fn, bed); -} - -inline BoundingBox bounding_box(const ExtendedBed &bed) -{ - BoundingBox bedbb; - visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); - - return bedbb; -} - -class Scene; - -// SceneBuilderBase is intended for Scene construction. A simple constructor -// is not enough here to capture all the possible ways of constructing a Scene. -// Subclasses of SceneBuilderBase can add more domain specific methods and -// overloads. An rvalue object of this class is handed over to the Scene -// constructor which can then establish itself using the provided builder. - -// A little CRTP is used to implement fluent interface returning Subclass -// references. -template -class SceneBuilderBase -{ -protected: - AnyPtr m_arrangeable_model; - - AnyPtr m_settings; - - ExtendedBed m_bed = arr2::InfiniteBed{}; - - coord_t m_brims_offs = 0; - coord_t m_skirt_offs = 0; - -public: - - virtual ~SceneBuilderBase() = default; - - SceneBuilderBase() = default; - SceneBuilderBase(const SceneBuilderBase &) = delete; - SceneBuilderBase& operator=(const SceneBuilderBase &) = delete; - SceneBuilderBase(SceneBuilderBase &&) = default; - SceneBuilderBase& operator=(SceneBuilderBase &&) = default; - - // All setters return an rvalue reference so that at the end, the - // build_scene method can be called fluently - - Subclass &&set_arrange_settings(AnyPtr settings) - { - m_settings = std::move(settings); - return std::move(static_cast(*this)); - } - - Subclass &&set_arrange_settings(const ArrangeSettingsView &settings) - { - m_settings = std::make_unique(settings); - return std::move(static_cast(*this)); - } - - Subclass &&set_bed(const Points &pts) - { - m_bed = arr2::to_arrange_bed(pts); - return std::move(static_cast(*this)); - } - - Subclass && set_bed(const arr2::ArrangeBed &bed) - { - m_bed = bed; - return std::move(static_cast(*this)); - } - - Subclass &&set_bed(const XLBed &bed) - { - m_bed = bed; - return std::move(static_cast(*this)); - } - - Subclass &&set_arrangeable_model(AnyPtr model) - { - m_arrangeable_model = std::move(model); - return std::move(static_cast(*this)); - } - - // Can only be called on an rvalue instance (hence the && at the end), - // the method will potentially move its content into sc - virtual void build_scene(Scene &sc) &&; -}; - -class BasicSceneBuilder: public SceneBuilderBase {}; - -// The Scene class captures all data needed to do an arrangement. -class Scene -{ - template friend class SceneBuilderBase; - - // These fields always need to be initialized to valid objects after - // construction of Scene which is ensured by the SceneBuilder - AnyPtr m_amodel; - AnyPtr m_settings; - ExtendedBed m_bed; - -public: - // Scene can only be built from an rvalue SceneBuilder whose content will - // potentially be moved to the constructed Scene object. - template - explicit Scene(SceneBuilderBase &&bld) - { - std::move(bld).build_scene(*this); - } - - const ArrangeableModel &model() const noexcept { return *m_amodel; } - ArrangeableModel &model() noexcept { return *m_amodel; } - - const ArrangeSettingsView &settings() const noexcept { return *m_settings; } - - template void visit_bed(BedFn &&fn) const - { - arr2::visit_bed(fn, m_bed); - } - - const ExtendedBed & bed() const { return m_bed; } - - std::vector selected_ids() const; -}; - -// Get all the ObjectIDs of Arrangeables which are in selected state -std::set selected_geometry_ids(const Scene &sc); - -// A dummy, empty ArrangeableModel for testing and as placeholder to avoiod using nullptr -class EmptyArrangeableModel: public ArrangeableModel -{ -public: - void for_each_arrangeable(std::function) override {} - void for_each_arrangeable(std::function) const override {} - void visit_arrangeable(const ObjectID &id, std::function) const override {} - void visit_arrangeable(const ObjectID &id, std::function) override {} - ObjectID add_arrangeable(const ObjectID &prototype_id) override { return {}; } -}; - -template -void SceneBuilderBase::build_scene(Scene &sc) && -{ - if (!m_arrangeable_model) - m_arrangeable_model = std::make_unique(); - - if (!m_settings) - m_settings = std::make_unique(); - - // Apply the bed minimum distance by making the original bed smaller - // and arranging on this smaller bed. - coord_t inset = std::max(scaled(m_settings->get_distance_from_bed()), - m_skirt_offs + m_brims_offs); - - // Objects have also a minimum distance from each other implemented - // as inflation applied to object outlines. This object distance - // does not apply to the bed, so the bed is inflated by this amount - // to compensate. - coord_t md = scaled(m_settings->get_distance_from_objects()); - md = md / 2 - inset; - - // Applying the final bed with the corrected dimensions to account - // for safety distances - visit_bed([md](auto &rawbed) { rawbed = offset(rawbed, md); }, m_bed); - - sc.m_settings = std::move(m_settings); - sc.m_amodel = std::move(m_arrangeable_model); - sc.m_bed = std::move(m_bed); -} - -// Arrange tasks produce an object implementing this interface. The arrange -// result can be applied to an ArrangeableModel which may or may not succeed. -// The ArrangeableModel could be in a different state (it's objects may have -// changed or removed) than it was at the time of arranging. -class ArrangeResult -{ -public: - virtual ~ArrangeResult() = default; - - virtual bool apply_on(ArrangeableModel &mdlwt) = 0; -}; - -enum class Tasks { Arrange, FillBed }; - -class ArrangeTaskCtl -{ -public: - virtual ~ArrangeTaskCtl() = default; - - virtual void update_status(int st) = 0; - - virtual bool was_canceled() const = 0; -}; - -class DummyCtl : public ArrangeTaskCtl -{ -public: - void update_status(int) override {} - bool was_canceled() const override { return false; } -}; - -class ArrangeTaskBase -{ -public: - using Ctl = ArrangeTaskCtl; - - virtual ~ArrangeTaskBase() = default; - - [[nodiscard]] virtual std::unique_ptr process(Ctl &ctl) = 0; - - [[nodiscard]] virtual int item_count_to_process() const = 0; - - [[nodiscard]] static std::unique_ptr create( - Tasks task_type, const Scene &sc); - - [[nodiscard]] std::unique_ptr process(Ctl &&ctl) - { - return process(ctl); - } - - [[nodiscard]] std::unique_ptr process() - { - return process(DummyCtl{}); - } -}; - -bool arrange(Scene &scene, ArrangeTaskCtl &ctl); -inline bool arrange(Scene &scene, ArrangeTaskCtl &&ctl = DummyCtl{}) -{ - return arrange(scene, ctl); -} - -inline bool arrange(Scene &&scene, ArrangeTaskCtl &ctl) -{ - return arrange(scene, ctl); -} - -inline bool arrange(Scene &&scene, ArrangeTaskCtl &&ctl = DummyCtl{}) -{ - return arrange(scene, ctl); -} - -template -bool arrange(SceneBuilderBase &&builder, Ctl &&ctl = {}) -{ - return arrange(Scene{std::move(builder)}, ctl); -} - -} // namespace arr2 -} // namespace Slic3r - -#endif // ARR2_SCENE_HPP diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp deleted file mode 100644 index 8656ef9..0000000 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ /dev/null @@ -1,942 +0,0 @@ - -#ifndef SCENEBUILDER_CPP -#define SCENEBUILDER_CPP - -#include "SceneBuilder.hpp" - -#include -#include -#include -#include -#include - -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/SLAPrint.hpp" -#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/Arrange/Scene.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/SLA/Pad.hpp" -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" - -namespace Slic3r { namespace arr2 { - -coord_t get_skirt_inset(const Print &fffprint) -{ - float skirt_inset = 0.f; - - if (fffprint.has_skirt()) { - float skirtflow = fffprint.objects().empty() - ? 0 - : fffprint.skirt_flow().width(); - skirt_inset = fffprint.config().skirts.value * skirtflow - + fffprint.config().skirt_distance.value; - } - - return scaled(skirt_inset); -} - -coord_t brim_offset(const PrintObject &po) -{ - const BrimType brim_type = po.config().brim_type.value; - const float brim_separation = po.config().brim_separation.getFloat(); - const float brim_width = po.config().brim_width.getFloat(); - const bool has_outer_brim = brim_type == BrimType::btOuterOnly || - brim_type == BrimType::btOuterAndInner; - - // How wide is the brim? (in scaled units) - return has_outer_brim ? scaled(brim_width + brim_separation) : 0; -} - -size_t model_instance_count (const Model &m) -{ - return std::accumulate(m.objects.begin(), - m.objects.end(), - size_t(0), - [](size_t s, const Slic3r::ModelObject *mo) { - return s + mo->instances.size(); - }); -} - -void transform_instance(ModelInstance &mi, - const Vec2d &transl_unscaled, - double rot, - const Transform3d &physical_tr) -{ - auto trafo = mi.get_transformation().get_matrix(); - auto tr = Transform3d::Identity(); - tr.translate(to_3d(transl_unscaled, 0.)); - trafo = physical_tr.inverse() * tr * Eigen::AngleAxisd(rot, Vec3d::UnitZ()) * physical_tr * trafo; - - mi.set_transformation(Geometry::Transformation{trafo}); - - mi.invalidate_object_bounding_box(); -} - -BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, - const Transform3d &tr, - bool dont_translate) -{ - BoundingBoxf3 bb; - const Transform3d inst_matrix - = dont_translate ? mi.get_transformation().get_matrix_no_offset() - : mi.get_transformation().get_matrix(); - - for (ModelVolume *v : mi.get_object()->volumes) { - if (v->is_model_part()) { - bb.merge(v->mesh().transformed_bounding_box(tr * inst_matrix - * v->get_matrix())); - } - } - - return bb; -} - -BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, bool dont_translate) -{ - return instance_bounding_box(mi, Transform3d::Identity(), dont_translate); -} - -bool check_coord_bounds(const BoundingBoxf &bb) -{ - return std::abs(bb.min.x()) < UnscaledCoordLimit && - std::abs(bb.min.y()) < UnscaledCoordLimit && - std::abs(bb.max.x()) < UnscaledCoordLimit && - std::abs(bb.max.y()) < UnscaledCoordLimit; -} - -ExPolygons extract_full_outline(const ModelInstance &inst, const Transform3d &tr) -{ - ExPolygons outline; - - if (check_coord_bounds(to_2d(instance_bounding_box(inst, tr)))) { - for (const ModelVolume *v : inst.get_object()->volumes) { - Polygons vol_outline; - - vol_outline = project_mesh(v->mesh().its, - tr * inst.get_matrix() * v->get_matrix(), - [] {}); - switch (v->type()) { - case ModelVolumeType::MODEL_PART: - outline = union_ex(outline, vol_outline); - break; - case ModelVolumeType::NEGATIVE_VOLUME: - outline = diff_ex(outline, vol_outline); - break; - default:; - } - } - } - - return outline; -} - -Polygon extract_convex_outline(const ModelInstance &inst, const Transform3d &tr) -{ - auto bb = to_2d(instance_bounding_box(inst, tr)); - Polygon ret; - - if (check_coord_bounds(bb)) { - ret = inst.get_object()->convex_hull_2d(tr * inst.get_matrix()); - } - - return ret; -} - -inline static bool is_infinite_bed(const ExtendedBed &ebed) noexcept -{ - bool ret = false; - visit_bed( - [&ret](auto &rawbed) { - ret = std::is_convertible_v; - }, - ebed); - - return ret; -} - -void SceneBuilder::set_brim_and_skirt() -{ - if (!m_fff_print) - return; - - m_brims_offs = 0; - - for (const PrintObject *po : m_fff_print->objects()) { - if (po) { - m_brims_offs = std::max(m_brims_offs, brim_offset(*po)); - } - } - - m_skirt_offs = get_skirt_inset(*m_fff_print); -} - -void SceneBuilder::build_scene(Scene &sc) && -{ - if (m_sla_print && !m_fff_print) { - m_arrangeable_model = std::make_unique(m_sla_print.get(), *this); - } else { - m_arrangeable_model = std::make_unique(*this); - } - - if (m_fff_print && !m_sla_print) { - if (is_infinite_bed(m_bed)) { - set_bed(*m_fff_print); - } else { - set_brim_and_skirt(); - } - } - - // Call the parent class implementation of build_scene to finish constructing of the scene - std::move(*this).SceneBuilderBase::build_scene(sc); -} - -void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel) -{ - if (!m_model) - m_model = std::make_unique(); - - if (!m_selection) - m_selection = std::make_unique(*m_model); - - if (!m_vbed_handler) { - m_vbed_handler = VirtualBedHandler::create(m_bed); - } - - if (!m_wipetower_handler) { - m_wipetower_handler = std::make_unique(); - } - - if (m_fff_print && !m_xl_printer) - m_xl_printer = is_XL_printer(m_fff_print->config()); - - bool has_wipe_tower = false; - m_wipetower_handler->visit( - [&has_wipe_tower](const Arrangeable &arrbl) { has_wipe_tower = true; }); - - if (m_xl_printer && !has_wipe_tower) { - m_bed = XLBed{bounding_box(m_bed)}; - } - - amodel.m_vbed_handler = std::move(m_vbed_handler); - amodel.m_model = std::move(m_model); - amodel.m_selmask = std::move(m_selection); - amodel.m_wth = std::move(m_wipetower_handler); - - amodel.m_wth->set_selection_predicate( - [&amodel] { return amodel.m_selmask->is_wipe_tower(); }); -} - -int XStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const -{ - int bedidx = 0; - auto stride_s = stride_scaled(); - if (stride_s > 0) { - double bedx = unscaled(m_start); - auto instance_bb = obj.bounding_box(); - auto reference_pos_x = (instance_bb.min.x() - bedx); - auto stride = unscaled(stride_s); - - auto bedidx_d = std::floor(reference_pos_x / stride); - - if (bedidx_d < std::numeric_limits::min()) - bedidx = std::numeric_limits::min(); - else if (bedidx_d > std::numeric_limits::max()) - bedidx = std::numeric_limits::max(); - else - bedidx = static_cast(bedidx_d); - } - - return bedidx; -} - -bool XStriderVBedHandler::assign_bed(VBedPlaceable &obj, int bed_index) -{ - bool ret = false; - auto stride_s = stride_scaled(); - if (bed_index == 0 || (bed_index > 0 && stride_s > 0)) { - auto current_bed_index = get_bed_index(obj); - auto stride = unscaled(stride_s); - auto transl = Vec2d{(bed_index - current_bed_index) * stride, 0.}; - obj.displace(transl, 0.); - - ret = true; - } - - return ret; -} - -Transform3d XStriderVBedHandler::get_physical_bed_trafo(int bed_index) const -{ - auto stride_s = stride_scaled(); - auto tr = Transform3d::Identity(); - tr.translate(Vec3d{-bed_index * unscaled(stride_s), 0., 0.}); - - return tr; -} - -int YStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const -{ - int bedidx = 0; - auto stride_s = stride_scaled(); - if (stride_s > 0) { - double ystart = unscaled(m_start); - auto instance_bb = obj.bounding_box(); - auto reference_pos_y = (instance_bb.min.y() - ystart); - auto stride = unscaled(stride_s); - - auto bedidx_d = std::floor(reference_pos_y / stride); - - if (bedidx_d < std::numeric_limits::min()) - bedidx = std::numeric_limits::min(); - else if (bedidx_d > std::numeric_limits::max()) - bedidx = std::numeric_limits::max(); - else - bedidx = static_cast(bedidx_d); - } - - return bedidx; -} - -bool YStriderVBedHandler::assign_bed(VBedPlaceable &obj, int bed_index) -{ - bool ret = false; - auto stride_s = stride_scaled(); - if (bed_index == 0 || (bed_index > 0 && stride_s > 0)) { - auto current_bed_index = get_bed_index(obj); - auto stride = unscaled(stride_s); - auto transl = Vec2d{0., (bed_index - current_bed_index) * stride}; - obj.displace(transl, 0.); - - ret = true; - } - - return ret; -} - -Transform3d YStriderVBedHandler::get_physical_bed_trafo(int bed_index) const -{ - auto stride_s = stride_scaled(); - auto tr = Transform3d::Identity(); - tr.translate(Vec3d{0., -bed_index * unscaled(stride_s), 0.}); - - return tr; -} - -const int GridStriderVBedHandler::Cols = - 2 * static_cast(std::sqrt(std::numeric_limits::max()) / 2); - -const int GridStriderVBedHandler::HalfCols = Cols / 2; -const int GridStriderVBedHandler::Offset = HalfCols + Cols * HalfCols; - -Vec2i GridStriderVBedHandler::raw2grid(int bed_idx) const -{ - bed_idx += Offset; - - Vec2i ret{bed_idx % Cols - HalfCols, bed_idx / Cols - HalfCols}; - - return ret; -} - -int GridStriderVBedHandler::grid2raw(const Vec2i &crd) const -{ - // Overlapping virtual beds will happen if the crd values exceed limits - assert((crd.x() < HalfCols - 1 && crd.x() >= -HalfCols) && - (crd.y() < HalfCols - 1 && crd.y() >= -HalfCols)); - - return (crd.x() + HalfCols) + Cols * (crd.y() + HalfCols) - Offset; -} - -int GridStriderVBedHandler::get_bed_index(const VBedPlaceable &obj) const -{ - Vec2i crd = {m_xstrider.get_bed_index(obj), m_ystrider.get_bed_index(obj)}; - - return grid2raw(crd); -} - -bool GridStriderVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) -{ - Vec2i crd = raw2grid(bed_idx); - - bool retx = m_xstrider.assign_bed(inst, crd.x()); - bool rety = m_ystrider.assign_bed(inst, crd.y()); - - return retx && rety; -} - -Transform3d GridStriderVBedHandler::get_physical_bed_trafo(int bed_idx) const -{ - Vec2i crd = raw2grid(bed_idx); - - Transform3d ret = m_xstrider.get_physical_bed_trafo(crd.x()) * - m_ystrider.get_physical_bed_trafo(crd.y()); - - return ret; -} - -FixedSelection::FixedSelection(const Model &m) : m_wp{true} -{ - m_seldata.resize(m.objects.size()); - for (size_t i = 0; i < m.objects.size(); ++i) { - m_seldata[i].resize(m.objects[i]->instances.size(), true); - } -} - -FixedSelection::FixedSelection(const SelectionMask &other) -{ - auto obj_sel = other.selected_objects(); - m_seldata.reserve(obj_sel.size()); - for (int oidx = 0; oidx < static_cast(obj_sel.size()); ++oidx) - m_seldata.emplace_back(other.selected_instances(oidx)); -} - -std::vector FixedSelection::selected_objects() const -{ - auto ret = Slic3r::reserve_vector(m_seldata.size()); - std::transform(m_seldata.begin(), - m_seldata.end(), - std::back_inserter(ret), - [](auto &a) { - return std::any_of(a.begin(), a.end(), [](bool b) { - return b; - }); - }); - return ret; -} - -static std::vector find_true_indices(const std::vector &v) -{ - auto ret = reserve_vector(v.size()); - - for (size_t i = 0; i < v.size(); ++i) - if (v[i]) - ret.emplace_back(i); - - return ret; -} - -std::vector selected_object_indices(const SelectionMask &sm) -{ - auto sel = sm.selected_objects(); - return find_true_indices(sel); -} - -std::vector selected_instance_indices(int obj_idx, const SelectionMask &sm) -{ - auto sel = sm.selected_instances(obj_idx); - return find_true_indices(sel); -} - -SceneBuilder::SceneBuilder() = default; -SceneBuilder::~SceneBuilder() = default; -SceneBuilder::SceneBuilder(SceneBuilder &&) = default; -SceneBuilder& SceneBuilder::operator=(SceneBuilder&&) = default; - -SceneBuilder &&SceneBuilder::set_model(AnyPtr mdl) -{ - m_model = std::move(mdl); - return std::move(*this); -} - -SceneBuilder &&SceneBuilder::set_model(Model &mdl) -{ - m_model = &mdl; - return std::move(*this); -} - -SceneBuilder &&SceneBuilder::set_fff_print(AnyPtr mdl_print) -{ - m_fff_print = std::move(mdl_print); - return std::move(*this); -} - -SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr mdl_print) -{ - m_sla_print = std::move(mdl_print); - - return std::move(*this); -} - -SceneBuilder &&SceneBuilder::set_bed(const DynamicPrintConfig &cfg) -{ - Points bedpts = get_bed_shape(cfg); - - if (is_XL_printer(cfg)) { - m_xl_printer = true; - } - - m_bed = arr2::to_arrange_bed(bedpts); - - return std::move(*this); -} - -SceneBuilder &&SceneBuilder::set_bed(const Print &print) -{ - Points bedpts = get_bed_shape(print.config()); - - if (is_XL_printer(print.config())) { - m_bed = XLBed{get_extents(bedpts)}; - } else { - m_bed = arr2::to_arrange_bed(bedpts); - } - - set_brim_and_skirt(); - - return std::move(*this); -} - -SceneBuilder &&SceneBuilder::set_sla_print(const SLAPrint *slaprint) -{ - m_sla_print = slaprint; - return std::move(*this); -} - -int ArrangeableWipeTowerBase::get_bed_index() const { return PhysicalBedId; } - -bool ArrangeableWipeTowerBase::assign_bed(int bed_idx) -{ - return bed_idx == PhysicalBedId; -} - -bool PhysicalOnlyVBedHandler::assign_bed(VBedPlaceable &inst, int bed_idx) -{ - return bed_idx == PhysicalBedId; -} - -ArrangeableSlicerModel::ArrangeableSlicerModel(SceneBuilder &builder) -{ - builder.build_arrangeable_slicer_model(*this); -} - -ArrangeableSlicerModel::~ArrangeableSlicerModel() = default; - -void ArrangeableSlicerModel::for_each_arrangeable( - std::function fn) -{ - for_each_arrangeable_(*this, fn); - - m_wth->visit(fn); -} - -void ArrangeableSlicerModel::for_each_arrangeable( - std::function fn) const -{ - for_each_arrangeable_(*this, fn); - - m_wth->visit(fn); -} - -ObjectID ArrangeableSlicerModel::add_arrangeable(const ObjectID &prototype_id) -{ - ObjectID ret; - - auto [inst, pos] = find_instance_by_id(*m_model, prototype_id); - if (inst) { - auto new_inst = inst->get_object()->add_instance(*inst); - if (new_inst) { - ret = new_inst->id(); - } - } - - return ret; -} - -template -void ArrangeableSlicerModel::for_each_arrangeable_(Self &&self, Fn &&fn) -{ - InstPos pos; - for (auto *obj : self.m_model->objects) { - for (auto *inst : obj->instances) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; - fn(ainst); - ++pos.inst_idx; - } - pos.inst_idx = 0; - ++pos.obj_idx; - } -} - -template -void ArrangeableSlicerModel::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) -{ - if (id == self.m_model->wipe_tower.id()) { - self.m_wth->visit(fn); - - return; - } - - auto [inst, pos] = find_instance_by_id(*self.m_model, id); - - if (inst) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), self.m_selmask.get(), pos}; - fn(ainst); - } -} - -void ArrangeableSlicerModel::visit_arrangeable( - const ObjectID &id, std::function fn) const -{ - visit_arrangeable_(*this, id, fn); -} - -void ArrangeableSlicerModel::visit_arrangeable( - const ObjectID &id, std::function fn) -{ - visit_arrangeable_(*this, id, fn); -} - -template -void ArrangeableSLAPrint::for_each_arrangeable_(Self &&self, Fn &&fn) -{ - InstPos pos; - for (auto *obj : self.m_model->objects) { - for (auto *inst : obj->instances) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), - self.m_selmask.get(), pos}; - - auto obj_id = inst->get_object()->id(); - const SLAPrintObject *po = - self.m_slaprint->get_print_object_by_model_object_id(obj_id); - - if (po) { - auto &vbh = self.m_vbed_handler; - auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst})); - ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()}; - fn(ainst_po); - } else { - fn(ainst); - } - - ++pos.inst_idx; - } - pos.inst_idx = 0; - ++pos.obj_idx; - } -} - -void ArrangeableSLAPrint::for_each_arrangeable( - std::function fn) -{ - for_each_arrangeable_(*this, fn); - - m_wth->visit(fn); -} - -void ArrangeableSLAPrint::for_each_arrangeable( - std::function fn) const -{ - for_each_arrangeable_(*this, fn); - - m_wth->visit(fn); -} - -template -void ArrangeableSLAPrint::visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) -{ - auto [inst, pos] = find_instance_by_id(*self.m_model, id); - - if (inst) { - ArrangeableModelInstance ainst{inst, self.m_vbed_handler.get(), - self.m_selmask.get(), pos}; - - auto obj_id = inst->get_object()->id(); - const SLAPrintObject *po = - self.m_slaprint->get_print_object_by_model_object_id(obj_id); - - if (po) { - auto &vbh = self.m_vbed_handler; - auto phtr = vbh->get_physical_bed_trafo(vbh->get_bed_index(VBedPlaceableMI{*inst})); - ArrangeableSLAPrintObject ainst_po{po, &ainst, phtr * inst->get_matrix()}; - fn(ainst_po); - } else { - fn(ainst); - } - } -} - -void ArrangeableSLAPrint::visit_arrangeable( - const ObjectID &id, std::function fn) const -{ - visit_arrangeable_(*this, id, fn); -} - -void ArrangeableSLAPrint::visit_arrangeable( - const ObjectID &id, std::function fn) -{ - visit_arrangeable_(*this, id, fn); -} - -template -ExPolygons ArrangeableModelInstance::full_outline() const -{ - int bedidx = m_vbedh->get_bed_index(*this); - auto tr = m_vbedh->get_physical_bed_trafo(bedidx); - - return extract_full_outline(*m_mi, tr); -} - -template -Polygon ArrangeableModelInstance::convex_outline() const -{ - int bedidx = m_vbedh->get_bed_index(*this); - auto tr = m_vbedh->get_physical_bed_trafo(bedidx); - - return extract_convex_outline(*m_mi, tr); -} - -template -bool ArrangeableModelInstance::is_selected() const -{ - bool ret = false; - - if (m_selmask) { - auto sel = m_selmask->selected_instances(m_pos_within_model.obj_idx); - if (m_pos_within_model.inst_idx < sel.size() && - sel[m_pos_within_model.inst_idx]) - ret = true; - } - - return ret; -} - -template -void ArrangeableModelInstance::transform(const Vec2d &transl, double rot) -{ - if constexpr (!std::is_const_v && !std::is_const_v) { - int bedidx = m_vbedh->get_bed_index(*this); - auto physical_trafo = m_vbedh->get_physical_bed_trafo(bedidx); - - transform_instance(*m_mi, transl, rot, physical_trafo); - } -} - -template -bool ArrangeableModelInstance::assign_bed(int bed_idx) -{ - bool ret = false; - - if constexpr (!std::is_const_v && !std::is_const_v) - ret = m_vbedh->assign_bed(*this, bed_idx); - - return ret; -} - -template class ArrangeableModelInstance; -template class ArrangeableModelInstance; - -ExPolygons ArrangeableSLAPrintObject::full_outline() const -{ - ExPolygons ret; - - auto laststep = m_po->last_completed_step(); - if (laststep < slaposCount && laststep > slaposSupportTree) { - Polygons polys; - auto omesh = m_po->get_mesh_to_print(); - auto &smesh = m_po->support_mesh(); - - Transform3d trafo_instance = m_inst_trafo * m_po->trafo().inverse(); - - if (omesh) { - Polygons ptmp = project_mesh(*omesh, trafo_instance, [] {}); - std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys)); - } - - Polygons ptmp = project_mesh(smesh.its, trafo_instance, [] {}); - std::move(ptmp.begin(), ptmp.end(), std::back_inserter(polys)); - ret = union_ex(polys); - } else { - ret = m_arrbl->full_outline(); - } - - return ret; -} - -ExPolygons ArrangeableSLAPrintObject::full_envelope() const -{ - ExPolygons ret = full_outline(); - - auto laststep = m_po->last_completed_step(); - if (laststep < slaposCount && laststep > slaposSupportTree) { - auto &pmesh = m_po->pad_mesh(); - if (!pmesh.empty()) { - - Transform3d trafo_instance = m_inst_trafo * m_po->trafo().inverse(); - - Polygons ptmp = project_mesh(pmesh.its, trafo_instance, [] {}); - ret = union_ex(ret, ptmp); - } - } - - return ret; -} - -Polygon ArrangeableSLAPrintObject::convex_outline() const -{ - Polygons polys; - - polys.emplace_back(m_arrbl->convex_outline()); - - auto laststep = m_po->last_completed_step(); - if (laststep < slaposCount && laststep > slaposSupportTree) { - auto omesh = m_po->get_mesh_to_print(); - auto &smesh = m_po->support_mesh(); - - Transform3f trafo_instance = m_inst_trafo.cast(); - trafo_instance = trafo_instance * m_po->trafo().cast().inverse(); - - Polygons polys; - polys.reserve(3); - auto zlvl = -m_po->get_elevation(); - - if (omesh) { - polys.emplace_back( - its_convex_hull_2d_above(*omesh, trafo_instance, zlvl)); - } - - polys.emplace_back( - its_convex_hull_2d_above(smesh.its, trafo_instance, zlvl)); - } - - return Geometry::convex_hull(polys); -} - -Polygon ArrangeableSLAPrintObject::convex_envelope() const -{ - Polygons polys; - - polys.emplace_back(convex_outline()); - - auto laststep = m_po->last_completed_step(); - if (laststep < slaposCount && laststep > slaposSupportTree) { - auto &pmesh = m_po->pad_mesh(); - if (!pmesh.empty()) { - - Transform3f trafo_instance = m_inst_trafo.cast(); - trafo_instance = trafo_instance * m_po->trafo().cast().inverse(); - auto zlvl = -m_po->get_elevation(); - - polys.emplace_back( - its_convex_hull_2d_above(pmesh.its, trafo_instance, zlvl)); - } - } - - return Geometry::convex_hull(polys); -} - -DuplicableModel::DuplicableModel(AnyPtr mdl, AnyPtr vbh, const BoundingBox &bedbb) - : m_model{std::move(mdl)}, m_vbh{std::move(vbh)}, m_duplicates(1), m_bedbb{bedbb} -{ -} - -DuplicableModel::~DuplicableModel() = default; - -ObjectID DuplicableModel::add_arrangeable(const ObjectID &prototype_id) -{ - ObjectID ret; - if (prototype_id.valid()) { - size_t idx = prototype_id.id - 1; - if (idx < m_duplicates.size()) { - ModelDuplicate md = m_duplicates[idx]; - md.id = m_duplicates.size(); - ret = md.id.id + 1; - m_duplicates.emplace_back(std::move(md)); - } - } - - return ret; -} - -void DuplicableModel::apply_duplicates() -{ - for (ModelObject *o : m_model->objects) { - // make a copy of the pointers in order to avoid recursion - // when appending their copies - ModelInstancePtrs instances = o->instances; - o->instances.clear(); - for (const ModelInstance *i : instances) { - for (const ModelDuplicate &md : m_duplicates) { - ModelInstance *instance = o->add_instance(*i); - arr2::transform_instance(*instance, md.tr, md.rot); - } - } - for (auto *i : instances) - delete i; - - instances.clear(); - - o->invalidate_bounding_box(); - } -} - -template -ObjectID ArrangeableFullModel::geometry_id() const { return m_mdl->id(); } - -template -ExPolygons ArrangeableFullModel::full_outline() const -{ - auto ret = reserve_vector(arr2::model_instance_count(*m_mdl)); - - auto transl = Transform3d::Identity(); - transl.translate(to_3d(m_dup->tr, 0.)); - Transform3d trafo = transl* Eigen::AngleAxisd(m_dup->rot, Vec3d::UnitZ()); - - for (auto *mo : m_mdl->objects) { - for (auto *mi : mo->instances) { - auto expolys = arr2::extract_full_outline(*mi, trafo); - std::move(expolys.begin(), expolys.end(), std::back_inserter(ret)); - } - } - - return ret; -} - -template -Polygon ArrangeableFullModel::convex_outline() const -{ - auto ret = reserve_polygons(arr2::model_instance_count(*m_mdl)); - - auto transl = Transform3d::Identity(); - transl.translate(to_3d(m_dup->tr, 0.)); - Transform3d trafo = transl* Eigen::AngleAxisd(m_dup->rot, Vec3d::UnitZ()); - - for (auto *mo : m_mdl->objects) { - for (auto *mi : mo->instances) { - ret.emplace_back(arr2::extract_convex_outline(*mi, trafo)); - } - } - - return Geometry::convex_hull(ret); -} - -template class ArrangeableFullModel; -template class ArrangeableFullModel; - -std::unique_ptr VirtualBedHandler::create(const ExtendedBed &bed) -{ - std::unique_ptr ret; - if (is_infinite_bed(bed)) { - ret = std::make_unique(); - } else { - // The gap between logical beds expressed in ratio of - // the current bed width. - constexpr double LogicalBedGap = 1. / 10.; - - BoundingBox bedbb; - visit_bed([&bedbb](auto &rawbed) { bedbb = bounding_box(rawbed); }, bed); - - auto bedwidth = bedbb.size().x(); - coord_t xgap = LogicalBedGap * bedwidth; - ret = std::make_unique(bedbb, xgap); - } - - return ret; -} - -}} // namespace Slic3r::arr2 - -#endif // SCENEBUILDER_CPP diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp deleted file mode 100644 index 77cbc3d..0000000 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ /dev/null @@ -1,714 +0,0 @@ - -#ifndef SCENEBUILDER_HPP -#define SCENEBUILDER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Scene.hpp" -#include "Core/ArrangeItemTraits.hpp" -#include "libslic3r/AnyPtr.hpp" -#include "libslic3r/Arrange/Core/Beds.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/ObjectID.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/libslic3r.h" - -namespace Slic3r { - -class Model; -class ModelInstance; -class ModelWipeTower; -class Print; -class SLAPrint; -class SLAPrintObject; -class PrintObject; -class DynamicPrintConfig; - -namespace arr2 { - -using SelectionPredicate = std::function; - -// Objects implementing this interface should know how to present the wipe tower -// as an Arrangeable. If the wipe tower is not present, the overloads of visit() shouldn't do -// anything. (See MissingWipeTowerHandler) -class WipeTowerHandler -{ -public: - virtual ~WipeTowerHandler() = default; - - virtual void visit(std::function) = 0; - virtual void visit(std::function) const = 0; - virtual void set_selection_predicate(SelectionPredicate pred) = 0; -}; - -// Something that has a bounding box and can be displaced by arbitrary 2D offset and rotated -// by arbitrary rotation. Used as targets to place on virtual beds. Normally this would correspond -// to ModelInstances but the same functionality was needed in more contexts. -class VBedPlaceable { -public: - virtual ~VBedPlaceable() = default; - - virtual BoundingBoxf bounding_box() const = 0; - virtual void displace(const Vec2d &transl, double rot) = 0; -}; - -// An interface to handle virtual beds for VBedPlaceable objects. A VBedPlaceable -// may be assigned to a logical bed identified by an integer index value (zero -// is the actual physical bed). The VBedPlaceable may still be outside of it's -// bed, regardless of being assigned to it. The handler object should provide -// means to read the assigned bed index of a VBedPlaceable, to assign a -// different bed index and to provide a trafo that maps it to the physical bed -// given a logical bed index. The reason is that the arrangement expects items -// to be in the coordinate system of the physical bed. -class VirtualBedHandler -{ -public: - virtual ~VirtualBedHandler() = default; - - // Returns the bed index on which the given VBedPlaceable is sitting. - virtual int get_bed_index(const VBedPlaceable &obj) const = 0; - - // The returned trafo can be used to displace the VBedPlaceable - // to the coordinate system of the physical bed, should that differ from - // the coordinate space of a logical bed. - virtual Transform3d get_physical_bed_trafo(int bed_index) const = 0; - - // Assign the VBedPlaceable to the given bed index. Note that this - // method can return false, indicating that the given bed is not available - // to be occupied (e.g. the handler has a limited amount of logical bed) - virtual bool assign_bed(VBedPlaceable &obj, int bed_idx) = 0; - - bool assign_bed(VBedPlaceable &&obj, int bed_idx) - { - return assign_bed(obj, bed_idx); - } - - static std::unique_ptr create(const ExtendedBed &bed); -}; - -// Holds the info about which object (ID) is selected/unselected -class SelectionMask -{ -public: - virtual ~SelectionMask() = default; - - virtual std::vector selected_objects() const = 0; - virtual std::vector selected_instances(int obj_id) const = 0; - virtual bool is_wipe_tower() const = 0; -}; - -class FixedSelection : public Slic3r::arr2::SelectionMask -{ - std::vector> m_seldata; - bool m_wp = false; - -public: - FixedSelection() = default; - - explicit FixedSelection(std::initializer_list> seld, - bool wp = false) - : m_seldata{std::move(seld)}, m_wp{wp} - {} - - explicit FixedSelection(const Model &m); - - explicit FixedSelection(const SelectionMask &other); - - std::vector selected_objects() const override; - - std::vector selected_instances(int obj_id) const override - { - return obj_id < int(m_seldata.size()) ? m_seldata[obj_id] : - std::vector{}; - } - - bool is_wipe_tower() const override { return m_wp; } -}; - -// Common part of any Arrangeable which is a wipe tower -struct ArrangeableWipeTowerBase: public Arrangeable -{ - ObjectID oid; - - Polygon poly; - SelectionPredicate selection_pred; - - ArrangeableWipeTowerBase( - const ObjectID &objid, - Polygon shape, - SelectionPredicate selection_predicate = [] { return false; }) - : oid{objid}, - poly{std::move(shape)}, - selection_pred{std::move(selection_predicate)} - {} - - ObjectID id() const override { return oid; } - ObjectID geometry_id() const override { return {}; } - - ExPolygons full_outline() const override - { - auto cpy = poly; - return {ExPolygon{std::move(cpy)}}; - } - - Polygon convex_outline() const override - { - return poly; - } - - bool is_selected() const override - { - return selection_pred(); - } - - int get_bed_index() const override; - bool assign_bed(int /*bed_idx*/) override; - - int priority() const override { return 1; } - - void transform(const Vec2d &transl, double rot) override {} - - void imbue_data(AnyWritable &datastore) const override - { - datastore.write("is_wipe_tower", {}); - } -}; - -class SceneBuilder; - -struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; - -// Implementing ArrangeableModel interface for QIDISlicer's Model, ModelObject, ModelInstance data -// hierarchy -class ArrangeableSlicerModel: public ArrangeableModel -{ -protected: - AnyPtr m_model; - AnyPtr m_wth; // Determines how wipe tower is handled - AnyPtr m_vbed_handler; // Determines how virtual beds are handled - AnyPtr m_selmask; // Determines which objects are selected/unselected - -private: - friend class SceneBuilder; - - template - static void for_each_arrangeable_(Self &&self, Fn &&fn); - - template - static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn); - -public: - explicit ArrangeableSlicerModel(SceneBuilder &builder); - ~ArrangeableSlicerModel(); - - void for_each_arrangeable(std::function) override; - void for_each_arrangeable(std::function) const override; - - void visit_arrangeable(const ObjectID &id, std::function) const override; - void visit_arrangeable(const ObjectID &id, std::function) override; - - ObjectID add_arrangeable(const ObjectID &prototype_id) override; - - Model & get_model() { return *m_model; } - const Model &get_model() const { return *m_model; } -}; - -// SceneBuilder implementation for QIDISlicer API. -class SceneBuilder: public SceneBuilderBase -{ -protected: - AnyPtr m_model; - AnyPtr m_wipetower_handler; - AnyPtr m_vbed_handler; - AnyPtr m_selection; - - AnyPtr m_sla_print; - AnyPtr m_fff_print; - bool m_xl_printer = false; - - void set_brim_and_skirt(); - -public: - SceneBuilder(); - ~SceneBuilder(); - SceneBuilder(SceneBuilder&&); - SceneBuilder& operator=(SceneBuilder&&); - - SceneBuilder && set_model(AnyPtr mdl); - - SceneBuilder && set_model(Model &mdl); - - SceneBuilder && set_fff_print(AnyPtr fffprint); - SceneBuilder && set_sla_print(AnyPtr mdl_print); - - using SceneBuilderBase::set_bed; - - SceneBuilder &&set_bed(const DynamicPrintConfig &cfg); - SceneBuilder &&set_bed(const Print &print); - - SceneBuilder && set_wipe_tower_handler(WipeTowerHandler &wth) - { - m_wipetower_handler = &wth; - return std::move(*this); - } - - SceneBuilder && set_wipe_tower_handler(AnyPtr wth) - { - m_wipetower_handler = std::move(wth); - return std::move(*this); - } - - SceneBuilder && set_virtual_bed_handler(AnyPtr vbedh) - { - m_vbed_handler = std::move(vbedh); - return std::move(*this); - } - - SceneBuilder && set_sla_print(const SLAPrint *slaprint); - - SceneBuilder && set_selection(AnyPtr sel) - { - m_selection = std::move(sel); - return std::move(*this); - } - - // Can only be called on an rvalue instance (hence the && at the end), - // the method will potentially move its content into sc - void build_scene(Scene &sc) && override; - - void build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel); -}; - -struct MissingWipeTowerHandler : public WipeTowerHandler -{ - void visit(std::function) override {} - void visit(std::function) const override {} - void set_selection_predicate(std::function) override {} -}; - -// Only a physical bed, non-zero bed index values are discarded. -class PhysicalOnlyVBedHandler final : public VirtualBedHandler -{ -public: - using VirtualBedHandler::assign_bed; - - int get_bed_index(const VBedPlaceable &obj) const override { return 0; } - - Transform3d get_physical_bed_trafo(int bed_index) const override - { - return Transform3d::Identity(); - } - - bool assign_bed(VBedPlaceable &inst, int bed_idx) override; -}; - -// A virtual bed handler implementation, that defines logical beds to be created -// on the right side of the physical bed along the X axis in a row -class XStriderVBedHandler final : public VirtualBedHandler -{ - coord_t m_stride_scaled; - coord_t m_start; - -public: - explicit XStriderVBedHandler(const BoundingBox &bedbb, coord_t xgap) - : m_stride_scaled{bedbb.size().x() + 2 * std::max(0, xgap)}, - m_start{bedbb.min.x() - std::max(0, xgap)} - { - } - - coord_t stride_scaled() const { return m_stride_scaled; } - - // Can return negative indices when the instance is to the left of the - // physical bed - int get_bed_index(const VBedPlaceable &obj) const override; - - // Only positive beds are accepted - bool assign_bed(VBedPlaceable &inst, int bed_idx) override; - - using VirtualBedHandler::assign_bed; - - Transform3d get_physical_bed_trafo(int bed_index) const override; -}; - -// Same as XStriderVBedHandler only that it lays out vbeds on the Y axis -class YStriderVBedHandler final : public VirtualBedHandler -{ - coord_t m_stride_scaled; - coord_t m_start; - -public: - coord_t stride_scaled() const { return m_stride_scaled; } - - explicit YStriderVBedHandler(const BoundingBox &bedbb, coord_t ygap) - : m_stride_scaled{bedbb.size().y() + 2 * std::max(0, ygap)} - , m_start{bedbb.min.y() - std::max(0, ygap)} - {} - - int get_bed_index(const VBedPlaceable &obj) const override; - bool assign_bed(VBedPlaceable &inst, int bed_idx) override; - - Transform3d get_physical_bed_trafo(int bed_index) const override; -}; - -class GridStriderVBedHandler: public VirtualBedHandler -{ - // This vbed handler defines a grid of virtual beds with a large number - // of columns so that it behaves as XStrider for regular cases. - // The goal is to handle objects residing at world coordinates - // not representable with scaled coordinates. Combining XStrider with - // YStrider takes care of the X and Y axis to be mapped into the physical - // bed's coordinate region (which is representable in scaled coords) - static const int Cols; - static const int HalfCols; - static const int Offset; - - XStriderVBedHandler m_xstrider; - YStriderVBedHandler m_ystrider; - -public: - GridStriderVBedHandler(const BoundingBox &bedbb, - coord_t gap) - : m_xstrider{bedbb, gap} - , m_ystrider{bedbb, gap} - {} - - Vec2i raw2grid(int bedidx) const; - int grid2raw(const Vec2i &crd) const; - - int get_bed_index(const VBedPlaceable &obj) const override; - bool assign_bed(VBedPlaceable &inst, int bed_idx) override; - - Transform3d get_physical_bed_trafo(int bed_index) const override; -}; - -std::vector selected_object_indices(const SelectionMask &sm); -std::vector selected_instance_indices(int obj_idx, const SelectionMask &sm); - -coord_t get_skirt_inset(const Print &fffprint); - -coord_t brim_offset(const PrintObject &po); - -// unscaled coords are necessary to be able to handle bigger coordinate range -// than what is available with scaled coords. This is useful when working with -// virtual beds. -void transform_instance(ModelInstance &mi, - const Vec2d &transl_unscaled, - double rot, - const Transform3d &physical_tr = Transform3d::Identity()); - -BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, - bool dont_translate = false); - -BoundingBoxf3 instance_bounding_box(const ModelInstance &mi, - const Transform3d &tr, - bool dont_translate = false); - -constexpr double UnscaledCoordLimit = 1000.; - -ExPolygons extract_full_outline(const ModelInstance &inst, - const Transform3d &tr = Transform3d::Identity()); - -Polygon extract_convex_outline(const ModelInstance &inst, - const Transform3d &tr = Transform3d::Identity()); - -size_t model_instance_count (const Model &m); - -class VBedPlaceableMI : public VBedPlaceable -{ - ModelInstance *m_mi; - -public: - explicit VBedPlaceableMI(ModelInstance &mi) : m_mi{&mi} {} - - BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); } - void displace(const Vec2d &transl, double rot) override - { - transform_instance(*m_mi, transl, rot); - } -}; - -// Arrangeable interface implementation for ModelInstances -template -class ArrangeableModelInstance : public Arrangeable, VBedPlaceable -{ - InstPtr *m_mi; - VBedHPtr *m_vbedh; - const SelectionMask *m_selmask; - InstPos m_pos_within_model; - -public: - explicit ArrangeableModelInstance(InstPtr *mi, - VBedHPtr *vbedh, - const SelectionMask *selmask, - const InstPos &pos) - : m_mi{mi}, m_vbedh{vbedh}, m_selmask{selmask}, m_pos_within_model{pos} - { - assert(m_mi != nullptr && m_vbedh != nullptr); - } - - // Arrangeable: - ObjectID id() const override { return m_mi->id(); } - ObjectID geometry_id() const override { return m_mi->get_object()->id(); } - ExPolygons full_outline() const override; - Polygon convex_outline() const override; - bool is_printable() const override { return m_mi->printable; } - bool is_selected() const override; - void transform(const Vec2d &tr, double rot) override; - - int get_bed_index() const override { return m_vbedh->get_bed_index(*this); } - bool assign_bed(int bed_idx) override; - - // VBedPlaceable: - BoundingBoxf bounding_box() const override { return to_2d(instance_bounding_box(*m_mi)); } - void displace(const Vec2d &transl, double rot) override - { - if constexpr (!std::is_const_v) - transform_instance(*m_mi, transl, rot); - } -}; - -extern template class ArrangeableModelInstance; -extern template class ArrangeableModelInstance; - -// Arrangeable implementation for an SLAPrintObject to be able to arrange with the supports and pad -class ArrangeableSLAPrintObject : public Arrangeable -{ - const SLAPrintObject *m_po; - Arrangeable *m_arrbl; - Transform3d m_inst_trafo; - -public: - ArrangeableSLAPrintObject(const SLAPrintObject *po, - Arrangeable *arrbl, - const Transform3d &inst_tr = Transform3d::Identity()) - : m_po{po}, m_arrbl{arrbl}, m_inst_trafo{inst_tr} - {} - - ObjectID id() const override { return m_arrbl->id(); } - ObjectID geometry_id() const override { return m_arrbl->geometry_id(); } - - ExPolygons full_outline() const override; - ExPolygons full_envelope() const override; - - Polygon convex_outline() const override; - Polygon convex_envelope() const override; - - void transform(const Vec2d &transl, double rot) override - { - m_arrbl->transform(transl, rot); - } - int get_bed_index() const override { return m_arrbl->get_bed_index(); } - bool assign_bed(int bedidx) override - { - return m_arrbl->assign_bed(bedidx); - } - - bool is_printable() const override { return m_arrbl->is_printable(); } - bool is_selected() const override { return m_arrbl->is_selected(); } - int priority() const override { return m_arrbl->priority(); } -}; - -// Extension of ArrangeableSlicerModel for SLA -class ArrangeableSLAPrint : public ArrangeableSlicerModel { - const SLAPrint *m_slaprint; - - friend class SceneBuilder; - - template - static void for_each_arrangeable_(Self &&self, Fn &&fn); - - template - static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn); - -public: - explicit ArrangeableSLAPrint(const SLAPrint *slaprint, SceneBuilder &builder) - : m_slaprint{slaprint} - , ArrangeableSlicerModel{builder} - { - assert(slaprint != nullptr); - } - - void for_each_arrangeable(std::function) override; - - void for_each_arrangeable( - std::function) const override; - - void visit_arrangeable( - const ObjectID &id, - std::function) const override; - - void visit_arrangeable(const ObjectID &id, - std::function) override; -}; - -template -auto find_instance_by_id(Mdl &&model, const ObjectID &id) -{ - std::remove_reference_t< - decltype(std::declval().objects[0]->instances[0])> - ret = nullptr; - - InstPos pos; - - for (auto * obj : model.objects) { - for (auto *inst : obj->instances) { - if (inst->id() == id) { - ret = inst; - break; - } - ++pos.inst_idx; - } - - if (ret) - break; - - ++pos.obj_idx; - pos.inst_idx = 0; - } - - return std::make_pair(ret, pos); -} - -struct ModelDuplicate -{ - ObjectID id; - Vec2d tr = Vec2d::Zero(); - double rot = 0.; - int bed_idx = Unarranged; -}; - -// Implementing the Arrangeable interface with the whole Model being one outline -// with all its objects and instances. -template -class ArrangeableFullModel: public Arrangeable, VBedPlaceable -{ - Mdl *m_mdl; - Dup *m_dup; - VBH *m_vbh; - -public: - explicit ArrangeableFullModel(Mdl *mdl, - Dup *md, - VBH *vbh) - : m_mdl{mdl}, m_dup{md}, m_vbh{vbh} - { - assert(m_mdl != nullptr); - } - - ObjectID id() const override { return m_dup->id.id + 1; } - ObjectID geometry_id() const override; - - ExPolygons full_outline() const override; - - Polygon convex_outline() const override; - - bool is_printable() const override { return true; } - bool is_selected() const override { return m_dup->id == 0; } - - int get_bed_index() const override - { - return m_vbh->get_bed_index(*this); - } - - void transform(const Vec2d &tr, double rot) override - { - if constexpr (!std::is_const_v && !std::is_const_v) { - m_dup->tr += tr; - m_dup->rot += rot; - } - } - - bool assign_bed(int bed_idx) override - { - bool ret = false; - - if constexpr (!std::is_const_v && !std::is_const_v) { - if ((ret = m_vbh->assign_bed(*this, bed_idx))) - m_dup->bed_idx = bed_idx; - } - - return ret; - } - - BoundingBoxf bounding_box() const override { return unscaled(get_extents(convex_outline())); } - void displace(const Vec2d &transl, double rot) override - { - transform(transl, rot); - } -}; - -extern template class ArrangeableFullModel; -extern template class ArrangeableFullModel; - -// An implementation of the ArrangeableModel to be used for the full model 'duplicate' feature -// accessible from CLI -class DuplicableModel: public ArrangeableModel { - AnyPtr m_model; - AnyPtr m_vbh; - std::vector m_duplicates; - BoundingBox m_bedbb; - - template - static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn) - { - if (id.valid()) { - size_t idx = id.id - 1; - if (idx < self.m_duplicates.size()) { - auto &md = self.m_duplicates[idx]; - ArrangeableFullModel arrbl{self.m_model.get(), &md, self.m_vbh.get()}; - fn(arrbl); - } - } - } - -public: - explicit DuplicableModel(AnyPtr mdl, - AnyPtr vbh, - const BoundingBox &bedbb); - ~DuplicableModel(); - - void for_each_arrangeable(std::function fn) override - { - for (ModelDuplicate &md : m_duplicates) { - ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()}; - fn(arrbl); - } - } - void for_each_arrangeable(std::function fn) const override - { - for (const ModelDuplicate &md : m_duplicates) { - ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()}; - fn(arrbl); - } - } - void visit_arrangeable(const ObjectID &id, std::function fn) const override - { - visit_arrangeable_(*this, id, fn); - } - void visit_arrangeable(const ObjectID &id, std::function fn) override - { - visit_arrangeable_(*this, id, fn); - } - - ObjectID add_arrangeable(const ObjectID &prototype_id) override; - - void apply_duplicates(); -}; - -} // namespace arr2 -} // namespace Slic3r - -#endif // SCENEBUILDER_HPP diff --git a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp deleted file mode 100644 index 0fca911..0000000 --- a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp +++ /dev/null @@ -1,110 +0,0 @@ - -#ifndef SEGMENTEDRECTANGLEBED_HPP -#define SEGMENTEDRECTANGLEBED_HPP - -#include "libslic3r/Arrange/Core/Beds.hpp" - -namespace Slic3r { namespace arr2 { - -enum class RectPivots { - Center, BottomLeft, BottomRight, TopLeft, TopRight -}; - -template struct IsSegmentedBed_ : public std::false_type {}; -template constexpr bool IsSegmentedBed = IsSegmentedBed_>::value; - -template -struct SegmentedRectangleBed { - Vec<2, size_t> segments = Vec<2, size_t>::Ones(); - BoundingBox bb; - RectPivots pivot = RectPivots::Center; - - SegmentedRectangleBed() = default; - SegmentedRectangleBed(const BoundingBox &bb, - size_t segments_x, - size_t segments_y, - const RectPivots pivot = RectPivots::Center) - : segments{segments_x, segments_y}, bb{bb}, pivot{pivot} - {} - - size_t segments_x() const noexcept { return segments.x(); } - size_t segments_y() const noexcept { return segments.y(); } - - auto alignment() const noexcept { return pivot; } -}; - -template -struct SegmentedRectangleBed, - std::integral_constant> -{ - BoundingBox bb; - RectPivots pivot = RectPivots::Center; - - SegmentedRectangleBed() = default; - - explicit SegmentedRectangleBed(const BoundingBox &b, - const RectPivots pivot = RectPivots::Center) - : bb{b} - {} - - size_t segments_x() const noexcept { return SegX; } - size_t segments_y() const noexcept { return SegY; } - - auto alignment() const noexcept { return pivot; } -}; - -template -struct SegmentedRectangleBed, - std::integral_constant, - std::integral_constant> -{ - BoundingBox bb; - - SegmentedRectangleBed() = default; - - explicit SegmentedRectangleBed(const BoundingBox &b) : bb{b} {} - - size_t segments_x() const noexcept { return SegX; } - size_t segments_y() const noexcept { return SegY; } - - auto alignment() const noexcept { return pivot; } -}; - -template -struct IsSegmentedBed_> - : public std::true_type {}; - -template -auto offset(const SegmentedRectangleBed &bed, coord_t val_scaled) -{ - auto cpy = bed; - cpy.bb.offset(val_scaled); - - return cpy; -} - -template -auto bounding_box(const SegmentedRectangleBed &bed) -{ - return bed.bb; -} - -template -auto area(const SegmentedRectangleBed &bed) -{ - return arr2::area(bed.bb); -} - -template -ExPolygons to_expolygons(const SegmentedRectangleBed &bed) -{ - return to_expolygons(RectangleBed{bed.bb}); -} - -template -struct IsRectangular_, void>> : public std::true_type -{}; - -}} // namespace Slic3r::arr2 - -#endif // SEGMENTEDRECTANGLEBED_HPP diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTask.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTask.hpp deleted file mode 100644 index 588b3a7..0000000 --- a/src/libslic3r/Arrange/Tasks/ArrangeTask.hpp +++ /dev/null @@ -1,82 +0,0 @@ - -#ifndef ARRANGETASK_HPP -#define ARRANGETASK_HPP - -#include "libslic3r/Arrange/Arrange.hpp" -#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp" - -namespace Slic3r { namespace arr2 { - -struct ArrangeTaskResult : public ArrangeResult -{ - std::vector items; - - bool apply_on(ArrangeableModel &mdl) override - { - bool ret = true; - for (auto &itm : items) { - if (is_arranged(itm)) - ret = ret && apply_arrangeitem(itm, mdl); - } - - return ret; - } - - template - void add_item(const ArrItem &itm) - { - items.emplace_back(itm); - if (auto id = retrieve_id(itm)) - imbue_id(items.back(), *id); - } - - template - void add_items(const Range &items_range) - { - for (auto &itm : items_range) - add_item(itm); - } -}; - -template struct ArrangeTask : public ArrangeTaskBase -{ - struct ArrangeSet - { - std::vector selected, unselected; - } printable, unprintable; - - ExtendedBed bed; - ArrangeSettings settings; - - static std::unique_ptr create( - const Scene &sc, - const ArrangeableToItemConverter &converter); - - static std::unique_ptr create(const Scene &sc) - { - auto conv = ArrangeableToItemConverter::create(sc); - return create(sc, *conv); - } - - std::unique_ptr process(Ctl &ctl) override - { - return process_native(ctl); - } - - std::unique_ptr process_native(Ctl &ctl); - std::unique_ptr process_native(Ctl &&ctl) - { - return process_native(ctl); - } - - int item_count_to_process() const override - { - return static_cast(printable.selected.size() + - unprintable.selected.size()); - } -}; - -} // namespace arr2 -} // namespace Slic3r - -#endif // ARRANGETASK_HPP diff --git a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp deleted file mode 100644 index 1657634..0000000 --- a/src/libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp +++ /dev/null @@ -1,151 +0,0 @@ - -#ifndef ARRANGETASK_IMPL_HPP -#define ARRANGETASK_IMPL_HPP - -#include - -#include - -#include "ArrangeTask.hpp" - -namespace Slic3r { namespace arr2 { - -// Prepare the selected and unselected items separately. If nothing is -// selected, behaves as if everything would be selected. -template -void extract_selected(ArrangeTask &task, - const ArrangeableModel &mdl, - const ArrangeableToItemConverter &itm_conv) -{ - // Go through the objects and check if inside the selection - mdl.for_each_arrangeable( - [&task, &itm_conv](const Arrangeable &arrbl) { - bool selected = arrbl.is_selected(); - bool printable = arrbl.is_printable(); - - try { - auto itm = itm_conv.convert(arrbl, selected ? 0 : -SCALED_EPSILON); - - auto &container_parent = printable ? task.printable : - task.unprintable; - - auto &container = selected ? - container_parent.selected : - container_parent.unselected; - - container.emplace_back(std::move(itm)); - } catch (const EmptyItemOutlineError &ex) { - BOOST_LOG_TRIVIAL(error) - << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); - } - }); - - // If the selection was empty arrange everything - if (task.printable.selected.empty() && task.unprintable.selected.empty()) { - task.printable.selected.swap(task.printable.unselected); - task.unprintable.selected.swap(task.unprintable.unselected); - } -} - -template -std::unique_ptr> ArrangeTask::create( - const Scene &sc, const ArrangeableToItemConverter &converter) -{ - auto task = std::make_unique>(); - - task->settings.set_from(sc.settings()); - - task->bed = get_corrected_bed(sc.bed(), converter); - - extract_selected(*task, sc.model(), converter); - - return task; -} - -// Remove all items on the physical bed (not occupyable for unprintable items) -// and shift all items to the next lower bed index, so that arrange will think -// that logical bed no. 1 is the physical one -template -void prepare_fixed_unselected(ItemCont &items, int shift) -{ - for (auto &itm : items) - set_bed_index(itm, get_bed_index(itm) - shift); - - items.erase(std::remove_if(items.begin(), items.end(), - [](auto &itm) { return !is_arranged(itm); }), - items.end()); -} - -inline int find_first_empty_bed(const std::vector& bed_indices, - int starting_from = 0) { - int ret = starting_from; - - for (int idx : bed_indices) { - if (idx == ret) { - ret++; - } else if (idx > ret) { - break; - } - } - - return ret; -} - -template -std::unique_ptr -ArrangeTask::process_native(Ctl &ctl) -{ - auto result = std::make_unique(); - - auto arranger = Arranger::create(settings); - - class TwoStepArrangeCtl: public Ctl - { - Ctl &parent; - ArrangeTask &self; - public: - TwoStepArrangeCtl(Ctl &p, ArrangeTask &slf) : parent{p}, self{slf} {} - - void update_status(int remaining) override - { - parent.update_status(remaining + self.unprintable.selected.size()); - } - - bool was_canceled() const override { return parent.was_canceled(); } - - } subctl{ctl, *this}; - - arranger->arrange(printable.selected, printable.unselected, bed, subctl); - - std::vector printable_bed_indices = - get_bed_indices(crange(printable.selected), crange(printable.unselected)); - - // If there are no printables, leave the physical bed empty - static constexpr int SearchFrom = 1; - - // Unprintable items should go to the first logical (!) bed not containing - // any printable items - int first_empty_bed = find_first_empty_bed(printable_bed_indices, SearchFrom); - - prepare_fixed_unselected(unprintable.unselected, first_empty_bed); - - arranger->arrange(unprintable.selected, unprintable.unselected, bed, ctl); - - result->add_items(crange(printable.selected)); - - for (auto &itm : unprintable.selected) { - if (is_arranged(itm)) { - int bedidx = get_bed_index(itm) + first_empty_bed; - arr2::set_bed_index(itm, bedidx); - } - - result->add_item(itm); - } - - return result; -} - -} // namespace arr2 -} // namespace Slic3r - -#endif //ARRANGETASK_IMPL_HPP diff --git a/src/libslic3r/Arrange/Tasks/FillBedTask.hpp b/src/libslic3r/Arrange/Tasks/FillBedTask.hpp deleted file mode 100644 index 7a857b6..0000000 --- a/src/libslic3r/Arrange/Tasks/FillBedTask.hpp +++ /dev/null @@ -1,58 +0,0 @@ - -#ifndef FILLBEDTASK_HPP -#define FILLBEDTASK_HPP - -#include "MultiplySelectionTask.hpp" - -#include "libslic3r/Arrange/Arrange.hpp" - -namespace Slic3r { namespace arr2 { - -struct FillBedTaskResult: public MultiplySelectionTaskResult {}; - -template -struct FillBedTask: public ArrangeTaskBase -{ - std::optional prototype_item; - - std::vector selected, unselected; - - // For workaround regarding "holes" when filling the bed with the same - // item's copies - std::vector selected_fillers; - - ArrangeSettings settings; - ExtendedBed bed; - size_t selected_existing_count = 0; - - std::unique_ptr process_native(Ctl &ctl); - std::unique_ptr process_native(Ctl &&ctl) - { - return process_native(ctl); - } - - std::unique_ptr process(Ctl &ctl) override - { - return process_native(ctl); - } - - int item_count_to_process() const override - { - return selected.size(); - } - - static std::unique_ptr create( - const Scene &sc, - const ArrangeableToItemConverter &converter); - - static std::unique_ptr create(const Scene &sc) - { - auto conv = ArrangeableToItemConverter::create(sc); - return create(sc, *conv); - } -}; - -} // namespace arr2 -} // namespace Slic3r - -#endif // FILLBEDTASK_HPP diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp deleted file mode 100644 index 5cdcfef..0000000 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ /dev/null @@ -1,208 +0,0 @@ - -#ifndef FILLBEDTASKIMPL_HPP -#define FILLBEDTASKIMPL_HPP - -#include "FillBedTask.hpp" - -#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" - -#include - -namespace Slic3r { namespace arr2 { - -template -int calculate_items_needed_to_fill_bed(const ExtendedBed &bed, - const ArrItem &prototype_item, - size_t prototype_count, - const std::vector &fixed) -{ - double poly_area = fixed_area(prototype_item); - - auto area_sum_fn = [](double s, const auto &itm) { - return s + (get_bed_index(itm) == 0) * fixed_area(itm); - }; - - double unsel_area = std::accumulate(fixed.begin(), - fixed.end(), - 0., - area_sum_fn); - - double fixed_area = unsel_area + prototype_count * poly_area; - double bed_area = 0.; - - visit_bed([&bed_area] (auto &realbed) { bed_area = area(realbed); }, bed); - - // This is the maximum number of items, - // the real number will always be close but less. - auto needed_items = static_cast( - std::ceil((bed_area - fixed_area) / poly_area)); - - return needed_items; -} - -template -void extract(FillBedTask &task, - const Scene &scene, - const ArrangeableToItemConverter &itm_conv) -{ - task.prototype_item = {}; - - auto selected_ids = scene.selected_ids(); - - if (selected_ids.empty()) - return; - - std::set selected_objects = selected_geometry_ids(scene); - - if (selected_objects.size() != 1) - return; - - ObjectID prototype_geometry_id = *(selected_objects.begin()); - - auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) { - if (arrbl.is_printable()) - task.prototype_item = itm_conv.convert(arrbl); - }; - - scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item); - - if (!task.prototype_item) - return; - - // Workaround for missing items when arranging the same geometry only: - // Injecting a number of items but with slightly shrinked shape, so that - // they can fill the emerging holes. - ArrItem prototype_item_shrinked; - scene.model().visit_arrangeable(selected_ids.front(), - [&prototype_item_shrinked, &itm_conv](const Arrangeable &arrbl) { - if (arrbl.is_printable()) - prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON); - }); - - set_bed_index(*task.prototype_item, Unarranged); - - auto collect_task_items = [&prototype_geometry_id, &task, - &itm_conv](const Arrangeable &arrbl) { - try { - if (arrbl.geometry_id() == prototype_geometry_id) { - if (arrbl.is_printable()) { - auto itm = itm_conv.convert(arrbl); - raise_priority(itm); - task.selected.emplace_back(std::move(itm)); - } - } else { - auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); - task.unselected.emplace_back(std::move(itm)); - } - } catch (const EmptyItemOutlineError &ex) { - BOOST_LOG_TRIVIAL(error) - << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); - } - }; - - scene.model().for_each_arrangeable(collect_task_items); - - int needed_items = calculate_items_needed_to_fill_bed(task.bed, - *task.prototype_item, - task.selected.size(), - task.unselected); - - task.selected_existing_count = task.selected.size(); - task.selected.reserve(task.selected.size() + needed_items); - std::fill_n(std::back_inserter(task.selected), needed_items, - *task.prototype_item); - - // Add as many filler items as there are needed items. Most of them will - // be discarded anyways. - std::fill_n(std::back_inserter(task.selected_fillers), needed_items, - prototype_item_shrinked); -} - - -template -std::unique_ptr> FillBedTask::create( - const Scene &sc, const ArrangeableToItemConverter &converter) -{ - auto task = std::make_unique>(); - - task->settings.set_from(sc.settings()); - - task->bed = get_corrected_bed(sc.bed(), converter); - - extract(*task, sc, converter); - - return task; -} - -template -std::unique_ptr FillBedTask::process_native( - Ctl &ctl) -{ - auto result = std::make_unique(); - - if (!prototype_item) - return result; - - result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{}); - - class FillBedCtl: public ArrangerCtl - { - ArrangeTaskCtl &parent; - FillBedTask &self; - bool do_stop = false; - - public: - FillBedCtl(ArrangeTaskCtl &p, FillBedTask &slf) : parent{p}, self{slf} {} - - void update_status(int remaining) override - { - parent.update_status(remaining); - } - - bool was_canceled() const override - { - return parent.was_canceled() || do_stop; - } - - void on_packed(ArrItem &itm) override - { - // Stop at the first filler that is not on the physical bed - do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) == 0; - } - - } subctl(ctl, *this); - - auto arranger = Arranger::create(settings); - - arranger->arrange(selected, unselected, bed, subctl); - - auto unsel_cpy = unselected; - for (const auto &itm : selected) { - unsel_cpy.emplace_back(itm); - } - - arranger->arrange(selected_fillers, unsel_cpy, bed, FillBedCtl{ctl, *this}); - - auto arranged_range = Range{selected.begin(), - selected.begin() + selected_existing_count}; - - result->add_arranged_items(arranged_range); - - auto to_add_range = Range{selected.begin() + selected_existing_count, - selected.end()}; - - for (auto &itm : to_add_range) - if (get_bed_index(itm) == PhysicalBedId) - result->add_new_item(itm); - - for (auto &itm : selected_fillers) - if (get_bed_index(itm) == PhysicalBedId) - result->add_new_item(itm); - - return result; -} - -} // namespace arr2 -} // namespace Slic3r - -#endif // FILLBEDTASKIMPL_HPP diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp deleted file mode 100644 index 665a18d..0000000 --- a/src/libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp +++ /dev/null @@ -1,109 +0,0 @@ - -#ifndef MULTIPLYSELECTIONTASK_HPP -#define MULTIPLYSELECTIONTASK_HPP - -#include "libslic3r/Arrange/Arrange.hpp" -#include "libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp" - -namespace Slic3r { namespace arr2 { - -struct MultiplySelectionTaskResult: public ArrangeResult { - ObjectID prototype_id; - - std::vector arranged_items; - std::vector to_add; - - bool apply_on(ArrangeableModel &mdl) override - { - bool ret = prototype_id.valid(); - - if (!ret) - return ret; - - for (auto &itm : to_add) { - auto id = mdl.add_arrangeable(prototype_id); - imbue_id(itm, id); - ret = ret && apply_arrangeitem(itm, mdl); - } - - for (auto &itm : arranged_items) { - if (is_arranged(itm)) - ret = ret && apply_arrangeitem(itm, mdl); - } - - return ret; - } - - template - void add_arranged_item(const ArrItem &itm) - { - arranged_items.emplace_back(itm); - if (auto id = retrieve_id(itm)) - imbue_id(arranged_items.back(), *id); - } - - template - void add_arranged_items(const Range &items_range) - { - arranged_items.reserve(items_range.size()); - for (auto &itm : items_range) - add_arranged_item(itm); - } - - template void add_new_item(const ArrItem &itm) - { - to_add.emplace_back(itm); - } - - template void add_new_items(const Range &items_range) - { - to_add.reserve(items_range.size()); - for (auto &itm : items_range) { - to_add.emplace_back(itm); - } - } -}; - -template -struct MultiplySelectionTask: public ArrangeTaskBase -{ - std::optional prototype_item; - - std::vector selected, unselected; - - ArrangeSettings settings; - ExtendedBed bed; - size_t selected_existing_count = 0; - - std::unique_ptr process_native(Ctl &ctl); - std::unique_ptr process_native(Ctl &&ctl) - { - return process_native(ctl); - } - - std::unique_ptr process(Ctl &ctl) override - { - return process_native(ctl); - } - - int item_count_to_process() const override - { - return selected.size(); - } - - static std::unique_ptr create( - const Scene &sc, - size_t multiply_count, - const ArrangeableToItemConverter &converter); - - static std::unique_ptr create(const Scene &sc, - size_t multiply_count) - { - auto conv = ArrangeableToItemConverter::create(sc); - return create(sc, multiply_count, *conv); - } -}; - -}} // namespace Slic3r::arr2 - -#endif // MULTIPLYSELECTIONTASK_HPP diff --git a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp deleted file mode 100644 index 3df1719..0000000 --- a/src/libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp +++ /dev/null @@ -1,128 +0,0 @@ - -#ifndef MULTIPLYSELECTIONTASKIMPL_HPP -#define MULTIPLYSELECTIONTASKIMPL_HPP - -#include "MultiplySelectionTask.hpp" - -#include - -namespace Slic3r { namespace arr2 { - -template -std::unique_ptr> MultiplySelectionTask::create( - const Scene &scene, size_t count, const ArrangeableToItemConverter &itm_conv) -{ - auto task_ptr = std::make_unique>(); - - auto &task = *task_ptr; - - task.settings.set_from(scene.settings()); - - task.bed = get_corrected_bed(scene.bed(), itm_conv); - - task.prototype_item = {}; - - auto selected_ids = scene.selected_ids(); - - if (selected_ids.empty()) - return task_ptr; - - std::set selected_objects = selected_geometry_ids(scene); - - if (selected_objects.size() != 1) - return task_ptr; - - ObjectID prototype_geometry_id = *(selected_objects.begin()); - - auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) { - if (arrbl.is_printable()) - task.prototype_item = itm_conv.convert(arrbl); - }; - - scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item); - - if (!task.prototype_item) - return task_ptr; - - set_bed_index(*task.prototype_item, Unarranged); - - auto collect_task_items = [&prototype_geometry_id, &task, - &itm_conv](const Arrangeable &arrbl) { - try { - if (arrbl.geometry_id() == prototype_geometry_id) { - if (arrbl.is_printable()) { - auto itm = itm_conv.convert(arrbl); - raise_priority(itm); - task.selected.emplace_back(std::move(itm)); - } - } else { - auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); - task.unselected.emplace_back(std::move(itm)); - } - } catch (const EmptyItemOutlineError &ex) { - BOOST_LOG_TRIVIAL(error) - << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); - } - }; - - scene.model().for_each_arrangeable(collect_task_items); - - task.selected_existing_count = task.selected.size(); - task.selected.reserve(task.selected.size() + count); - std::fill_n(std::back_inserter(task.selected), count, *task.prototype_item); - - return task_ptr; -} - -template -std::unique_ptr -MultiplySelectionTask::process_native(Ctl &ctl) -{ - auto result = std::make_unique(); - - if (!prototype_item) - return result; - - result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{}); - - class MultiplySelectionCtl: public ArrangerCtl - { - ArrangeTaskCtl &parent; - MultiplySelectionTask &self; - - public: - MultiplySelectionCtl(ArrangeTaskCtl &p, MultiplySelectionTask &slf) - : parent{p}, self{slf} {} - - void update_status(int remaining) override - { - parent.update_status(remaining); - } - - bool was_canceled() const override - { - return parent.was_canceled(); - } - - } subctl(ctl, *this); - - auto arranger = Arranger::create(settings); - - arranger->arrange(selected, unselected, bed, subctl); - - auto arranged_range = Range{selected.begin(), - selected.begin() + selected_existing_count}; - - result->add_arranged_items(arranged_range); - - auto to_add_range = Range{selected.begin() + selected_existing_count, - selected.end()}; - - result->add_new_items(to_add_range); - - return result; -} - -}} // namespace Slic3r::arr2 - -#endif // MULTIPLYSELECTIONTASKIMPL_HPP diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index 230fd72..2377882 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -17,6 +17,8 @@ #include "libslic3r/Geometry/Circle.hpp" #include "libslic3r/Polygon.hpp" +#include "MultipleBeds.hpp" + namespace Slic3r { //B52 @@ -286,8 +288,25 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con 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, bool ignore_bottom) const +BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo_orig, bool may_be_below_bed, bool ignore_bottom, int* bed_idx) const { + ObjectState out = ObjectState::Outside; + if (bed_idx) + *bed_idx = -1; + + // When loading an old project with more than the maximum number of beds, + // we still want to move the objects to the respective positions. + // Max beds number is momentarily increased when doing the rearrange, so use it. + const int max_bed = s_multiple_beds.get_loading_project_flag() + ? s_multiple_beds.get_number_of_beds() - 1 + : std::min(s_multiple_beds.get_number_of_beds(), s_multiple_beds.get_max_beds() - 1); + + for (int bed_id = 0; bed_id <= max_bed; ++bed_id) { + + + Transform3f trafo = trafo_orig; + trafo.pretranslate(-s_multiple_beds.get_bed_translation(bed_id).cast()); + switch (m_type) { case Type::Rectangle: { @@ -300,28 +319,44 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& i // 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); }); + out = object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f& pt) { return build_volumef.contains(pt); }); + break; } case Type::Circle: { Geometry::Circlef circle { unscaled(m_circle.center), unscaled(m_circle.radius + SceneEpsilon) }; - return m_max_print_height == 0.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)); }); + out = m_max_print_height == 0.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)); }); + break; } 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.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()); }); + out = m_max_print_height == 0.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()); }); + break; case Type::Invalid: default: - return ObjectState::Inside; + out = ObjectState::Inside; + break; } + + if (out != ObjectState::Outside) { + if (bed_idx) + *bed_idx = bed_id; + break; + } + + + + } + + return out; } -BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const +BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3 volume_bbox_orig, bool ignore_bottom, int* bed_idx) const { assert(m_type == Type::Rectangle); BoundingBox3Base build_volume = this->bounding_volume().inflated(SceneEpsilon); @@ -329,9 +364,25 @@ BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& vol build_volume.max.z() = std::numeric_limits::max(); if (ignore_bottom) build_volume.min.z() = -std::numeric_limits::max(); - return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below : - build_volume.contains(volume_bbox) ? ObjectState::Inside : - build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside; + + ObjectState state = ObjectState::Outside; + int obj_bed_id = -1; + for (int bed_id = 0; bed_id <= std::min(s_multiple_beds.get_number_of_beds(), s_multiple_beds.get_max_beds() - 1); ++bed_id) { + BoundingBoxf3 volume_bbox = volume_bbox_orig; + volume_bbox.translate(-s_multiple_beds.get_bed_translation(bed_id)); + + state = build_volume.max.z() <= -SceneEpsilon ? ObjectState::Below : + build_volume.contains(volume_bbox) ? ObjectState::Inside : + build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside; + if (state != ObjectState::Outside) { + obj_bed_id = bed_id; + break; + } + } + + if (bed_idx) + *bed_idx = obj_bed_id; + return state; } // B66 diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index 221c757..1dc4539 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -88,10 +88,11 @@ public: // 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, bool ignore_bottom = true) const; + ObjectState object_state(const indexed_triangle_set &its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom = true, int* bed_idx = nullptr) 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, bool ignore_bottom = true) const; + ObjectState volume_state_bbox(BoundingBoxf3 volume_bbox, bool ignore_bottom, int* bed_idx) const; + // B66 BuildVolume::ObjectState check_outside(Polygon hull) const; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 21b280f..0221939 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -27,6 +27,8 @@ set(SLIC3R_SOURCES AABBTreeLines.hpp AABBMesh.hpp AABBMesh.cpp + Algorithm/LineSegmentation/LineSegmentation.cpp + Algorithm/LineSegmentation/LineSegmentation.hpp Algorithm/PathSorting.hpp Algorithm/RegionExpansion.hpp Algorithm/RegionExpansion.cpp @@ -80,6 +82,8 @@ set(SLIC3R_SOURCES ExtrusionSimulator.cpp ExtrusionSimulator.hpp FileParserError.hpp + Feature/FuzzySkin/FuzzySkin.cpp + Feature/FuzzySkin/FuzzySkin.hpp Fill/Fill.cpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp @@ -192,6 +196,8 @@ set(SLIC3R_SOURCES GCode/SeamRandom.hpp GCode/SeamPainting.cpp GCode/SeamPainting.hpp + GCode/SeamScarf.cpp + GCode/SeamScarf.hpp GCode/ModelVisibility.cpp GCode/ModelVisibility.hpp GCode/SmoothPath.cpp @@ -253,8 +259,6 @@ set(SLIC3R_SOURCES CutUtils.hpp Model.cpp Model.hpp - ModelArrange.hpp - ModelArrange.cpp MultiMaterialSegmentation.cpp MultiMaterialSegmentation.hpp MeshNormals.hpp @@ -264,55 +268,6 @@ set(SLIC3R_SOURCES MeasureUtils.hpp CustomGCode.cpp CustomGCode.hpp - Arrange/Arrange.hpp - Arrange/ArrangeImpl.hpp - Arrange/Items/ArrangeItem.hpp - Arrange/Items/ArrangeItem.cpp - Arrange/Items/SimpleArrangeItem.hpp - Arrange/Items/SimpleArrangeItem.cpp - Arrange/Items/TrafoOnlyArrangeItem.hpp - Arrange/Items/MutableItemTraits.hpp - Arrange/Items/ArbitraryDataStore.hpp - Arrange/ArrangeSettingsView.hpp - Arrange/ArrangeSettingsDb_AppCfg.hpp - Arrange/ArrangeSettingsDb_AppCfg.cpp - Arrange/Scene.hpp - Arrange/Scene.cpp - Arrange/SceneBuilder.hpp - Arrange/SceneBuilder.cpp - Arrange/Tasks/ArrangeTask.hpp - Arrange/Tasks/ArrangeTaskImpl.hpp - Arrange/Tasks/FillBedTask.hpp - Arrange/Tasks/FillBedTaskImpl.hpp - Arrange/Tasks/MultiplySelectionTask.hpp - Arrange/Tasks/MultiplySelectionTaskImpl.hpp - Arrange/SegmentedRectangleBed.hpp - Arrange/Core/ArrangeItemTraits.hpp - Arrange/Core/DataStoreTraits.hpp - Arrange/Core/ArrangeBase.hpp - Arrange/Core/PackingContext.hpp - Arrange/Core/ArrangeFirstFit.hpp - Arrange/Core/Beds.hpp - Arrange/Core/Beds.cpp - Arrange/Core/NFP/NFP.hpp - Arrange/Core/NFP/NFP.cpp - Arrange/Core/NFP/NFPConcave_CGAL.hpp - Arrange/Core/NFP/NFPConcave_CGAL.cpp - Arrange/Core/NFP/NFPConcave_Tesselate.hpp - Arrange/Core/NFP/NFPConcave_Tesselate.cpp - Arrange/Core/NFP/EdgeCache.hpp - Arrange/Core/NFP/EdgeCache.cpp - Arrange/Core/NFP/CircularEdgeIterator.hpp - Arrange/Core/NFP/NFPArrangeItemTraits.hpp - Arrange/Core/NFP/PackStrategyNFP.hpp - Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp - Arrange/Core/NFP/Kernels/KernelTraits.hpp - Arrange/Core/NFP/Kernels/GravityKernel.hpp - Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp - Arrange/Core/NFP/Kernels/CompactifyKernel.hpp - Arrange/Core/NFP/Kernels/RectangleOverfitKernelWrapper.hpp - Arrange/Core/NFP/Kernels/SVGDebugOutputKernelWrapper.hpp - Arrange/Core/NFP/Kernels/KernelUtils.hpp MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp @@ -436,6 +391,8 @@ set(SLIC3R_SOURCES miniz_extension.hpp miniz_extension.cpp MarchingSquares.hpp + MultipleBeds.cpp + MultipleBeds.hpp Execution/Execution.hpp Execution/ExecutionSeq.hpp Execution/ExecutionTBB.hpp @@ -564,7 +521,7 @@ cmake_policy(SET CMP0011 NEW) find_package(CGAL REQUIRED) cmake_policy(POP) -add_library(libslic3r_cgal STATIC +add_library(libslic3r_cgal STATIC CutSurface.hpp CutSurface.cpp Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiUtilsCgal.cpp IntersectionPoints.hpp IntersectionPoints.cpp @@ -636,6 +593,7 @@ target_link_libraries(libslic3r PUBLIC libigl agg ankerl + boost_headeronly ) if (APPLE) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 2c66238..e8d7157 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -9,6 +9,9 @@ #include "libslic3r/Surface.hpp" #include "libslic3r/libslic3r.h" +#include +#include + // #define CLIPPER_UTILS_TIMING #ifdef CLIPPER_UTILS_TIMING @@ -734,6 +737,8 @@ Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyF { return to_polygons(clipper_do(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), fillType, ApplySafetyOffset::No)); } Slic3r::Polygons union_(const Slic3r::ExPolygons &subject) { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygon &subject2) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::SinglePathProvider(subject2.points), ApplySafetyOffset::No); } Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); } Slic3r::Polygons union_(Slic3r::Polygons &&subject, const Slic3r::Polygons &subject2) { @@ -1001,6 +1006,21 @@ Polygons union_pt_chained_outside_in(const Polygons &subject) return retval; } +Polygons union_parallel_reduce(const Polygons &subject) +{ + return tbb::parallel_reduce( + tbb::blocked_range(0, subject.size()), Polygons(), + [&subject](tbb::blocked_range range, Polygons partial_union) { + for (size_t subject_idx = range.begin(); subject_idx < range.end(); ++subject_idx) { + partial_union = union_(partial_union, subject[subject_idx]); + } + return partial_union; + }, + [](const Polygons &a, const Polygons &b) { + return union_(a, b); + }); +} + Polygons simplify_polygons(const Polygons &subject) { CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT); diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index ba1465e..dc8bb5e 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -515,6 +515,7 @@ inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r:: Slic3r::Polygons union_(const Slic3r::Polygons &subject); Slic3r::Polygons union_(const Slic3r::ExPolygons &subject); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygon &subject2); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2); Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2); // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). @@ -533,6 +534,11 @@ ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject); Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject); +// Perform union operation on Polygons using parallel reduction to merge Polygons one by one. +// When many detailed Polygons overlap, performing union over all Polygons at once can be quite slow. +// However, performing the union operation incrementally can be significantly faster in such cases. +Slic3r::Polygons union_parallel_reduce(const Slic3r::Polygons &subject); + ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes); // Implementing generalized loop (foreach) over a list of nodes which can be diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp index 001a3f2..658fa9b 100644 --- a/src/libslic3r/ClipperZUtils.hpp +++ b/src/libslic3r/ClipperZUtils.hpp @@ -67,6 +67,24 @@ inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx) return out; } +// Convert multiple expolygons into z-paths with a given Z coordinate. +// If Open, then duplicate the first point of each path at its end. +template +inline ZPaths expolygons_to_zpaths_with_same_z(const ExPolygons &src, const coord_t z) +{ + ZPaths out; + out.reserve(std::accumulate(src.begin(), src.end(), size_t(0), + [](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); })); + for (const ExPolygon &expoly : src) { + out.emplace_back(to_zpath(expoly.contour.points, z)); + for (const Polygon &hole : expoly.holes) { + out.emplace_back(to_zpath(hole.points, z)); + } + } + + return out; +} + // Convert a single path to path with a given Z coordinate. // If Open, then duplicate the first point at the end. template diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 49367c6..16c1419 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -48,6 +48,8 @@ namespace Slic3r { double value; bool percent; + double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } + private: friend class cereal::access; template void serialize(Archive& ar) { ar(this->value); ar(this->percent); } @@ -2244,17 +2246,18 @@ public: bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; } template ConfigOption* load_option_from_archive(Archive &archive) const { - if (this->nullable) { - switch (this->type) { - case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; } - case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; } - case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } - case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } - case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } - case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); - } - } else { + if (this->nullable) { + switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; } + case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercentsNullable(); archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } + case coFloatsOrPercents: { auto opt = new ConfigOptionFloatsOrPercentsNullable(); archive(*opt); return opt; } + default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + } + } else { switch (this->type) { case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } @@ -2279,16 +2282,17 @@ public: } template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { - if (this->nullable) { - switch (this->type) { - case coFloat: archive(*static_cast(opt)); break; - case coInt: archive(*static_cast(opt)); break; - case coFloats: archive(*static_cast(opt)); break; - case coInts: archive(*static_cast(opt)); break; - case coPercents: archive(*static_cast(opt));break; - case coBools: archive(*static_cast(opt)); break; - default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); - } + if (this->nullable) { + switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coFloatsOrPercents: archive(*static_cast(opt)); break; + default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + } } else { switch (this->type) { case coFloat: archive(*static_cast(opt)); break; diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 1734f3a..1570cd3 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -1086,7 +1087,7 @@ std::unique_ptr Emboss::create_font_file( std::unique_ptr Emboss::create_font_file(const char *file_path) { - FILE *file = std::fopen(file_path, "rb"); + FILE *file = boost::nowide::fopen(file_path, "rb"); if (file == nullptr) { assert(false); BOOST_LOG_TRIVIAL(error) << "Couldn't open " << file_path << " for reading."; diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp new file mode 100644 index 0000000..5400c9f --- /dev/null +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp @@ -0,0 +1,230 @@ +#include + +#include "libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include "FuzzySkin.hpp" + +using namespace Slic3r; + +namespace Slic3r::Feature::FuzzySkin { + +// Produces a random value between 0 and 1. Thread-safe. +static double random_value() +{ + thread_local std::random_device rd; + // Hash thread ID for random number seed if no hardware rng seed is available + thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); + thread_local std::uniform_real_distribution dist(0.0, 1.0); + return dist(gen); +} + +void fuzzy_polyline(Points &poly, const bool closed, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance) +{ + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point + + Points out; + out.reserve(poly.size()); + + // Skip the first point for open polyline. + Point *p0 = closed ? &poly.back() : &poly.front(); + for (auto it_pt1 = closed ? poly.begin() : std::next(poly.begin()); it_pt1 != poly.end(); ++it_pt1) { + Point &p1 = *it_pt1; + + // 'a' is the (next) new point between p0 and p1 + Vec2d p0p1 = (p1 - *p0).cast(); + double p0p1_size = p0p1.norm(); + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); + } + + dist_left_over = p0pa_dist - p0p1_size; + p0 = &p1; + } + + while (out.size() < 3) { + size_t point_idx = poly.size() - 2; + out.emplace_back(poly[point_idx]); + if (point_idx == 0) { + break; + } + + --point_idx; + } + + if (out.size() >= 3) { + poly = std::move(out); + } +} + +void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance) +{ + fuzzy_polyline(polygon.points, true, fuzzy_skin_thickness, fuzzy_skin_point_distance); +} + +void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance) +{ + const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value + const double range_random_point_dist = fuzzy_skin_point_distance / 2.; + double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point + + Arachne::ExtrusionJunction *p0 = &ext_lines.front(); + Arachne::ExtrusionJunctions out; + out.reserve(ext_lines.size()); + for (auto &p1 : ext_lines) { + if (p0->p == p1.p) { + // Copy the first point. + out.emplace_back(p1.p, p1.w, p1.perimeter_index); + continue; + } + + // 'a' is the (next) new point between p0 and p1 + Vec2d p0p1 = (p1.p - p0->p).cast(); + double p0p1_size = p0p1.norm(); + double p0pa_dist = dist_left_over; + for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { + double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness; + out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); + } + + dist_left_over = p0pa_dist - p0p1_size; + p0 = &p1; + } + + while (out.size() < 3) { + size_t point_idx = ext_lines.size() - 2; + out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); + if (point_idx == 0) { + break; + } + + --point_idx; + } + + if (ext_lines.back().p == ext_lines.front().p) { + // Connect endpoints. + out.front().p = out.back().p; + } + + if (out.size() >= 3) { + ext_lines.junctions = std::move(out); + } +} + +bool should_fuzzify(const PrintRegionConfig &config, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + const FuzzySkinType fuzzy_skin_type = config.fuzzy_skin.value; + + if (fuzzy_skin_type == FuzzySkinType::None || layer_idx <= 0) { + return false; + } + + const bool fuzzify_contours = perimeter_idx == 0; + const bool fuzzify_holes = fuzzify_contours && fuzzy_skin_type == FuzzySkinType::All; + + return is_contour ? fuzzify_contours : fuzzify_holes; +} + +Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + using namespace Slic3r::Algorithm::LineSegmentation; + + auto apply_fuzzy_skin_on_polygon = [&layer_idx, &perimeter_idx, &is_contour](const Polygon &polygon, const PrintRegionConfig &config) -> Polygon { + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + Polygon fuzzified_polygon = polygon; + fuzzy_polygon(fuzzified_polygon, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value)); + + return fuzzified_polygon; + } else { + return polygon; + } + }; + + if (perimeter_regions.empty()) { + return apply_fuzzy_skin_on_polygon(polygon, base_config); + } + + PolylineRegionSegments segments = polygon_segmentation(polygon, base_config, perimeter_regions); + if (segments.size() == 1) { + const PrintRegionConfig &config = segments.front().config; + return apply_fuzzy_skin_on_polygon(polygon, config); + } + + Polygon fuzzified_polygon; + for (PolylineRegionSegment &segment : segments) { + const PrintRegionConfig &config = segment.config; + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + fuzzy_polyline(segment.polyline.points, false, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value)); + } + + assert(!segment.polyline.empty()); + if (segment.polyline.empty()) { + continue; + } else if (!fuzzified_polygon.empty() && fuzzified_polygon.back() == segment.polyline.front()) { + // Remove the last point to avoid duplicate points. + fuzzified_polygon.points.pop_back(); + } + + Slic3r::append(fuzzified_polygon.points, std::move(segment.polyline.points)); + } + + assert(!fuzzified_polygon.empty()); + if (fuzzified_polygon.front() == fuzzified_polygon.back()) { + // Remove the last point to avoid duplicity between the first and the last point. + fuzzified_polygon.points.pop_back(); + } + + return fuzzified_polygon; +} + +Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour) +{ + using namespace Slic3r::Algorithm::LineSegmentation; + using namespace Slic3r::Arachne; + + if (perimeter_regions.empty()) { + if (should_fuzzify(base_config, layer_idx, perimeter_idx, is_contour)) { + ExtrusionLine fuzzified_extrusion = extrusion; + fuzzy_extrusion_line(fuzzified_extrusion, scaled(base_config.fuzzy_skin_thickness.value), scaled(base_config.fuzzy_skin_point_dist.value)); + + return fuzzified_extrusion; + } else { + return extrusion; + } + } + + ExtrusionRegionSegments segments = extrusion_segmentation(extrusion, base_config, perimeter_regions); + ExtrusionLine fuzzified_extrusion(extrusion.inset_idx, extrusion.is_odd, extrusion.is_closed); + + for (ExtrusionRegionSegment &segment : segments) { + const PrintRegionConfig &config = segment.config; + if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) { + fuzzy_extrusion_line(segment.extrusion, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value)); + } + + assert(!segment.extrusion.empty()); + if (segment.extrusion.empty()) { + continue; + } else if (!fuzzified_extrusion.empty() && fuzzified_extrusion.back().p == segment.extrusion.front().p) { + // Remove the last point to avoid duplicate points (We don't care if the width of both points is different.). + fuzzified_extrusion.junctions.pop_back(); + } + + Slic3r::append(fuzzified_extrusion.junctions, std::move(segment.extrusion.junctions)); + } + + assert(!fuzzified_extrusion.empty()); + + return fuzzified_extrusion; +} + +} // namespace Slic3r::Feature::FuzzySkin diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp new file mode 100644 index 0000000..fb5db54 --- /dev/null +++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp @@ -0,0 +1,26 @@ +#ifndef libslic3r_FuzzySkin_hpp_ +#define libslic3r_FuzzySkin_hpp_ + +namespace Slic3r::Arachne { +struct ExtrusionLine; +} // namespace Slic3r::Arachne + +namespace Slic3r::PerimeterGenerator { +struct Parameters; +} // namespace Slic3r::PerimeterGenerator + +namespace Slic3r::Feature::FuzzySkin { + +void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance); + +void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist); + +bool should_fuzzify(const PrintRegionConfig &config, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour); + +} // namespace Slic3r::Feature::FuzzySkin + +#endif // libslic3r_FuzzySkin_hpp_ diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index c1f9b71..64325d9 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -220,37 +220,64 @@ double Flow::mm3_per_mm() const return res; } +static float min_nozzle_diameter(const PrintObject &print_object) +{ + const ConfigOptionFloats &nozzle_diameters = print_object.print()->config().nozzle_diameter; + float min_nozzle_diameter = std::numeric_limits::max(); + + for (const double nozzle_diameter : nozzle_diameters.values) { + min_nozzle_diameter = std::min(min_nozzle_diameter, static_cast(nozzle_diameter)); + } + + return min_nozzle_diameter; +} + Flow support_material_flow(const PrintObject *object, float layer_height) { + const PrintConfig &print_config = object->print()->config(); + const int extruder = object->config().support_material_extruder - 1; + + // If object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), use the smallest nozzle diameter. + const float nozzle_diameter = extruder >= 0 ? static_cast(print_config.nozzle_diameter.get_at(extruder)) : min_nozzle_diameter(*object); + return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, - // if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), + nozzle_diameter, (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) { const PrintConfig &print_config = object->print()->config(); - const auto &width = (print_config.first_layer_extrusion_width.value > 0) ? print_config.first_layer_extrusion_width : object->config().support_material_extrusion_width; + const int extruder = object->config().support_material_extruder - 1; + + // If object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), use the smallest nozzle diameter. + const float nozzle_diameter = extruder >= 0 ? static_cast(print_config.nozzle_diameter.get_at(extruder)) : min_nozzle_diameter(*object); + const auto &width = (print_config.first_layer_extrusion_width.value > 0) ? print_config.first_layer_extrusion_width : object->config().support_material_extrusion_width; + return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().extrusion_width, - float(print_config.nozzle_diameter.get_at(object->config().support_material_extruder-1)), + nozzle_diameter, (layer_height > 0.f) ? layer_height : float(print_config.first_layer_height.get_abs_value(object->config().layer_height.value))); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) { + const PrintConfig &print_config = object->print()->config(); + const int extruder = object->config().support_material_interface_extruder - 1; + + // If object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), use the smallest nozzle diameter. + const float nozzle_diameter = extruder >= 0 ? static_cast(print_config.nozzle_diameter.get_at(extruder)) : min_nozzle_diameter(*object); + return Flow::new_from_config_width( frSupportMaterialInterface, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width, - // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)), + nozzle_diameter, (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 3ac200b..6f4990d 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -42,6 +42,8 @@ namespace pt = boost::property_tree; #include "libslic3r/NSVGUtils.hpp" +#include "libslic3r/MultipleBeds.hpp" + #include // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, @@ -85,6 +87,7 @@ const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/QIDI_Slicer_layer_config_ const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/QIDI_Slicer_custom_gcode_per_print_z.xml"; +const std::string WIPE_TOWER_INFORMATION_FILE = "Metadata/QIDI_Slicer_wipe_tower_information.xml"; const std::string CUT_INFORMATION_FILE = "Metadata/QIDI_Slicer_cut_information.xml"; static constexpr const char *RELATIONSHIP_TAG = "Relationship"; @@ -127,6 +130,7 @@ static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; +static constexpr const char* FUZZY_SKIN_ATTR = "slic3rpe:fuzzy_skin"; static constexpr const char* KEY_ATTR = "key"; static constexpr const char* VALUE_ATTR = "value"; @@ -368,6 +372,7 @@ namespace Slic3r { std::vector custom_supports; std::vector custom_seam; std::vector mm_segmentation; + std::vector fuzzy_skin; bool empty() { return vertices.empty() || triangles.empty(); } @@ -377,6 +382,7 @@ namespace Slic3r { custom_supports.clear(); custom_seam.clear(); mm_segmentation.clear(); + fuzzy_skin.clear(); } }; @@ -465,7 +471,7 @@ namespace Slic3r { float r_tolerance; float h_tolerance; }; - CutObjectBase id; + CutId id; std::vector connectors; }; @@ -549,6 +555,8 @@ namespace Slic3r { void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_wipe_tower_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); + void _extract_wipe_tower_information_from_archive_legacy(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); @@ -761,6 +769,9 @@ namespace Slic3r { } } + // Initialize the wipe tower position (see the end of this function): + model.get_wipe_tower_vector().front().position.x() = std::numeric_limits::max(); + // Read root model file if (start_part_stat.m_file_index < num_entries) { try { @@ -817,6 +828,10 @@ namespace Slic3r { // extract slic3r layer config ranges file _extract_custom_gcode_per_print_z_from_archive(archive, stat); } + else if (boost::algorithm::iequals(name, WIPE_TOWER_INFORMATION_FILE)) { + // extract wipe tower information file + _extract_wipe_tower_information_from_archive(archive, stat, model); + } else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_model_config_from_archive(archive, stat, model)) { @@ -831,6 +846,27 @@ namespace Slic3r { } } + + if (model.get_wipe_tower_vector().front().position.x() == std::numeric_limits::max()) { + // into config, not into Model. Try to load it from the config file. + // First set default in case we do not find it (these were the default values of the config options). + model.get_wipe_tower_vector().front().position.x() = 180; + model.get_wipe_tower_vector().front().position.y() = 140; + model.get_wipe_tower_vector().front().rotation = 0.; + + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + _extract_wipe_tower_information_from_archive_legacy(archive, stat, model); + break; + } + } + } + } + close_zip_reader(&archive); if (m_version == 0) { @@ -1145,15 +1181,15 @@ namespace Slic3r { continue; } - CutObjectBase cut_id; + CutId cut_id; std::vector connectors; for (const auto& obj_cut_info : object_tree) { if (obj_cut_info.first == "cut_id") { pt::ptree cut_id_tree = obj_cut_info.second; - cut_id = CutObjectBase(ObjectID( cut_id_tree.get(".id")), - cut_id_tree.get(".check_sum"), - cut_id_tree.get(".connectors_cnt")); + cut_id = CutId(cut_id_tree.get(".id"), + cut_id_tree.get(".check_sum"), + cut_id_tree.get(".connectors_cnt")); } if (obj_cut_info.first == "connectors") { pt::ptree cut_connectors_tree = obj_cut_info.second; @@ -1567,45 +1603,141 @@ namespace Slic3r { if (main_tree.front().first != "custom_gcodes_per_print_z") return; - pt::ptree code_tree = main_tree.front().second; - m_model->custom_gcode_per_print_z.gcodes.clear(); + for (CustomGCode::Info& info : m_model->get_custom_gcode_per_print_z_vector()) + info.gcodes.clear(); - for (const auto& code : code_tree) { - if (code.first == "mode") { - pt::ptree tree = code.second; - std::string mode = tree.get(".value"); - m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : - mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : - CustomGCode::Mode::MultiExtruder; + for (const auto& bed_block : main_tree) { + if (bed_block.first != "custom_gcodes_per_print_z") + continue; + int bed_idx = 0; + try { + bed_idx = bed_block.second.get(".bed_idx"); + } catch (const boost::property_tree::ptree_bad_path&) { + // Probably an old project with no bed_idx info. Imagine that we saw 0. } - if (code.first != "code") + if (bed_idx >= int(m_model->get_custom_gcode_per_print_z_vector().size())) continue; - pt::ptree tree = code.second; - double print_z = tree.get (".print_z" ); - int extruder = tree.get (".extruder"); - std::string color = tree.get (".color" ); + pt::ptree code_tree = bed_block.second; - CustomGCode::Type type; - std::string extra; - pt::ptree attr_tree = tree.find("")->second; - if (attr_tree.find("type") == attr_tree.not_found()) { - // It means that data was saved in old version (2.2.0 and older) of QIDISlicer - // read old data ... - std::string gcode = tree.get (".gcode"); - // ... and interpret them to the new data - type = gcode == "M600" ? CustomGCode::ColorChange : - gcode == "M601" ? CustomGCode::PausePrint : - gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; - extra = type == CustomGCode::PausePrint ? color : - type == CustomGCode::Custom ? gcode : ""; + for (const auto& code : code_tree) { + if (code.first == "mode") { + pt::ptree tree = code.second; + std::string mode = tree.get(".value"); + m_model->get_custom_gcode_per_print_z_vector()[bed_idx].mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : + mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : + CustomGCode::Mode::MultiExtruder; + } + if (code.first != "code") + continue; + + pt::ptree tree = code.second; + double print_z = tree.get (".print_z" ); + int extruder = tree.get (".extruder"); + std::string color = tree.get (".color" ); + + CustomGCode::Type type; + std::string extra; + pt::ptree attr_tree = tree.find("")->second; + if (attr_tree.find("type") == attr_tree.not_found()) { + // read old data ... + std::string gcode = tree.get (".gcode"); + // ... and interpret them to the new data + type = gcode == "M600" ? CustomGCode::ColorChange : + gcode == "M601" ? CustomGCode::PausePrint : + gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; + extra = type == CustomGCode::PausePrint ? color : + type == CustomGCode::Custom ? gcode : ""; + } + else { + type = static_cast(tree.get(".type")); + extra = tree.get(".extra"); + } + m_model->get_custom_gcode_per_print_z_vector()[bed_idx].gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}); } - else { - type = static_cast(tree.get(".type")); - extra = tree.get(".extra"); + } + } + } + + void _3MF_Importer::_extract_wipe_tower_information_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading wipe tower information data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree main_tree; + pt::read_xml(iss, main_tree); + + for (const auto& bed_block : main_tree) { + if (bed_block.first != "wipe_tower_information") + continue; + try { + int bed_idx = 0; + try { + bed_idx = bed_block.second.get(".bed_idx"); + } catch (const boost::property_tree::ptree_bad_path&) { + // Probably an old project with no bed_idx info - pretend that we saw 0. + } + if (bed_idx >= int(m_model->get_wipe_tower_vector().size())) + continue; + double pos_x = bed_block.second.get(".position_x"); + double pos_y = bed_block.second.get(".position_y"); + double rot_deg = bed_block.second.get(".rotation_deg"); + model.get_wipe_tower_vector()[bed_idx].position = Vec2d(pos_x, pos_y); + model.get_wipe_tower_vector()[bed_idx].rotation = rot_deg; + } + catch (const boost::property_tree::ptree_bad_path&) { + // Handles missing node or attribute. + add_error("Error while reading wipe tower information."); + return; + } + } + + } + } + + void _3MF_Importer::_extract_wipe_tower_information_from_archive_legacy(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, Model& model) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading config data to buffer"); + return; + } + + // Do not load the config as usual, it no longer knows those values. + std::istringstream iss(buffer); + std::string line; + + while (iss) { + std::getline(iss, line); + boost::algorithm::trim_left_if(line, [](char ch) { return std::isspace(ch) || ch == ';'; }); + if (boost::starts_with(line, "wipe_tower_x") || boost::starts_with(line, "wipe_tower_y") || boost::starts_with(line, "wipe_tower_rotation_angle")) { + std::string value_str; + try { + value_str = line.substr(line.find("=") + 1, std::string::npos); + } catch (const std::out_of_range&) { + continue; + } + double val = 0.; + std::istringstream value_ss(value_str); + value_ss >> val; + if (! value_ss.fail()) { + if (boost::starts_with(line, "wipe_tower_x")) + model.get_wipe_tower_vector().front().position.x() = val; + else if (boost::starts_with(line, "wipe_tower_y")) + model.get_wipe_tower_vector().front().position.y() = val; + else + model.get_wipe_tower_vector().front().rotation = val; + } } - m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ; } } } @@ -1967,6 +2099,7 @@ namespace Slic3r { m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); + m_curr_object.geometry.fuzzy_skin.push_back(get_attribute_value_string(attributes, num_attributes, FUZZY_SKIN_ATTR)); std::string mm_segmentation_serialized = get_attribute_value_string(attributes, num_attributes, MM_SEGMENTATION_ATTR); if (mm_segmentation_serialized.empty()) mm_segmentation_serialized = get_attribute_value_string(attributes, num_attributes, "paint_color"); @@ -2467,25 +2600,26 @@ namespace Slic3r { if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - // recreate custom supports, seam and mm segmentation from previously loaded attribute + // recreate custom supports, seam, mm segmentation and fuzzy skin from previously loaded attribute volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mm_segmentation_facets.reserve(triangles_count); + volume->fuzzy_skin_facets.reserve(triangles_count); for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); - if (! geometry.custom_seam[index].empty()) - volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); - if (! geometry.mm_segmentation[index].empty()) - volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]); + + volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); + volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); + volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]); + volume->fuzzy_skin_facets.set_triangle_from_string(i, geometry.fuzzy_skin[index]); } volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mm_segmentation_facets.shrink_to_fit(); + volume->fuzzy_skin_facets.shrink_to_fit(); if (auto &es = volume_data.shape_configuration; es.has_value()) volume->emboss_shape = std::move(es); @@ -2636,9 +2770,10 @@ namespace Slic3r { bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); + bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, const Model& model); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); + bool _add_wipe_tower_information_file_to_archive( mz_zip_archive& archive, Model& model); }; bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) @@ -2745,10 +2880,17 @@ namespace Slic3r { return false; } + if (!_add_wipe_tower_information_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). // This file contains the content of FullPrintConfing / SLAFullPrintConfig. if (config != nullptr) { - if (!_add_print_config_file_to_archive(archive, *config)) { + if (!_add_print_config_file_to_archive(archive, *config, model)) { close_zip_writer(&archive); boost::filesystem::remove(filename); return false; @@ -3158,6 +3300,15 @@ namespace Slic3r { output_buffer += "\""; } + std::string fuzzy_skin_data_string = volume->fuzzy_skin_facets.get_triangle_as_string(i); + if (!fuzzy_skin_data_string.empty()) { + output_buffer += " "; + output_buffer += FUZZY_SKIN_ATTR; + output_buffer += "=\""; + output_buffer += fuzzy_skin_data_string; + output_buffer += "\""; + } + output_buffer += "/>\n"; if (! flush()) @@ -3224,7 +3375,7 @@ namespace Slic3r { pt::ptree& cut_id_tree = obj_tree.add("cut_id", ""); // store cut_id atributes - cut_id_tree.put(".id", object->cut_id.id().id); + cut_id_tree.put(".id", object->cut_id.id()); cut_id_tree.put(".check_sum", object->cut_id.check_sum()); cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); @@ -3447,16 +3598,38 @@ namespace Slic3r { return true; } - bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, const Model& model) { assert(is_decimal_separator_point()); char buffer[1024]; sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); std::string out = buffer; - for (const std::string &key : config.keys()) - if (key != "compatible_printers") - out += "; " + key + " = " + config.opt_serialize(key) + "\n"; + t_config_option_keys keys = config.keys(); + + // Wipe tower values were historically stored in the config, but they were moved into + for (const std::string s : {"wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle"}) + if (! config.has(s)) + keys.emplace_back(s); + sort_remove_duplicates(keys); + + for (const std::string& key : keys) { + if (key == "compatible_printers") + continue; + + std::string opt_serialized; + + if (key == "wipe_tower_x") + opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().position.x()); + else if (key == "wipe_tower_y") + opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().position.y()); + else if (key == "wipe_tower_rotation_angle") + opt_serialized = float_to_string_decimal_point(model.get_wipe_tower_vector().front().rotation); + else + opt_serialized = config.opt_serialize(key); + + out += "; " + key + " = " + opt_serialized + "\n"; + } if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { @@ -3606,34 +3779,42 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv { std::string out = ""; - if (!model.custom_gcode_per_print_z.gcodes.empty()) { + if (std::any_of(model.get_custom_gcode_per_print_z_vector().begin(), model.get_custom_gcode_per_print_z_vector().end(), [](const auto& cg) { return !cg.gcodes.empty(); })) { pt::ptree tree; - pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); + for (size_t bed_idx=0; bed_idx.bed_idx" , bed_idx); - // store data of custom_gcode_per_print_z - code_tree.put(".print_z" , code.print_z ); - code_tree.put(".type" , static_cast(code.type)); - code_tree.put(".extruder" , code.extruder ); - code_tree.put(".color" , code.color ); - code_tree.put(".extra" , code.extra ); + for (const CustomGCode::Item& code : model.get_custom_gcode_per_print_z_vector()[bed_idx].gcodes) { + pt::ptree& code_tree = main_tree.add("code", ""); - // add gcode field data for the old version of the QIDISlicer - std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : - code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : - code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : - code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; - code_tree.put(".gcode" , gcode ); + // store data of custom_gcode_per_print_z + code_tree.put(".print_z" , code.print_z ); + code_tree.put(".type" , static_cast(code.type)); + code_tree.put(".extruder" , code.extruder ); + code_tree.put(".color" , code.color ); + code_tree.put(".extra" , code.extra ); + + std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : + code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : + code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : + code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; + code_tree.put(".gcode" , gcode ); + } + + pt::ptree& mode_tree = main_tree.add("mode", ""); + // store mode of a custom_gcode_per_print_z + mode_tree.put(".value", model.custom_gcode_per_print_z().mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : + model.custom_gcode_per_print_z().mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : + CustomGCode::MultiExtruderMode); } - pt::ptree& mode_tree = main_tree.add("mode", ""); - // store mode of a custom_gcode_per_print_z - mode_tree.put(".value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : - model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : - CustomGCode::MultiExtruderMode); - if (!tree.empty()) { std::ostringstream oss; boost::property_tree::write_xml(oss, tree); @@ -3654,9 +3835,47 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv return true; } -// Perform conversions based on the config values available. -static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config, const boost::optional& qidislicer_generator_version) +bool _3MF_Exporter::_add_wipe_tower_information_file_to_archive( mz_zip_archive& archive, Model& model) { + std::string out = ""; + + pt::ptree tree; + + size_t bed_idx = 0; + for (const ModelWipeTower& wipe_tower : model.get_wipe_tower_vector()) { + pt::ptree& main_tree = tree.add("wipe_tower_information", ""); + + main_tree.put(".bed_idx", bed_idx); + main_tree.put(".position_x", wipe_tower.position.x()); + main_tree.put(".position_y", wipe_tower.position.y()); + main_tree.put(".rotation_deg", wipe_tower.rotation); + ++bed_idx; + if (bed_idx >= s_multiple_beds.get_number_of_beds()) + break; + } + + std::ostringstream oss; + boost::property_tree::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string + boost::replace_all(out, "><", ">\n<"); + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, WIPE_TOWER_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add wipe tower information file to archive"); + return false; + } + } + + return true; +} + +// Perform conversions based on the config values available. +static void handle_legacy_project_loaded( + DynamicPrintConfig& config, + const boost::optional& qidislicer_generator_version +) { if (! config.has("brim_separation")) { if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { // Conversion from older QIDISlicer which applied brim separation equal to elephant foot compensation. @@ -3664,7 +3883,7 @@ static void handle_legacy_project_loaded(unsigned int version_project_file, Dyna opt_brim_separation->value = opt_elephant_foot->value; } } - + // In QIDISlicer 2.5.0-alpha2 and 2.5.0-alpha3, we introduce several parameters for Arachne that depend // on nozzle size . Later we decided to make default values for those parameters computed automatically // until the user changes them. @@ -3713,7 +3932,14 @@ bool is_project_3mf(const std::string& filename) return config_found; } -bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) +bool load_3mf( + const char* path, + DynamicPrintConfig& config, + ConfigSubstitutionContext& config_substitutions, + Model* model, + bool check_version, + boost::optional &qidislicer_generator_version +) { if (path == nullptr || model == nullptr) return false; @@ -3723,7 +3949,8 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); - handle_legacy_project_loaded(importer.version(), config, importer.qidislicer_generator_version()); + handle_legacy_project_loaded(config, importer.qidislicer_generator_version()); + qidislicer_generator_version = importer.qidislicer_generator_version(); return res; } diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 3f58035..f8548ad 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Format_3mf_hpp_ #define slic3r_Format_3mf_hpp_ +#include "libslic3r/Semver.hpp" +#include namespace Slic3r { /* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used. @@ -33,7 +35,14 @@ namespace Slic3r { extern bool is_project_3mf(const std::string& filename); // Load the content of a 3mf file into the given model and preset bundle. - extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version); + extern bool load_3mf( + const char* path, + DynamicPrintConfig& config, + ConfigSubstitutionContext& config_substitutions, + Model* model, + bool check_version, + boost::optional &qidislicer_generator_version + ); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 7f4b429..5464009 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -48,7 +48,7 @@ namespace pt = boost::property_tree; // 3 : Added volumes' matrices and source data, meshes transformed back to their coordinate system on loading. // WARNING !! -> the version number has been rolled back to 2 // the next change should use 4 -const unsigned int VERSION_AMF = 2; +// const unsigned int VERSION_AMF = 2; Commented-out after we removed AMF export. const unsigned int VERSION_AMF_COMPATIBLE = 3; const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; @@ -691,7 +691,7 @@ void AMFParserContext::endElement(const char * /* name */) CustomGCode::Type type = static_cast(atoi(m_value[3].c_str())); const std::string& extra= m_value[4]; - m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}); + m_model.custom_gcode_per_print_z().gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}); for (std::string& val: m_value) val.clear(); @@ -701,9 +701,9 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_CUSTOM_GCODE_MODE: { const std::string& mode = m_value[0]; - m_model.custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : - mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : - CustomGCode::Mode::MultiExtruder; + m_model.custom_gcode_per_print_z().mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : + mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : + CustomGCode::Mode::MultiExtruder; for (std::string& val: m_value) val.clear(); break; @@ -1092,283 +1092,4 @@ bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionCo return false; } -bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources) -{ - if ((path == nullptr) || (model == nullptr)) - return false; - - // forces ".zip.amf" extension - std::string export_path = path; - if (!boost::iends_with(export_path, ".zip.amf")) - export_path = boost::filesystem::path(export_path).replace_extension(".zip.amf").string(); - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_writer(&archive, export_path)) return false; - - std::stringstream stream; - // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 - // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double). - // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact. - // The default value of std::stream precision is 6 digits only! - stream << std::setprecision(std::numeric_limits::max_digits10); - stream << "\n"; - stream << "\n"; - stream << "Slic3r " << SLIC3R_VERSION << "\n"; - stream << "" << VERSION_AMF << "\n"; - - if (config != nullptr) - { - std::string str_config = "\n"; - for (const std::string &key : config->keys()) - if (key != "compatible_printers") - str_config += "; " + key + " = " + config->opt_serialize(key) + "\n"; - stream << "" << xml_escape(str_config) << "\n"; - } - - for (const auto &material : model->materials) { - if (material.first.empty()) - continue; - // note that material-id must never be 0 since it's reserved by the AMF spec - stream << " \n"; - for (const auto &attr : material.second->attributes) - stream << " " << attr.second << "\n"; - for (const std::string &key : material.second->config.keys()) - stream << " " << material.second->config.opt_serialize(key) << "\n"; - stream << " \n"; - } - std::string instances; - for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) { - ModelObject *object = model->objects[object_id]; - stream << " \n"; - for (const std::string &key : object->config.keys()) - stream << " " << object->config.opt_serialize(key) << "\n"; - if (!object->name.empty()) - stream << " " << xml_escape(object->name) << "\n"; - const std::vector &layer_height_profile = object->layer_height_profile.get(); - if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) { - // Store the layer height profile as a single semicolon separated list. - stream << " "; - stream << layer_height_profile.front(); - for (size_t i = 1; i < layer_height_profile.size(); ++i) - stream << ";" << layer_height_profile[i]; - stream << "\n \n"; - } - - // Export layer height ranges including the layer range specific config overrides. - const t_layer_config_ranges& config_ranges = object->layer_config_ranges; - if (!config_ranges.empty()) - { - // Store the layer config range as a single semicolon separated list. - stream << " \n"; - size_t layer_counter = 0; - for (const auto &range : config_ranges) { - stream << " \n"; - - stream << " "; - stream << range.first.first << ";" << range.first.second << "\n"; - - for (const std::string& key : range.second.keys()) - stream << " " << range.second.opt_serialize(key) << "\n"; - - stream << " \n"; - layer_counter++; - } - - stream << " \n"; - } - - - const std::vector& sla_support_points = object->sla_support_points; - if (!sla_support_points.empty()) { - // Store the SLA supports as a single semicolon separated list. - stream << " "; - for (size_t i = 0; i < sla_support_points.size(); ++i) { - if (i != 0) - stream << ";"; - stream << sla_support_points[i].pos(0) << ";" << sla_support_points[i].pos(1) << ";" << sla_support_points[i].pos(2) << ";" << sla_support_points[i].head_front_radius << ";" << sla_support_points[i].is_new_island; - } - stream << "\n \n"; - } - - stream << " \n"; - stream << " \n"; - std::vector vertices_offsets; - int num_vertices = 0; - for (ModelVolume *volume : object->volumes) { - vertices_offsets.push_back(num_vertices); - const indexed_triangle_set &its = volume->mesh().its; - const Transform3d& matrix = volume->get_matrix(); - for (size_t i = 0; i < its.vertices.size(); ++i) { - stream << " \n"; - stream << " \n"; - Vec3f v = (matrix * its.vertices[i].cast()).cast(); - stream << " " << v(0) << "\n"; - stream << " " << v(1) << "\n"; - stream << " " << v(2) << "\n"; - stream << " \n"; - stream << " \n"; - } - num_vertices += (int)its.vertices.size(); - } - stream << " \n"; - for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { - ModelVolume *volume = object->volumes[i_volume]; - int vertices_offset = vertices_offsets[i_volume]; - if (volume->material_id().empty()) - stream << " \n"; - else - stream << " material_id() << "\">\n"; - for (const std::string &key : volume->config.keys()) - stream << " " << volume->config.opt_serialize(key) << "\n"; - if (!volume->name.empty()) - stream << " " << xml_escape(volume->name) << "\n"; - if (volume->is_modifier()) - stream << " 1\n"; - stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; - stream << " "; - const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix(); - stream << std::setprecision(std::numeric_limits::max_digits10); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - stream << matrix(r, c); - if (r != 3 || c != 3) - stream << " "; - } - } - stream << "\n"; - if (!volume->source.input_file.empty()) { - std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); - stream << " " << input_file << "\n"; - stream << " " << volume->source.object_idx << "\n"; - stream << " " << volume->source.volume_idx << "\n"; - stream << " " << volume->source.mesh_offset(0) << "\n"; - stream << " " << volume->source.mesh_offset(1) << "\n"; - stream << " " << volume->source.mesh_offset(2) << "\n"; - } - assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); - if (volume->source.is_converted_from_inches) - stream << " 1\n"; - else if (volume->source.is_converted_from_meters) - stream << " 1\n"; - if (volume->source.is_from_builtin_objects) - stream << " 1\n"; - stream << std::setprecision(std::numeric_limits::max_digits10); - const indexed_triangle_set &its = volume->mesh().its; - for (size_t i = 0; i < its.indices.size(); ++i) { - stream << " \n"; - for (int j = 0; j < 3; ++j) - stream << " " << its.indices[i][j] + vertices_offset << "\n"; - stream << " \n"; - } - stream << " \n"; - } - stream << " \n"; - stream << " \n"; - if (!object->instances.empty()) { - for (ModelInstance *instance : object->instances) { - std::stringstream buf; - buf << " \n" - << " " << instance->get_offset(X) << "\n" - << " " << instance->get_offset(Y) << "\n" - << " " << instance->get_offset(Z) << "\n" - << " " << instance->get_rotation(X) << "\n" - << " " << instance->get_rotation(Y) << "\n" - << " " << instance->get_rotation(Z) << "\n" - << " " << instance->get_scaling_factor(X) << "\n" - << " " << instance->get_scaling_factor(Y) << "\n" - << " " << instance->get_scaling_factor(Z) << "\n" - << " " << instance->get_mirror(X) << "\n" - << " " << instance->get_mirror(Y) << "\n" - << " " << instance->get_mirror(Z) << "\n" - << " " << instance->printable << "\n" - << " \n"; - - //FIXME missing instance->scaling_factor - instances.append(buf.str()); - } - } - } - if (! instances.empty()) { - stream << " \n"; - stream << instances; - stream << " \n"; - } - - if (!model->custom_gcode_per_print_z.gcodes.empty()) - { - std::string out = ""; - pt::ptree tree; - - pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); - - for (const CustomGCode::Item& code : model->custom_gcode_per_print_z.gcodes) - { - pt::ptree& code_tree = main_tree.add("code", ""); - // store custom_gcode_per_print_z gcodes information - code_tree.put(".print_z" , code.print_z ); - code_tree.put(".type" , static_cast(code.type)); - code_tree.put(".extruder" , code.extruder ); - code_tree.put(".color" , code.color ); - code_tree.put(".extra" , code.extra ); - - // add gcode field data for the old version of the QIDISlicer - std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : - code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : - code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : - code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; - code_tree.put(".gcode" , gcode ); - } - - pt::ptree& mode_tree = main_tree.add("mode", ""); - // store mode of a custom_gcode_per_print_z - mode_tree.put(".value", - model->custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : - model->custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? - CustomGCode::MultiAsSingleMode : CustomGCode::MultiExtruderMode); - - if (!tree.empty()) - { - std::ostringstream oss; - pt::write_xml(oss, tree); - out = oss.str(); - - size_t del_header_pos = out.find("\n \n <", ">\n<"); - - stream << out << "\n"; - } - } - - stream << "\n"; - - std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf"); - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) - { - close_zip_writer(&archive); - boost::filesystem::remove(export_path); - return false; - } - - if (!mz_zip_writer_finalize_archive(&archive)) - { - close_zip_writer(&archive); - boost::filesystem::remove(export_path); - return false; - } - - close_zip_writer(&archive); - - return true; -} - }; // namespace Slic3r diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index a073071..dd5bf57 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -9,9 +9,7 @@ class DynamicPrintConfig; // Load the content of an amf file into the given model and configuration. extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version); -// Save the given model and the config data into an amf file. -// The model could be modified during the export process if meshes are not repaired or have no shared vertices -extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources); +// The option has been missing in the UI since 2.4.0 (except for CLI). } // namespace Slic3r diff --git a/src/libslic3r/Format/PrintRequest.cpp b/src/libslic3r/Format/PrintRequest.cpp index 7dceaea..5584a21 100644 --- a/src/libslic3r/Format/PrintRequest.cpp +++ b/src/libslic3r/Format/PrintRequest.cpp @@ -24,7 +24,7 @@ void read_file(const char* input_file, pt::ptree& tree) try { pt::read_xml(ifs, tree); } - catch (const boost::property_tree::xml_parser::xml_parser_error& err) { + catch (const boost::property_tree::xml_parser::xml_parser_error&) { throw Slic3r::RuntimeError("Failed reading PrintRequest file. File format is corrupt."); } } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 81d2074..cf12e6b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -24,6 +24,7 @@ #include "libslic3r.h" #include "LocalesUtils.hpp" #include "format.hpp" +#include "Time.hpp" #include #include @@ -443,9 +444,11 @@ namespace DoExport { static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) { 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.normal_print_time_seconds = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time; + print_statistics.silent_print_time_seconds = result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time; + print_statistics.estimated_normal_print_time = get_time_dhms(print_statistics.normal_print_time_seconds); 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"; + get_time_dhms(print_statistics.silent_print_time_seconds) : "N/A"; // update filament statictics double total_extruded_volume = 0.0; @@ -523,7 +526,7 @@ namespace DoExport { } } if (ret.size() < MAX_TAGS_COUNT) { - const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; + const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z(); for (const auto& gcode : custom_gcode_per_print_z.gcodes) { check(_u8L("Custom G-code"), gcode.extra); if (ret.size() == MAX_TAGS_COUNT) @@ -930,6 +933,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // file data binary_data.file_metadata.raw_data.emplace_back("Producer", std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION)); + binary_data.file_metadata.raw_data.emplace_back("Produced on", Utils::utc_timestamp()); // config data encode_full_config(*m_print, binary_data.slicer_metadata.raw_data); @@ -1267,7 +1271,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail file.write(m_label_objects.maybe_stop_instance()); const double last_z{this->writer().get_position().z()}; file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position")); - file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", [](){return "";})); + const Vec3crd from{to_3d(*this->last_position, scaled(this->m_last_layer_z))}; + const Vec3crd to{0, 0, scaled(this->m_last_layer_z)}; + file.write(this->travel_to(from, to, ExtrusionRole::None, "move to origin position for next object", [](){return "";})); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1304,7 +1310,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail std::vector> layers_to_print = collect_layers_to_print(print); // QIDI Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { - m_wipe_tower = std::make_unique(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()); + m_wipe_tower = std::make_unique(print.model().wipe_tower().position.cast(), print.model().wipe_tower().rotation, print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()); // Set position for wipe tower generation. Vec3d new_position = this->writer().get_position(); @@ -2272,12 +2278,10 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode( )}; std::string travel_gcode; - Vec3d previous_point{this->point_to_gcode(travel.front())}; for (const Vec3crd &point : travel) { const Vec3d gcode_point{this->point_to_gcode(point)}; travel_gcode += this->m_writer - .get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change"); - previous_point = gcode_point; + .get_travel_to_xyz_gcode(gcode_point, "layer change"); } return travel_gcode; } @@ -2285,6 +2289,8 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode( #ifndef NDEBUG static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) { + assert(!smooth_path.empty()); + for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) { assert(it->path.size() >= 2); assert(std::prev(it)->path.back().point == it->path.front().point); @@ -2294,54 +2300,124 @@ static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bo } #endif //NDEBUG +namespace GCode { + +std::pair split_with_seam( + const ExtrusionLoop &loop, + const boost::variant &seam, + const bool flipped, + const GCode::SmoothPathCache &smooth_path_cache, + const double scaled_resolution, + const double seam_point_merge_distance_threshold +) { + if (loop.paths.empty() || loop.paths.front().empty()) { + return {SmoothPath{}, 0}; + } + if (const auto seam_point{boost::get(&seam)}; seam_point != nullptr) { + return { + smooth_path_cache.resolve_or_fit_split_with_seam( + loop, flipped, scaled_resolution, *seam_point, seam_point_merge_distance_threshold + ), + 0}; + } else if (const auto scarf{boost::get(&seam)}; scarf != nullptr) { + ExtrusionPaths paths{loop.paths}; + const auto apply_smoothing{[&](tcb::span paths){ + return smooth_path_cache.resolve_or_fit(paths, false, scaled(0.0015)); + }}; + return Seams::Scarf::add_scarf_seam(std::move(paths), *scarf, apply_smoothing, flipped); + } else { + throw std::runtime_error{"Unknown seam type!"}; + } +} +} // namespace GCode + +static inline double get_seam_gap_distance_value(const PrintConfig &config, const unsigned extruder_id) +{ + const double nozzle_diameter = config.nozzle_diameter.get_at(extruder_id); + const FloatOrPercent seam_gap_distance_override = config.filament_seam_gap_distance.get_at(extruder_id); + if (!std::isnan(seam_gap_distance_override.value)) { + return seam_gap_distance_override.get_abs_value(nozzle_diameter); + } + + return config.seam_gap_distance.get_abs_value(nozzle_diameter); +} + using GCode::ExtrusionOrder::InstancePoint; -struct SmoothPathGenerator { +struct SmoothPathGenerator +{ const Seams::Placer &seam_placer; const GCode::SmoothPathCaches &smooth_path_caches; double scaled_resolution; const PrintConfig &config; bool enable_loop_clipping; - GCode::SmoothPath operator()(const Layer *layer, const ExtrusionEntityReference &extrusion_reference, const unsigned extruder_id, std::optional &previous_position) { + GCode::ExtrusionOrder::PathSmoothingResult operator()( + const Layer *layer, + const PrintRegion *region, + const ExtrusionEntityReference &extrusion_reference, + const unsigned extruder_id, + std::optional &previous_position + ) { const ExtrusionEntity *extrusion_entity{&extrusion_reference.extrusion_entity()}; GCode::SmoothPath result; + std::size_t wipe_offset{0}; if (auto loop = dynamic_cast(extrusion_entity)) { - Point seam_point = previous_position ? previous_position->local_point : Point::Zero(); - if (!config.spiral_vase && loop->role().is_perimeter() && layer != nullptr) { - seam_point = this->seam_placer.place_seam(layer, *loop, seam_point); - } - - const GCode::SmoothPathCache &smooth_path_cache{loop->role().is_perimeter() ? smooth_path_caches.layer_local() : smooth_path_caches.global()}; // Because the G-code export has 1um resolution, don't generate segments shorter // than 1.5 microns, thus empty path segments will not be produced by G-code export. - GCode::SmoothPath smooth_path = - smooth_path_cache.resolve_or_fit_split_with_seam( - *loop, extrusion_reference.flipped(), scaled_resolution, seam_point, scaled(0.0015) + const auto seam_point_merge_distance_threshold{scaled(0.0015)}; + const GCode::SmoothPathCache &smooth_path_cache{ + loop->role().is_perimeter() ? smooth_path_caches.layer_local() : + smooth_path_caches.global()}; + const Point previous_point{ + previous_position ? previous_position->local_point : Point::Zero()}; + + if (!config.spiral_vase && loop->role().is_perimeter() && layer != nullptr && region != nullptr) { + boost::variant seam{ + this->seam_placer + .place_seam(layer, region, *loop, extrusion_reference.flipped(), previous_point)}; + std::tie(result, wipe_offset) = split_with_seam( + *loop, seam, extrusion_reference.flipped(), smooth_path_cache, + scaled_resolution, seam_point_merge_distance_threshold ); + } else { + result = smooth_path_cache.resolve_or_fit_split_with_seam( + *loop, extrusion_reference.flipped(), scaled_resolution, previous_point, + seam_point_merge_distance_threshold + ); + } // Clip the path to avoid the extruder to get exactly on the first point of the // loop; if polyline was shorter than the clipping distance we'd get a null // polyline, so we discard it in that case. - const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)}; - if (enable_loop_clipping) { - //Y21 + if (const double extrusion_clipping = get_seam_gap_distance_value(config, extruder_id); enable_loop_clipping && extrusion_clipping > 0.) { clip_end( - smooth_path, - scale_(nozzle_diameter) * (config.seam_gap.value / 100), + result, + scaled(extrusion_clipping), scaled(GCode::ExtrusionOrder::min_gcode_segment_length) ); + } else if (enable_loop_clipping && extrusion_clipping < 0.) { + // Extend the extrusion slightly after the seam. + const double smooth_path_extension_length = -1. * scaled(extrusion_clipping); + const double smooth_path_extension_cut_length = length(result) - smooth_path_extension_length; + GCode::SmoothPath smooth_path_extension = result; + + clip_end(smooth_path_extension, smooth_path_extension_cut_length, scaled(GCode::ExtrusionOrder::min_gcode_segment_length)); + Slic3r::append(result, smooth_path_extension); } - assert(validate_smooth_path(smooth_path, !enable_loop_clipping)); - - result = smooth_path; + assert(validate_smooth_path(result, !enable_loop_clipping)); } else if (auto multipath = dynamic_cast(extrusion_entity)) { - result = smooth_path_caches.layer_local().resolve_or_fit(*multipath, extrusion_reference.flipped(), scaled_resolution); + result = + smooth_path_caches.layer_local() + .resolve_or_fit(*multipath, extrusion_reference.flipped(), scaled_resolution); } else if (auto path = dynamic_cast(extrusion_entity)) { - result = GCode::SmoothPath{GCode::SmoothPathElement{path->attributes(), smooth_path_caches.layer_local().resolve_or_fit(*path, extrusion_reference.flipped(), scaled_resolution)}}; + result = GCode::SmoothPath{GCode::SmoothPathElement{ + path->attributes(), + smooth_path_caches.layer_local() + .resolve_or_fit(*path, extrusion_reference.flipped(), scaled_resolution)}}; } for (auto it{result.rbegin()}; it != result.rend(); ++it) { if (!it->path.empty()) { @@ -2350,9 +2426,8 @@ struct SmoothPathGenerator { } } - return result; + return {result, wipe_offset}; } - }; std::vector GCodeGenerator::get_sorted_extrusions( @@ -2480,6 +2555,8 @@ LayerResult GCodeGenerator::process_layer( m_enable_loop_clipping = !enable; } + const float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; + using GCode::ExtrusionOrder::ExtruderExtrusions; const std::vector extrusions{ this->get_sorted_extrusions(print, layers, layer_tools, instances_to_print, smooth_path_caches, first_layer)}; @@ -2487,7 +2564,8 @@ LayerResult GCodeGenerator::process_layer( if (extrusions.empty()) { return result; } - const Point first_point{*GCode::ExtrusionOrder::get_first_point(extrusions)}; + const Geometry::ArcWelder::Segment first_segment{*GCode::ExtrusionOrder::get_first_point(extrusions)}; + const Vec3crd first_point{to_3d(first_segment.point, scaled(print_z + (first_segment.height_fraction - 1.0) * height))}; const PrintInstance* first_instance{get_first_instance(extrusions, instances_to_print)}; m_label_objects.update(first_instance); @@ -2501,7 +2579,6 @@ LayerResult GCodeGenerator::process_layer( gcode += std::string(";Z:") + float_to_string_decimal_point(print_z) + "\n"; // export layer height - float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + float_to_string_decimal_point(height) + "\n"; @@ -2521,7 +2598,24 @@ LayerResult GCodeGenerator::process_layer( print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } - gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable, first_point, first_layer); // this will increase m_layer_index + + // Initialize avoid crossing perimeters before a layer change. + if (!instances_to_print.empty() && print.config().avoid_crossing_perimeters) { + const InstanceToPrint instance_to_print{instances_to_print.front()}; + this->m_avoid_crossing_perimeters.init_layer( + *layers[instance_to_print.object_layer_to_print_id].layer()); + this->set_origin(unscale(first_instance->shift)); + + const GCode::PrintObjectInstance next_instance{ + &instances_to_print.front().print_object, + int(instances_to_print.front().instance_id) + }; + if (m_current_instance != next_instance) { + m_avoid_crossing_perimeters.use_external_mp_once = true; + } + } + + gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable, first_point.head<2>(), first_layer); // this will increase m_layer_index m_layer = &layer; if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) m_travel_obstacle_tracker.init_layer(layer, layers); @@ -2614,14 +2708,23 @@ LayerResult GCodeGenerator::process_layer( } if (!this->m_moved_to_first_layer_point) { - const Vec3crd point{to_3d(first_point, scaled(print_z))}; + const Point shift{first_instance->shift}; + this->set_origin(unscale(shift)); - gcode += this->travel_to_first_position(point, print_z, ExtrusionRole::Mixed, [this]() { + const GCode::PrintObjectInstance next_instance{ + &instances_to_print.front().print_object, + int(instances_to_print.front().instance_id) + }; + if (m_current_instance != next_instance) { + m_avoid_crossing_perimeters.use_external_mp_once = true; + } + gcode += this->travel_to_first_position(first_point - to_3d(shift, 0), print_z, ExtrusionRole::Mixed, [this]() { if (m_writer.multiple_extruders) { return std::string{""}; } return m_label_objects.maybe_change_instance(m_writer); }); + this->set_origin({0, 0}); } if (!extruder_extrusions.skirt.empty()) { @@ -2667,7 +2770,7 @@ LayerResult GCodeGenerator::process_layer( if (is_empty(overriden_extrusions.slices_extrusions)) { continue; } - this->initialize_instance(instance, layers[instance.object_layer_to_print_id]); + this->initialize_instance(instance, layers[instance.object_layer_to_print_id], i == 0); gcode += this->extrude_slices( instance, layers[instance.object_layer_to_print_id], overriden_extrusions.slices_extrusions @@ -2689,7 +2792,7 @@ LayerResult GCodeGenerator::process_layer( if (support_extrusions.empty() && is_empty(slices_extrusions)) { continue; } - this->initialize_instance(instance, layers[instance.object_layer_to_print_id]); + this->initialize_instance(instance, layers[instance.object_layer_to_print_id], i == 0); if (!support_extrusions.empty()) { m_layer = layer_to_print.support_layer; @@ -2717,22 +2820,28 @@ static const auto comment_perimeter = "perimeter"sv; void GCodeGenerator::initialize_instance( const InstanceToPrint &print_instance, - const ObjectLayerToPrint &layer_to_print + const ObjectLayerToPrint &layer_to_print, + const bool is_first ) { const PrintObject &print_object = print_instance.print_object; const Print &print = *print_object.print(); m_config.apply(print_object.config(), true); m_layer = layer_to_print.layer(); - if (print.config().avoid_crossing_perimeters) - m_avoid_crossing_perimeters.init_layer(*m_layer); - // When starting a new object, use the external motion planner for the first travel move. const Point &offset = print_object.instances()[print_instance.instance_id].shift; GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; - if (m_current_instance != next_instance) { - m_avoid_crossing_perimeters.use_external_mp_once = true; + + if (print.config().avoid_crossing_perimeters && !is_first) { + m_avoid_crossing_perimeters.init_layer(*m_layer); + + // When starting a new object, use the external motion planner for the first travel move. + if (m_current_instance != next_instance) { + m_avoid_crossing_perimeters.use_external_mp_once = true; + } } + m_current_instance = next_instance; + this->set_origin(unscale(offset)); m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]); } @@ -2901,7 +3010,11 @@ std::string GCodeGenerator::change_layer( } std::string GCodeGenerator::extrude_smooth_path( - const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed + const GCode::SmoothPath &smooth_path, + const bool is_loop, + const std::string_view description, + const double speed, + const std::size_t wipe_offset ) { std::string gcode; @@ -2940,8 +3053,13 @@ std::string GCodeGenerator::extrude_smooth_path( gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); if (is_loop) { - m_wipe.set_path(GCode::SmoothPath{smooth_path}); + GCode::SmoothPath wipe{smooth_path.begin() + wipe_offset, smooth_path.end()}; + m_wipe.set_path(std::move(wipe)); } else { + if (wipe_offset > 0) { + throw std::runtime_error("Wipe offset is not supported for non looped paths!"); + } + GCode::SmoothPath reversed_smooth_path{smooth_path}; GCode::reverse(reversed_smooth_path); m_wipe.set_path(std::move(reversed_smooth_path)); @@ -3001,12 +3119,24 @@ std::string GCodeGenerator::extrude_perimeters( speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); is_small_perimeter_length = true; } - gcode += this->extrude_smooth_path(perimeter.smooth_path, true, comment_perimeter, speed); + gcode += this->extrude_smooth_path(perimeter.smooth_path, true, comment_perimeter, speed, perimeter.wipe_offset); this->m_travel_obstacle_tracker.mark_extruded( perimeter.extrusion_entity, print_instance.object_layer_to_print_id, print_instance.instance_id ); - if (!m_wipe.enabled() && perimeter.extrusion_entity->role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { + const bool is_extruding{ + !perimeter.smooth_path.empty() + && !perimeter.smooth_path.front().path.empty() + && perimeter.smooth_path.front().path.front().e_fraction > 0 + }; + + if ( + !m_wipe.enabled() + && perimeter.extrusion_entity->role().is_external_perimeter() + && m_layer != nullptr + && m_config.perimeters.value > 1 + && is_extruding + ) { // Only wipe inside if the wipe along the perimeter is disabled. // Make a little move inwards before leaving loop. if (std::optional pt = wipe_hide_seam(perimeter.smooth_path, perimeter.reversed, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) { @@ -3110,11 +3240,9 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); if (!EXTRUDER_CONFIG(travel_ramping_lift) && this->last_position) { - Vec3d writer_position{this->writer().get_position()}; - writer_position.z() = 0.0; // Endofrce z generation! - this->writer().update_position(writer_position); + const Vec3crd from{to_3d(*this->last_position, scaled(from_z))}; gcode = this->travel_to( - *this->last_position, point.head<2>(), role, "travel to first layer point", insert_gcode + from, point, role, "travel to first layer point", insert_gcode ); } else { double lift{ @@ -3202,7 +3330,9 @@ std::string GCodeGenerator::_extrude( comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [this](){ + const Vec3crd from{to_3d(*this->last_position, scaled(this->m_last_layer_z))}; + const Vec3crd to{to_3d(path.front().point, scaled(this->m_last_layer_z + (path.front().height_fraction - 1.0) * path_attr.height))}; + const std::string travel_gcode{this->travel_to(from, to, path_attr.role, comment, [this](){ return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); })}; gcode += travel_gcode; @@ -3433,7 +3563,7 @@ std::string GCodeGenerator::_extrude( for (++ it; it != end; ++ it) { Vec2d p_exact = this->point_to_gcode(it->point); Vec2d p = GCodeFormatter::quantize(p_exact); - assert(p != prev); + //assert(p != prev); if (p != prev) { // Center of the radius to be emitted into the G-code: Either by radius or by center offset. double radius = 0; @@ -3454,8 +3584,15 @@ std::string GCodeGenerator::_extrude( } if (radius == 0) { // Extrude line segment. - if (const double line_length = (p - prev).norm(); line_length > 0) - gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); + if (const double line_length = (p - prev).norm(); line_length > 0) { + double extrusion_amount{e_per_mm * line_length * it->e_fraction}; + if (it->height_fraction < 1.0 || std::prev(it)->height_fraction < 1.0) { + const Vec3d destination{to_3d(p, this->m_last_layer_z + (it->height_fraction - 1) * m_last_height)}; + gcode += m_writer.extrude_to_xyz(destination, extrusion_amount); + } else { + gcode += m_writer.extrude_to_xy(p, extrusion_amount, comment); + } + } } else { double angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), double(radius)); assert(angle > 0); @@ -3503,7 +3640,6 @@ std::string GCodeGenerator::generate_travel_gcode( // use G1 because we rely on paths being straight (G0 may make round paths) gcode += this->m_writer.set_travel_acceleration(acceleration); - Vec3d previous_point{this->point_to_gcode(travel.front())}; bool already_inserted{false}; for (std::size_t i{0}; i < travel.size(); ++i) { const Vec3crd& point{travel[i]}; @@ -3514,9 +3650,8 @@ std::string GCodeGenerator::generate_travel_gcode( already_inserted = true; } - gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment); + gcode += this->m_writer.travel_to_xyz(gcode_point, comment); this->last_position = point.head<2>(); - previous_point = gcode_point; } if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) { @@ -3609,19 +3744,21 @@ Polyline GCodeGenerator::generate_travel_xy_path( // This method accepts &point in print coordinates. std::string GCodeGenerator::travel_to( - const Point &start_point, - const Point &end_point, + const Vec3crd &start_point, + const Vec3crd &end_point, ExtrusionRole role, const std::string &comment, const std::function& insert_gcode ) { + const double initial_elevation{unscaled(start_point.z())}; + // check whether a straight travel move would need retraction bool could_be_wipe_disabled {false}; - bool needs_retraction = this->needs_retraction(Polyline{start_point, end_point}, role); + bool needs_retraction = this->needs_retraction(Polyline{start_point.head<2>(), end_point.head<2>()}, role); Polyline xy_path{generate_travel_xy_path( - start_point, end_point, needs_retraction, could_be_wipe_disabled + start_point.head<2>(), end_point.head<2>(), needs_retraction, could_be_wipe_disabled )}; needs_retraction = this->needs_retraction(xy_path, role); @@ -3637,7 +3774,7 @@ std::string GCodeGenerator::travel_to( if (*this->last_position != position_before_wipe) { xy_path = generate_travel_xy_path( - *this->last_position, end_point, needs_retraction, could_be_wipe_disabled + *this->last_position, end_point.head<2>(), needs_retraction, could_be_wipe_disabled ); } } else { @@ -3651,7 +3788,6 @@ std::string GCodeGenerator::travel_to( const unsigned extruder_id = this->m_writer.extruder()->id(); const double retract_length = this->m_config.retract_length.get_at(extruder_id); bool can_be_flat{!needs_retraction || retract_length == 0}; - const double initial_elevation = this->m_last_layer_z; const double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id); const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id); @@ -3660,7 +3796,7 @@ std::string GCodeGenerator::travel_to( can_be_flat = true; } - const Points3 travel = ( + Points3 travel = ( can_be_flat ? GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) : GCode::Impl::Travels::generate_travel_to_extrusion( @@ -3672,6 +3808,14 @@ std::string GCodeGenerator::travel_to( scaled(m_origin) ) ); + if (this->config().scarf_seam_placement != ScarfSeamPlacement::nowhere && + role == ExtrusionRole::ExternalPerimeter && can_be_flat && travel.size() == 2 && + scaled(2.0) > xy_path.length()) { + + // Go directly to the outter perimeter. + travel.pop_back(); + } + travel.emplace_back(end_point); return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode); } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index cc9369b..171bf6a 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -270,9 +270,15 @@ private: const bool first_layer ); std::string extrude_smooth_path( - const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed + const GCode::SmoothPath &smooth_path, + const bool is_loop, + const std::string_view description, + const double speed, + const std::size_t wipe_offset = 0 + ); + std::string extrude_skirt( + GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override ); - std::string extrude_skirt(GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override); std::vector sort_print_object_instances( // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. @@ -295,7 +301,8 @@ private: void initialize_instance( const InstanceToPrint &print_instance, - const ObjectLayerToPrint &layer_to_print + const ObjectLayerToPrint &layer_to_print, + const bool is_first ); std::string extrude_slices( @@ -320,8 +327,8 @@ private: bool& could_be_wipe_disabled ); std::string travel_to( - const Point &start_point, - const Point &end_point, + const Vec3crd &start_point, + const Vec3crd &end_point, ExtrusionRole role, const std::string &comment, const std::function& insert_gcode diff --git a/src/libslic3r/GCode/ExtrusionOrder.cpp b/src/libslic3r/GCode/ExtrusionOrder.cpp index 713bac7..58a2b61 100644 --- a/src/libslic3r/GCode/ExtrusionOrder.cpp +++ b/src/libslic3r/GCode/ExtrusionOrder.cpp @@ -129,10 +129,10 @@ std::vector extract_perimeter_extrusions( const bool is_hole = loop->is_clockwise(); reverse_loop = print.config().prefer_clockwise_movements ? !is_hole : is_hole; } - SmoothPath path{smooth_path(&layer, ExtrusionEntityReference{*ee, reverse_loop}, extruder_id, last_position)}; + auto [path, wipe_offset]{smooth_path(&layer, ®ion, ExtrusionEntityReference{*ee, reverse_loop}, extruder_id, last_position)}; previous_position = get_gcode_point(last_position, offset); if (!path.empty()) { - result.push_back(Perimeter{std::move(path), reverse_loop, ee}); + result.push_back(Perimeter{std::move(path), reverse_loop, ee, wipe_offset}); } } } @@ -194,7 +194,7 @@ std::vector extract_infill_ranges( std::vector paths; for (const ExtrusionEntityReference &extrusion_reference : sorted_extrusions) { std::optional last_position{get_instance_point(previous_position, offset)}; - SmoothPath path{smooth_path(&layer, extrusion_reference, extruder_id, last_position)}; + auto [path, _]{smooth_path(&layer, ®ion, extrusion_reference, extruder_id, last_position)}; if (!path.empty()) { paths.push_back(std::move(path)); } @@ -367,7 +367,7 @@ std::vector get_support_extrusions( if (collection != nullptr) { for (const ExtrusionEntity * sub_entity : *collection) { std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, {*sub_entity, entity_reference.flipped()}, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, {*sub_entity, entity_reference.flipped()}, extruder_id, last_position)}; if (!path.empty()) { paths.push_back({std::move(path), is_interface}); } @@ -375,7 +375,7 @@ std::vector get_support_extrusions( } } else { std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, entity_reference, extruder_id, last_position)}; if (!path.empty()) { paths.push_back({std::move(path), is_interface}); } @@ -561,7 +561,7 @@ std::vector get_extrusions( } const ExtrusionEntityReference entity{*print.skirt().entities[i], reverse}; std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, entity, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, entity, extruder_id, last_position)}; previous_position = get_gcode_point(last_position, {0, 0}); extruder_extrusions.skirt.emplace_back(i, std::move(path)); } @@ -580,7 +580,7 @@ std::vector get_extrusions( const ExtrusionEntityReference entity_reference{*entity, reverse}; std::optional last_position{get_instance_point(previous_position, {0, 0})}; - SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + auto [path, _]{smooth_path(nullptr, nullptr, entity_reference, extruder_id, last_position)}; previous_position = get_gcode_point(last_position, {0, 0}); extruder_extrusions.brim.push_back({std::move(path), is_loop}); } @@ -607,16 +607,16 @@ std::vector get_extrusions( return extrusions; } -std::optional get_first_point(const SmoothPath &path) { +std::optional get_first_point(const SmoothPath &path) { for (const SmoothPathElement & element : path) { if (!element.path.empty()) { - return InstancePoint{element.path.front().point}; + return element.path.front(); } } return std::nullopt; } -std::optional get_first_point(const std::vector &smooth_paths) { +std::optional get_first_point(const std::vector &smooth_paths) { for (const SmoothPath &path : smooth_paths) { if (auto result = get_first_point(path)) { return result; @@ -625,7 +625,7 @@ std::optional get_first_point(const std::vector &smoo return std::nullopt; } -std::optional get_first_point(const std::vector &infill_ranges) { +std::optional get_first_point(const std::vector &infill_ranges) { for (const InfillRange &infill_range : infill_ranges) { if (auto result = get_first_point(infill_range.items)) { return result; @@ -634,7 +634,7 @@ std::optional get_first_point(const std::vector &inf return std::nullopt; } -std::optional get_first_point(const std::vector &perimeters) { +std::optional get_first_point(const std::vector &perimeters) { for (const Perimeter &perimeter : perimeters) { if (auto result = get_first_point(perimeter.smooth_path)) { return result; @@ -643,7 +643,7 @@ std::optional get_first_point(const std::vector &perim return std::nullopt; } -std::optional get_first_point(const std::vector &extrusions) { +std::optional get_first_point(const std::vector &extrusions) { for (const IslandExtrusions &island : extrusions) { if (island.infill_first) { if (auto result = get_first_point(island.infill_ranges)) { @@ -664,7 +664,7 @@ std::optional get_first_point(const std::vector return std::nullopt; } -std::optional get_first_point(const std::vector &extrusions) { +std::optional get_first_point(const std::vector &extrusions) { for (const SliceExtrusions &slice : extrusions) { if (auto result = get_first_point(slice.common_extrusions)) { return result; @@ -673,44 +673,47 @@ std::optional get_first_point(const std::vector return std::nullopt; } -std::optional get_first_point(const ExtruderExtrusions &extrusions) { +std::optional get_first_point(const ExtruderExtrusions &extrusions) { for (const auto&[_, path] : extrusions.skirt) { if (auto result = get_first_point(path)) { - return result->local_point; + return result; }; } for (const BrimPath &brim_path : extrusions.brim) { if (auto result = get_first_point(brim_path.path)) { - return result->local_point; + return result; }; } for (const OverridenExtrusions &overriden_extrusions : extrusions.overriden_extrusions) { if (auto result = get_first_point(overriden_extrusions.slices_extrusions)) { - return result->local_point - overriden_extrusions.instance_offset; + result->point += overriden_extrusions.instance_offset; + return result; } } for (const NormalExtrusions &normal_extrusions : extrusions.normal_extrusions) { for (const SupportPath &support_path : normal_extrusions.support_extrusions) { if (auto result = get_first_point(support_path.path)) { - return result->local_point + normal_extrusions.instance_offset; + result->point += normal_extrusions.instance_offset; + return result; } } if (auto result = get_first_point(normal_extrusions.slices_extrusions)) { - return result->local_point + normal_extrusions.instance_offset; + result->point += normal_extrusions.instance_offset; + return result; } } return std::nullopt; } -std::optional get_first_point(const std::vector &extrusions) { +std::optional get_first_point(const std::vector &extrusions) { if (extrusions.empty()) { return std::nullopt; } if (extrusions.front().wipe_tower_start) { - return extrusions.front().wipe_tower_start; + return {{*(extrusions.front().wipe_tower_start)}}; } for (const ExtruderExtrusions &extruder_extrusions : extrusions) { @@ -726,6 +729,9 @@ const PrintInstance * get_first_instance( const std::vector &extrusions, const std::vector &instances_to_print ) { + if (instances_to_print.empty()) { + return nullptr; + } for (const ExtruderExtrusions &extruder_extrusions : extrusions) { if (!extruder_extrusions.overriden_extrusions.empty()) { for (std::size_t i{0}; i < instances_to_print.size(); ++i) { @@ -746,7 +752,8 @@ const PrintInstance * get_first_instance( } } } - return nullptr; + const InstanceToPrint &instance{instances_to_print.front()}; + return &instance.print_object.instances()[instance.instance_id]; } } diff --git a/src/libslic3r/GCode/ExtrusionOrder.hpp b/src/libslic3r/GCode/ExtrusionOrder.hpp index c9409f0..dcd2abe 100644 --- a/src/libslic3r/GCode/ExtrusionOrder.hpp +++ b/src/libslic3r/GCode/ExtrusionOrder.hpp @@ -83,6 +83,7 @@ struct Perimeter { GCode::SmoothPath smooth_path; bool reversed; const ExtrusionEntity *extrusion_entity; + std::size_t wipe_offset; }; struct IslandExtrusions { @@ -122,8 +123,9 @@ struct InstancePoint { Point local_point; }; -using PathSmoothingFunction = std::function &previous_position +using PathSmoothingResult = std::pair; +using PathSmoothingFunction = std::function &previous_position )>; struct BrimPath { @@ -158,7 +160,7 @@ std::vector get_extrusions( std::optional previous_position ); -std::optional get_first_point(const std::vector &extrusions); +std::optional get_first_point(const std::vector &extrusions); const PrintInstance * get_first_instance( const std::vector &extrusions, diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c17b29b..5888681 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -315,95 +315,94 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P // detect actual speed moves required to render toolpaths using actual speed if (mode == PrintEstimatedStatistics::ETimeMode::Normal) { GCodeProcessorResult::MoveVertex& curr_move = result.moves[block.move_id]; - if (curr_move.type != EMoveType::Extrude && - curr_move.type != EMoveType::Travel && - curr_move.type != EMoveType::Wipe) - continue; + if (curr_move.type == EMoveType::Extrude || + curr_move.type == EMoveType::Travel || + curr_move.type == EMoveType::Wipe) { + assert(curr_move.actual_feedrate == 0.0f); - assert(curr_move.actual_feedrate == 0.0f); + GCodeProcessorResult::MoveVertex& prev_move = result.moves[block.move_id - 1]; + const bool interpolate = (prev_move.type == curr_move.type); + if (!interpolate && + prev_move.type != EMoveType::Extrude && + prev_move.type != EMoveType::Travel && + prev_move.type != EMoveType::Wipe) + prev_move.actual_feedrate = block.feedrate_profile.entry; - GCodeProcessorResult::MoveVertex& prev_move = result.moves[block.move_id - 1]; - const bool interpolate = (prev_move.type == curr_move.type); - if (!interpolate && - prev_move.type != EMoveType::Extrude && - prev_move.type != EMoveType::Travel && - prev_move.type != EMoveType::Wipe) - prev_move.actual_feedrate = block.feedrate_profile.entry; - - if (EPSILON < block.trapezoid.accelerate_until && block.trapezoid.accelerate_until < block.distance - EPSILON) { - const float t = block.trapezoid.accelerate_until / block.distance; - const Vec3f position = lerp(prev_move.position, curr_move.position, t); - if ((position - prev_move.position).norm() > EPSILON && - (position - curr_move.position).norm() > EPSILON) { - const float delta_extruder = interpolate ? lerp(prev_move.delta_extruder, curr_move.delta_extruder, t) : curr_move.delta_extruder; - const float feedrate = interpolate ? lerp(prev_move.feedrate, curr_move.feedrate, t) : curr_move.feedrate; - const float width = interpolate ? lerp(prev_move.width, curr_move.width, t) : curr_move.width; - const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height; - const float mm3_per_mm = interpolate ? lerp(prev_move.mm3_per_mm, curr_move.mm3_per_mm, t) : curr_move.mm3_per_mm; - const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed; - const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature; - actual_speed_moves.push_back({ - block.move_id, - position, - block.trapezoid.cruise_feedrate, - delta_extruder, - feedrate, - width, - height, - mm3_per_mm, - fan_speed, - temperature - }); + if (EPSILON < block.trapezoid.accelerate_until && block.trapezoid.accelerate_until < block.distance - EPSILON) { + const float t = block.trapezoid.accelerate_until / block.distance; + const Vec3f position = lerp(prev_move.position, curr_move.position, t); + if ((position - prev_move.position).norm() > EPSILON && + (position - curr_move.position).norm() > EPSILON) { + const float delta_extruder = interpolate ? lerp(prev_move.delta_extruder, curr_move.delta_extruder, t) : curr_move.delta_extruder; + const float feedrate = interpolate ? lerp(prev_move.feedrate, curr_move.feedrate, t) : curr_move.feedrate; + const float width = interpolate ? lerp(prev_move.width, curr_move.width, t) : curr_move.width; + const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height; + const float mm3_per_mm = interpolate ? lerp(prev_move.mm3_per_mm, curr_move.mm3_per_mm, t) : curr_move.mm3_per_mm; + const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed; + const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature; + actual_speed_moves.push_back({ + block.move_id, + position, + block.trapezoid.cruise_feedrate, + delta_extruder, + feedrate, + width, + height, + mm3_per_mm, + fan_speed, + temperature + }); + } } - } - const bool has_deceleration = block.trapezoid.deceleration_distance(block.distance) > EPSILON; - if (has_deceleration && block.trapezoid.decelerate_after > block.trapezoid.accelerate_until + EPSILON) { - const float t = block.trapezoid.decelerate_after / block.distance; - const Vec3f position = lerp(prev_move.position, curr_move.position, t); - if ((position - prev_move.position).norm() > EPSILON && - (position - curr_move.position).norm() > EPSILON) { - const float delta_extruder = interpolate ? lerp(prev_move.delta_extruder, curr_move.delta_extruder, t) : curr_move.delta_extruder; - const float feedrate = interpolate ? lerp(prev_move.feedrate, curr_move.feedrate, t) : curr_move.feedrate; - const float width = interpolate ? lerp(prev_move.width, curr_move.width, t) : curr_move.width; - const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height; - const float mm3_per_mm = interpolate ? lerp(prev_move.mm3_per_mm, curr_move.mm3_per_mm, t) : curr_move.mm3_per_mm; - const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed; - const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature; - actual_speed_moves.push_back({ - block.move_id, - position, - block.trapezoid.cruise_feedrate, - delta_extruder, - feedrate, - width, - height, - mm3_per_mm, - fan_speed, - temperature - }); + const bool has_deceleration = block.trapezoid.deceleration_distance(block.distance) > EPSILON; + if (has_deceleration && block.trapezoid.decelerate_after > block.trapezoid.accelerate_until + EPSILON) { + const float t = block.trapezoid.decelerate_after / block.distance; + const Vec3f position = lerp(prev_move.position, curr_move.position, t); + if ((position - prev_move.position).norm() > EPSILON && + (position - curr_move.position).norm() > EPSILON) { + const float delta_extruder = interpolate ? lerp(prev_move.delta_extruder, curr_move.delta_extruder, t) : curr_move.delta_extruder; + const float feedrate = interpolate ? lerp(prev_move.feedrate, curr_move.feedrate, t) : curr_move.feedrate; + const float width = interpolate ? lerp(prev_move.width, curr_move.width, t) : curr_move.width; + const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height; + const float mm3_per_mm = interpolate ? lerp(prev_move.mm3_per_mm, curr_move.mm3_per_mm, t) : curr_move.mm3_per_mm; + const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed; + const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature; + actual_speed_moves.push_back({ + block.move_id, + position, + block.trapezoid.cruise_feedrate, + delta_extruder, + feedrate, + width, + height, + mm3_per_mm, + fan_speed, + temperature + }); + } } - } - const bool is_cruise_only = block.trapezoid.is_cruise_only(block.distance); - actual_speed_moves.push_back({ - block.move_id, - std::nullopt, - (is_cruise_only || !has_deceleration) ? block.trapezoid.cruise_feedrate : block.feedrate_profile.exit, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt - }); + const bool is_cruise_only = block.trapezoid.is_cruise_only(block.distance); + actual_speed_moves.push_back({ + block.move_id, + std::nullopt, + (is_cruise_only || !has_deceleration) ? block.trapezoid.cruise_feedrate : block.feedrate_profile.exit, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt + }); + } } g1_times_cache.push_back({ block.g1_line_id, block.remaining_internal_g1_lines, float(time) }); // update times for remaining time to printer stop placeholders auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id, [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; }); - if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id) + if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id >= block.g1_line_id) it_stop_time->elapsed_time = float(time); } @@ -2833,33 +2832,24 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes if (m_time_processor.machines[0].blocks.size() > TimeProcessor::Planner::refresh_threshold) calculate_time(m_result, TimeProcessor::Planner::queue_size); - if (m_seams_detector.is_active()) { - // check for seam starting vertex - if (type == EMoveType::Extrude && m_extrusion_role == GCodeExtrusionRole::ExternalPerimeter && !m_seams_detector.has_first_vertex()) - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); - // check for seam ending vertex and store the resulting move - else if ((type != EMoveType::Extrude || (m_extrusion_role != GCodeExtrusionRole::ExternalPerimeter && m_extrusion_role != GCodeExtrusionRole::OverhangPerimeter)) && m_seams_detector.has_first_vertex()) { - auto set_end_position = [this](const Vec3f& pos) { - m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); - }; - - const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); - const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; - const std::optional first_vertex = m_seams_detector.get_first_vertex(); - // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later - - if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) { - set_end_position(0.5f * (new_pos + *first_vertex) + m_z_offset * Vec3f::UnitZ()); - store_move_vertex(EMoveType::Seam); - set_end_position(curr_pos); - } - - m_seams_detector.activate(false); + if (m_seams_detector.is_active() && ( + type != EMoveType::Extrude + || ( + m_extrusion_role != GCodeExtrusionRole::ExternalPerimeter + && m_extrusion_role != GCodeExtrusionRole::OverhangPerimeter + ) + )) { + const AxisCoords curr_pos = m_end_position; + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; + for (unsigned char a = X; a < E; ++a) { + m_end_position[a] = double(new_pos[a]); } - } - else if (type == EMoveType::Extrude && m_extrusion_role == GCodeExtrusionRole::ExternalPerimeter) { + store_move_vertex(EMoveType::Seam); + m_end_position = curr_pos; + + m_seams_detector.activate(false); + } else if (type == EMoveType::Extrude && m_extrusion_role == GCodeExtrusionRole::ExternalPerimeter) { m_seams_detector.activate(true); - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); } // store move diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 0c6ebef..a2080df 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -772,6 +772,7 @@ namespace Slic3r { void update_estimated_statistics(); double extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section); + }; } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index f0ec47f..e1704aa 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -333,19 +333,20 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij return w.string(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const std::string_view comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d &to, const std::string_view comment) { if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) { return this->travel_to_z(to.z(), comment); } else if (std::abs(to.z() - m_pos.z()) < EPSILON) { return this->travel_to_xy(to.head<2>(), comment); } else { + std::string result{this->get_travel_to_xyz_gcode(to, comment)}; m_pos = to; - return this->get_travel_to_xyz_gcode(from, to, comment); + return result; } } -std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const { +std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &to, const std::string_view comment) const { GCodeG1Formatter w; w.emit_xyz(to); @@ -353,13 +354,13 @@ std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d if (speed_z == 0.) speed_z = this->config.travel_speed.value; - const double distance_xy{(to.head<2>() - from.head<2>()).norm()}; - const double distnace_z{std::abs(to.z() - from.z())}; + const double distance_xy{(to.head<2>() - m_pos.head<2>()).norm()}; + const double distnace_z{std::abs(to.z() - m_pos.z())}; const double time_z = distnace_z / speed_z; const double time_xy = distance_xy / this->config.travel_speed.value; const double factor = time_z > 0 ? time_xy / time_z : 1; if (factor < 1) { - w.emit_f((this->config.travel_speed.value * factor + (1 - factor) * speed_z) * 60.0); + w.emit_f(std::floor((this->config.travel_speed.value * factor + (1 - factor) * speed_z) * 60.0)); } else { w.emit_f(this->config.travel_speed.value * 60.0); } @@ -393,7 +394,7 @@ std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment) { - assert(dE != 0); + //assert(dE != 0); assert(std::abs(dE) < 1000.0); m_pos.head<2>() = point.head<2>(); @@ -405,6 +406,17 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: return w.string(); } +std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment) +{ + m_pos = point; + + GCodeG1Formatter w; + w.emit_xyz(point); + w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} + std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment) { assert(std::abs(dE) < 1000.0); diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index 1a02332..a1142c4 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -83,11 +83,12 @@ public: * @param to Where to travel to. * @param comment Description of the travel purpose. */ - std::string get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const; - std::string travel_to_xyz(const Vec3d &from, const Vec3d &to, const std::string_view comment = {}); + std::string get_travel_to_xyz_gcode(const Vec3d &to, const std::string_view comment) const; + std::string travel_to_xyz(const Vec3d &to, const std::string_view comment = {}); std::string get_travel_to_z_gcode(double z, const std::string_view comment) const; std::string travel_to_z(double z, const std::string_view comment = {}); std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); + std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); // std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); std::string retract(bool before_wipe = false); @@ -226,6 +227,10 @@ public: } void emit_e(const std::string_view axis, double v) { + const double precision{std::pow(10.0, -E_EXPORT_DIGITS)}; + if (std::abs(v) < precision) { + v = v < 0 ? -precision : precision; + } if (! axis.empty()) { // not gcfNoExtrusion this->emit_axis(axis[0], v, E_EXPORT_DIGITS); diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index bd7beb1..27f66de 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -79,8 +79,26 @@ void LabelObjects::init(const SpanOfConstPtrs& objects, LabelObject bool object_has_more_instances = print_instances.size() > 1u; int instance_id = int(std::find(model_object->instances.begin(), model_object->instances.end(), pi->model_instance) - model_object->instances.begin()); - // Now compose the name of the object and define whether indexing is 0 or 1-based. + // The limit in FW is 96 chars, OctoPrint may add no more than 12 chars at the end (checksum). + // QIDISlicer may add instance designation couple of lines below. Let's limit the name itself + // to 60 characters in all cases so we do not complicate it too much. std::string name = model_object->name; + const size_t len_lim = 60; + if (name.size() > len_lim) { + // Make sure that we tear no UTF-8 sequence apart. + auto is_utf8_start_byte = [](char c) { + return (c & 0b10000000) == 0 // ASCII byte (0xxxxxxx) + || (c & 0b11100000) == 0b11000000 // Start of 2-byte sequence + || (c & 0b11110000) == 0b11100000 // Start of 3-byte sequence + || (c & 0b11111000) == 0b11110000; // Start of 4-byte sequence + }; + size_t i = len_lim; + while (i > 0 && ! is_utf8_start_byte(name[i])) + --i; + name = name.substr(0,i) + "..."; + } + + // Now compose the name of the object and define whether indexing is 0 or 1-based. if (m_label_objects_style == LabelObjectsStyle::Octoprint) { // use zero-based indexing for objects and instances, as we always have done name += " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id); diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 73ac662..426bbcd 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -36,6 +36,11 @@ static constexpr int max_look_back_limit = 128; // Lines where some extruder pressure will remain (so we should equalize between these small travels). static constexpr double max_ignored_gap_between_extruding_segments = 3.; +// Minimum feedrate change that will be emitted into the G-code. +// Changes below this value will not be emitted into the G-code to filter out tiny changes +// of feedrate and reduce the size of the G-code. +static constexpr float min_emitted_feedrate_change = 0.20f * 60.f; + PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value) { // Preallocate some data, so that output_buffer.data() will return an empty string. @@ -319,8 +324,6 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo { // G0, G1: A FFF 3D printer does not make a difference between the two. buf.adjustable_flow = this->opened_extrude_set_speed_block; - buf.extrude_set_speed_tag = found_extrude_set_speed_tag; - buf.extrude_end_tag = found_extrude_end_tag; float new_pos[5]; memcpy(new_pos, m_current_pos, sizeof(float)*5); bool changed[5] = { false, false, false, false, false }; @@ -472,6 +475,30 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo return true; } +void PressureEqualizer::GCodeLine::update_end_position(const float *position_end, const bool *position_provided_original) +{ + assert(position_end != nullptr); + if (position_end == nullptr) + return; + + for (int i = 0; i < 4; ++i) { + this->pos_end[i] = position_end[i]; + this->pos_provided[i] = position_provided_original[i] || (this->pos_end[i] != this->pos_start[i]); + } +} + +void PressureEqualizer::GCodeLine::update_end_position(const float *position_start, const float *position_end, const float t, const bool *position_provided_original) +{ + assert(position_start != nullptr && position_end != nullptr); + if (position_start == nullptr || position_end == nullptr) + return; + + for (size_t i = 0; i < 4; ++i) { + this->pos_end[i] = position_start[i] + (position_end[i] - position_start[i]) * t; + this->pos_provided[i] = position_provided_original[i] || (this->pos_end[i] != this->pos_start[i]); + } +} + void PressureEqualizer::output_gcode_line(const size_t line_idx) { GCodeLine &line = m_gcode_lines[line_idx]; @@ -488,21 +515,27 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) comment = nullptr; // Emit the line with lowered extrusion rates. - float l = line.dist_xyz(); - if (auto nSegments = size_t(ceil(l / max_segment_length)); nSegments == 1) { // Just update this segment. + const float l = line.dist_xyz(); + const float feedrate_start = line.volumetric_extrusion_rate_start * line.feedrate() / line.volumetric_extrusion_rate; + const float feedrate_end = line.volumetric_extrusion_rate_end * line.feedrate() / line.volumetric_extrusion_rate; + const float feedrate_avg = 0.5f * (feedrate_start + feedrate_end); + if (std::abs(feedrate_avg - line.pos_end[4]) <= min_emitted_feedrate_change) { + // The average feedrate is close to the original feedrate, so we emit the line with the original feedrate. + push_line_to_output(line_idx, line.pos_end[4], comment); + } else if (auto nSegments = size_t(ceil(l / max_segment_length)); nSegments == 1) { // Just update this segment. push_line_to_output(line_idx, line.feedrate() * line.volumetric_correction_avg(), comment); } else { bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end; // Update the initial and final feed rate values. - line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate; - line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate; - float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]); + line.pos_start[4] = feedrate_start; + line.pos_end [4] = feedrate_end; + // Limiting volumetric extrusion rate slope for this segment. float max_volumetric_extrusion_rate_slope = accelerating ? line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative; // Total time for the segment, corrected for the possibly lowered volumetric feed rate, // if accelerating / decelerating over the complete segment. - float t_total = line.dist_xyz() / feed_avg; + float t_total = line.dist_xyz() / feedrate_avg; // Time of the acceleration / deceleration part of the segment, if accelerating / decelerating // with the maximum volumetric extrusion rate slope. float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope; @@ -510,7 +543,7 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) float l_steady = 0.f; if (t_acc < t_total) { // One may achieve higher print speeds if part of the segment is not speed limited. - l_acc = t_acc * feed_avg; + l_acc = t_acc * feedrate_avg; l_steady = l - l_acc; if (l_steady < 0.5f * max_segment_length) { l_acc = l; @@ -518,11 +551,15 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) } else nSegments = size_t(ceil(l_acc / max_segment_length)); } + float pos_start[5]; float pos_end[5]; float pos_end2[4]; memcpy(pos_start, line.pos_start, sizeof(float) * 5); memcpy(pos_end, line.pos_end, sizeof(float) * 5); + + bool pos_provided_original[5]; + memcpy(pos_provided_original, line.pos_provided, sizeof(bool) * 5); if (l_steady > 0.f) { // There will be a steady feed segment emitted. if (accelerating) { @@ -531,15 +568,11 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) float t = l_acc / l; for (int i = 0; i < 4; ++ i) { pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; - line.pos_provided[i] = true; } } else { // Emit the steady feed rate segment. - float t = l_steady / l; - for (int i = 0; i < 4; ++ i) { - line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; - line.pos_provided[i] = true; - } + const float t = l_steady / l; + line.update_end_position(pos_start, pos_end, t, pos_provided_original); push_line_to_output(line_idx, pos_start[4], comment); comment = nullptr; @@ -552,29 +585,23 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) pos_start[4] = new_pos_start_feedrate; } } + // Split the segment into pieces. for (size_t i = 1; i < nSegments; ++ i) { - float t = float(i) / float(nSegments); - for (size_t j = 0; j < 4; ++ j) { - line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t; - line.pos_provided[j] = true; - } + const float t = float(i) / float(nSegments); + line.update_end_position(pos_start, pos_end, t, pos_provided_original); + // Interpolate the feed rate at the center of the segment. push_line_to_output(line_idx, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); comment = nullptr; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); } + if (l_steady > 0.f && accelerating) { - for (int i = 0; i < 4; ++ i) { - line.pos_end[i] = pos_end2[i]; - line.pos_provided[i] = true; - } + line.update_end_position(pos_end2, pos_provided_original); push_line_to_output(line_idx, pos_end[4], comment); } else { - for (int i = 0; i < 4; ++ i) { - line.pos_end[i] = pos_end[i]; - line.pos_provided[i] = true; - } + line.update_end_position(pos_end, pos_provided_original); push_line_to_output(line_idx, pos_end[4], comment); } } diff --git a/src/libslic3r/GCode/PressureEqualizer.hpp b/src/libslic3r/GCode/PressureEqualizer.hpp index 5d4208a..1b2e698 100644 --- a/src/libslic3r/GCode/PressureEqualizer.hpp +++ b/src/libslic3r/GCode/PressureEqualizer.hpp @@ -173,8 +173,8 @@ private: bool adjustable_flow = false; - bool extrude_set_speed_tag = false; - bool extrude_end_tag = false; + void update_end_position(const float *position_end, const bool *position_provided_original); + void update_end_position(const float *position_start, const float *position_end, float t, const bool *position_provided_original); }; using GCodeLines = std::vector; diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index cb1e68f..60e9c6c 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -146,8 +146,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ // Wipe tower extrusions are saved as if the tower was at the origin with no rotation // We need to get position and angle of the wipe tower to transform them to actual position. Transform2d trafo = - Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) * - Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)); + Eigen::Translation2d(print.model().wipe_tower().position.x(), print.model().wipe_tower().position.y()) * + Eigen::Rotation2Dd(Geometry::deg2rad(print.model().wipe_tower().rotation)); BoundingBoxf bbox; for (const std::vector &tool_changes : print.wipe_tower_data().tool_changes) { diff --git a/src/libslic3r/GCode/SeamAligned.cpp b/src/libslic3r/GCode/SeamAligned.cpp index 7a16d0b..4572e5c 100644 --- a/src/libslic3r/GCode/SeamAligned.cpp +++ b/src/libslic3r/GCode/SeamAligned.cpp @@ -122,8 +122,8 @@ std::optional snap_to_angle( } return false; }}; - Geometry::visit_near_backward(search_start, positions.size(), visitor); - Geometry::visit_near_forward(search_start, positions.size(), visitor); + Geometry::visit_backward(search_start, positions.size(), visitor); + Geometry::visit_forward(search_start, positions.size(), visitor); if (match) { return match; } @@ -131,8 +131,8 @@ std::optional snap_to_angle( min_distance = std::numeric_limits::infinity(); angle_type = AngleType::concave; - Geometry::visit_near_backward(search_start, positions.size(), visitor); - Geometry::visit_near_forward(search_start, positions.size(), visitor); + Geometry::visit_backward(search_start, positions.size(), visitor); + Geometry::visit_forward(search_start, positions.size(), visitor); return match; } @@ -328,7 +328,6 @@ SeamCandidate get_seam_candidate( choice_visibilities[slice_index] > least_visible.visibility + params.jump_visibility_threshold}; const bool can_be_on_edge{ - !is_on_edge && perimeter.angle_types[least_visible.choice.next_index] != AngleType::smooth}; if (is_too_far || (can_be_on_edge && is_too_visible)) { candidate = least_visible.choice; diff --git a/src/libslic3r/GCode/SeamGeometry.cpp b/src/libslic3r/GCode/SeamGeometry.cpp index fbc4d07..47a9d60 100644 --- a/src/libslic3r/GCode/SeamGeometry.cpp +++ b/src/libslic3r/GCode/SeamGeometry.cpp @@ -66,7 +66,7 @@ Vec2d get_polygon_normal( std::optional previous_index; std::optional next_index; - visit_near_forward(index, points.size(), [&](const std::size_t index_candidate) { + visit_forward(index, points.size(), [&](const std::size_t index_candidate) { if (index == index_candidate) { return false; } @@ -77,7 +77,7 @@ Vec2d get_polygon_normal( } return false; }); - visit_near_backward(index, points.size(), [&](const std::size_t index_candidate) { + visit_backward(index, points.size(), [&](const std::size_t index_candidate) { const double distance{(points[index_candidate] - points[index]).norm()}; if (distance > min_arm_length) { previous_index = index_candidate; @@ -280,14 +280,14 @@ std::vector oversample_edge(const Vec2d &from, const Vec2d &to, const dou return result; } -void visit_near_forward( +void visit_forward( const std::size_t start_index, const std::size_t loop_size, const std::function &visitor ) { std::size_t last_index{loop_size - 1}; std::size_t index{start_index}; - for (unsigned _{0}; _ < 30; ++_) { // Do not visit too far. + for (unsigned _{0}; _ < loop_size + 1; ++_) { if (visitor(index)) { return; } @@ -295,14 +295,14 @@ void visit_near_forward( } } -void visit_near_backward( +void visit_backward( const std::size_t start_index, const std::size_t loop_size, const std::function &visitor ) { std::size_t last_index{loop_size - 1}; std::size_t index{start_index == 0 ? loop_size - 1 : start_index - 1}; - for (unsigned _{0}; _ < 30; ++_) { // Do not visit too far. + for (unsigned _{0}; _ < loop_size; ++_) { if (visitor(index)) { return; } @@ -374,7 +374,7 @@ std::vector get_vertex_angles(const std::vector &points, const do std::optional previous_index; std::optional next_index; - visit_near_forward(index, points.size(), [&](const std::size_t index_candidate) { + visit_forward(index, points.size(), [&](const std::size_t index_candidate) { if (index == index_candidate) { return false; } @@ -385,7 +385,7 @@ std::vector get_vertex_angles(const std::vector &points, const do } return false; }); - visit_near_backward(index, points.size(), [&](const std::size_t index_candidate) { + visit_backward(index, points.size(), [&](const std::size_t index_candidate) { const double distance{(points[index_candidate] - points[index]).norm()}; if (distance > min_arm_length) { previous_index = index_candidate; @@ -440,4 +440,57 @@ Polygon to_polygon(const ExtrusionLoop &loop) { } return Polygon{loop_points}; } + +std::optional offset_along_lines( + const Vec2d &point, + const std::size_t loop_line_index, + const Linesf &loop_lines, + const double offset, + const Direction1D direction +) { + const Linef initial_line{loop_lines[loop_line_index]}; + double distance{ + direction == Direction1D::forward ? (initial_line.b - point).norm() : + (point - initial_line.a).norm()}; + if (distance >= offset) { + const Vec2d edge_direction{(initial_line.b - initial_line.a).normalized()}; + const Vec2d offset_point{direction == Direction1D::forward ? Vec2d{point + offset * edge_direction} : Vec2d{point - offset * edge_direction}}; + return {{offset_point, loop_line_index}}; + } + + std::optional offset_point; + + bool skip_first{direction == Direction1D::forward}; + const auto visitor{[&](std::size_t index) { + if (skip_first) { + skip_first = false; + return false; + } + const Vec2d previous_point{ + direction == Direction1D::forward ? loop_lines[index].a : loop_lines[index].b}; + const Vec2d next_point{ + direction == Direction1D::forward ? loop_lines[index].b : loop_lines[index].a}; + const Vec2d edge{next_point - previous_point}; + + if (distance + edge.norm() > offset) { + const double remaining_distance{offset - distance}; + offset_point = + PointOnLine{previous_point + remaining_distance * edge.normalized(), index}; + return true; + } + + distance += edge.norm(); + + return false; + }}; + + if (direction == Direction1D::forward) { + Geometry::visit_forward(loop_line_index, loop_lines.size(), visitor); + } else { + Geometry::visit_backward(loop_line_index, loop_lines.size(), visitor); + } + + return offset_point; +} + } // namespace Slic3r::Seams::Geometry diff --git a/src/libslic3r/GCode/SeamGeometry.hpp b/src/libslic3r/GCode/SeamGeometry.hpp index c861e3d..9b5c1c4 100644 --- a/src/libslic3r/GCode/SeamGeometry.hpp +++ b/src/libslic3r/GCode/SeamGeometry.hpp @@ -147,12 +147,12 @@ void iterate_nested(const NestedVector &nested_vector, const std::function &visitor ); -void visit_near_backward( +void visit_backward( const std::size_t start_index, const std::size_t loop_size, const std::function &visitor @@ -192,6 +192,24 @@ std::pair pick_closest_bounding_box( Polygon to_polygon(const ExtrusionLoop &loop); +enum class Direction1D { + forward, + backward +}; + +struct PointOnLine{ + Vec2d point; + std::size_t line_index; +}; + +std::optional offset_along_lines( + const Vec2d &point, + const std::size_t loop_line_index, + const Linesf &loop_lines, + const double offset, + const Direction1D direction +); + } // namespace Slic3r::Seams::Geometry #endif // libslic3r_SeamGeometry_hpp_ diff --git a/src/libslic3r/GCode/SeamPerimeters.cpp b/src/libslic3r/GCode/SeamPerimeters.cpp index d8879f9..7971cdf 100644 --- a/src/libslic3r/GCode/SeamPerimeters.cpp +++ b/src/libslic3r/GCode/SeamPerimeters.cpp @@ -58,10 +58,7 @@ std::pair, std::vector> remove_redundant_points( const std::int64_t index{std::distance(points.begin(), iterator)}; if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) { std::vector simplification_result; - douglas_peucker( - range_start, next(iterator), std::back_inserter(simplification_result), tolerance, - [](const Vec2d &point) { return point; } - ); + douglas_peucker(range_start, next(iterator), std::back_inserter(simplification_result), tolerance); points_result.insert( points_result.end(), simplification_result.begin(), simplification_result.end() @@ -164,7 +161,7 @@ std::vector merge_angle_types( resulting_type = smooth_angle_type; // Check if there is a sharp angle in the vicinity. If so, do not use the smooth angle. - Geometry::visit_near_forward(index, angle_types.size(), [&](const std::size_t forward_index) { + Geometry::visit_forward(index, angle_types.size(), [&](const std::size_t forward_index) { const double distance{(points[forward_index] - points[index]).norm()}; if (distance > min_arm_length) { return true; @@ -174,7 +171,7 @@ std::vector merge_angle_types( } return false; }); - Geometry::visit_near_backward(index, angle_types.size(), [&](const std::size_t backward_index) { + Geometry::visit_backward(index, angle_types.size(), [&](const std::size_t backward_index) { const double distance{(points[backward_index] - points[index]).norm()}; if (distance > min_arm_length) { return true; diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 5a65ac7..889a6c5 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -3,6 +3,7 @@ #include "SeamPlacer.hpp" +#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/GCode/SeamShells.hpp" #include "libslic3r/GCode/SeamAligned.hpp" #include "libslic3r/GCode/SeamRear.hpp" @@ -123,7 +124,7 @@ Params Placer::get_params(const DynamicPrintConfig &config) { params.staggered_inner_seams = config.opt_bool("staggered_inner_seams"); params.max_nearest_detour = 1.0; - params.rear_tolerance = 0.2; + params.rear_tolerance = 1.0; params.rear_y_offset = 20; params.aligned.jump_visibility_threshold = 0.6; params.max_distance = 5.0; @@ -131,8 +132,8 @@ Params Placer::get_params(const DynamicPrintConfig &config) { params.perimeter.embedding_threshold = 0.5; params.perimeter.painting_radius = 0.1; params.perimeter.simplification_epsilon = 0.001; - params.perimeter.smooth_angle_arm_length = 0.2; - params.perimeter.sharp_angle_arm_length = 0.05; + params.perimeter.smooth_angle_arm_length = 0.5; + params.perimeter.sharp_angle_arm_length = 0.25; params.visibility.raycasting_visibility_samples_count = 30000; params.visibility.fast_decimation_triangle_count_target = 16000; @@ -188,10 +189,10 @@ const SeamPerimeterChoice &choose_closest_seam( } std::pair project_to_extrusion_loop( - const SeamChoice &seam_choice, const Perimeters::Perimeter &perimeter, const Linesf &loop_lines + const SeamChoice &seam_choice, + const Perimeters::Perimeter &perimeter, + const AABBTreeLines::LinesDistancer &distancer ) { - const AABBTreeLines::LinesDistancer distancer{loop_lines}; - const bool is_at_vertex{seam_choice.previous_index == seam_choice.next_index}; const Vec2d edge{ perimeter.positions[seam_choice.next_index] - @@ -209,65 +210,163 @@ std::pair project_to_extrusion_loop( return {loop_line_index, loop_point}; } -std::optional offset_along_loop_lines( - const Vec2d &point, - const std::size_t loop_line_index, - const Linesf &loop_lines, - const double offset -) { - double distance{0}; - Vec2d previous_point{point}; - std::optional offset_point; - Geometry::visit_near_forward(loop_line_index, loop_lines.size(), [&](std::size_t index) { - const Vec2d next_point{loop_lines[index].b}; - const Vec2d edge{next_point - previous_point}; - - if (distance + edge.norm() > offset) { - const double remaining_distance{offset - distance}; - offset_point = previous_point + remaining_distance * edge.normalized(); - return true; - } - - distance += edge.norm(); - previous_point = next_point; - - return false; - }); - - return offset_point; -} - double get_angle(const SeamChoice &seam_choice, const Perimeters::Perimeter &perimeter) { const bool is_at_vertex{seam_choice.previous_index == seam_choice.next_index}; return is_at_vertex ? perimeter.angles[seam_choice.previous_index] : 0.0; } -Point finalize_seam_position( - const Polygon &loop_polygon, - const SeamChoice &seam_choice, - const Perimeters::Perimeter &perimeter, - const double loop_width, - const bool do_staggering +SeamChoice to_seam_choice( + const Geometry::PointOnLine &point_on_line, const Perimeters::Perimeter &perimeter ) { + SeamChoice result; + + result.position = point_on_line.point; + result.previous_index = point_on_line.line_index; + result.next_index = point_on_line.line_index == perimeter.positions.size() - 1 ? + 0 : + point_on_line.line_index + 1; + return result; +} + +boost::variant finalize_seam_position( + const ExtrusionLoop &loop, + const PrintRegion *region, + SeamChoice seam_choice, + const Perimeters::Perimeter &perimeter, + const bool staggered_inner_seams, + const bool flipped +) { + const Polygon loop_polygon{Geometry::to_polygon(loop)}; + const bool do_staggering{staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter}; + const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()}; + + const ExPolygon perimeter_polygon{Geometry::scaled(perimeter.positions)}; + const Linesf perimeter_lines{to_unscaled_linesf({perimeter_polygon})}; const Linesf loop_lines{to_unscaled_linesf({ExPolygon{loop_polygon}})}; - const auto [loop_line_index, loop_point]{ - project_to_extrusion_loop(seam_choice, perimeter, loop_lines)}; + const AABBTreeLines::LinesDistancer distancer{loop_lines}; + + auto [loop_line_index, loop_point]{ + project_to_extrusion_loop(seam_choice, perimeter, distancer)}; + + const Geometry::Direction1D offset_direction{ + flipped ? Geometry::Direction1D::forward : Geometry::Direction1D::backward}; // ExtrusionRole::Perimeter is inner perimeter. if (do_staggering) { const double depth = (loop_point - seam_choice.position).norm() - loop_width / 2.0; - const double angle{get_angle(seam_choice, perimeter)}; - const double initial_offset{angle > 0 ? angle / 2.0 * depth : 0.0}; - const double additional_offset{angle < 0 ? std::cos(angle / 2.0) * depth : depth}; - const double staggering_offset{initial_offset + additional_offset}; + const double staggering_offset{depth}; - std::optional staggered_point{ - offset_along_loop_lines(loop_point, loop_line_index, loop_lines, staggering_offset)}; + std::optional staggered_point{Geometry::offset_along_lines( + loop_point, seam_choice.previous_index, perimeter_lines, staggering_offset, + offset_direction + )}; if (staggered_point) { - return scaled(*staggered_point); + seam_choice = to_seam_choice(*staggered_point, perimeter); + std::tie(loop_line_index, loop_point) = project_to_extrusion_loop(seam_choice, perimeter, distancer); + } + } + + bool place_scarf_seam { + region->config().scarf_seam_placement == ScarfSeamPlacement::everywhere + || (region->config().scarf_seam_placement == ScarfSeamPlacement::countours && !perimeter.is_hole) + }; + const bool is_smooth{ + seam_choice.previous_index != seam_choice.next_index || + perimeter.angle_types[seam_choice.previous_index] == Perimeters::AngleType::smooth + }; + + if (region->config().scarf_seam_only_on_smooth && !is_smooth) { + place_scarf_seam = false; + } + + if (region->config().scarf_seam_length.value <= std::numeric_limits::epsilon()) { + place_scarf_seam = false; + } + + if (place_scarf_seam) { + Scarf::Scarf scarf{}; + scarf.entire_loop = region->config().scarf_seam_entire_loop; + scarf.max_segment_length = region->config().scarf_seam_max_segment_length; + scarf.start_height = std::min(region->config().scarf_seam_start_height.get_abs_value(1.0), 1.0); + + const double offset{scarf.entire_loop ? 0.0 : region->config().scarf_seam_length.value}; + const std::optional outter_scarf_start_point{Geometry::offset_along_lines( + seam_choice.position, + seam_choice.previous_index, + perimeter_lines, + offset, + offset_direction + )}; + if (!outter_scarf_start_point) { + return scaled(loop_point); + } + + if (loop.role() != ExtrusionRole::Perimeter) { // Outter perimeter + scarf.start_point = scaled(project_to_extrusion_loop( + to_seam_choice(*outter_scarf_start_point, perimeter), + perimeter, + distancer + ).second); + scarf.end_point = scaled(loop_point); + scarf.end_point_previous_index = loop_line_index; + return scarf; + } else { + Geometry::PointOnLine inner_scarf_end_point{ + *outter_scarf_start_point + }; + + if (region->config().external_perimeters_first.value) { + const auto external_first_offset_direction{ + offset_direction == Geometry::Direction1D::forward ? + Geometry::Direction1D::backward : + Geometry::Direction1D::forward + }; + if (auto result{Geometry::offset_along_lines( + seam_choice.position, + seam_choice.previous_index, + perimeter_lines, + offset, + external_first_offset_direction + )}) { + inner_scarf_end_point = *result; + } else { + return scaled(seam_choice.position); + } + } + + if (!region->config().scarf_seam_on_inner_perimeters) { + return scaled(inner_scarf_end_point.point); + } + + const std::optional inner_scarf_start_point{Geometry::offset_along_lines( + inner_scarf_end_point.point, + inner_scarf_end_point.line_index, + perimeter_lines, + offset, + offset_direction + )}; + + if (!inner_scarf_start_point) { + return scaled(inner_scarf_end_point.point); + } + + scarf.start_point = scaled(project_to_extrusion_loop( + to_seam_choice(*inner_scarf_start_point, perimeter), + perimeter, + distancer + ).second); + + const auto [end_point_previous_index, end_point]{project_to_extrusion_loop( + to_seam_choice(inner_scarf_end_point, perimeter), + perimeter, + distancer + )}; + scarf.end_point = scaled(end_point); + scarf.end_point_previous_index = end_point_previous_index; + return scarf; } } @@ -347,7 +446,9 @@ int get_perimeter_count(const Layer *layer){ return count; } -Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const { +boost::variant Placer::place_seam( + const Layer *layer, const PrintRegion *region, const ExtrusionLoop &loop, const bool flipped, const Point &last_pos +) const { const PrintObject *po = layer->object(); // Must not be called with supprot layer. assert(dynamic_cast(layer) == nullptr); @@ -355,16 +456,15 @@ Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Po assert(layer->id() >= po->slicing_parameters().raft_layers()); const size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); - const Polygon loop_polygon{Geometry::to_polygon(loop)}; - - const bool do_staggering{this->params.staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter}; - const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()}; - - if (po->config().seam_position.value == spNearest) { - const std::vector &perimeters{this->perimeters_per_layer.at(po)[layer_index]}; - const auto [seam_choice, perimeter_index] = place_seam_near(perimeters, loop, last_pos, this->params.max_nearest_detour); - return finalize_seam_position(loop_polygon, seam_choice, perimeters[perimeter_index].perimeter, loop_width, do_staggering); + const std::vector &perimeters{ + this->perimeters_per_layer.at(po)[layer_index]}; + const auto [seam_choice, perimeter_index] = + place_seam_near(perimeters, loop, last_pos, this->params.max_nearest_detour); + return finalize_seam_position( + loop, region, seam_choice, perimeters[perimeter_index].perimeter, + this->params.staggered_inner_seams, flipped + ); } else { const std::vector &seams_on_perimeters{this->seams_per_object.at(po)[layer_index]}; @@ -380,14 +480,18 @@ Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Po seams_on_perimeters[0].perimeter.is_hole ? seams_on_perimeters[1] : seams_on_perimeters[0]}; return finalize_seam_position( - loop_polygon, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, - loop_width, do_staggering + loop, region, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, + this->params.staggered_inner_seams, flipped ); } } - const SeamPerimeterChoice &seam_perimeter_choice{choose_closest_seam(seams_on_perimeters, loop_polygon)}; - return finalize_seam_position(loop_polygon, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, loop_width, do_staggering); + const SeamPerimeterChoice &seam_perimeter_choice{ + choose_closest_seam(seams_on_perimeters, Geometry::to_polygon(loop))}; + return finalize_seam_position( + loop, region, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, + this->params.staggered_inner_seams, flipped + ); } } } // namespace Slic3r::Seams diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 5f3efb8..b195fce 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -7,6 +7,7 @@ #include #include "libslic3r/GCode/SeamAligned.hpp" +#include "libslic3r/GCode/SeamScarf.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/Point.hpp" @@ -32,7 +33,7 @@ struct Params double concave_visibility_modifier{}; Perimeters::PerimeterParams perimeter; Slic3r::ModelInfo::Visibility::Params visibility; - bool staggered_inner_seams; + bool staggered_inner_seams{}; }; std::ostream& operator<<(std::ostream& os, const Params& params); @@ -48,7 +49,9 @@ public: const std::function &throw_if_canceled ); - Point place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const; + boost::variant place_seam( + const Layer *layer, const PrintRegion *region, const ExtrusionLoop &loop, const bool flipped, const Point &last_pos + ) const; private: Params params; diff --git a/src/libslic3r/GCode/SeamRear.cpp b/src/libslic3r/GCode/SeamRear.cpp index f17dd79..727315f 100644 --- a/src/libslic3r/GCode/SeamRear.cpp +++ b/src/libslic3r/GCode/SeamRear.cpp @@ -59,16 +59,28 @@ struct RearestPointCalculator { const Vec2d prefered_position{center_x, bounding_box.max.y() + rear_y_offset}; auto [_, line_index, point] = possible_distancer.distance_from_lines_extra(prefered_position); const Vec2d location_at_bb{center_x, bounding_box.max.y()}; - auto [_d, line_index_at_bb, point_at_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); - const double y_distance{point.y() - point_at_bb.y()}; + auto [_d, line_index_at_bb, point_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); + const double y_distance{point.y() - point_bb.y()}; Vec2d result{point}; if (y_distance < 0) { - result = point_at_bb; + result = point_bb; } else if (y_distance <= rear_tolerance) { const double factor{y_distance / rear_tolerance}; - result = factor * point + (1 - factor) * point_at_bb; + result = factor * point + (1 - factor) * point_bb; } + + if (bounding_box.max.y() - result.y() > rear_tolerance) { + for (const PerimeterLine &line : possible_lines) { + if (line.a.y() > result.y()) { + result = line.a; + } + if (line.b.y() > result.y()) { + result = line.b; + } + } + } + return SeamChoice{possible_lines[line_index].previous_index, possible_lines[line_index].next_index, result}; } }; diff --git a/src/libslic3r/GCode/SeamScarf.cpp b/src/libslic3r/GCode/SeamScarf.cpp new file mode 100644 index 0000000..c3bab18 --- /dev/null +++ b/src/libslic3r/GCode/SeamScarf.cpp @@ -0,0 +1,370 @@ +#include "libslic3r/GCode/SeamScarf.hpp" +#include "libslic3r/GCode/SmoothPath.hpp" + +namespace Slic3r::Seams::Scarf { + +namespace Impl { +PathPoint get_path_point( + const ExtrusionPaths &paths, const Point &point, const std::size_t global_index +) { + std::size_t path_start_index{0}; + for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) { + const ExtrusionPath &path{paths[path_index]}; + if (global_index - path_start_index < path.size()) { + return {point, path_index, global_index - path_start_index}; + } + path_start_index += path.size(); + } + throw std::runtime_error("Failed translating global path index!"); +} + +std::pair split_path( + const ExtrusionPath &path, const Point &point, const std::size_t point_previous_index +) { + if (static_cast(point_previous_index) >= static_cast(path.size()) - 1) { + throw std::runtime_error( + "Invalid path split index " + std::to_string(point_previous_index) + + " for path of size " + std::to_string(path.size()) + "!" + ); + } + const Point previous_point{path.polyline.points.at(point_previous_index)}; + const Point next_point{path.polyline.points.at(point_previous_index + 1)}; + + Polyline first; + for (std::size_t i{0}; i <= point_previous_index; ++i) { + first.points.push_back(path.polyline[i]); + } + first.points.push_back(point); + Polyline second; + second.points.push_back(point); + + for (std::size_t i{point_previous_index + 1}; i < path.size(); ++i) { + second.points.push_back(path.polyline[i]); + } + + return {ExtrusionPath{first, path.attributes()}, ExtrusionPath{second, path.attributes()}}; +} + +ExtrusionPaths split_paths(ExtrusionPaths &&paths, const PathPoint &path_point) { + ExtrusionPaths result{std::move(paths)}; + std::pair split{ + split_path(result[path_point.path_index], path_point.point, path_point.previous_point_on_path_index)}; + + auto path_iterator{result.begin() + path_point.path_index}; + path_iterator = result.erase(path_iterator); + path_iterator = result.insert(path_iterator, split.second); + result.insert(path_iterator, split.first); + + return result; +} + +double get_length(tcb::span smooth_path) { + if (smooth_path.empty() || smooth_path.front().path.empty()) { + return 0; + } + + double result{0}; + + Point previous_point{smooth_path.front().path.front().point}; + + for (const GCode::SmoothPathElement &element : smooth_path) { + for (const Geometry::ArcWelder::Segment &segment : element.path) { + result += (segment.point - previous_point).cast().norm(); + previous_point = segment.point; + } + } + return result; +} + +GCode::SmoothPath convert_to_smooth(tcb::span paths) { + GCode::SmoothPath result; + for (const ExtrusionPath &path : paths) { + Geometry::ArcWelder::Path smooth_path; + for (const Point &point : path.polyline) { + smooth_path.push_back(Geometry::ArcWelder::Segment{point}); + } + result.push_back({path.attributes(), smooth_path}); + } + + return result; +} + +Points linspace(const Point &from, const Point &to, const std::size_t count) { + if (count < 2) { + throw std::runtime_error("Invalid count for linspace!"); + } + + Points result; + result.push_back(from); + + Point offset{(to - from) / (count - 1)}; + for (std::size_t i{1}; i < count - 1; ++i) { + result.push_back(from + i * offset); + } + result.push_back(to); + + return result; +} + +Points ensure_max_distance(const Points &points, const double max_distance) { + if (points.size() < 2) { + return points; + } + + Points result; + result.push_back(points.front()); + for (std::size_t i{1}; i < points.size(); ++i) { + const Point ¤t_point{points[i]}; + const Point &previous_point{points[i - 1]}; + const double distance = (current_point - previous_point).cast().norm(); + + if (distance > max_distance) { + const std::size_t points_count{ + static_cast(std::ceil(distance / max_distance)) + 1}; + const Points subdivided{linspace(previous_point, current_point, points_count)}; + result.insert(result.end(), std::next(subdivided.begin()), subdivided.end()); + } else { + result.push_back(current_point); + } + } + return result; +} + +ExtrusionPaths ensure_scarf_resolution( + ExtrusionPaths &&paths, const std::size_t scarf_paths_count, const double max_distance +) { + ExtrusionPaths result{std::move(paths)}; + auto scarf{tcb::span{result}.first(scarf_paths_count)}; + + for (ExtrusionPath &path : scarf) { + path.polyline.points = ensure_max_distance(path.polyline.points, max_distance); + } + + return result; +} + +GCode::SmoothPath lineary_increase_extrusion_height( + GCode::SmoothPath &&smooth_path, const double start_height +) { + GCode::SmoothPath result{std::move(smooth_path)}; + const double length{get_length(result)}; + double distance{0}; + + std::optional previous_point{}; + for (GCode::SmoothPathElement &element : result) { + for (Geometry::ArcWelder::Segment &segment : element.path) { + if (!previous_point) { + segment.e_fraction = 0; + segment.height_fraction = start_height; + } else { + distance += (segment.point - *previous_point).cast().norm(); + + if (distance >= length) { + segment.e_fraction = 1.0; + segment.height_fraction = 1.0; + } else { + segment.e_fraction = distance / length; + segment.height_fraction = start_height + (distance / length) * (1.0 - start_height); + } + } + previous_point = segment.point; + } + } + + return result; +} + +GCode::SmoothPath lineary_readuce_extrusion_amount( + GCode::SmoothPath &&smooth_path +) { + GCode::SmoothPath result{std::move(smooth_path)}; + const double length{get_length(result)}; + double distance{0}; + + std::optional previous_point{}; + for (GCode::SmoothPathElement &element : result) { + for (Geometry::ArcWelder::Segment &segment : element.path) { + if (!previous_point) { + segment.e_fraction = 1.0; + } else { + distance += (segment.point - *previous_point).cast().norm(); + + if (distance >= length) { + segment.e_fraction = 0.0; + } else { + segment.e_fraction = 1.0 - distance / length; + } + } + previous_point = segment.point; + } + } + + return result; +} + +GCode::SmoothPath elevate_scarf( + const ExtrusionPaths &paths, + const std::size_t scarf_paths_count, + const std::function)> &apply_smoothing, + const double start_height +) { + const auto scarf_at_start{tcb::span{paths}.first(scarf_paths_count)}; + GCode::SmoothPath first_segment{convert_to_smooth(scarf_at_start)}; + first_segment = + lineary_increase_extrusion_height(std::move(first_segment), start_height); + + std::size_t normal_extrusions_size{paths.size() - 2 * scarf_paths_count}; + const auto normal_extrusions{ + tcb::span{paths}.subspan(scarf_paths_count, normal_extrusions_size)}; + const GCode::SmoothPath middle_segment{apply_smoothing(normal_extrusions)}; + + const auto scarf_at_end{tcb::span{paths}.last(scarf_paths_count)}; + GCode::SmoothPath last_segment{convert_to_smooth(scarf_at_end)}; + last_segment = + lineary_readuce_extrusion_amount(std::move(last_segment)); + + first_segment.insert(first_segment.end(), middle_segment.begin(), middle_segment.end()); + first_segment.insert(first_segment.end(), last_segment.begin(), last_segment.end()); + + return first_segment; +} + +bool is_on_line(const Point &point, const Line &line, const double tolerance) { + return line.distance_to_squared(point) < tolerance * tolerance; +} + +std::optional find_path_point_from_end( + const ExtrusionPaths &paths, + const Point &point, + const double tolerance +) { + if (paths.empty()) { + return std::nullopt; + } + for (int path_index{static_cast(paths.size() - 1)}; path_index >= 0; --path_index) { + const ExtrusionPath &path{paths[path_index]}; + if (path.polyline.size() < 2) { + throw std::runtime_error( + "Invalid path: less than two points: " + std::to_string(path.size()) + "!" + ); + } + for (int point_index{static_cast(path.polyline.size() - 2)}; point_index >= 0; + --point_index) { + const Point &previous_point{path.polyline[point_index + 1]}; + const Point ¤t_point{path.polyline[point_index]}; + const Line line{previous_point, current_point}; + if (is_on_line(point, line, tolerance)) { + return PathPoint{ + point, + static_cast(path_index), static_cast(point_index) + }; + } + } + } + return std::nullopt; +} + +std::optional get_point_offset_from_end(const ExtrusionPaths &paths, const double length) { + double distance{0.0}; + + if (paths.empty()) { + return std::nullopt; + } + for (int path_index{static_cast(paths.size() - 1)}; path_index >= 0; --path_index) { + const ExtrusionPath &path{paths[path_index]}; + if (path.polyline.size() < 2) { + throw std::runtime_error( + "Invalid path: less than two points: " + std::to_string(path.size()) + "!" + ); + } + for (int point_index{static_cast(path.polyline.size() - 2)}; point_index >= 0; + --point_index) { + const Point &previous_point{path.polyline[point_index + 1]}; + const Point ¤t_point{path.polyline[point_index]}; + const Vec2d edge{(current_point - previous_point).cast()}; + const double edge_length{edge.norm()}; + const Vec2d edge_direction{edge.normalized()}; + if (distance + edge_length > length) { + return PathPoint{ + previous_point + (edge_direction * (length - distance)).cast(), + static_cast(path_index), static_cast(point_index)}; + } + distance += edge_length; + } + } + return std::nullopt; +} + +ExtrusionPaths reverse(ExtrusionPaths &&paths) { + ExtrusionPaths result{std::move(paths)}; + std::reverse(result.begin(), result.end()); + for (ExtrusionPath &path : result) { + std::reverse(path.polyline.begin(), path.polyline.end()); + } + return result; +} +} // namespace Impl + +std::pair add_scarf_seam( + ExtrusionPaths &&paths, + const Scarf &scarf, + const std::function)> &apply_smoothing, + const bool flipped +) { + Impl::PathPoint end_point{ + Impl::get_path_point(paths, scarf.end_point, scarf.end_point_previous_index)}; + + const ExtrusionPath &path{paths[end_point.path_index]}; + if (end_point.previous_point_on_path_index == static_cast(path.size()) - 1) { + // Last point of the path is picked. This is invalid for splitting. + if (static_cast(end_point.path_index) < static_cast(paths.size()) - 2) { + // First point of next path and last point of previous path should be the same. + // Pick the first point of the next path. + end_point = {end_point.point, end_point.path_index + 1, 0}; + } else { + // There is no next path. + // This should be very rare case. + if (end_point.previous_point_on_path_index == 0) { + throw std::runtime_error("Could not split path!"); + } + end_point = {end_point.point, end_point.path_index, end_point.previous_point_on_path_index - 1}; + } + } + + paths = split_paths(std::move(paths), end_point); + + // End with scarf. + std::rotate(paths.begin(), std::next(paths.begin(), end_point.path_index + 1), paths.end()); + + if (flipped) { + paths = Impl::reverse(std::move(paths)); + } + + std::optional start_point; + if (!scarf.entire_loop) { + const double tolerance{scaled(1e-2 /* mm */)}; + start_point = Impl::find_path_point_from_end(paths, scarf.start_point, tolerance); + } + if (!start_point) { + start_point = Impl::PathPoint{ + paths.front().first_point(), + 0, + 0 + }; + } + paths = split_paths(std::move(paths), *start_point); + + const std::size_t scarf_paths_count{paths.size() - start_point->path_index - 1}; + // Start with scarf. + std::rotate(paths.begin(), std::next(paths.begin(), start_point->path_index + 1), paths.end()); + + const double max_distance{scale_(scarf.max_segment_length)}; + paths = Impl::ensure_scarf_resolution(std::move(paths), scarf_paths_count, max_distance); + // This reserve protects agains iterator invalidation. + paths.reserve(paths.size() + scarf_paths_count); + std::copy_n(paths.begin(), scarf_paths_count, std::back_inserter(paths)); + + GCode::SmoothPath smooth_path{Impl::elevate_scarf(paths, scarf_paths_count, apply_smoothing, scarf.start_height)}; + return {std::move(smooth_path), scarf_paths_count}; +} +} // namespace Slic3r::Seams::Scarf diff --git a/src/libslic3r/GCode/SeamScarf.hpp b/src/libslic3r/GCode/SeamScarf.hpp new file mode 100644 index 0000000..c014e92 --- /dev/null +++ b/src/libslic3r/GCode/SeamScarf.hpp @@ -0,0 +1,91 @@ +#ifndef libslic3r_SeamScarf_hpp_ +#define libslic3r_SeamScarf_hpp_ + +#include "libslic3r/ExtrusionEntity.hpp" +#include "tcbspan/span.hpp" + +namespace Slic3r::GCode { + struct SmoothPathElement; + using SmoothPath = std::vector; +} + +namespace Slic3r::Seams::Scarf { + +struct Scarf +{ + Point start_point; + Point end_point; + std::size_t end_point_previous_index{}; + double max_segment_length{}; + bool entire_loop{}; + double start_height{}; +}; + +using SmoothingFunction = std::function)>; + +namespace Impl { +struct PathPoint +{ + Point point; + std::size_t path_index{}; + std::size_t previous_point_on_path_index{}; +}; + +PathPoint get_path_point( + const ExtrusionPaths &paths, const Point &point, const std::size_t global_index +); + +std::pair split_path( + const ExtrusionPath &path, const Point &point, const std::size_t point_previous_index +); + +ExtrusionPaths split_paths(ExtrusionPaths &&paths, const PathPoint &path_point); + +double get_length(tcb::span smooth_path); + +GCode::SmoothPath convert_to_smooth(tcb::span paths); + +/** + * @param count: Points count including the first and last point. + */ +Points linspace(const Point &from, const Point &to, const std::size_t count); + +Points ensure_max_distance(const Points &points, const double max_distance); + +ExtrusionPaths ensure_scarf_resolution( + ExtrusionPaths &&paths, const std::size_t scarf_paths_count, const double max_distance +); + +GCode::SmoothPath lineary_increase_extrusion_height( + GCode::SmoothPath &&smooth_path, const double start_height +); + +GCode::SmoothPath lineary_readuce_extrusion_amount( + GCode::SmoothPath &&smooth_path +); + +GCode::SmoothPath elevate_scarf( + const ExtrusionPaths &paths, + const std::size_t scarf_paths_count, + const SmoothingFunction &apply_smoothing, + const double start_height +); + +std::optional get_point_offset_from_end(const ExtrusionPaths &paths, const double length); + +std::optional find_path_point_from_end( + const ExtrusionPaths &paths, + const Point &point, + const double tolerance +); +} // namespace Impl + +std::pair add_scarf_seam( + ExtrusionPaths &&paths, + const Scarf &scarf, + const SmoothingFunction &apply_smoothing, + const bool flipped +); +} // namespace Slic3r::Seams::Scarf + +#endif // libslic3r_SeamScarf_hpp_ diff --git a/src/libslic3r/GCode/SmoothPath.cpp b/src/libslic3r/GCode/SmoothPath.cpp index 54ba3c1..c08c666 100644 --- a/src/libslic3r/GCode/SmoothPath.cpp +++ b/src/libslic3r/GCode/SmoothPath.cpp @@ -187,15 +187,15 @@ Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &p return out; } -SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const +SmoothPath SmoothPathCache::resolve_or_fit(tcb::span paths, bool reverse, double resolution) const { SmoothPath out; out.reserve(paths.size()); if (reverse) { - for (auto it = paths.crbegin(); it != paths.crend(); ++ it) + for (auto it = paths.rbegin(); it != paths.rend(); ++ it) out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) }); } else { - for (auto it = paths.cbegin(); it != paths.cend(); ++ it) + for (auto it = paths.begin(); it != paths.end(); ++ it) out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) }); } return out; @@ -203,14 +203,14 @@ SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool rev SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const { - return this->resolve_or_fit(multipath.paths, reverse, resolution); + return this->resolve_or_fit(tcb::span{multipath.paths}, reverse, resolution); } SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam( const ExtrusionLoop &loop, const bool reverse, const double resolution, const Point &seam_point, const double seam_point_merge_distance_threshold) const { - SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution); + SmoothPath out = this->resolve_or_fit(tcb::span{loop.paths}, reverse, resolution); assert(! out.empty()); if (! out.empty()) { // Find a closest point on a vector of smooth paths. diff --git a/src/libslic3r/GCode/SmoothPath.hpp b/src/libslic3r/GCode/SmoothPath.hpp index f4240e8..61ccd91 100644 --- a/src/libslic3r/GCode/SmoothPath.hpp +++ b/src/libslic3r/GCode/SmoothPath.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../ExtrusionEntity.hpp" #include "../Geometry/ArcWelder.hpp" @@ -59,7 +60,7 @@ public: Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const; // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. - SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const; + SmoothPath resolve_or_fit(tcb::span paths, bool reverse, double resolution) const; SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const; // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. @@ -78,7 +79,7 @@ public: SmoothPathCaches() = delete; SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) : m_global(&global), m_layer_local(&layer_local) {} - SmoothPathCaches operator=(const SmoothPathCaches &rhs) + SmoothPathCaches& operator=(const SmoothPathCaches &rhs) { m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; } const SmoothPathCache& global() const { return *m_global; } diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp index f82d61c..344312e 100644 --- a/src/libslic3r/GCode/ThumbnailData.hpp +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -25,7 +25,7 @@ using ThumbnailsList = std::vector; struct ThumbnailsParams { - const Vec2ds sizes; + Vec2ds sizes; bool printable_only; bool parts_only; bool show_bed; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 95b40a6..ac65ce6 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -160,17 +160,17 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool std::vector> per_layer_extruder_switches; auto num_extruders = unsigned(print.config().nozzle_diameter.size()); if (num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle - print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) { + print.model().custom_gcode_per_print_z().mode == CustomGCode::MultiAsSingle) { // Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material). // There may be custom per-layer tool changes available at the model. - per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders); + per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z(), num_extruders); } // Color changes for each layer to determine which extruder needs to be picked before color change. // This is done just for multi-extruder printers without enabled Single Extruder Multi Material (tool changer printers). std::vector> per_layer_color_changes; - if (num_extruders > 1 && print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) { - per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z, num_extruders); + if (num_extruders > 1 && print.model().custom_gcode_per_print_z().mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) { + per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z(), num_extruders); } // Collect extruders required to print the layers. @@ -607,7 +607,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print) // Only valid for non-sequential print. assert(! print.config().complete_objects.value); - const CustomGCode::Info &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; + const CustomGCode::Info &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z(); if (custom_gcode_per_print_z.gcodes.empty()) return; @@ -615,7 +615,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print) CustomGCode::Mode mode = (num_extruders == 1) ? CustomGCode::SingleExtruder : print.object_extruders().size() == 1 ? CustomGCode::MultiAsSingle : CustomGCode::MultiExtruder; - CustomGCode::Mode model_mode = print.model().custom_gcode_per_print_z.mode; + CustomGCode::Mode model_mode = print.model().custom_gcode_per_print_z().mode; std::vector extruder_printing_above(num_extruders, false); auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin(); // Tool changes and color changes will be ignored, if the model's tool/color changes were entered in mm mode and the print is in non mm mode diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 9da69b9..b4a979c 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -456,7 +456,6 @@ Points3 generate_travel_to_extrusion( ElevatedTravelFormula{elevation_params} )}; - result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation)); return result; } } // namespace Slic3r::GCode::Impl::Travels diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index e2bfdd4..4cd5dec 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -534,11 +534,10 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, -WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector>& wiping_matrix, size_t initial_tool) : +WipeTower::WipeTower(const Vec2f& pos, double rotation_deg, const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector>& wiping_matrix, size_t initial_tool) : m_semm(config.single_extruder_multi_material.value), - m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y), + m_wipe_tower_pos(pos), m_wipe_tower_width(float(config.wipe_tower_width)), - m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)), m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)), m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)), m_extra_flow(float(config.wipe_tower_extra_flow/100.)), @@ -580,7 +579,8 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default m_set_extruder_trimpot = config.high_current_on_filament_swap; } - m_is_mk4mmu3 = boost::icontains(config.printer_notes.value, "PRINTER_MODEL_MK4") && boost::icontains(config.printer_notes.value, "MMU"); + m_is_mk4mmu3 = boost::icontains(config.printer_notes.value, "PRINTER_MODEL_MK4") && boost::icontains(config.printer_notes.value, "MMU"); + m_switch_filament_monitoring = m_is_mk4mmu3 || is_XL_printer(config); // Calculate where the priming lines should be - very naive test not detecting parallelograms etc. const std::vector& bed_points = config.bed_shape.values; @@ -929,7 +929,7 @@ void WipeTower::toolchange_Unload( } } - if (m_is_mk4mmu3) { + if (m_switch_filament_monitoring) { writer.switch_filament_monitoring(false); writer.wait(1.5f); } @@ -1078,7 +1078,7 @@ void WipeTower::toolchange_Change( //writer.append("[end_filament_gcode]\n"); writer.append("[toolchange_gcode_from_wipe_tower_generator]\n"); - if (m_is_mk4mmu3) + if (m_switch_filament_monitoring) writer.switch_filament_monitoring(true); // Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc) diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 14b54a7..020c611 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -135,7 +135,9 @@ public: // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm - WipeTower(const PrintConfig& config, + WipeTower(const Vec2f& position, + double rotation_deg, + const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector>& wiping_matrix, size_t initial_tool); @@ -271,6 +273,7 @@ private: bool m_semm = true; // Are we using a single extruder multimaterial printer? bool m_is_mk4mmu3 = false; + bool m_switch_filament_monitoring = false; Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower @@ -278,7 +281,6 @@ private: float m_wipe_tower_cone_angle = 0.f; float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation - float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) float m_internal_rotation = 0.f; float m_y_shift = 0.f; // y shift passed to writer float m_z_pos = 0.f; // Current Z position. diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 0b82e03..5fc3d18 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -63,17 +63,18 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || will_go_down); // don't dig into the print if (should_travel_to_tower) { const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); + const Vec3crd to{to_3d(xy_point, scaled(z))}; gcode += gcodegen.m_label_objects.maybe_stop_instance(); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; const std::string comment{"Travel to a Wipe Tower"}; if (!gcodegen.m_moved_to_first_layer_point) { - const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";}); + gcode += gcodegen.travel_to_first_position(to, current_z, ExtrusionRole::Mixed, [](){return "";}); } else { if (gcodegen.last_position) { + const Vec3crd from{to_3d(*gcodegen.last_position, scaled(current_z))}; gcode += gcodegen.travel_to( - *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";} + from, to, ExtrusionRole::Mixed, comment, [](){return "";} ); } else { gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); diff --git a/src/libslic3r/GCode/WipeTowerIntegration.hpp b/src/libslic3r/GCode/WipeTowerIntegration.hpp index beb5614..6d7369e 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.hpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.hpp @@ -20,14 +20,16 @@ namespace GCode { class WipeTowerIntegration { public: WipeTowerIntegration( + Vec2f pos, + double rotation, const PrintConfig &print_config, const std::vector &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : - m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), - m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), - m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), - m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), + m_left( 0.f), + m_right(float(print_config.wipe_tower_width.value)), + m_wipe_tower_pos(pos), + m_wipe_tower_rotation(rotation), m_extruder_offsets(print_config.extruder_offset.values), m_priming(priming), m_tool_changes(tool_changes), diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 34e5983..4f047c9 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -98,8 +98,6 @@ static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance) { // The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated. - assert(std::abs((*begin - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); - assert(std::abs((*std::prev(end) - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); assert(end - begin >= 3); // Test the 1st point. @@ -741,10 +739,17 @@ double clip_end(Path &path, double distance) // Rotate the segment end point in reverse towards the start point. if (last.ccw()) angle *= -1.; - path.push_back({ - last.point.rotated(angle * (distance / len), - arc_center(path.back().point.cast(), last.point.cast(), double(last.radius), last.ccw()).cast()), - last.radius, last.orientation }); + + const double rotate_by_angle = angle * (distance / len); + + // When we are clipping the arc with a negative radius (we are taking the longer angle here), + // we have to check if we still need to take the longer angle after clipping. + // Otherwise, we must flip the radius sign to take the shorter angle. + const bool flip_radius_sign = last.radius < 0 && std::abs(angle) > M_PI && std::abs(angle - rotate_by_angle) <= M_PI; + + path.push_back({last.point.rotated(rotate_by_angle, arc_center(path.back().point.cast(), last.point.cast(), double(last.radius), last.ccw()).cast()), + (flip_radius_sign ? -last.radius : last.radius), last.orientation}); + // Length to go is zero. return 0; } diff --git a/src/libslic3r/Geometry/ArcWelder.hpp b/src/libslic3r/Geometry/ArcWelder.hpp index 477f535..0ce1444 100644 --- a/src/libslic3r/Geometry/ArcWelder.hpp +++ b/src/libslic3r/Geometry/ArcWelder.hpp @@ -424,6 +424,9 @@ struct Segment // CCW or CW. Ignored for zero radius (linear segment). Orientation orientation{ Orientation::CCW }; + float height_fraction{ 1.f }; + float e_fraction{ 1.f }; + bool linear() const { return radius == 0; } bool ccw() const { return orientation == Orientation::CCW; } bool cw() const { return orientation == Orientation::CW; } diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index e43f8ea..ab79a70 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -19,6 +19,7 @@ #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/LayerRegion.hpp" +#include "libslic3r/PerimeterGenerator.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Surface.hpp" #include "libslic3r/SurfaceCollection.hpp" @@ -622,6 +623,36 @@ ExPolygons Layer::merged(float offset_scaled) const return out; } +// If there is any incompatibility, separate LayerRegions have to be created. +inline bool has_compatible_dynamic_overhang_speed(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds == other_config.enable_dynamic_overhang_speeds; + if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) { + dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 && + config.overhang_speed_1 == other_config.overhang_speed_1 && + config.overhang_speed_2 == other_config.overhang_speed_2 && + config.overhang_speed_3 == other_config.overhang_speed_3; + } + + return dynamic_overhang_speed_compatibility; +} + +// If there is any incompatibility, separate LayerRegions have to be created. +inline bool has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + return config.perimeter_extruder == other_config.perimeter_extruder && + config.perimeters == other_config.perimeters && + config.perimeter_speed == other_config.perimeter_speed && + config.external_perimeter_speed == other_config.external_perimeter_speed && + (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) && + config.overhangs == other_config.overhangs && + config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") && + config.thin_walls == other_config.thin_walls && + config.external_perimeters_first == other_config.external_perimeters_first && + config.infill_overlap == other_config.infill_overlap && + has_compatible_dynamic_overhang_speed(config, other_config); +} + // Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters. // The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region. // The resulting fill surface is split back among the originating regions. @@ -654,115 +685,108 @@ void Layer::make_perimeters() for (LayerSlice &lslice : this->lslices_ex) lslice.islands.clear(); - for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) - if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) { - layer_region_reset_perimeters(**layerm); - if (! (*layerm)->slices().empty()) { - BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; - done[region_id] = true; - const PrintRegionConfig &config = (*layerm)->region().config(); - - perimeter_and_gapfill_ranges.clear(); - fill_expolygons.clear(); - fill_expolygons_ranges.clear(); - surfaces_to_merge.clear(); - - // find compatible regions - layer_region_ids.clear(); - layer_region_ids.push_back(region_id); - for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) - if (! (*it)->slices().empty()) { - LayerRegion *other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region().config(); - bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds == - other_config.enable_dynamic_overhang_speeds; - if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) { - dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 && - config.overhang_speed_1 == other_config.overhang_speed_1 && - config.overhang_speed_2 == other_config.overhang_speed_2 && - config.overhang_speed_3 == other_config.overhang_speed_3; - } - - if (config.perimeter_extruder == other_config.perimeter_extruder - && config.perimeters == other_config.perimeters - && config.perimeter_speed == other_config.perimeter_speed - && config.external_perimeter_speed == other_config.external_perimeter_speed - && dynamic_overhang_speed_compatibility - && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == - (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) - && config.overhangs == other_config.overhangs - && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") - && config.thin_walls == other_config.thin_walls - && config.external_perimeters_first == other_config.external_perimeters_first - && config.infill_overlap == other_config.infill_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) - { - layer_region_reset_perimeters(*other_layerm); - layer_region_ids.push_back(it - m_regions.begin()); - done[it - m_regions.begin()] = true; - } - } - - if (layer_region_ids.size() == 1) { // optimization - //w21 - (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges,(*layerm)->fill_no_overlap_expolygons); - this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); - } else { - SurfaceCollection new_slices; - // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. - uint32_t region_id_config = layer_region_ids.front(); - LayerRegion* layerm_config = m_regions[region_id_config]; - { - // Merge slices (surfaces) according to number of extra perimeters. - for (uint32_t region_id : layer_region_ids) { - LayerRegion &layerm = *m_regions[region_id]; - for (const Surface &surface : layerm.slices()) - surfaces_to_merge.emplace_back(&surface); - if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) { - region_id_config = region_id; - layerm_config = &layerm; - } - } - std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; }); - for (size_t i = 0; i < surfaces_to_merge.size();) { - size_t j = i; - const Surface &first = *surfaces_to_merge[i]; - size_t extra_perimeters = first.extra_perimeters; - for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ; - if (i + 1 == j) - // Nothing to merge, just copy. - new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); - else { - surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); - new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); - } - i = j; - } - } - // make perimeters - //w21 - ExPolygons fill_no_overlap; - layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges,fill_no_overlap); - //w21 - if (!new_slices.surfaces.empty()) { - for (size_t idx : layer_region_ids) { - // Separate the fill surfaces. - LayerRegion &layer = *m_regions[idx]; - ExPolygons expp = intersection_ex(new_slices.surfaces, layer.slices().surfaces); - layer.m_fill_expolygons = std::move(expp); - if (!layer.m_fill_expolygons.empty()) { - layer.m_fill_surfaces.set(std::move(layer.m_fill_expolygons), layer.slices().surfaces.front()); - layer.fill_no_overlap_expolygons = intersection_ex(layer.slices().surfaces, fill_no_overlap); - } - - } - } - this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); - } - } + for (auto it_curr_region = m_regions.cbegin(); it_curr_region != m_regions.cend(); ++it_curr_region) { + const size_t curr_region_id = std::distance(m_regions.cbegin(), it_curr_region); + if (done[curr_region_id]) { + continue; } + + LayerRegion &curr_region = **it_curr_region; + layer_region_reset_perimeters(curr_region); + if (curr_region.slices().empty()) { + continue; + } + + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << curr_region_id; + done[curr_region_id] = true; + const PrintRegionConfig &curr_config = curr_region.region().config(); + + perimeter_and_gapfill_ranges.clear(); + fill_expolygons.clear(); + fill_expolygons_ranges.clear(); + surfaces_to_merge.clear(); + + // Find compatible regions. + layer_region_ids.clear(); + layer_region_ids.push_back(curr_region_id); + + PerimeterRegions perimeter_regions; + for (auto it_next_region = std::next(it_curr_region); it_next_region != m_regions.cend(); ++it_next_region) { + const size_t next_region_id = std::distance(m_regions.cbegin(), it_next_region); + LayerRegion &next_region = **it_next_region; + const PrintRegionConfig &next_config = next_region.region().config(); + if (next_region.slices().empty()) { + continue; + } + + if (!has_compatible_layer_regions(curr_config, next_config)) { + continue; + } + + // Now, we are sure that we want to merge LayerRegions in any case. + layer_region_reset_perimeters(next_region); + layer_region_ids.push_back(next_region_id); + done[next_region_id] = true; + + // If any parameters affecting just perimeters are incompatible, then we also create PerimeterRegion. + if (!PerimeterRegion::has_compatible_perimeter_regions(curr_config, next_config)) { + perimeter_regions.emplace_back(next_region); + } + } + + if (layer_region_ids.size() == 1) { // Optimization. + //w21 + curr_region.make_perimeters(curr_region.slices(), perimeter_regions, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges, curr_region.fill_no_overlap_expolygons); + this->sort_perimeters_into_islands(curr_region.slices(), curr_region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } else { + SurfaceCollection new_slices; + // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. + uint32_t region_id_config = layer_region_ids.front(); + LayerRegion *layerm_config = m_regions[region_id_config]; + { + // Merge slices (surfaces) according to number of extra perimeters. + for (uint32_t region_id : layer_region_ids) { + LayerRegion &layerm = *m_regions[region_id]; + for (const Surface &surface : layerm.slices()) + surfaces_to_merge.emplace_back(&surface); + if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) { + region_id_config = region_id; + layerm_config = &layerm; + } + } + + std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r) { return l->extra_perimeters < r->extra_perimeters; }); + for (size_t i = 0; i < surfaces_to_merge.size();) { + size_t j = i; + const Surface &first = *surfaces_to_merge[i]; + size_t extra_perimeters = first.extra_perimeters; + for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++j); + + if (i + 1 == j) { + // Nothing to merge, just copy. + new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); + } else { + surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); + new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); + } + + i = j; + } + } + + // Try to merge compatible PerimeterRegions. + if (perimeter_regions.size() > 1) { + PerimeterRegion::merge_compatible_perimeter_regions(perimeter_regions); + } + + // Make perimeters. + //w21 + ExPolygons fill_no_overlap; + layerm_config->make_perimeters(new_slices, perimeter_regions, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges, fill_no_overlap); + this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } + } + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index f42021b..55a1f8e 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -84,6 +84,8 @@ void LayerRegion::slices_to_fill_surfaces_clipped() void LayerRegion::make_perimeters( // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. const SurfaceCollection &slices, + // Configuration regions that will be applied to parts of created perimeters. + const PerimeterRegions &perimeter_regions, // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing // newly created extrusions stored at this LayerRegion. std::vector> &perimeter_and_gapfill_ranges, @@ -120,6 +122,7 @@ void LayerRegion::make_perimeters( region_config, this->layer()->object()->config(), print_config, + perimeter_regions, spiral_vase ); diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp index af77729..7e19f27 100644 --- a/src/libslic3r/LayerRegion.hpp +++ b/src/libslic3r/LayerRegion.hpp @@ -29,6 +29,9 @@ class PrintObject; using LayerPtrs = std::vector; class PrintRegion; +struct PerimeterRegion; +using PerimeterRegions = std::vector; + // Range of indices, providing support for range based loops. template class IndexRange @@ -121,6 +124,8 @@ public: void make_perimeters( // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. const SurfaceCollection &slices, + // Configuration regions that will be applied to parts of created perimeters. + const PerimeterRegions &perimeter_regions, // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing // newly created extrusions stored at this LayerRegion. std::vector> &perimeter_and_gapfill_ranges, @@ -156,9 +161,10 @@ protected: private: // Modifying m_slices - friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); template - friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel); + friend void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel); + template + friend void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel); Layer *m_layer; const PrintRegion *m_region; diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index e4ff442..8148a2d 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -180,6 +180,11 @@ template bool intersection(const L &l1, const L &l2, Vec, Scalar return false; // not intersecting } +inline Point midpoint(const Point &a, const Point &b) { + return (a + b) / 2; +} + + } // namespace line_alg class Line @@ -194,7 +199,7 @@ public: void rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); } void reverse() { std::swap(this->a, this->b); } double length() const { return (b.cast() - a.cast()).norm(); } - Point midpoint() const { return (this->a + this->b) / 2; } + Point midpoint() const { return line_alg::midpoint(this->a, this->b); } bool intersection_infinite(const Line &other, Point* point) const; bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index 3a36efd..3a97e87 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -45,7 +45,7 @@ struct NeighborVisitor { void visit(Visitor visitor) { // find the next unvisited facet and push the index - auto facet = std::find(m_visited.begin() + m_seed, m_visited.end(), false); + auto facet = std::find(m_visited.begin() + m_seed, m_visited.end(), 0); m_seed = facet - m_visited.begin(); if (facet != m_visited.end()) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d26fc4c..22d8f6b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -3,11 +3,11 @@ #include "BuildVolume.hpp" #include "Exception.hpp" #include "Model.hpp" -#include "ModelArrange.hpp" #include "Geometry/ConvexHull.hpp" #include "MTUtils.hpp" #include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" +#include "MultipleBeds.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -55,7 +55,9 @@ Model& Model::assign_copy(const Model &rhs) } // copy custom code per height - this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z; + this->custom_gcode_per_print_z_vector = rhs.custom_gcode_per_print_z_vector; + this->wipe_tower_vector = rhs.wipe_tower_vector; + return *this; } @@ -76,7 +78,9 @@ Model& Model::assign_copy(Model &&rhs) rhs.objects.clear(); // copy custom code per height - this->custom_gcode_per_print_z = std::move(rhs.custom_gcode_per_print_z); + this->custom_gcode_per_print_z_vector = std::move(rhs.custom_gcode_per_print_z_vector); + this->wipe_tower_vector = rhs.wipe_tower_vector; + return *this; } @@ -102,6 +106,37 @@ void Model::update_links_bottom_up_recursive() } } +ModelWipeTower& Model::wipe_tower() +{ + return const_cast(const_cast(this)->wipe_tower()); +} + +const ModelWipeTower& Model::wipe_tower() const +{ + return wipe_tower_vector[s_multiple_beds.get_active_bed()]; +} + +const ModelWipeTower& Model::wipe_tower(const int bed_index) const +{ + return wipe_tower_vector[bed_index]; +} + +ModelWipeTower& Model::wipe_tower(const int bed_index) +{ + return wipe_tower_vector[bed_index]; +} + +CustomGCode::Info& Model::custom_gcode_per_print_z() +{ + return const_cast(const_cast(this)->custom_gcode_per_print_z()); +} + +const CustomGCode::Info& Model::custom_gcode_per_print_z() const +{ + return custom_gcode_per_print_z_vector[s_multiple_beds.get_active_bed()]; +} + + // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) { @@ -123,10 +158,11 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c result = load_step(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); - else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) + else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) { //FIXME options & LoadAttribute::CheckVersion ? - result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); - else if (boost::algorithm::iends_with(input_file, ".svg")) + boost::optional qidislicer_generator_version; + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false, qidislicer_generator_version); + } else if (boost::algorithm::iends_with(input_file, ".svg")) result = load_svg(input_file, model); else if (boost::ends_with(input_file, ".printRequest")) result = load_printRequest(input_file.c_str(), &model); @@ -138,33 +174,40 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c if (model.objects.empty()) throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); - + if (!boost::ends_with(input_file, ".printRequest")) for (ModelObject *o : model.objects) o->input_file = input_file; - + if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); - CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); - CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z); + for (CustomGCode::Info& info : model.custom_gcode_per_print_z_vector) { + CustomGCode::update_custom_gcode_per_print_z_from_config(info, config); + CustomGCode::check_mode_for_custom_gcode_per_print_z(info); + } sort_remove_duplicates(config_substitutions->substitutions); return model; } // Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ). -Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) -{ +Model Model::read_from_archive( + const std::string& input_file, + DynamicPrintConfig* config, + ConfigSubstitutionContext* config_substitutions, + boost::optional &qidislicer_generator_version, + LoadAttributes options +) { assert(config != nullptr); assert(config_substitutions != nullptr); Model model; bool result = false; - if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) - result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion); - else if (boost::algorithm::iends_with(input_file, ".zip.amf")) + if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) { + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion, qidislicer_generator_version); + } else if (boost::algorithm::iends_with(input_file, ".zip.amf")) result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); @@ -185,8 +228,10 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); - CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); - CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z); + for (CustomGCode::Info& info : model.custom_gcode_per_print_z_vector) { + CustomGCode::update_custom_gcode_per_print_z_from_config(info, config); + CustomGCode::check_mode_for_custom_gcode_per_print_z(info); + } handle_legacy_sla(*config); @@ -359,8 +404,10 @@ double Model::max_z() const unsigned int Model::update_print_volume_state(const BuildVolume &build_volume) { unsigned int num_printable = 0; + s_multiple_beds.clear_inst_map(); for (ModelObject* model_object : this->objects) num_printable += model_object->update_instances_print_volume_state(build_volume); + s_multiple_beds.inst_map_updated(); return num_printable; } @@ -633,6 +680,11 @@ bool Model::is_mm_painted() const return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); }); } +bool Model::is_fuzzy_skin_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fuzzy_skin_painted(); }); +} + ModelObject::~ModelObject() { this->clear_volumes(); @@ -658,7 +710,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->layer_height_profile = rhs.layer_height_profile; this->printable = rhs.printable; this->origin_translation = rhs.origin_translation; - this->cut_id.copy(rhs.cut_id); + this->cut_id = rhs.cut_id; this->copy_transformation_caches(rhs); this->clear_volumes(); @@ -816,6 +868,11 @@ bool ModelObject::is_mm_painted() const return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); } +bool ModelObject::is_fuzzy_skin_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fuzzy_skin_painted(); }); +} + bool ModelObject::is_text() const { return this->volumes.size() == 1 && this->volumes[0]->is_text(); @@ -1243,6 +1300,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con vol->supported_facets.assign(volume->supported_facets); vol->seam_facets.assign(volume->seam_facets); vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets); + vol->fuzzy_skin_facets.assign(volume->fuzzy_skin_facets); // Perform conversion only if the target "imperial" state is different from the current one. // This check supports conversion of "mixed" set of volumes, each with different "imperial" state. @@ -1355,6 +1413,7 @@ void ModelVolume::reset_extra_facets() this->supported_facets.reset(); this->seam_facets.reset(); this->mm_segmentation_facets.reset(); + this->fuzzy_skin_facets.reset(); } @@ -1577,11 +1636,15 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume OUTSIDE = 2 }; for (ModelInstance* model_instance : this->instances) { + int bed_idx = -1; unsigned int inside_outside = 0; for (const ModelVolume* vol : this->volumes) if (vol->is_model_part()) { const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix(); - BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */); + int bed = -1; + BuildVolume::ObjectState state = build_volume.object_state(vol->mesh().its, matrix.cast(), true /* may be below print bed */, true /*ignore_bottom*/, &bed); + if (bed_idx == -1) // instance will be assigned to the bed the first volume is assigned to. + bed_idx = bed; if (state == BuildVolume::ObjectState::Inside) // Volume is completely inside. inside_outside |= INSIDE; @@ -1600,6 +1663,8 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume inside_outside == INSIDE ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside; if (inside_outside == INSIDE) ++num_printable; + if (bed_idx != -1) + s_multiple_beds.set_instance_bed(model_instance->id(), model_instance->printable, bed_idx); } return num_printable; } @@ -1921,6 +1986,7 @@ void ModelVolume::assign_new_unique_ids_recursive() supported_facets.set_new_unique_id(); seam_facets.set_new_unique_id(); mm_segmentation_facets.set_new_unique_id(); + fuzzy_skin_facets.set_new_unique_id(); } void ModelVolume::rotate(double angle, Axis axis) @@ -2038,35 +2104,46 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, TriangleStateType type) const -{ +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume &mv, TriangleStateType type) const { TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). selector.deserialize(m_data, false); return selector.get_facets(type); } -indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, TriangleStateType type) const -{ +indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume &mv, TriangleStateType type) const { TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). selector.deserialize(m_data, false); return selector.get_facets_strict(type); } -bool FacetsAnnotation::has_facets(const ModelVolume& mv, TriangleStateType type) const -{ +indexed_triangle_set_with_color FacetsAnnotation::get_all_facets_with_colors(const ModelVolume &mv) const { + TriangleSelector selector(mv.mesh()); + // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). + selector.deserialize(m_data, false); + return selector.get_all_facets_with_colors(); +} + +indexed_triangle_set_with_color FacetsAnnotation::get_all_facets_strict_with_colors(const ModelVolume &mv) const { + TriangleSelector selector(mv.mesh()); + // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). + selector.deserialize(m_data, false); + return selector.get_all_facets_strict_with_colors(); +} + +bool FacetsAnnotation::has_facets(const ModelVolume &mv, TriangleStateType type) const { return TriangleSelector::has_facets(m_data, type); } -bool FacetsAnnotation::set(const TriangleSelector& selector) -{ +bool FacetsAnnotation::set(const TriangleSelector &selector) { TriangleSelector::TriangleSplittingData sel_map = selector.serialize(); if (sel_map != m_data) { m_data = std::move(sel_map); this->touch(); return true; } + return false; } @@ -2106,9 +2183,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const // Recover triangle splitting & state from string of hexadecimal values previously // generated by get_triangle_as_string. Used to load from 3MF. -void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) +void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string &str) { - assert(! str.empty()); + if (str.empty()) { + // The triangle isn't painted, so it means that it will use the default extruder. + m_data.used_states[static_cast(TriangleStateType::NONE)] = true; + return; + } + + assert(!str.empty()); assert(m_data.triangles_to_split.empty() || m_data.triangles_to_split.back().triangle_idx < triangle_id); m_data.triangles_to_split.emplace_back(triangle_id, int(m_data.bitstream.size())); @@ -2251,6 +2334,13 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); }); } +bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new) +{ + return model_property_changed(mo, mo_new, + [](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; }, + [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.fuzzy_skin_facets.timestamp_matches(mv_new.fuzzy_skin_facets); }); +} + bool model_has_parameter_modifiers_in_objects(const Model &model) { for (const auto& model_object : model.objects) @@ -2260,14 +2350,6 @@ bool model_has_parameter_modifiers_in_objects(const Model &model) return false; } -bool model_has_multi_part_objects(const Model &model) -{ - for (const ModelObject *model_object : model.objects) - if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part()) - return true; - return false; -} - bool model_has_advanced_features(const Model &model) { auto config_is_advanced = [](const ModelConfig &config) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0a9051f..4a116f3 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace cereal { class BinaryInputArchive; @@ -221,6 +222,54 @@ private: friend class ModelObject; }; + +class CutId +{ + size_t m_unique_id; // 0 = invalid + size_t m_check_sum; // check sum of CutParts in initial Object + size_t m_connectors_cnt; // connectors count + +public: + CutId() { invalidate(); } + CutId(size_t id, size_t check_sum, size_t connectors_cnt) : + m_unique_id{ id }, m_check_sum{ check_sum }, m_connectors_cnt{ connectors_cnt } {} + + bool operator< (const CutId& rhs) const { return this->m_unique_id < rhs.m_unique_id; } + CutId& operator=(const CutId& rhs) { + this->m_unique_id = rhs.id(); + this->m_check_sum = rhs.check_sum(); + this->m_connectors_cnt = rhs.connectors_cnt(); + return *this; + } + + void invalidate() { + m_unique_id = 0; + m_check_sum = 1; + m_connectors_cnt = 0; + } + void init() { + std::random_device rd; + std::mt19937_64 mt(rd() + time(NULL)); + std::uniform_int_distribution dist(1, std::numeric_limits::max()); + m_unique_id = dist(mt); + } + bool has_same_id(const CutId& rhs) const { return id() == rhs.id(); } + bool is_equal(const CutId& rhs) const { return id() == rhs.id() && + check_sum() == rhs.check_sum() && + connectors_cnt() == rhs.connectors_cnt() ; } + size_t id() const { return m_unique_id; } + bool valid() const { return m_unique_id != 0; } + size_t check_sum() const { return m_check_sum; } + void increase_check_sum(size_t cnt) { m_check_sum += cnt; } + + size_t connectors_cnt() const { return m_connectors_cnt; } + void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; } + + template void serialize(Archive &ar) { + ar(m_unique_id, m_check_sum, m_connectors_cnt); + } +}; + enum class CutConnectorType : int { Plug , Dowel @@ -259,10 +308,6 @@ struct CutConnectorAttributes CutConnectorAttributes(const CutConnectorAttributes& rhs) : CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {} - bool operator==(const CutConnectorAttributes& other) const; - - bool operator!=(const CutConnectorAttributes& other) const { return !(other == (*this)); } - bool operator<(const CutConnectorAttributes& other) const { return this->type < other.type || (this->type == other.type && this->style < other.style) || @@ -296,10 +341,6 @@ struct CutConnector CutConnector(const CutConnector& rhs) : CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.z_angle, rhs.attribs) {} - bool operator==(const CutConnector& other) const; - - bool operator!=(const CutConnector& other) const { return !(other == (*this)); } - template inline void serialize(Archive& ar) { ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, z_angle, attribs); } @@ -356,7 +397,7 @@ public: // Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform CutConnectors cut_connectors; - CutObjectBase cut_id; + CutId cut_id; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation @@ -381,6 +422,8 @@ public: bool is_seam_painted() const; // Checks if any of object volume is painted using the multi-material painting gizmo. bool is_mm_painted() const; + // Checks if any of object volume is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const; // Checks if object contains just one volume and it's a text bool is_text() const; // This object may have a varying layer height by painting or by a table. @@ -492,7 +535,7 @@ public: bool has_negative_volume_mesh() const; // Detect if object has at least one sla drain hole bool has_sla_drain_holes() const { return !sla_drain_holes.empty(); } - bool is_cut() const { return cut_id.id().valid(); } + bool is_cut() const { return cut_id.valid(); } bool has_connectors() const; private: @@ -655,10 +698,12 @@ public: void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; } - bool set(const TriangleSelector& selector); - indexed_triangle_set get_facets(const ModelVolume& mv, TriangleStateType type) const; - indexed_triangle_set get_facets_strict(const ModelVolume& mv, TriangleStateType type) const; - bool has_facets(const ModelVolume& mv, TriangleStateType type) const; + bool set(const TriangleSelector &selector); + indexed_triangle_set get_facets(const ModelVolume &mv, TriangleStateType type) const; + indexed_triangle_set get_facets_strict(const ModelVolume &mv, TriangleStateType type) const; + indexed_triangle_set_with_color get_all_facets_with_colors(const ModelVolume &mv) const; + indexed_triangle_set_with_color get_all_facets_strict_with_colors(const ModelVolume &mv) const; + bool has_facets(const ModelVolume &mv, TriangleStateType type) const; bool empty() const { return m_data.triangles_to_split.empty(); } // Following method clears the config and increases its timestamp, so the deleted @@ -788,6 +833,9 @@ public: // List of mesh facets painted for MM segmentation. FacetsAnnotation mm_segmentation_facets; + // List of mesh facets painted for fuzzy skin. + FacetsAnnotation fuzzy_skin_facets; + // Is set only when volume is Embossed Text type // Contain information how to re-create volume std::optional text_configuration; @@ -892,11 +940,13 @@ public: this->supported_facets.set_new_unique_id(); this->seam_facets.set_new_unique_id(); this->mm_segmentation_facets.set_new_unique_id(); + this->fuzzy_skin_facets.set_new_unique_id(); } bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); } + bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); } // Returns 0-based indices of extruders painted by multi-material painting gizmo. std::vector get_extruders_from_multi_material_painting() const; @@ -944,10 +994,12 @@ private: assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mm_segmentation_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); return true; } @@ -974,13 +1026,14 @@ private: name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets), - cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) + fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); @@ -990,6 +1043,7 @@ private: assert(this->supported_facets.id() == other.supported_facets.id()); assert(this->seam_facets.id() == other.seam_facets.id()); assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id()); + assert(this->fuzzy_skin_facets.id() == other.fuzzy_skin_facets.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. @@ -1002,10 +1056,12 @@ private: assert(this->supported_facets.id().valid()); assert(this->seam_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid()); + assert(this->fuzzy_skin_facets.id().valid()); assert(this->id() != this->config.id()); assert(this->id() != this->supported_facets.id()); assert(this->id() != this->seam_facets.id()); assert(this->id() != this->mm_segmentation_facets.id()); + assert(this->id() != this->fuzzy_skin_facets.id()); assert(this->id() != other.id()); assert(this->config.id() == other.config.id()); this->set_material_id(other.material_id()); @@ -1017,10 +1073,12 @@ private: assert(this->supported_facets.id() != other.supported_facets.id()); assert(this->seam_facets.id() != other.seam_facets.id()); assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id()); + assert(this->fuzzy_skin_facets.id() != other.fuzzy_skin_facets.id()); assert(this->id() != this->config.id()); assert(this->supported_facets.empty()); assert(this->seam_facets.empty()); assert(this->mm_segmentation_facets.empty()); + assert(this->fuzzy_skin_facets.empty()); } ModelVolume& operator=(ModelVolume &rhs) = delete; @@ -1028,12 +1086,13 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) { + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), fuzzy_skin_facets(-1), object(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->supported_facets.id().invalid()); assert(this->seam_facets.id().invalid()); assert(this->mm_segmentation_facets.id().invalid()); + assert(this->fuzzy_skin_facets.id().invalid()); } template void load(Archive &ar) { bool has_convex_hull; @@ -1041,6 +1100,7 @@ private: cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, seam_facets); cereal::load_by_value(ar, mm_segmentation_facets); + cereal::load_by_value(ar, fuzzy_skin_facets); cereal::load_by_value(ar, config); cereal::load(ar, text_configuration); cereal::load(ar, emboss_shape); @@ -1059,6 +1119,7 @@ private: cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, mm_segmentation_facets); + cereal::save_by_value(ar, fuzzy_skin_facets); cereal::save_by_value(ar, config); cereal::save(ar, text_configuration); cereal::save(ar, emboss_shape); @@ -1180,31 +1241,23 @@ private: }; -class ModelWipeTower final : public ObjectBase +// Note: The following class does not have to inherit from ObjectID, it is currently +// only used for arrangement. It might be good to refactor this in future. +class ModelWipeTower { public: - Vec2d position; - double rotation; + Vec2d position = Vec2d(180., 140.); + double rotation = 0.; -private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - friend class Model; + bool operator==(const ModelWipeTower& other) const { return position == other.position && rotation == other.rotation; } + bool operator!=(const ModelWipeTower& other) const { return !((*this) == other); } + + // Assignment operator does not touch the ID! + ModelWipeTower& operator=(const ModelWipeTower& rhs) { position = rhs.position; rotation = rhs.rotation; return *this; } - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. explicit ModelWipeTower() {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit ModelWipeTower(int) : ObjectBase(-1) {} - // Copy constructor copies the ID. explicit ModelWipeTower(const ModelWipeTower &cfg) = default; - // Disabled methods. - ModelWipeTower(ModelWipeTower &&rhs) = delete; - ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; - ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; - // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. template void serialize(Archive &ar) { ar(position, rotation); } }; @@ -1222,12 +1275,26 @@ public: ModelMaterialMap materials; // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). ModelObjectPtrs objects; + + ModelWipeTower& wipe_tower(); + const ModelWipeTower& wipe_tower() const; + const ModelWipeTower& wipe_tower(const int bed_index) const; + ModelWipeTower& wipe_tower(const int bed_index); + std::vector& get_wipe_tower_vector() { return wipe_tower_vector; } + const std::vector& get_wipe_tower_vector() const { return wipe_tower_vector; } + + CustomGCode::Info& custom_gcode_per_print_z(); + const CustomGCode::Info& custom_gcode_per_print_z() const; + std::vector& get_custom_gcode_per_print_z_vector() { return custom_gcode_per_print_z_vector; } + +private: // Wipe tower object. - ModelWipeTower wipe_tower; + std::vector wipe_tower_vector = std::vector(MAX_NUMBER_OF_BEDS); // Extensions for color print - CustomGCode::Info custom_gcode_per_print_z; - + std::vector custom_gcode_per_print_z_vector = std::vector(MAX_NUMBER_OF_BEDS); + +public: // Default constructor assigns a new ID to the model. Model() { assert(this->id().valid()); } ~Model() { this->clear_objects(); this->clear_materials(); } @@ -1252,9 +1319,12 @@ public: DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, LoadAttributes options = LoadAttribute::AddDefaultInstances); static Model read_from_archive( - const std::string& input_file, - DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, - LoadAttributes options = LoadAttribute::AddDefaultInstances); + const std::string& input_file, + DynamicPrintConfig* config, + ConfigSubstitutionContext* config_substitutions, + boost::optional &qidislicer_generator_version, + LoadAttributes options = LoadAttribute::AddDefaultInstances + ); // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); @@ -1317,6 +1387,8 @@ public: bool is_seam_painted() const; // Checks if any of objects is painted using the multi-material painting gizmo. bool is_mm_painted() const; + // Checks if any of objects is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const; private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } @@ -1326,8 +1398,7 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; template void serialize(Archive &ar) { - Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); - ar(materials, objects, wipe_tower_wrapper); + ar(materials, objects, wipe_tower_vector); } }; @@ -1361,12 +1432,13 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo // The function assumes that volumes list is synchronized. extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); +// Test whether the now ModelObject has newer fuzzy skin data than the old one. +// The function assumes that volumes list is synchronized. +extern bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new); + // If the model has object(s) which contains a modofoer, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. bool model_has_parameter_modifiers_in_objects(const Model& model); -// If the model has multi-part objects, then it is currently not supported by the SLA mode. -// Either the model cannot be loaded, or a SLA printer has to be activated. -bool model_has_multi_part_objects(const Model &model); // If the model has advanced features, then it cannot be processed in simple mode. bool model_has_advanced_features(const Model &model); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 6e5762e..d8b4eef 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1,11 +1,9 @@ #include -#include #include #include #include #include -#include -#include +#include #include #include #include @@ -18,14 +16,15 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" -#include "EdgeGrid.hpp" #include "Layer.hpp" #include "Print.hpp" #include "Geometry/VoronoiUtils.hpp" #include "MutablePolygon.hpp" #include "admesh/stl.h" +#include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/ExPolygon.hpp" #include "libslic3r/Flow.hpp" +#include "libslic3r/format.hpp" #include "libslic3r/Geometry/VoronoiOffset.hpp" #include "libslic3r/LayerRegion.hpp" #include "libslic3r/Line.hpp" @@ -34,520 +33,135 @@ #include "libslic3r/Polygon.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Surface.hpp" +#include "libslic3r/SVG.hpp" #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/TriangleSelector.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/libslic3r.h" #include "MultiMaterialSegmentation.hpp" -//#define MM_SEGMENTATION_DEBUG_GRAPH -//#define MM_SEGMENTATION_DEBUG_REGIONS -//#define MM_SEGMENTATION_DEBUG_INPUT -//#define MM_SEGMENTATION_DEBUG_PAINTED_LINES -//#define MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - -#if defined(MM_SEGMENTATION_DEBUG_GRAPH) || defined(MM_SEGMENTATION_DEBUG_REGIONS) || \ - defined(MM_SEGMENTATION_DEBUG_INPUT) || defined(MM_SEGMENTATION_DEBUG_PAINTED_LINES) || \ - defined(MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) -#define MM_SEGMENTATION_DEBUG -#endif - -//#define MM_SEGMENTATION_DEBUG_TOP_BOTTOM +constexpr bool MM_SEGMENTATION_DEBUG_GRAPH = false; +constexpr bool MM_SEGMENTATION_DEBUG_REGIONS = false; +constexpr bool MM_SEGMENTATION_DEBUG_INPUT = false; +constexpr bool MM_SEGMENTATION_DEBUG_FILTERED_COLOR_LINES = false; +constexpr bool MM_SEGMENTATION_DEBUG_COLOR_RANGES = false; +constexpr bool MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS = false; +constexpr bool MM_SEGMENTATION_DEBUG_TOP_BOTTOM = false; namespace Slic3r { -// Assumes that is at most same projected_l length or below than projection_l -static bool project_line_on_line(const Line &projection_l, const Line &projected_l, Line *new_projected) -{ - const Vec2d v1 = (projection_l.b - projection_l.a).cast(); - const Vec2d va = (projected_l.a - projection_l.a).cast(); - const Vec2d vb = (projected_l.b - projection_l.a).cast(); - const double l2 = v1.squaredNorm(); // avoid a sqrt - if (l2 == 0.0) - return false; - double t1 = va.dot(v1) / l2; - double t2 = vb.dot(v1) / l2; - t1 = std::clamp(t1, 0., 1.); - t2 = std::clamp(t2, 0., 1.); - assert(t1 >= 0.); - assert(t2 >= 0.); - assert(t1 <= 1.); - assert(t2 <= 1.); - - Point p1 = projection_l.a + (t1 * v1).cast(); - Point p2 = projection_l.a + (t2 * v1).cast(); - *new_projected = Line(p1, p2); - return true; -} - -struct PaintedLine -{ - size_t contour_idx; - size_t line_idx; - Line projected_line; - int color; -}; - -struct PaintedLineVisitor -{ - PaintedLineVisitor(const EdgeGrid::Grid &grid, std::vector &painted_lines, std::mutex &painted_lines_mutex, size_t reserve) : grid(grid), painted_lines(painted_lines), painted_lines_mutex(painted_lines_mutex) - { - painted_lines_set.reserve(reserve); - } - - void reset() { painted_lines_set.clear(); } - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and column of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - const Vec2d v1 = line_to_test.vector().cast(); - const double v1_sqr_norm = v1.squaredNorm(); - const double heuristic_thr_part = line_to_test.length() + append_threshold; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - Line grid_line = grid.line(*it_contour_and_segment); - const Vec2d v2 = grid_line.vector().cast(); - double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length()); - - // An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other. - // This helps filter out cases when the following expensive calculations are useless. - if ((grid_line.a - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || - (grid_line.b - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || - (grid_line.a - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr || - (grid_line.b - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr) - continue; - - // When lines have too different length, it is necessary to normalize them - if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) { - // The two vectors are nearly collinear (their mutual angle is lower than 30 degrees) - if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) { - if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 || - grid_line.distance_to_squared(line_to_test.b) < append_threshold2 || - line_to_test.distance_to_squared(grid_line.a) < append_threshold2 || - line_to_test.distance_to_squared(grid_line.b) < append_threshold2) { - Line line_to_test_projected; - project_line_on_line(grid_line, line_to_test, &line_to_test_projected); - - if ((line_to_test_projected.a - grid_line.a).cast().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast().squaredNorm()) - line_to_test_projected.reverse(); - - painted_lines_set.insert(*it_contour_and_segment); - { - boost::lock_guard lock(painted_lines_mutex); - painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color}); - } - } - } - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &painted_lines; - std::mutex &painted_lines_mutex; - Line line_to_test; - std::unordered_set, boost::hash>> painted_lines_set; - int color = -1; - - static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.)); - static inline const double append_threshold = 50 * SCALED_EPSILON; - static inline const double append_threshold2 = Slic3r::sqr(append_threshold); -}; - -BoundingBox get_extents(const std::vector &colored_polygons) { - BoundingBox bbox; - for (const ColoredLines &colored_lines : colored_polygons) { - for (const ColoredLine &colored_line : colored_lines) { - bbox.merge(colored_line.line.a); - bbox.merge(colored_line.line.b); - } - } - return bbox; -} - -// Flatten the vector of vectors into a vector. -static inline ColoredLines to_lines(const std::vector &c_lines) -{ - size_t n_lines = 0; - for (const auto &c_line : c_lines) - n_lines += c_line.size(); - ColoredLines lines; - lines.reserve(n_lines); - for (const auto &c_line : c_lines) - lines.insert(lines.end(), c_line.begin(), c_line.end()); - return lines; -} - -static std::vector> get_segments(const ColoredLines &polygon) -{ - std::vector> segments; - - size_t segment_end = 0; - while (segment_end + 1 < polygon.size() && polygon[segment_end].color == polygon[segment_end + 1].color) - segment_end++; - - if (segment_end == polygon.size() - 1) - return {std::make_pair(0, polygon.size() - 1)}; - - size_t first_different_color = (segment_end + 1) % polygon.size(); - for (size_t line_offset_idx = 0; line_offset_idx < polygon.size(); ++line_offset_idx) { - size_t start_s = (first_different_color + line_offset_idx) % polygon.size(); - size_t end_s = start_s; - - while (line_offset_idx + 1 < polygon.size() && polygon[start_s].color == polygon[(first_different_color + line_offset_idx + 1) % polygon.size()].color) { - end_s = (first_different_color + line_offset_idx + 1) % polygon.size(); - line_offset_idx++; - } - segments.emplace_back(start_s, end_s); - } - return segments; -} - -static std::vector filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_lines) -{ - const int filter_eps_value = scale_(0.1f); - std::vector filtered_lines; - filtered_lines.emplace_back(painted_lines[start_idx]); - for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) { - // line_to_process is already all colored. Skip another possible duplicate coloring. - if(filtered_lines.back().projected_line.b == line_to_process.b) - break; - - PaintedLine &prev = filtered_lines.back(); - const PaintedLine &curr = painted_lines[line_idx]; - - double prev_length = prev.projected_line.length(); - double curr_dist_start = (curr.projected_line.a - prev.projected_line.a).cast().norm(); - double dist_between_lines = curr_dist_start - prev_length; - - if (dist_between_lines >= 0) { - if (prev.color == curr.color) { - if (dist_between_lines <= filter_eps_value) { - prev.projected_line.b = curr.projected_line.b; - } else { - filtered_lines.emplace_back(curr); - } - } else { - filtered_lines.emplace_back(curr); - } - } else { - double curr_dist_end = (curr.projected_line.b - prev.projected_line.a).cast().norm(); - if (curr_dist_end > prev_length) { - if (prev.color == curr.color) - prev.projected_line.b = curr.projected_line.b; - else - filtered_lines.push_back({curr.contour_idx, curr.line_idx, Line{prev.projected_line.b, curr.projected_line.b}, curr.color}); - } - } - } - - if (double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast().norm(); dist_to_start <= filter_eps_value) - filtered_lines.front().projected_line.a = line_to_process.a; - - if (double dist_to_end = (filtered_lines.back().projected_line.b - line_to_process.b).cast().norm(); dist_to_end <= filter_eps_value) - filtered_lines.back().projected_line.b = line_to_process.b; - - return filtered_lines; -} - -static std::vector> post_process_painted_lines(const std::vector &contours, std::vector &&painted_lines) -{ - if (painted_lines.empty()) - return {}; - - auto comp = [&contours](const PaintedLine &first, const PaintedLine &second) { - Point first_start_p = contours[first.contour_idx].segment_start(first.line_idx); - return first.contour_idx < second.contour_idx || - (first.contour_idx == second.contour_idx && - (first.line_idx < second.line_idx || - (first.line_idx == second.line_idx && - ((first.projected_line.a - first_start_p).cast().squaredNorm() < (second.projected_line.a - first_start_p).cast().squaredNorm() || - ((first.projected_line.a - first_start_p).cast().squaredNorm() == (second.projected_line.a - first_start_p).cast().squaredNorm() && - (first.projected_line.b - first.projected_line.a).cast().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast().squaredNorm()))))); - }; - std::sort(painted_lines.begin(), painted_lines.end(), comp); - - std::vector> filtered_painted_lines(contours.size()); - size_t prev_painted_line_idx = 0; - for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_lines.size(); ++curr_painted_line_idx) { - size_t next_painted_line_idx = curr_painted_line_idx + 1; - if (next_painted_line_idx >= painted_lines.size() || painted_lines[curr_painted_line_idx].contour_idx != painted_lines[next_painted_line_idx].contour_idx || painted_lines[curr_painted_line_idx].line_idx != painted_lines[next_painted_line_idx].line_idx) { - const PaintedLine &start_line = painted_lines[prev_painted_line_idx]; - const Line &line_to_process = contours[start_line.contour_idx].get_segment(start_line.line_idx); - Slic3r::append(filtered_painted_lines[painted_lines[curr_painted_line_idx].contour_idx], filter_painted_lines(line_to_process, prev_painted_line_idx, curr_painted_line_idx, painted_lines)); - prev_painted_line_idx = next_painted_line_idx; - } - } - - return filtered_painted_lines; -} - -#ifndef NDEBUG -static bool are_lines_connected(const ColoredLines &colored_lines) -{ - for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx) - if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a) - return false; - return true; -} -#endif - -static ColoredLines colorize_line(const Line &line_to_process, - const size_t start_idx, - const size_t end_idx, - const std::vector &painted_contour) -{ - assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx); - assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; })); - - const int filter_eps_value = scale_(0.1f); - ColoredLines final_lines; - const PaintedLine &first_line = painted_contour[start_idx]; - if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast().norm(); dist_to_start > filter_eps_value) - final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0}); - final_lines.push_back({first_line.projected_line, first_line.color}); - - for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) { - ColoredLine &prev = final_lines.back(); - const PaintedLine &curr = painted_contour[line_idx]; - - double line_dist = (curr.projected_line.a - prev.line.b).cast().norm(); - if (line_dist <= filter_eps_value) { - if (prev.color == curr.color) { - prev.line.b = curr.projected_line.b; - } else { - prev.line.b = curr.projected_line.a; - final_lines.push_back({curr.projected_line, curr.color}); - } - } else { - final_lines.push_back({Line(prev.line.b, curr.projected_line.a), 0}); - final_lines.push_back({curr.projected_line, curr.color}); - } - } - - // If there is non-painted space, then inserts line painted by a default color. - if (double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast().norm(); dist_to_end > filter_eps_value) - final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0}); - - // Make sure all the lines are connected. - assert(are_lines_connected(final_lines)); - - for (size_t line_idx = 2; line_idx < final_lines.size(); ++line_idx) { - const ColoredLine &line_0 = final_lines[line_idx - 2]; - ColoredLine &line_1 = final_lines[line_idx - 1]; - const ColoredLine &line_2 = final_lines[line_idx - 0]; - - if (line_0.color == line_2.color && line_0.color != line_1.color) - if (line_1.line.length() <= scale_(0.2)) line_1.color = line_0.color; - } - - ColoredLines colored_lines_simple; - colored_lines_simple.emplace_back(final_lines.front()); - for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) { - const ColoredLine &line_0 = final_lines[line_idx]; - - if (colored_lines_simple.back().color == line_0.color) - colored_lines_simple.back().line.b = line_0.line.b; - else - colored_lines_simple.emplace_back(line_0); - } - - final_lines = colored_lines_simple; - - if (final_lines.size() > 1) - if (final_lines.front().color != final_lines[1].color && final_lines.front().line.length() <= scale_(0.2)) { - final_lines[1].line.a = final_lines.front().line.a; - final_lines.erase(final_lines.begin()); - } - - if (final_lines.size() > 1) - if (final_lines.back().color != final_lines[final_lines.size() - 2].color && final_lines.back().line.length() <= scale_(0.2)) { - final_lines[final_lines.size() - 2].line.b = final_lines.back().line.b; - final_lines.pop_back(); - } - - return final_lines; -} - -static ColoredLines filter_colorized_polygon(ColoredLines &&new_lines) { - for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) { - const ColoredLine &line_0 = new_lines[line_idx - 2]; - ColoredLine &line_1 = new_lines[line_idx - 1]; - const ColoredLine &line_2 = new_lines[line_idx - 0]; - - if (line_0.color == line_2.color && line_0.color != line_1.color && line_0.color >= 1) { - if (line_1.line.length() <= scale_(0.5)) line_1.color = line_0.color; - } - } - - for (size_t line_idx = 3; line_idx < new_lines.size(); ++line_idx) { - const ColoredLine &line_0 = new_lines[line_idx - 3]; - ColoredLine &line_1 = new_lines[line_idx - 2]; - ColoredLine &line_2 = new_lines[line_idx - 1]; - const ColoredLine &line_3 = new_lines[line_idx - 0]; - - if (line_0.color == line_3.color && (line_0.color != line_1.color || line_0.color != line_2.color) && line_0.color >= 1 && line_3.color >= 1) { - if ((line_1.line.length() + line_2.line.length()) <= scale_(0.5)) { - line_1.color = line_0.color; - line_2.color = line_0.color; - } - } - } - - std::vector> segments = get_segments(new_lines); - auto segment_length = [&new_lines](const std::pair &segment) { - double total_length = 0; - for (size_t seg_start_idx = segment.first; seg_start_idx != segment.second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - total_length += new_lines[seg_start_idx].line.length(); - total_length += new_lines[segment.second].line.length(); - return total_length; - }; - - if (segments.size() >= 2) - for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) { - size_t next_idx = next_idx_modulo(curr_idx, segments.size()); - assert(curr_idx != next_idx); - - int color0 = new_lines[segments[curr_idx].first].color; - int color1 = new_lines[segments[next_idx].first].color; - - double seg0l = segment_length(segments[curr_idx]); - double seg1l = segment_length(segments[next_idx]); - - if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) { - for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - new_lines[seg_start_idx].color = color0; - new_lines[segments[next_idx].second].color = color0; - } - } - - segments = get_segments(new_lines); - if (segments.size() >= 2) - for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) { - size_t next_idx = next_idx_modulo(curr_idx, segments.size()); - assert(curr_idx != next_idx); - - int color0 = new_lines[segments[curr_idx].first].color; - int color1 = new_lines[segments[next_idx].first].color; - double seg1l = segment_length(segments[next_idx]); - - if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) { - for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - new_lines[seg_start_idx].color = color0; - new_lines[segments[next_idx].second].color = color0; - } - } - - segments = get_segments(new_lines); - if (segments.size() >= 3) - for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) { - size_t next_idx = next_idx_modulo(curr_idx, segments.size()); - size_t next_next_idx = next_idx_modulo(next_idx, segments.size()); - - int color0 = new_lines[segments[curr_idx].first].color; - int color1 = new_lines[segments[next_idx].first].color; - int color2 = new_lines[segments[next_next_idx].first].color; - - if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[next_idx]) <= scale_(0.5)) { - for (size_t seg_start_idx = segments[next_next_idx].first; seg_start_idx != segments[next_next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0) - new_lines[seg_start_idx].color = color0; - new_lines[segments[next_next_idx].second].color = color0; - } - } - - return std::move(new_lines); -} - -static ColoredLines colorize_contour(const EdgeGrid::Contour &contour, const std::vector &painted_contour) { - assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; })); - - ColoredLines colorized_contour; - if (painted_contour.empty()) { - // Appends contour with default color for lines before the first PaintedLine. - colorized_contour.reserve(contour.num_segments()); - for (const Line &line : contour.get_segments()) - colorized_contour.emplace_back(ColoredLine{line, 0}); - return colorized_contour; - } - - colorized_contour.reserve(contour.num_segments() + painted_contour.size()); - for (size_t idx = 0; idx < painted_contour.front().line_idx; ++idx) - colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0}); - - size_t prev_painted_line_idx = 0; - for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_contour.size(); ++curr_painted_line_idx) { - size_t next_painted_line_idx = curr_painted_line_idx + 1; - if (next_painted_line_idx >= painted_contour.size() || painted_contour[curr_painted_line_idx].line_idx != painted_contour[next_painted_line_idx].line_idx) { - const std::vector &painted_contour_copy = painted_contour; - Slic3r::append(colorized_contour, colorize_line(contour.get_segment(painted_contour[prev_painted_line_idx].line_idx), prev_painted_line_idx, curr_painted_line_idx, painted_contour_copy)); - - // Appends contour with default color for lines between the current and the next PaintedLine. - if (next_painted_line_idx < painted_contour.size()) - for (size_t idx = painted_contour[curr_painted_line_idx].line_idx + 1; idx < painted_contour[next_painted_line_idx].line_idx; ++idx) - colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0}); - - prev_painted_line_idx = next_painted_line_idx; - } - } - - // Appends contour with default color for lines after the last PaintedLine. - for (size_t idx = painted_contour.back().line_idx + 1; idx < contour.num_segments(); ++idx) - colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0}); - - assert(!colorized_contour.empty()); - return filter_colorized_polygon(std::move(colorized_contour)); -} - -static std::vector colorize_contours(const std::vector &contours, const std::vector> &painted_contours) -{ - assert(contours.size() == painted_contours.size()); - std::vector colorized_contours(contours.size()); - for (const std::vector &painted_contour : painted_contours) { - size_t contour_idx = &painted_contour - &painted_contours.front(); - colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]); - } - - size_t poly_idx = 0; - for (ColoredLines &color_lines : colorized_contours) { - size_t line_idx = 0; - for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) { - color_lines[color_line_idx].poly_idx = int(poly_idx); - color_lines[color_line_idx].local_line_idx = int(line_idx); - ++line_idx; - } - ++poly_idx; - } - - return colorized_contours; -} - -// Determines if the line points from the point between two contour lines is pointing inside polygon or outside. -static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point) -{ - // TODO: Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside - auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d { - assert(left != middle); - assert(middle != right); - return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); - }; - - assert(contour_first.b == contour_second.a); - Vec2d inward_normal = three_points_inward_normal(contour_first.a, contour_first.b, contour_second.b); - Vec2d edge_norm = (new_point - contour_first.b).cast().normalized(); - double side = inward_normal.dot(edge_norm); - // assert(side != 0.); - return side > 0.; -} +const constexpr double POLYGON_FILTER_MIN_AREA_SCALED = scaled(0.1f); +const constexpr double POLYGON_FILTER_MIN_OFFSET_SCALED = scaled(0.01f); +const constexpr double POLYGON_COLOR_FILTER_DISTANCE_SCALED = scaled(0.2); +const constexpr double POLYGON_COLOR_FILTER_TOLERANCE_SCALED = scaled(0.02); +const constexpr double INPUT_POLYGONS_FILTER_TOLERANCE_SCALED = scaled(0.001); +const constexpr double MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED = scaled(0.4); +const constexpr double MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED = scaled(0.01); enum VD_ANNOTATION : Voronoi::VD::cell_type::color_type { VERTEX_ON_CONTOUR = 1, DELETED = 2 }; -#ifdef MM_SEGMENTATION_DEBUG_GRAPH -static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { +struct ColorLine { + static const constexpr int Dim = 2; + using Scalar = Point::Scalar; + + ColorLine(const Point &a, const Point &b, ColorPolygon::Color color) : a(a), b(b), color(color) {} + + Point a; + Point b; + ColorPolygon::Color color; + + Line line() const { return {a, b}; } +}; + +using ColorLines = std::vector; + +struct ColorChange { + explicit ColorChange(double t, uint8_t color_next) : t(t), color_next(color_next) {} + + // Relative position on the line from range <0, 1> + double t = 0.; + // Color after (including) t value on the line. + uint8_t color_next = 0; + + friend bool operator<(const ColorChange &lhs, const ColorChange &rhs) { return lhs.t < rhs.t; } +}; + +using ColorChanges = std::vector; + +struct ColorProjectionRange { + ColorProjectionRange() = delete; + + ColorProjectionRange(double from_t, double from_distance, double to_t, double to_distance, ColorPolygon::Color color) + : from_t(from_t), from_distance(from_distance), to_t(to_t), to_distance(to_distance), color(color) + {} + + double from_t = 0.; + double from_distance = 0.; + + double to_t = 0.; + double to_distance = 0.; + + ColorPolygon::Color color = 0; + + bool contains(const double t) const { return this->from_t <= t && t <= to_t; } + + double distance_at(const double t) const { + assert(this->to_t != this->from_t); + return (t - this->from_t) / (this->to_t - this->from_t) * (this->to_distance - this->from_distance) + this->from_distance; + } + + friend bool operator<(const ColorProjectionRange &lhs, const ColorProjectionRange &rhs) { + return lhs.from_t < rhs.from_t || (lhs.from_t == rhs.from_t && lhs.from_distance < rhs.from_distance); + } + + friend bool operator==(const ColorProjectionRange &lhs, const ColorProjectionRange &rhs) { + return lhs.from_t == rhs.from_t && lhs.from_distance == rhs.from_distance && lhs.to_t == rhs.to_t && lhs.to_distance == rhs.to_distance && lhs.color == rhs.color; + } +}; + +using ColorProjectionRanges = std::vector; + +struct ColorProjectionLine +{ + explicit ColorProjectionLine(const Line &line) : a(line.a), b(line.b){}; + + Point a; + Point b; + + ColorProjectionRanges color_projection_ranges; + ColorChanges color_changes; +}; + +using ColorProjectionLines = std::vector; + +struct ColorProjectionLineWrapper +{ + static const constexpr int Dim = 2; + using Scalar = Point::Scalar; + + explicit ColorProjectionLineWrapper(ColorProjectionLine *const color_projection_line) : a(color_projection_line->a), b(color_projection_line->b), color_projection_line(color_projection_line){}; + + const Point a; + const Point b; + ColorProjectionLine *const color_projection_line; +}; + +struct ColorPoint +{ + Point p; + uint8_t color_prev; + uint8_t color_next; + + explicit ColorPoint(const Point &p, uint8_t color_prev, uint8_t color_next) : p(p), color_prev(color_prev), color_next(color_next) {} +}; + +using ColorPoints = std::vector; + + +[[maybe_unused]] static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { const coordf_t stroke_width = scaled(0.05f); const BoundingBox bbox = get_extents(colored_polygons); @@ -577,7 +191,199 @@ static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, svg.draw(Line(from, to), "red", stroke_width); } } -#endif // MM_SEGMENTATION_DEBUG_GRAPH + +[[maybe_unused]] static void export_regions_to_svg(const std::string &path, const std::vector ®ions, const ExPolygons &lslices) { + const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; + const coordf_t stroke_width = scaled(0.05); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + svg.draw_outline(lslices, "green", "lime", stroke_width); + + for (const ExPolygons &by_extruder : regions) { + if (const size_t extrude_idx = &by_extruder - ®ions.front(); extrude_idx < colors.size()) { + svg.draw(by_extruder, colors[extrude_idx]); + } else { + svg.draw(by_extruder, "black"); + } + } +} + +[[maybe_unused]] void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons) { + const coordf_t stroke_width = scaled(0.05); + BoundingBox bbox = get_extents(regions); + bbox.merge(get_extents(processed_input_expolygons)); + + ::Slic3r::SVG svg(path.c_str(), bbox); + + for (LayerRegion *region : regions) { + for (const Surface &surface : region->slices()) { + svg.draw_outline(surface, "blue", "cyan", stroke_width); + } + } + + svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width); +} + +[[maybe_unused]] static void export_color_polygons_points_to_svg(const std::string &path, const std::vector &color_polygons_points, const ExPolygons &lslices) { + const std::vector colors = {"aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow"}; + const coordf_t stroke_width = scaled(0.02); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + + for (const ColorPoints &color_polygon_points : color_polygons_points) { + for (size_t pt_idx = 1; pt_idx < color_polygon_points.size(); ++pt_idx) { + const ColorPoint &prev_color_pt = color_polygon_points[pt_idx - 1]; + const ColorPoint &curr_color_pt = color_polygon_points[pt_idx]; + svg.draw(Line(prev_color_pt.p, curr_color_pt.p), colors[prev_color_pt.color_next]); + } + + svg.draw(Line(color_polygon_points.back().p, color_polygon_points.front().p), colors[color_polygon_points.back().color_next], stroke_width); + } +} + +[[maybe_unused]] static void export_color_polygons_to_svg(const std::string &path, const ColorPolygons &color_polygons, const ExPolygons &lslices) { + const std::vector colors = {"blue", "cyan", "red", "orange", "pink", "yellow", "magenta", "purple", "black"}; + const std::string default_color = "black"; + const coordf_t stroke_width = scaled(0.05); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + for (const ColorPolygon &color_polygon : color_polygons) { + for (size_t pt_idx = 1; pt_idx < color_polygon.size(); ++pt_idx) { + const uint8_t color = color_polygon.colors[pt_idx - 1]; + svg.draw(Line(color_polygon.points[pt_idx - 1], color_polygon.points[pt_idx]), (color < colors.size() ? colors[color] : default_color), stroke_width); + } + + const uint8_t color = color_polygon.colors.back(); + svg.draw(Line(color_polygon.points.back(), color_polygon.points.front()), (color < colors.size() ? colors[color] : default_color), stroke_width); + } +} + +[[maybe_unused]] static void export_color_polygons_lines_to_svg(const std::string &path, const std::vector &color_polygons_lines, const ExPolygons &lslices) { + const std::vector colors = {"blue", "cyan", "red", "orange", "pink", "yellow", "magenta", "purple", "black"}; + const std::string default_color = "black"; + const coordf_t stroke_width = scaled(0.05); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + for (const ColorLines &color_polygon_lines : color_polygons_lines) { + for (const ColorLine &color_line : color_polygon_lines) { + svg.draw(Line(color_line.a, color_line.b), (color_line.color < colors.size() ? colors[color_line.color] : default_color), stroke_width); + } + } +} + +[[maybe_unused]] static void export_color_projection_lines_color_ranges_to_svg(const std::string &path, const std::vector &color_polygons_projection_lines, const ExPolygons &lslices) { + const std::vector colors = {"blue", "cyan", "red", "orange", "pink", "yellow", "magenta", "purple", "black"}; + const std::string default_color = "black"; + const coordf_t stroke_width = scaled(0.05); + const BoundingBox bbox = get_extents(lslices); + + ::Slic3r::SVG svg(path.c_str(), bbox); + + for (const ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { + for (const ColorProjectionLine &color_projection_line : color_polygon_projection_lines) { + svg.draw(Line(color_projection_line.a, color_projection_line.b), default_color, stroke_width); + + for (const ColorProjectionRange &range : color_projection_line.color_projection_ranges) { + const Vec2d color_projection_line_vec = (color_projection_line.b - color_projection_line.a).cast(); + const Point from_pt = (range.from_t * color_projection_line_vec).cast() + color_projection_line.a; + const Point to_pt = (range.to_t * color_projection_line_vec).cast() + color_projection_line.a; + + svg.draw(Line(from_pt, to_pt), (range.color < colors.size() ? colors[range.color] : default_color), stroke_width); + } + } + } +} + +template +inline OutputIterator douglas_peucker(ColorPoints::const_iterator begin, ColorPoints::const_iterator end, OutputIterator out, const double tolerance, const double max_different_color_length) { + const int64_t tolerance_sq = static_cast(sqr(tolerance)); + const double max_different_color_length_sq = sqr(max_different_color_length); + + auto point_getter = [](const ColorPoint &color_point) -> Point { + return color_point.p; + }; + + auto take_floater_predicate = [&tolerance_sq, &max_different_color_length_sq](ColorPoints::const_iterator anchor_it, ColorPoints::const_iterator floater_it, const int64_t max_dist_sq) -> bool { + // We allow removing points between the anchor and the floater only when the color after the anchor is the same as the color before the floater. + if (max_dist_sq > tolerance_sq || anchor_it->color_next != floater_it->color_prev) + return false; + + const uint8_t anchor_color = anchor_it->color_next; + double different_color_length_sq = 0.; + std::optional color_point_prev; + for (auto cp_it = std::next(anchor_it); cp_it != floater_it; ++cp_it) { + if (cp_it->color_next == anchor_color) { + if (!color_point_prev.has_value()) + continue; + + different_color_length_sq += (cp_it->p - color_point_prev->p).cast().squaredNorm(); + color_point_prev.reset(); + } else if (color_point_prev.has_value()) { + different_color_length_sq += (cp_it->p - color_point_prev->p).cast().squaredNorm(); + color_point_prev = *cp_it; + } else { + assert(!color_point_prev.has_value()); + different_color_length_sq = 0.; + color_point_prev = *cp_it; + } + + if (different_color_length_sq > max_different_color_length_sq) + return false; + } + + return true; + }; + + return douglas_peucker(begin, end, out, take_floater_predicate, point_getter); +} + +BoundingBox get_extents(const std::vector &colored_polygons) { + BoundingBox bbox; + for (const ColoredLines &colored_lines : colored_polygons) { + for (const ColoredLine &colored_line : colored_lines) { + bbox.merge(colored_line.line.a); + bbox.merge(colored_line.line.b); + } + } + return bbox; +} + +// Flatten the vector of vectors into a vector. +static inline ColoredLines to_lines(const std::vector &c_lines) { + size_t n_lines = 0; + for (const auto &c_line : c_lines) { + n_lines += c_line.size(); + } + + ColoredLines lines; + lines.reserve(n_lines); + for (const auto &c_line : c_lines) { + lines.insert(lines.end(), c_line.begin(), c_line.end()); + } + + return lines; +} + +// Determines if the line points from the point between two contour lines is pointing inside polygon or outside. +static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point) { + // Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside + auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d { + assert(left != middle); + assert(middle != right); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); + }; + + assert(contour_first.b == contour_second.a); + Vec2d inward_normal = three_points_inward_normal(contour_first.a, contour_first.b, contour_second.b); + Vec2d edge_norm = (new_point - contour_first.b).cast().normalized(); + double side = inward_normal.dot(edge_norm); + // assert(side != 0.); + return side > 0.; +} static size_t non_deleted_edge_count(const VD::vertex_type &vertex) { size_t non_deleted_edge_cnt = 0; @@ -714,7 +520,7 @@ static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) { // It iterates through all nodes on the border between two different colors, and from this point, // start selection always left most edges for every node to construct CCW polygons. static std::vector extract_colored_segments(const std::vector &colored_polygons, - const size_t num_extruders, + const size_t num_facets_states, const size_t layer_idx) { const ColoredLines colored_lines = to_lines(colored_polygons); @@ -791,15 +597,12 @@ static std::vector extract_colored_segments(const std::vector segmented_expolygons_per_extruder(num_extruders + 1); + std::vector segmented_expolygons_per_extruder(num_facets_states); for (const Voronoi::VD::cell_type &cell : vd.cells()) { if (cell.is_degenerate() || !cell.contains_segment()) continue; @@ -823,10 +626,8 @@ static std::vector extract_colored_segments(const std::vector()); edge->color(VD_ANNOTATION::DELETED); - if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) { - assert(next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR); + if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) break; - } edge = edge->twin(); } while (edge = edge->twin()->next(), edge != cell_range.edge_begin); @@ -852,7 +653,7 @@ static void cut_segmented_layers(const std::vector &input_exp const float interlocking_depth, const std::function &throw_on_cancel_callback) { - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - begin"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Cutting segmented layers in parallel - Begin"; const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f; tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { @@ -868,25 +669,43 @@ static void cut_segmented_layers(const std::vector &input_exp } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Cutting segmented layers in parallel - End"; } -static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) -{ +static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) { const Transform3f trafo_f = trafo.cast(); - for (const stl_vertex &vertex : its.vertices) - if ((trafo_f * vertex).z() < SINKING_Z_THRESHOLD) return true; + for (const stl_vertex &vertex : its.vertices) { + if ((trafo_f * vertex).z() < SINKING_Z_THRESHOLD) + return true; + } + return false; } -// Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo -static inline std::vector> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object, - const std::vector &input_expolygons, - const std::function &throw_on_cancel_callback) +static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_trim, const size_t top_or_bottom_layer_idx, const std::vector> &top_or_bottom_raw_by_extruder) { - const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1; - const size_t num_layers = input_expolygons.size(); - const SpanOfConstPtrs layers = print_object.layers(); + for (const std::vector &top_or_bottom_raw : top_or_bottom_raw_by_extruder) { + if (top_or_bottom_raw.empty()) + continue; + + if (const Polygons &top_or_bottom = top_or_bottom_raw[top_or_bottom_layer_idx]; !top_or_bottom.empty()) { + expolygons_to_trim = diff_ex(expolygons_to_trim, top_or_bottom); + } + } + + return expolygons_to_trim; +} + +// Returns segmentation of top and bottom layers based on painting in segmentation gizmos. +static inline std::vector> segmentation_top_and_bottom_layers(const PrintObject &print_object, + const std::vector &input_expolygons, + const std::function &extract_facets_info, + const size_t num_facets_states, + const std::function &throw_on_cancel_callback) +{ + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - Begin"; + const size_t num_layers = input_expolygons.size(); + const SpanOfConstPtrs layers = print_object.layers(); // Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group. int max_top_layers = 0; @@ -901,26 +720,21 @@ static inline std::vector> mm_segmentation_top_and_botto // Project upwards pointing painted triangles over top surfaces, // project downards pointing painted triangles over bottom surfaces. - std::vector> top_raw(num_extruders), bottom_raw(num_extruders); + std::vector> top_raw(num_facets_states), bottom_raw(num_facets_states); std::vector zs = zs_from_layers(layers); Transform3d object_trafo = print_object.trafo_centered(); -#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM - static int iRun = 0; -#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM - if (max_top_layers > 0 || max_bottom_layers > 0) { for (const ModelVolume *mv : print_object.model_object()->volumes) if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(extruder_idx)); -#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM - { - static int iRun = 0; - its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str()); + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { + const indexed_triangle_set painted = extract_facets_info(*mv).facets_annotation.get_facets_strict(*mv, TriangleStateType(extruder_idx)); + + if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) { + its_write_obj(painted, debug_out_path("mm-painted-patch-%d.obj", extruder_idx).c_str()); } -#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM + if (! painted.indices.empty()) { std::vector top, bottom; if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) { @@ -963,46 +777,68 @@ static inline std::vector> mm_segmentation_top_and_botto } } - auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector> &raw_surfaces, double min_area) -> void { - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) - if (!raw_surfaces[extruder_idx].empty()) - for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) - if (!raw_surfaces[extruder_idx][layer_idx].empty()) - remove_small(raw_surfaces[extruder_idx][layer_idx], min_area); + auto filter_out_small_polygons = [&num_facets_states, &num_layers](std::vector> &raw_surfaces, double min_area) -> void { + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { + if (raw_surfaces[extruder_idx].empty()) + continue; + + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { + if (raw_surfaces[extruder_idx][layer_idx].empty()) + continue; + + remove_small(raw_surfaces[extruder_idx][layer_idx], min_area); + } + } }; // Filter out polygons less than 0.1mm^2, because they are unprintable and causing dimples on outer primers (#7104) - filter_out_small_polygons(top_raw, Slic3r::sqr(scale_(0.1f))); - filter_out_small_polygons(bottom_raw, Slic3r::sqr(scale_(0.1f))); + filter_out_small_polygons(top_raw, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); + filter_out_small_polygons(bottom_raw, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); -#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM - { - const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" }; - static int iRun = 0; + // Remove top and bottom surfaces that are covered by the previous or next sliced layer. + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { + for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { + const bool has_top_surface = !top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty(); + const bool has_bottom_surface = !bottom_raw[extruder_idx].empty() && !bottom_raw[extruder_idx][layer_idx].empty(); + + if (has_top_surface && layer_idx < (num_layers - 1)) { + top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]); + } + + if (has_bottom_surface && layer_idx > 0) { + bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]); + } + } + } + + if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) { + const std::vector colors = {"aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow"}; for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) { std::vector> svg; - for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - if (! top_raw[extruder_idx].empty() && ! top_raw[extruder_idx][layer_id].empty()) - if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); ! expoly.empty()) { - const char *color = colors[extruder_idx]; - svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("top%d", extruder_idx), color, color, color }); + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { + if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_id].empty()) { + if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); !expoly.empty()) { + const std::string &color = colors[extruder_idx]; + svg.emplace_back(expoly, SVG::ExPolygonAttributes{format("top%d", extruder_idx), color, color, color}); } - if (! bottom_raw[extruder_idx].empty() && ! bottom_raw[extruder_idx][layer_id].empty()) - if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); ! expoly.empty()) { - const char *color = colors[extruder_idx + 8]; - svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("bottom%d", extruder_idx), color, color, color }); - } - } - SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%d-%lf.svg", iRun, layer_id, zs[layer_id]), svg); - } - ++ iRun; - } -#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM + } - std::vector> triangles_by_color_bottom(num_extruders); - std::vector> triangles_by_color_top(num_extruders); - triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers * 2)); - triangles_by_color_top.assign(num_extruders, std::vector(num_layers * 2)); + if (!bottom_raw[extruder_idx].empty() && !bottom_raw[extruder_idx][layer_id].empty()) { + if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); !expoly.empty()) { + const std::string &color = colors[extruder_idx + 8]; + svg.emplace_back(expoly, SVG::ExPolygonAttributes{format("bottom%d", extruder_idx), color, color, color}); + } + } + } + + SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%lf.svg", layer_id, zs[layer_id]), svg); + } + } + + std::vector> triangles_by_color_bottom(num_facets_states); + std::vector> triangles_by_color_top(num_facets_states); + triangles_by_color_bottom.assign(num_facets_states, std::vector(num_layers * 2)); + triangles_by_color_top.assign(num_facets_states, std::vector(num_layers * 2)); struct LayerColorStat { // Number of regions for a queried color. @@ -1040,20 +876,21 @@ static inline std::vector> mm_segmentation_top_and_botto return out; }; - tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top, + tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top, &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom](const tbb::blocked_range &range) { size_t group_idx = range.begin() / granularity; size_t layer_idx_offset = (group_idx & 1) * num_layers; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) { + for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) { throw_on_cancel_callback(); LayerColorStat stat = layer_color_stat(layer_idx, color_idx); - if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) - if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) { + if (std::vector &top = top_raw[color_idx]; !top.empty() && !top[layer_idx].empty()) { + if (ExPolygons top_ex = union_ex(top[layer_idx]); !top_ex.empty()) { // Clean up thin projections. They are not printable anyways. if (stat.small_region_threshold > 0) top_ex = opening_ex(top_ex, stat.small_region_threshold); - if (! top_ex.empty()) { + + if (!top_ex.empty()) { append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); float offset = 0.f; ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; @@ -1061,20 +898,29 @@ static inline std::vector> mm_segmentation_top_and_botto offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); ExPolygons last = intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)); + + // Trim this propagated top layer by the painted bottom layer. + last = trim_by_top_or_bottom_layer(last, size_t(last_idx), bottom_raw); + if (stat.small_region_threshold > 0) last = opening_ex(last, stat.small_region_threshold); + if (last.empty()) break; + append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); } } } - if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) - if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) { + } + + if (std::vector &bottom = bottom_raw[color_idx]; !bottom.empty() && !bottom[layer_idx].empty()) { + if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); !bottom_ex.empty()) { // Clean up thin projections. They are not printable anyways. if (stat.small_region_threshold > 0) bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); - if (! bottom_ex.empty()) { + + if (!bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); float offset = 0.f; ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; @@ -1082,20 +928,27 @@ static inline std::vector> mm_segmentation_top_and_botto offset -= stat.extrusion_width; layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); ExPolygons last = intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)); + + // Trim this propagated bottom layer by the painted top layer. + last = trim_by_top_or_bottom_layer(last, size_t(last_idx), top_raw); + if (stat.small_region_threshold > 0) last = opening_ex(last, stat.small_region_threshold); + if (last.empty()) break; + append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); } } } + } } } }); - std::vector> triangles_by_color_merged(num_extruders); - triangles_by_color_merged.assign(num_extruders, std::vector(num_layers)); + std::vector> triangles_by_color_merged(num_facets_states); + triangles_by_color_merged.assign(num_facets_states, std::vector(num_layers)); tbb::parallel_for(tbb::blocked_range(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { throw_on_cancel_callback(); @@ -1113,38 +966,42 @@ static inline std::vector> mm_segmentation_top_and_botto triangles_by_color_merged[color_idx - 1][layer_idx]); } }); + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - End"; return triangles_by_color_merged; } -static std::vector> merge_segmented_layers( - const std::vector> &segmented_regions, - std::vector> &&top_and_bottom_layers, - const size_t num_extruders, - const std::function &throw_on_cancel_callback) +static std::vector> merge_segmented_layers(const std::vector> &segmented_regions, + std::vector> &&top_and_bottom_layers, + const size_t num_facets_states, + const std::function &throw_on_cancel_callback) { const size_t num_layers = segmented_regions.size(); std::vector> segmented_regions_merged(num_layers); - segmented_regions_merged.assign(num_layers, std::vector(num_extruders)); - assert(num_extruders + 1 == top_and_bottom_layers.size()); + segmented_regions_merged.assign(num_layers, std::vector(num_facets_states - 1)); + assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size()); - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - assert(segmented_regions[layer_idx].size() == num_extruders + 1); + assert(segmented_regions[layer_idx].size() == num_facets_states); // Zero is skipped because it is the default color of the volume - for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) { + for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) { throw_on_cancel_callback(); if (!segmented_regions[layer_idx][extruder_id].empty()) { ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id]; - for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) - if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) - segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]); + if (!top_and_bottom_layers.empty()) { + for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) { + if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) { + segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]); + } + } + } segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed); } - if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) { + if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) { bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty(); append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]); @@ -1155,127 +1012,674 @@ static std::vector> merge_segmented_layers( } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - End"; return segmented_regions_merged; } -#ifdef MM_SEGMENTATION_DEBUG_REGIONS -static void export_regions_to_svg(const std::string &path, const std::vector ®ions, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - svg.draw_outline(lslices, "green", "lime", stroke_width); - for (const ExPolygons &by_extruder : regions) { - size_t extrude_idx = &by_extruder - ®ions.front(); - if (extrude_idx < int(colors.size())) - svg.draw(by_extruder, colors[extrude_idx]); - else - svg.draw(by_extruder, "black"); - } -} -#endif // MM_SEGMENTATION_DEBUG_REGIONS - -#ifdef MM_SEGMENTATION_DEBUG_INPUT -void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons) -{ - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(regions); - bbox.merge(get_extents(processed_input_expolygons)); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - for (LayerRegion *region : regions) - for (const Surface &surface : region->slices()) - svg.draw_outline(surface, "blue", "cyan", stroke_width); - - svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width); -} -#endif // MM_SEGMENTATION_DEBUG_INPUT - -#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES -static void export_painted_lines_to_svg(const std::string &path, const std::vector> &all_painted_lines, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - for (const Line &line : to_lines(lslices)) - svg.draw(line, "green", stroke_width); - - for (const std::vector &painted_lines : all_painted_lines) - for (const PaintedLine &painted_line : painted_lines) - svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width); -} -#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - -#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS -static void export_colorized_polygons_to_svg(const std::string &path, const std::vector &colorized_polygons, const ExPolygons &lslices) -{ - const std::vector colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"}; - coordf_t stroke_width = scale_(0.05); - BoundingBox bbox = get_extents(lslices); - bbox.offset(scale_(1.)); - ::Slic3r::SVG svg(path.c_str(), bbox); - - for (const ColoredLines &colorized_polygon : colorized_polygons) - for (const ColoredLine &colorized_line : colorized_polygon) - svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width); -} -#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - // Check if all ColoredLine representing a single layer uses the same color. -static bool has_layer_only_one_color(const std::vector &colored_polygons) -{ +static bool has_layer_only_one_color(const std::vector &colored_polygons) { assert(!colored_polygons.empty()); assert(!colored_polygons.front().empty()); int first_line_color = colored_polygons.front().front().color; - for (const ColoredLines &colored_polygon : colored_polygons) - for (const ColoredLine &colored_line : colored_polygon) + for (const ColoredLines &colored_polygon : colored_polygons) { + for (const ColoredLine &colored_line : colored_polygon) { if (first_line_color != colored_line.color) return false; + } + } return true; } -std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) +BoundingBox get_extents(const ColorPolygon &c_poly) { + return c_poly.bounding_box(); +} + +BoundingBox get_extents(const ColorPolygons &c_polygons) { + BoundingBox bb; + if (!c_polygons.empty()) { + bb = get_extents(c_polygons.front()); + for (size_t i = 1; i < c_polygons.size(); ++i) { + bb.merge(get_extents(c_polygons[i])); + } + } + + return bb; +} + +// Filter out small ColorPolygons based on minimum area and by applying polygon offset. +bool filter_out_small_color_polygons(ColorPolygons &color_polygons, const double filter_min_area, const float filter_offset) { + assert(filter_offset >= 0.); + + bool modified = false; + size_t first_free_idx = 0; + + for (ColorPolygon &color_polygon : color_polygons) { + if (std::abs(color_polygon.area()) >= filter_min_area && (filter_offset <= 0. || !offset(Polygon(color_polygon.points), filter_offset).empty())) { + if (const size_t color_polygon_idx = &color_polygon - color_polygons.data(); first_free_idx < color_polygon_idx) { + std::swap(color_polygon.points, color_polygons[first_free_idx].points); + std::swap(color_polygon.colors, color_polygons[first_free_idx].colors); + } + + ++first_free_idx; + } else { + modified = true; + } + } + + if (first_free_idx < color_polygons.size()) { + color_polygons.erase(color_polygons.begin() + int(first_free_idx), color_polygons.end()); + } + + return modified; +} + +ColorPoints color_polygon_to_color_points(const ColorPolygon &color_polygon) { + assert(!color_polygon.empty()); + assert(color_polygon.points.size() == color_polygon.colors.size()); + + ColorPoints color_points_out; + color_points_out.reserve(color_polygon.size()); + + for (const Point &pt : color_polygon.points) { + const size_t pt_idx = &pt - color_polygon.points.data(); + const uint8_t color_prev = (pt_idx == 0) ? color_polygon.colors.back() : color_polygon.colors[pt_idx - 1]; + const uint8_t color_next = color_polygon.colors[pt_idx]; + + color_points_out.emplace_back(pt, color_prev, color_next); + } + + return color_points_out; +} + +std::vector color_polygons_to_color_points(const ColorPolygons &color_polygons) { + std::vector color_polygons_points_out; + color_polygons_points_out.reserve(color_polygons.size()); + + for (const ColorPolygon &color_polygon : color_polygons) + color_polygons_points_out.emplace_back(color_polygon_to_color_points(color_polygon)); + + return color_polygons_points_out; +} + +std::vector color_points_to_colored_lines(const std::vector &color_polygons_points) { + std::vector colored_lines_vec_out(color_polygons_points.size()); + + for (const ColorPoints &color_polygon_points : color_polygons_points) { + const size_t color_polygon_idx = &color_polygon_points - color_polygons_points.data(); + ColoredLines &colored_lines = colored_lines_vec_out[color_polygon_idx]; + colored_lines.reserve(color_polygon_points.size()); + + for (size_t cpt_idx = 0; cpt_idx < color_polygon_points.size() - 1; ++cpt_idx) { + const ColorPoint &curr_color_point = color_polygon_points[cpt_idx]; + const ColorPoint &next_color_point = color_polygon_points[cpt_idx + 1]; + colored_lines.push_back({Line(curr_color_point.p, next_color_point.p), curr_color_point.color_next, int(color_polygon_idx), int(cpt_idx)}); + } + + colored_lines.push_back({Line(color_polygon_points.back().p, color_polygon_points.front().p), color_polygon_points.back().color_next, int(color_polygon_idx), int(color_polygon_points.size() - 1)}); + } + + return colored_lines_vec_out; +} + +ColorLines color_points_to_color_lines(const ColorPoints &color_points) { + ColorLines color_lines_out; + color_lines_out.reserve(color_points.size()); + + for (size_t cpt_idx = 1; cpt_idx < color_points.size(); ++cpt_idx) { + const ColorPoint &prev_cpt = color_points[cpt_idx - 1]; + const ColorPoint &curr_cpt = color_points[cpt_idx]; + color_lines_out.emplace_back(prev_cpt.p, curr_cpt.p, prev_cpt.color_next); + } + + color_lines_out.emplace_back(color_points.back().p, color_points.front().p, color_points.back().color_next); + + return color_lines_out; +} + +// Create the flat vector of ColorLine from the vector of ColorLines. +static ColorLines flatten_color_lines(const std::vector &color_polygons_lines) { + const size_t total_color_lines_count = std::accumulate(color_polygons_lines.begin(), color_polygons_lines.end(), size_t(0), + [](const size_t acc, const ColorLines &color_lines) { + return acc + color_lines.size(); + }); + + ColorLines color_lines_out; + color_lines_out.reserve(total_color_lines_count); + for (const ColorLines &color_lines : color_polygons_lines) { + Slic3r::append(color_lines_out, color_lines); + } + + return color_lines_out; +} + +static std::vector get_print_object_layers_zs(const SpanOfConstPtrs &layers) { + std::vector layers_zs; + layers_zs.reserve(layers.size()); + + for (const Layer *layer : layers) { + layers_zs.emplace_back(static_cast(layer->slice_z)); + } + + return layers_zs; +} + +void static filter_color_of_small_segments(ColorPoints &color_polygon_points, const double max_different_color_length) { + struct ColorSegment + { + explicit ColorSegment(size_t color_pt_begin_idx, size_t color_pt_end_idx, uint8_t color, double length) + : color_pt_begin_idx(color_pt_begin_idx), color_pt_end_idx(color_pt_end_idx), color(color), length(length) {} + + size_t color_pt_begin_idx = 0; + size_t color_pt_end_idx = 0; + uint8_t color = 0; + double length = 0.; + }; + + auto pt_length = [](const ColorPoint &color_pt_a, const ColorPoint &color_pt_b) -> double { + return (color_pt_b.p.cast() - color_pt_a.p.cast()).norm(); + }; + + std::vector color_segments; + color_segments.emplace_back(0, 0, color_polygon_points.front().color_next, 0.); + + for (size_t color_pt_idx = 1; color_pt_idx < color_polygon_points.size(); ++color_pt_idx) { + const ColorPoint &prev_color_pt = color_polygon_points[color_pt_idx - 1]; + const ColorPoint &curr_color_pt = color_polygon_points[color_pt_idx]; + + ColorSegment &last_color_segment = color_segments.back(); + + if (last_color_segment.color == curr_color_pt.color_next) { + last_color_segment.color_pt_end_idx = color_pt_idx; + last_color_segment.length += pt_length(prev_color_pt, curr_color_pt); + } else { + last_color_segment.color_pt_end_idx = color_pt_idx; + last_color_segment.length += pt_length(prev_color_pt, curr_color_pt); + color_segments.emplace_back(color_pt_idx, color_pt_idx, curr_color_pt.color_next, 0.); + } + } + + ColorSegment &last_color_segment = color_segments.back(); + last_color_segment.color_pt_end_idx = 0; + last_color_segment.length += pt_length(color_polygon_points.back(), color_polygon_points.front()); + + if (color_segments.size() > 2 && color_segments.front().color == last_color_segment.color) { + color_segments.front().color_pt_begin_idx = last_color_segment.color_pt_begin_idx; + color_segments.front().length += last_color_segment.length; + color_segments.pop_back(); + } + + auto next_segment_idx = [&color_segments](const size_t curr_segment_idx) -> size_t { + return curr_segment_idx < (color_segments.size() - 1) ? curr_segment_idx + 1 : 0; + }; + + for (size_t from_segment_idx = 0; from_segment_idx < color_segments.size();) { + size_t to_segment_idx = next_segment_idx(from_segment_idx); + double total_diff_color_length = 0.; + + bool update_color = false; + while (from_segment_idx != to_segment_idx) { + if (total_diff_color_length > max_different_color_length) { + break; + } else if (color_segments[from_segment_idx].color == color_segments[to_segment_idx].color) { + update_color = true; + break; + } + + total_diff_color_length += color_segments[to_segment_idx].length; + to_segment_idx = next_segment_idx(to_segment_idx); + } + + if (!update_color) { + ++from_segment_idx; + continue; + } + + const uint8_t new_color = color_segments[from_segment_idx].color; + for (size_t curr_segment_idx = next_segment_idx(from_segment_idx); curr_segment_idx != to_segment_idx; curr_segment_idx = next_segment_idx(curr_segment_idx)) { + for (size_t pt_idx = color_segments[curr_segment_idx].color_pt_begin_idx; pt_idx != color_segments[curr_segment_idx].color_pt_end_idx; pt_idx = (pt_idx < color_polygon_points.size() - 1) ? pt_idx + 1 : 0) { + color_polygon_points[pt_idx].color_prev = new_color; + color_polygon_points[pt_idx].color_next = new_color; + } + + color_polygon_points[color_segments[curr_segment_idx].color_pt_end_idx].color_prev = new_color; + color_polygon_points[color_segments[curr_segment_idx].color_pt_end_idx].color_next = new_color; + + color_segments[curr_segment_idx].color = new_color; + } + + if (from_segment_idx < to_segment_idx) { + from_segment_idx = to_segment_idx; + } else { + // We already processed all segments. + break; + } + } +} + +[[maybe_unused]] static bool is_valid_color_polygon_points(const ColorPoints &color_polygon_points) { + for (size_t pt_idx = 1; pt_idx < color_polygon_points.size(); ++pt_idx) { + const ColorPoint &prev_color_pt = color_polygon_points[pt_idx - 1]; + const ColorPoint &curr_color_pt = color_polygon_points[pt_idx]; + + if (prev_color_pt.color_next != curr_color_pt.color_prev) + return false; + } + + if (color_polygon_points.back().color_next != color_polygon_points.front().color_prev) + return false; + + return true; +} + +static std::vector create_color_projection_lines(const ExPolygon &ex_polygon) { + std::vector color_projection_lines(ex_polygon.num_contours()); + + for (size_t contour_idx = 0; contour_idx < ex_polygon.num_contours(); ++contour_idx) { + const Lines lines = ex_polygon.contour_or_hole(contour_idx).lines(); + color_projection_lines[contour_idx].reserve(lines.size()); + + for (const Line &line : lines) { + color_projection_lines[contour_idx].emplace_back(line); + } + } + + return color_projection_lines; +} + +static std::vector create_color_projection_lines(const ExPolygons &ex_polygons) { + std::vector color_projection_lines; + color_projection_lines.reserve(number_polygons(ex_polygons)); + + for (const ExPolygon &ex_polygon : ex_polygons) { + Slic3r::append(color_projection_lines, create_color_projection_lines(ex_polygon)); + } + + return color_projection_lines; +} + +// Create the flat vector of ColorProjectionLineWrapper where each ColorProjectionLineWrapper +// is pointing into the one ColorProjectionLine in the vector of ColorProjectionLines. +static std::vector create_color_projection_lines_mapping(std::vector &color_polygons_projection_lines) { + auto total_lines_count = [&color_polygons_projection_lines]() { + return std::accumulate(color_polygons_projection_lines.begin(), color_polygons_projection_lines.end(), size_t(0), + [](const size_t acc, const ColorProjectionLines &color_projection_lines) { + return acc + color_projection_lines.size(); + }); + }; + + std::vector color_projection_lines_mapping; + color_projection_lines_mapping.reserve(total_lines_count()); + + for (ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { + for (ColorProjectionLine &color_projection_line : color_polygon_projection_lines) { + color_projection_lines_mapping.emplace_back(&color_projection_line); + } + } + + return color_projection_lines_mapping; +} + +// Return the color of the first part of the first line of the polygon (after projection). +static uint8_t get_color_of_first_polygon_line(const ColorProjectionLines &color_polygon_projection_lines) { + assert(!color_polygon_projection_lines.empty()); + + if (color_polygon_projection_lines.empty()) { + return 0; + } else if (const ColorProjectionLine &first_line = color_polygon_projection_lines.front(); !first_line.color_changes.empty() && first_line.color_changes.front().t == 0.f) { + return first_line.color_changes.front().color_next; + } + + auto last_projection_line_it = std::find_if(color_polygon_projection_lines.rbegin(), color_polygon_projection_lines.rend(), [](const ColorProjectionLine &line) { + return !line.color_changes.empty(); + }); + + assert(last_projection_line_it != color_polygon_projection_lines.rend()); + if (last_projection_line_it == color_polygon_projection_lines.rend()) { + // There is no projected color on this whole polygon. + return 0; + } else { + return last_projection_line_it->color_changes.back().color_next; + } +} + +static void filter_projected_color_points_on_polygons(std::vector &color_polygons_projection_lines) { + for (ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { + for (ColorProjectionLine &color_line : color_polygon_projection_lines) { + if (color_line.color_changes.empty()) + continue; + + std::sort(color_line.color_changes.begin(), color_line.color_changes.end()); + + // Snap projected points to the first endpoint of the line. + const double line_length = (color_line.b - color_line.a).cast().norm(); + + std::vector snap_candidates; + for (ColorChange &color_change : color_line.color_changes) { + if (const double endpoint_dist = color_change.t * line_length; endpoint_dist < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { + snap_candidates.emplace_back(&color_change); + } else { + break; + } + } + + if (snap_candidates.size() == 1) { + snap_candidates.front()->t = 0.; + } else if (snap_candidates.size() > 1) { + ColorChange &first_candidate = *snap_candidates.front(); + ColorChange &last_candidate = *snap_candidates.back(); + + first_candidate.t = 0.; + for (auto cr_it = std::next(snap_candidates.begin()); cr_it != snap_candidates.end(); ++cr_it) { + (*cr_it)->color_next = last_candidate.color_next; + } + } + + snap_candidates.clear(); + + // Snap projected points to the second endpoint of the line. + for (auto cr_it = color_line.color_changes.rbegin(); cr_it != color_line.color_changes.rend(); ++cr_it) { + ColorChange &color_change = *cr_it; + if (const double endpoint_dist = (1. - color_change.t) * line_length; endpoint_dist < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { + snap_candidates.emplace_back(&color_change); + } else { + break; + } + } + + while (snap_candidates.size() > 1) { + snap_candidates.pop_back(); + color_line.color_changes.pop_back(); + } + + if (!snap_candidates.empty()) { + assert(snap_candidates.size() == 1); + snap_candidates.back()->t = 1.; + } + + // Remove color ranges that just repeating the same color. + // We don't care about color_prev, because both color_prev and color_next may not be connected. + // Also, we will not use color_prev during the final stage of producing ColorPolygons. + if (color_line.color_changes.size() > 1) { + ColorChanges color_changes_filtered; + color_changes_filtered.reserve(color_line.color_changes.size()); + + color_changes_filtered.emplace_back(color_line.color_changes.front()); + for (auto cr_it = std::next(color_line.color_changes.begin()); cr_it != color_line.color_changes.end(); ++cr_it) { + ColorChange &color_change = *cr_it; + + if (color_changes_filtered.back().color_next == color_change.color_next) { + continue; + } else if (const double t_diff = (color_change.t - color_changes_filtered.back().t); t_diff * line_length < MM_SEGMENTATION_MAX_SNAP_DISTANCE_SCALED) { + color_changes_filtered.back().color_next = color_change.color_next; + } else { + color_changes_filtered.emplace_back(color_change); + } + } + + color_line.color_changes = std::move(color_changes_filtered); + } + } + } +} + +static std::vector convert_color_polygons_projection_lines_to_color_points(const std::vector &color_polygons_projection_lines) { + std::vector color_polygons_points; + color_polygons_points.reserve(color_polygons_projection_lines.size()); + + for (const ColorProjectionLines &color_polygon_projection_lines : color_polygons_projection_lines) { + if (color_polygon_projection_lines.empty()) + continue; + + ColorPoints color_polygon_points; + color_polygon_points.reserve(color_polygon_projection_lines.size()); + + uint8_t prev_color = get_color_of_first_polygon_line(color_polygon_projection_lines); + uint8_t curr_color = prev_color; + for (const ColorProjectionLine &color_line : color_polygon_projection_lines) { + if (color_line.color_changes.empty()) { + color_polygon_points.emplace_back(color_line.a, prev_color, curr_color); + prev_color = curr_color; + } else { + if (const ColorChange &first_color_change = color_line.color_changes.front(); first_color_change.t != 0.) { + color_polygon_points.emplace_back(color_line.a, prev_color, curr_color); + prev_color = curr_color; + } + + for (const ColorChange &color_change : color_line.color_changes) { + if (color_change.t != 1.) { + const Vec2d color_line_vec = (color_line.b - color_line.a).cast(); + const Point color_line_new_pt = (color_change.t * color_line_vec).cast() + color_line.a; + + color_polygon_points.emplace_back(color_line_new_pt, prev_color, color_change.color_next); + curr_color = color_change.color_next; + prev_color = curr_color; + } + } + + if (const ColorChange &last_color_change = color_line.color_changes.back(); last_color_change.t == 1.) { + curr_color = last_color_change.color_next; + } + } + } + + ColorPoints color_polygon_points_filtered; + color_polygon_points_filtered.reserve(color_polygon_points.size()); + + douglas_peucker(color_polygon_points.begin(), color_polygon_points.end(), std::back_inserter(color_polygon_points_filtered), INPUT_POLYGONS_FILTER_TOLERANCE_SCALED, POLYGON_COLOR_FILTER_DISTANCE_SCALED); + + if (color_polygon_points_filtered.size() < 3) + continue; + + filter_color_of_small_segments(color_polygon_points_filtered, POLYGON_COLOR_FILTER_DISTANCE_SCALED); + + color_polygons_points.emplace_back(std::move(color_polygon_points_filtered)); + } + + return color_polygons_points; +} + +static std::optional project_color_line_on_projection_line(const ColorLine &color_line, const ColorProjectionLine &projection_line, const double max_projection_distance_scaled) { + const Vec2d projection_line_vec = (projection_line.b - projection_line.a).cast(); + const Vec2d color_line_vec_a = (color_line.a - projection_line.a).cast(); + const Vec2d color_line_vec_b = (color_line.b - projection_line.a).cast(); + + const double projection_line_length_sqr = projection_line_vec.squaredNorm(); + if (projection_line_length_sqr == 0.) + return std::nullopt; + + // Project both endpoints of color_line on the projection_line. + const double t_a_raw = color_line_vec_a.dot(projection_line_vec) / projection_line_length_sqr; + const double t_b_raw = color_line_vec_b.dot(projection_line_vec) / projection_line_length_sqr; + + const double t_a_clamped = std::clamp(t_a_raw, 0., 1.); + const double t_b_clamped = std::clamp(t_b_raw, 0., 1.); + + if (t_a_clamped == t_b_clamped) + return std::nullopt; + + auto distance_to_color_line = [&projection_line_vec, &projection_line, &color_line](const double t_raw, const double t_clamped, const Vec2d &color_line_vec_pt) -> double { + if (0. <= t_raw && t_raw <= 1.) { + return (t_clamped * projection_line_vec - color_line_vec_pt).norm(); + } else { + // T value is outside <0, 1>, so we calculate the distance between the clamped T value and the nearest point on the color_line. + // That means that we calculate the distance between one of the endpoints of the projection_line and the color_line. + const Point &projection_line_nearest_pt = (t_raw < 0.) ? projection_line.a : projection_line.b; + return line_alg::distance_to(color_line.line(), projection_line_nearest_pt); + } + }; + + // Calculate the distance of both endpoints of color_line to the projection_line. + const double color_line_a_dist = distance_to_color_line(t_a_raw, t_a_clamped, color_line_vec_a); + const double color_line_b_dist = distance_to_color_line(t_b_raw, t_b_clamped, color_line_vec_b); + + ColorProjectionRange range = t_a_clamped < t_b_clamped ? ColorProjectionRange{t_a_clamped, color_line_a_dist, t_b_clamped, color_line_b_dist, color_line.color} + : ColorProjectionRange{t_b_clamped, color_line_b_dist, t_a_clamped, color_line_a_dist, color_line.color}; + + if (range.from_distance <= max_projection_distance_scaled && range.to_distance <= max_projection_distance_scaled) { + // Both endpoints are close enough to the line, so we don't have to do linear interpolation. + return range; + } else if (range.from_distance > max_projection_distance_scaled && range.to_distance > max_projection_distance_scaled) { + // Both endpoints are too distant from the projection_line. + return std::nullopt; + } + + // Calculate for which value of T we reach the distance of max_projection_distance_scaled. + const double t_max = (max_projection_distance_scaled - range.from_distance) / (range.to_distance - range.from_distance) * (range.to_t - range.from_t) + range.from_t; + if (range.from_distance > max_projection_distance_scaled) { + range.from_t = t_max; + range.from_distance = max_projection_distance_scaled; + } else { + range.to_t = t_max; + range.to_distance = max_projection_distance_scaled; + } + + return range; +} + +inline void update_color_changes_using_color_projection_ranges(ColorProjectionLine &projection_line) { + ColorProjectionRanges &ranges = projection_line.color_projection_ranges; + Slic3r::sort_remove_duplicates(ranges); + + // First, calculate event points in which the nearest color could change. + std::vector event_points; + for (const ColorProjectionRange &range : ranges) { + event_points.emplace_back(range.from_t); + event_points.emplace_back(range.to_t); + } + + auto make_linef = [](const ColorProjectionRange &range) -> Linef { + return {Vec2d(range.from_t, range.from_distance), Vec2d(range.to_t, range.to_distance)}; + }; + + for (auto curr_range_it = ranges.begin(); curr_range_it != ranges.end(); ++curr_range_it) { + for (auto next_range_it = std::next(curr_range_it); next_range_it != ranges.end(); ++next_range_it) { + if (curr_range_it->to_t == next_range_it->from_t) { + continue; + } else if (!curr_range_it->contains(next_range_it->from_t)) { + // Ranges are sorted based on the from_t, so when the next->from_t isn't inside the current range, we can skip all other succeeding ranges. + break; + } + + Vec2d intersect_pt; + if (line_alg::intersection(make_linef(*curr_range_it), make_linef(*next_range_it), &intersect_pt)) { + event_points.emplace_back(intersect_pt.x()); + } + } + } + + Slic3r::sort_remove_duplicates(event_points); + + for (size_t event_idx = 1; event_idx < event_points.size(); ++event_idx) { + const double range_start = event_points[event_idx - 1]; + const double range_end = event_points[event_idx]; + + double min_area_value = std::numeric_limits::max(); + ColorPolygon::Color min_area_color = 0; + for (const ColorProjectionRange &range : ranges) { + if (!range.contains(range_start) || !range.contains(range_end)) + continue; + + // Minimize the area of the trapezoid defined by range length and distance in its endpoints. + const double range_area = range.distance_at(range_start) + range.distance_at(range_end); + if (range_area < min_area_value) { + min_area_value = range_area; + min_area_color = range.color; + } + } + + if (min_area_value != std::numeric_limits::max()) { + projection_line.color_changes.emplace_back(range_start, min_area_color); + } + } +} + +static void update_color_changes_using_color_projection_ranges(std::vector &polygons_projection_lines) { + for (ColorProjectionLines &polygon_projection_lines : polygons_projection_lines) { + for (ColorProjectionLine &projection_line : polygon_projection_lines) { + update_color_changes_using_color_projection_ranges(projection_line); + } + } +} + +static std::vector slice_model_volume_with_color(const ModelVolume &model_volume, + const std::function &extract_facets_info, + const std::vector &layer_zs, + const PrintObject &print_object, + const size_t num_facets_states) { - const size_t num_extruders = print_object.print()->config().nozzle_diameter.size(); - const size_t num_layers = print_object.layers().size(); - std::vector> segmented_regions(num_layers); - segmented_regions.assign(num_layers, std::vector(num_extruders + 1)); - std::vector> painted_lines(num_layers); - std::array painted_lines_mutex; - std::vector edge_grids(num_layers); - const SpanOfConstPtrs layers = print_object.layers(); - std::vector input_expolygons(num_layers); + const ModelVolumeFacetsInfo facets_info = extract_facets_info(model_volume); - throw_on_cancel_callback(); + const auto extract_mesh_with_color = [&model_volume, &facets_info]() -> indexed_triangle_set_with_color { + if (const int volume_extruder_id = model_volume.extruder_id(); facets_info.replace_default_extruder && !facets_info.is_painted && volume_extruder_id >= 0) { + const TriangleMesh &mesh = model_volume.mesh(); + return {mesh.its.indices, mesh.its.vertices, std::vector(mesh.its.indices.size(), uint8_t(volume_extruder_id))}; + } -#ifdef MM_SEGMENTATION_DEBUG - static int iRun = 0; -#endif // MM_SEGMENTATION_DEBUG + return facets_info.facets_annotation.get_all_facets_strict_with_colors(model_volume); + }; + + const indexed_triangle_set_with_color mesh_with_color = extract_mesh_with_color(); + const Transform3d trafo = print_object.trafo_centered() * model_volume.get_matrix(); + const MeshSlicingParams slicing_params{trafo}; + + std::vector color_polygons_per_layer = slice_mesh(mesh_with_color, layer_zs, slicing_params); + + // Replace default painted color (TriangleStateType::NONE) with volume extruder. + if (const int volume_extruder_id = model_volume.extruder_id(); facets_info.replace_default_extruder && facets_info.is_painted && volume_extruder_id > 0) { + for (ColorPolygons &color_polygons : color_polygons_per_layer) { + for (ColorPolygon &color_polygon : color_polygons) { + std::replace(color_polygon.colors.begin(), color_polygon.colors.end(), static_cast(TriangleStateType::NONE), static_cast(volume_extruder_id)); + } + } + } + + // Replace any non-existing painted color with the default (TriangleStateType::NONE). + for (ColorPolygons &color_polygons : color_polygons_per_layer) { + for (ColorPolygon &color_polygon : color_polygons) { + std::replace_if(color_polygon.colors.begin(), color_polygon.colors.end(), + [&num_facets_states](const uint8_t color) { return color >= num_facets_states; }, + static_cast(TriangleStateType::NONE)); + } + } + + return color_polygons_per_layer; +} + +std::vector> segmentation_by_painting(const PrintObject &print_object, + const std::function &extract_facets_info, + const size_t num_facets_states, + const float segmentation_max_width, + const float segmentation_interlocking_depth, + const IncludeTopAndBottomLayers include_top_and_bottom_layers, + const std::function &throw_on_cancel_callback) +{ + const size_t num_layers = print_object.layers().size(); + const SpanOfConstPtrs layers = print_object.layers(); + + std::vector input_expolygons(num_layers); + std::vector> input_polygons_projection_lines_layers(num_layers); + std::vector> color_polygons_lines_layers(num_layers); // Merge all regions and remove small holes - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - Begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); + ExPolygons ex_polygons; - for (LayerRegion *region : layers[layer_idx]->regions()) - for (const Surface &surface : region->slices()) + for (LayerRegion *region : layers[layer_idx]->regions()) { + for (const Surface &surface : region->slices()) { Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON))); + } + } + // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON // to ensure that very close polygons will be merged. ex_polygons = union_ex(ex_polygons); // Remove all expolygons and holes with an area less than 0.1mm^2 - remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f))); + remove_small_and_small_holes(ex_polygons, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED)); // Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams // and consequently with the extraction of colored segments by function extract_colored_segments. // Calling simplify_polygons removes these self-intersections. @@ -1283,182 +1687,206 @@ std::vector> multi_material_segmentation_by_painting(con // Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices. // This consequently leads to issues with the extraction of colored segments by function extract_colored_segments. // Calling expolygons_simplify fixed these issues. - input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI/6); + input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled(0.01), PI / 6); + input_polygons_projection_lines_layers[layer_idx] = create_color_projection_lines(input_expolygons[layer_idx]); -#ifdef MM_SEGMENTATION_DEBUG_INPUT - export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun), layers[layer_idx]->regions(), input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_INPUT + if constexpr (MM_SEGMENTATION_DEBUG_INPUT) { + export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d.svg", layer_idx), layers[layer_idx]->regions(), input_expolygons[layer_idx]); + } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - End"; - std::vector layer_bboxes(num_layers); - for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { - throw_on_cancel_callback(); - layer_bboxes[layer_idx] = get_extents(layers[layer_idx]->regions()); - layer_bboxes[layer_idx].merge(get_extents(input_expolygons[layer_idx])); - } - - for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) { - throw_on_cancel_callback(); - BoundingBox bbox = layer_bboxes[layer_idx]; - // Projected triangles could, in rare cases (as in GH issue #7299), belongs to polygons printed in the previous or the next layer. - // Let's merge the bounding box of the current layer with bounding boxes of the previous and the next layer to ensure that - // every projected triangle will be inside the resulting bounding box. - if (layer_idx > 1) bbox.merge(layer_bboxes[layer_idx - 1]); - if (layer_idx < num_layers - 1) bbox.merge(layer_bboxes[layer_idx + 1]); - // Projected triangles may slightly exceed the input polygons. - bbox.offset(20 * SCALED_EPSILON); - edge_grids[layer_idx].set_bbox(bbox); - edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); - } - - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin"; + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slicing painted triangles - Begin"; + const std::vector layer_zs = get_print_object_layers_zs(layers); for (const ModelVolume *mv : print_object.model_object()->volumes) { - tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { - for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { + std::vector color_polygons_per_layer = slice_model_volume_with_color(*mv, extract_facets_info, layer_zs, print_object, num_facets_states); + + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_per_layer, &color_polygons_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); - const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, TriangleStateType(extruder_idx)); - if (!mv->is_model_part() || custom_facets.indices.empty()) + + ColorPolygons &raw_color_polygons = color_polygons_per_layer[layer_idx]; + filter_out_small_color_polygons(raw_color_polygons, POLYGON_FILTER_MIN_AREA_SCALED, POLYGON_FILTER_MIN_OFFSET_SCALED); + + if (raw_color_polygons.empty()) continue; - const Transform3f tr = print_object.trafo().cast() * mv->get_matrix().cast(); - tbb::parallel_for(tbb::blocked_range(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range &range) { - for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) { - float min_z = std::numeric_limits::max(); - float max_z = std::numeric_limits::lowest(); + // Convert ColorPolygons into the vector of ColorPoints to perform several filtrations that are performed on points. + color_polygons_lines_layers[layer_idx].reserve(color_polygons_lines_layers[layer_idx].size() + raw_color_polygons.size()); + for (const ColorPoints &color_polygon_points : color_polygons_to_color_points(raw_color_polygons)) { + ColorPoints color_polygon_points_filtered; + color_polygon_points_filtered.reserve(color_polygon_points.size()); - std::array facet; - for (int p_idx = 0; p_idx < 3; ++p_idx) { - facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)]; - max_z = std::max(max_z, facet[p_idx].z()); - min_z = std::min(min_z, facet[p_idx].z()); - } + douglas_peucker(color_polygon_points.begin(), color_polygon_points.end(), std::back_inserter(color_polygon_points_filtered), POLYGON_COLOR_FILTER_TOLERANCE_SCALED, POLYGON_COLOR_FILTER_DISTANCE_SCALED); - // Sort the vertices by z-axis for simplification of projected_facet on slices - std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); }); + if (color_polygon_points_filtered.size() < 3) + continue; - // Find lowest slice not below the triangle. - auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z; }); - auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z + EPSILON), - [](float z, const Layer *l1) { return z < l1->slice_z; }); - --last_layer; + filter_color_of_small_segments(color_polygon_points_filtered, POLYGON_COLOR_FILTER_DISTANCE_SCALED); + assert(is_valid_color_polygon_points(color_polygon_points_filtered)); - for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) { - const Layer *layer = *layer_it; - size_t layer_idx = layer_it - layers.begin(); - if (input_expolygons[layer_idx].empty() || facet[0].z() > layer->slice_z || layer->slice_z > facet[2].z()) - continue; - - // https://kandepet.com/3d-printing-slicing-3d-objects/ - float t = (float(layer->slice_z) - facet[0].z()) / (facet[2].z() - facet[0].z()); - Vec3f line_start_f = facet[0] + t * (facet[2] - facet[0]); - Vec3f line_end_f; - - if (facet[1].z() > layer->slice_z) { - // [P0, P2] and [P0, P1] - float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z()); - line_end_f = facet[0] + t1 * (facet[1] - facet[0]); - } else { - // [P0, P2] and [P1, P2] - float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z()); - line_end_f = facet[1] + t2 * (facet[2] - facet[1]); - } - - Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())), - Point(scale_(line_end_f.x()), scale_(line_end_f.y()))); - line_to_test.translate(-print_object.center_offset()); - - // BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could - // be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618). - // To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases - // when any of the endpoints of the line are outside the EdgeGrid's BoundingBox. - if (const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); !edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) { - // If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line. - if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) || - !line_to_test.clip_with_bbox(edge_grid_bbox)) - continue; - } - - size_t mutex_idx = layer_idx & 0x3F; - assert(mutex_idx < painted_lines_mutex.size()); - - PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16); - visitor.line_to_test = line_to_test; - visitor.color = int(extruder_idx); - edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor); - } - } - }); // end of parallel_for + color_polygons_lines_layers[layer_idx].emplace_back(color_points_to_color_lines(color_polygon_points_filtered)); + } } }); // end of parallel_for } - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end"; - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: " - << std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); }); + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slicing painted triangles - End"; - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin"; - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) { + if constexpr (MM_SEGMENTATION_DEBUG_FILTERED_COLOR_LINES) { + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { + export_color_polygons_lines_to_svg(debug_out_path("mm-filtered-color-line-%d.svg", layer_idx), color_polygons_lines_layers[layer_idx], input_expolygons[layer_idx]); + } + } + + // Project sliced ColorPolygons on sliced layers (input_expolygons). + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Projection of painted triangles - Begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_lines_layers, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { throw_on_cancel_callback(); - if (!painted_lines[layer_idx].empty()) { -#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun), {painted_lines[layer_idx]}, input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - std::vector> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx])); + // For each ColorLine, find the nearest ColorProjectionLines and project the ColorLine on each ColorProjectionLine. + const AABBTreeLines::LinesDistancer color_projection_lines_distancer{create_color_projection_lines_mapping(input_polygons_projection_lines_layers[layer_idx])}; + for (const ColorLines &color_polygon : color_polygons_lines_layers[layer_idx]) { + for (const ColorLine &color_line : color_polygon) { + std::vector nearest_projection_line_indices; + Slic3r::append(nearest_projection_line_indices, color_projection_lines_distancer.all_lines_in_radius(color_line.a, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::append(nearest_projection_line_indices, color_projection_lines_distancer.all_lines_in_radius(color_line.b, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::sort_remove_duplicates(nearest_projection_line_indices); -#ifdef MM_SEGMENTATION_DEBUG_PAINTED_LINES - export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun), post_processed_painted_lines, input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_PAINTED_LINES - - std::vector color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines); - -#ifdef MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun), color_poly, input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS - - assert(!color_poly.empty()); - assert(!color_poly.front().empty()); - if (has_layer_only_one_color(color_poly)) { - // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. - segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; - } else { - segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); + for (size_t nearest_projection_line_idx : nearest_projection_line_indices) { + ColorProjectionLine &color_projection_line = *color_projection_lines_distancer.get_line(nearest_projection_line_idx).color_projection_line; + std::optional projection = project_color_line_on_projection_line(color_line, color_projection_line, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED); + if (projection.has_value()) { + color_projection_line.color_projection_ranges.emplace_back(*projection); + } + } } + } -#ifdef MM_SEGMENTATION_DEBUG_REGIONS - export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_REGIONS + // For each ColorProjectionLine, find the nearest ColorLines and project them on the ColorProjectionLine. + const AABBTreeLines::LinesDistancer color_lines_distancer{flatten_color_lines(color_polygons_lines_layers[layer_idx])}; + for (ColorProjectionLines &input_polygon_projection_lines : input_polygons_projection_lines_layers[layer_idx]) { + for (ColorProjectionLine &projection_lines : input_polygon_projection_lines) { + std::vector nearest_color_line_indices; + Slic3r::append(nearest_color_line_indices, color_lines_distancer.all_lines_in_radius(projection_lines.a, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::append(nearest_color_line_indices, color_lines_distancer.all_lines_in_radius(projection_lines.b, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED)); + Slic3r::sort_remove_duplicates(nearest_color_line_indices); + + for (size_t nearest_color_line_idx : nearest_color_line_indices) { + const ColorLine &color_line = color_lines_distancer.get_line(nearest_color_line_idx); + std::optional projection = project_color_line_on_projection_line(color_line, projection_lines, MM_SEGMENTATION_MAX_PROJECTION_DISTANCE_SCALED); + if (projection.has_value()) { + projection_lines.color_projection_ranges.emplace_back(*projection); + } + } + } } } }); // end of parallel_for - BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End"; + + std::vector> segmented_regions(num_layers); + segmented_regions.assign(num_layers, std::vector(num_facets_states)); + + // Be aware that after the projection of the ColorPolygons and its postprocessing isn't + // ensured that consistency of the color_prev. So, only color_next can be used. + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - Begin"; + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel_callback(); + + std::vector &input_polygons_projection_lines = input_polygons_projection_lines_layers[layer_idx]; + if (input_polygons_projection_lines.empty()) { + continue; + } + + if constexpr (MM_SEGMENTATION_DEBUG_COLOR_RANGES) { + export_color_projection_lines_color_ranges_to_svg(debug_out_path("mm-color-ranges-%d.svg", layer_idx), input_polygons_projection_lines, input_expolygons[layer_idx]); + } + + update_color_changes_using_color_projection_ranges(input_polygons_projection_lines); + filter_projected_color_points_on_polygons(input_polygons_projection_lines); + + const std::vector color_polygons_points = convert_color_polygons_projection_lines_to_color_points(input_polygons_projection_lines); + const std::vector colored_polygons = color_points_to_colored_lines(color_polygons_points); + + if constexpr (MM_SEGMENTATION_DEBUG_COLORIZED_POLYGONS) { + export_color_polygons_points_to_svg(debug_out_path("mm-projected-color_polygon-%d.svg", layer_idx), color_polygons_points, input_expolygons[layer_idx]); + } + + assert(!colored_polygons.empty()); + if (has_layer_only_one_color(colored_polygons)) { + // When the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. + assert(!colored_polygons.front().empty()); + segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)] = input_expolygons[layer_idx]; + } else { + segmented_regions[layer_idx] = extract_colored_segments(colored_polygons, num_facets_states, layer_idx); + } + + if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) { + export_regions_to_svg(debug_out_path("mm-regions-non-merged-%d.svg", layer_idx), segmented_regions[layer_idx], input_expolygons[layer_idx]); + } + } + }); // end of parallel_for + BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - End"; throw_on_cancel_callback(); - if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) { - cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback); + // The first index is extruder number (includes default extruder), and the second one is layer number + std::vector> top_and_bottom_layers; + if (include_top_and_bottom_layers == IncludeTopAndBottomLayers::Yes) { + top_and_bottom_layers = segmentation_top_and_bottom_layers(print_object, input_expolygons, extract_facets_info, num_facets_states, throw_on_cancel_callback); throw_on_cancel_callback(); } - // The first index is extruder number (includes default extruder), and the second one is layer number - std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback); + if (segmentation_max_width > 0.f) { + cut_segmented_layers(input_expolygons, segmented_regions, scaled(segmentation_max_width), scaled(segmentation_interlocking_depth), throw_on_cancel_callback); + throw_on_cancel_callback(); + } + + std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback); throw_on_cancel_callback(); - std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback); - throw_on_cancel_callback(); - -#ifdef MM_SEGMENTATION_DEBUG_REGIONS - for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) - export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); -#endif // MM_SEGMENTATION_DEBUG_REGIONS - -#ifdef MM_SEGMENTATION_DEBUG - ++iRun; -#endif // MM_SEGMENTATION_DEBUG + if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) { + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) { + export_regions_to_svg(debug_out_path("mm-regions-merged-%d.svg", layer_idx), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); + } + } return segmented_regions_merged; } -} // namespace Slic3r +// Returns multi-material segmentation based on painting in multi-material segmentation gizmo +std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { + const size_t num_facets_states = print_object.print()->config().nozzle_diameter.size() + 1; + const float max_width = float(print_object.config().mmu_segmented_region_max_width.value); + const float interlocking_depth = float(print_object.config().mmu_segmented_region_interlocking_depth.value); + + const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo { + return {mv.mm_segmentation_facets, mv.is_mm_painted(), false}; + }; + + return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_width, interlocking_depth, IncludeTopAndBottomLayers::Yes, throw_on_cancel_callback); +} + +// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo +std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { + const size_t num_facets_states = 2; // Unpainted facets and facets painted with fuzzy skin. + + const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo { + return {mv.fuzzy_skin_facets, mv.is_fuzzy_skin_painted(), false}; + }; + + // Because we apply fuzzy skin just on external perimeters, we limit the depth of fuzzy skin + // by the maximal extrusion width of external perimeters. + float max_external_perimeter_width = 0.; + for (size_t region_idx = 0; region_idx < print_object.num_printing_regions(); ++region_idx) { + const PrintRegion ®ion = print_object.printing_region(region_idx); + max_external_perimeter_width = std::max(max_external_perimeter_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width()); + } + + return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_external_perimeter_width, 0.f, IncludeTopAndBottomLayers::No, throw_on_cancel_callback); +} + + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 0f464d4..0211e67 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -13,8 +13,10 @@ namespace Slic3r { -class PrintObject; class ExPolygon; +class ModelVolume; +class PrintObject; +class FacetsAnnotation; using ExPolygons = std::vector; @@ -28,9 +30,36 @@ struct ColoredLine using ColoredLines = std::vector; -// Returns MMU segmentation based on painting in MMU segmentation gizmo +enum class IncludeTopAndBottomLayers { + Yes, + No +}; + +struct ModelVolumeFacetsInfo { + const FacetsAnnotation &facets_annotation; + // Indicate if model volume is painted. + const bool is_painted; + // Indicate if the default extruder (TriangleStateType::NONE) should be replaced with the volume extruder. + const bool replace_default_extruder; +}; + +BoundingBox get_extents(const std::vector &colored_polygons); + +// Returns segmentation based on painting in segmentation gizmos. +std::vector> segmentation_by_painting(const PrintObject &print_object, + const std::function &extract_facets_info, + size_t num_facets_states, + float segmentation_max_width, + float segmentation_interlocking_depth, + IncludeTopAndBottomLayers include_top_and_bottom_layers, + const std::function &throw_on_cancel_callback); + +// Returns multi-material segmentation based on painting in multi-material segmentation gizmo std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); +// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo +std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback); + } // namespace Slic3r namespace boost::polygon { diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 2b28c60..5533007 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -28,8 +28,8 @@ class BoundingBox3; // Reduces polyline in the -inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter) +template +inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, TakeFloaterPredicate take_floater_predicate, PointGetter point_getter) { using InputIteratorCategory = typename std::iterator_traits::iterator_category; static_assert(std::is_base_of_v); @@ -45,7 +45,6 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou // Two points input. *out ++ = std::move(*next); } else { - const auto tolerance_sq = SquareLengthType(sqr(tolerance)); InputIterator anchor = begin; InputIterator floater = std::prev(end); std::vector dpStack; @@ -61,17 +60,17 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou // Two point segment. Accept the floater. take_floater = true; } else { - SquareLengthType max_dist_sq = 0; + std::optional max_dist_sq; // Find point furthest from line seg created by (anchor, floater) and note it. const Vector v = (f - a).template cast(); if (const SquareLengthType l2 = v.squaredNorm(); l2 == 0) { // Zero length segment, find the furthest point between anchor and floater. - for (auto it = std::next(anchor); it != floater; ++ it) - if (SquareLengthType dist_sq = (point_getter(*it) - a).template cast().squaredNorm(); - dist_sq > max_dist_sq) { - max_dist_sq = dist_sq; - furthest = it; + for (auto it = std::next(anchor); it != floater; ++ it) { + if (SquareLengthType dist_sq = (point_getter(*it) - a).template cast().squaredNorm(); !max_dist_sq.has_value() || dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest = it; } + } } else { // Find Find the furthest point from the line . const double dl2 = double(l2); @@ -93,15 +92,20 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou const Vector w = (dt * dv).cast(); dist_sq = (w - va).squaredNorm(); } - if (dist_sq > max_dist_sq) { + + if (!max_dist_sq.has_value() || dist_sq > max_dist_sq) { max_dist_sq = dist_sq; furthest = it; } } } - // remove point if less than tolerance - take_floater = max_dist_sq <= tolerance_sq; + + assert(max_dist_sq.has_value()); + + // Remove points between the anchor and the floater when the predicate is satisfied. + take_floater = take_floater_predicate(anchor, floater, *max_dist_sq); } + if (take_floater) { // The points between anchor and floater are close to the line. // Drop the points between them. @@ -112,6 +116,7 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou dpStack.pop_back(); if (dpStack.empty()) break; + floater = dpStack.back(); f = point_getter(*floater); } else { @@ -127,14 +132,29 @@ inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, Ou return out; } -// Reduces polyline in the +inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter) { + const auto tolerance_sq = static_cast(sqr(tolerance)); + + const auto take_floater_predicate = [&tolerance_sq](InputIterator, InputIterator, const SquareLengthType max_dist_sq) -> bool { + return max_dist_sq <= tolerance_sq; + }; + + return douglas_peucker(begin, end, out, take_floater_predicate, point_getter); +} + template inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance) { return douglas_peucker(begin, end, out, tolerance, [](const Point &p) { return p; }); } +template +inline OutputIterator douglas_peucker(Pointfs::const_iterator begin, Pointfs::const_iterator end, OutputIterator out, const double tolerance) +{ + return douglas_peucker(begin, end, out, tolerance, [](const Vec2d &p) { return p; }); +} + inline Points douglas_peucker(const Points &src, const double tolerance) { Points out; @@ -147,12 +167,13 @@ class MultiPoint { public: Points points; - + MultiPoint() = default; MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} MultiPoint(std::initializer_list list) : points(list) {} explicit MultiPoint(const Points &_points) : points(_points) {} + virtual ~MultiPoint() = default; MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); @@ -162,7 +183,7 @@ public: void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); void rotate(double angle, const Point ¢er); - void reverse() { std::reverse(this->points.begin(), this->points.end()); } + virtual void reverse() { std::reverse(this->points.begin(), this->points.end()); } const Point& front() const { return this->points.front(); } const Point& back() const { return this->points.back(); } diff --git a/src/libslic3r/MultipleBeds.cpp b/src/libslic3r/MultipleBeds.cpp new file mode 100644 index 0000000..bf171ae --- /dev/null +++ b/src/libslic3r/MultipleBeds.cpp @@ -0,0 +1,519 @@ +#include "MultipleBeds.hpp" + +#include "BuildVolume.hpp" +#include "Model.hpp" +#include "Print.hpp" + +#include +#include + +namespace Slic3r { + +MultipleBeds s_multiple_beds; +bool s_reload_preview_after_switching_beds = false; +bool s_beds_just_switched = false; +bool s_beds_switched_since_last_gcode_load = false; + +bool is_sliceable(const PrintStatus status) { + if (status == PrintStatus::empty) { + return false; + } + if (status == PrintStatus::invalid) { + return false; + } + if (status == PrintStatus::outside) { + return false; + } + return true; +} + +namespace BedsGrid { +Index grid_coords_abs2index(GridCoords coords) { + coords = {std::abs(coords.x()), std::abs(coords.y())}; + + const int x{coords.x() + 1}; + const int y{coords.y() + 1}; + const int a{std::max(x, y)}; + + if (x == a && y == a) { + return a*a - 1; + } else if (x == a) { + return a*a - 2 * (a - 1) + coords.y() - 1; + } else { + assert(y == a); + return a*a - (a - 1) + coords.x() - 1; + } +} + +const int quadrant_offset{std::numeric_limits::max() / 4}; + +Index grid_coords2index(const GridCoords &coords) { + const int index{grid_coords_abs2index(coords)}; + + if (index >= quadrant_offset) { + throw std::runtime_error("Object is too far from center!"); + } + + if (coords.x() >= 0 && coords.y() >= 0) { + return index; + } else if (coords.x() >= 0 && coords.y() < 0) { + return quadrant_offset + index; + } else if (coords.x() < 0 && coords.y() >= 0) { + return 2*quadrant_offset + index; + } else { + return 3*quadrant_offset + index; + } +} + +GridCoords index2grid_coords(Index index) { + if (index < 0) { + throw std::runtime_error{"Negative bed index cannot be translated to coords!"}; + } + + const int quadrant{index / quadrant_offset}; + index = index % quadrant_offset; + + GridCoords result{GridCoords::Zero()}; + if (index == 0) { + return result; + } + + int id = index; + ++id; + int a = 1; + while ((a+1)*(a+1) < id) + ++a; + id = id - a*a; + result.x()=a; + result.y()=a; + if (id <= a) + result.y() = id-1; + else + result.x() = id-a-1; + + if (quadrant == 1) { + result.y() = -result.y(); + } else if (quadrant == 2) { + result.x() = -result.x(); + } else if (quadrant == 3) { + result.y() = -result.y(); + result.x() = -result.x(); + } else if (quadrant != 0){ + throw std::runtime_error{"Impossible bed index > max int!"}; + } + return result; +} +} + +Vec3d MultipleBeds::get_bed_translation(int id) const +{ + if (id == 0) + return Vec3d::Zero(); + int x = 0; + int y = 0; + if (m_legacy_layout) + x = id; + else { + BedsGrid::GridCoords coords{BedsGrid::index2grid_coords(id)}; + x = coords.x(); + y = coords.y(); + } + + // As for the m_legacy_layout switch, see comments at definition of bed_gap_relative. + Vec2d gap = bed_gap(); + double gap_x = (m_legacy_layout ? m_build_volume_bb.size().x() * (2./10.) : gap.x()); + return Vec3d(x * (m_build_volume_bb.size().x() + gap_x), + y * (m_build_volume_bb.size().y() + gap.y()), // When using legacy layout, y is zero anyway. + 0.); +} + +void MultipleBeds::clear_inst_map() +{ + m_inst_to_bed.clear(); + m_occupied_beds_cache.fill(false); +} + +void MultipleBeds::set_instance_bed(ObjectID id, bool printable, int bed_idx) +{ + assert(bed_idx < get_max_beds()); + m_inst_to_bed[id] = bed_idx; + + if (printable) + m_occupied_beds_cache[bed_idx] = true; +} + +void MultipleBeds::inst_map_updated() +{ + int max_bed_idx = 0; + for (const auto& [obj_id, bed_idx] : m_inst_to_bed) + max_bed_idx = std::max(max_bed_idx, bed_idx); + + if (m_number_of_beds != max_bed_idx + 1) { + m_number_of_beds = max_bed_idx + 1; + m_active_bed = m_number_of_beds - 1; + request_next_bed(false); + } + if (m_active_bed >= m_number_of_beds) + m_active_bed = m_number_of_beds - 1; +} + +void MultipleBeds::request_next_bed(bool show) +{ + m_show_next_bed = (get_number_of_beds() < get_max_beds() ? show : false); +} + +void MultipleBeds::set_active_bed(int i) +{ + assert(i < get_max_beds()); + if (iinstances) { + result.emplace_back(mi->get_offset()); + } + } + return result; +} + +ObjectInstances get_object_instances(const Model& model) { + ObjectInstances result; + + std::transform( + model.objects.begin(), + model.objects.end(), + std::back_inserter(result), + [](ModelObject *object){ + return std::pair{object, object->instances}; + } + ); + + return result; +} + +void restore_instance_offsets(Model& model, const InstanceOffsets &offsets) +{ + size_t i = 0; + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + mi->set_offset(offsets[i++]); + } + } +} + +void restore_object_instances(Model& model, const ObjectInstances &object_instances) { + ModelObjectPtrs objects; + + std::transform( + object_instances.begin(), + object_instances.end(), + std::back_inserter(objects), + [](const std::pair &key_value){ + auto [object, instances]{key_value}; + object->instances = std::move(instances); + return object; + } + ); + + model.objects = objects; +} + +void with_single_bed_model_fff(Model &model, const int bed_index, const std::function &callable) { + const InstanceOffsets original_offssets{MultipleBedsUtils::get_instance_offsets(model)}; + const ObjectInstances original_objects{get_object_instances(model)}; + const int original_bed{s_multiple_beds.get_active_bed()}; + Slic3r::ScopeGuard guard([&]() { + restore_object_instances(model, original_objects); + restore_instance_offsets(model, original_offssets); + s_multiple_beds.set_active_bed(original_bed); + }); + + s_multiple_beds.move_from_bed_to_first_bed(model, bed_index); + s_multiple_beds.remove_instances_outside_outside_bed(model, bed_index); + s_multiple_beds.set_active_bed(bed_index); + callable(); +} + +using InstancesPrintability = std::vector; + +InstancesPrintability get_instances_printability(const Model &model) { + InstancesPrintability result; + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + result.emplace_back(mi->printable); + } + } + return result; +} + +void restore_instances_printability(Model& model, const InstancesPrintability &printability) +{ + size_t i = 0; + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + mi->printable = printability[i++]; + } + } +} + +void with_single_bed_model_sla(Model &model, const int bed_index, const std::function &callable) { + const InstanceOffsets original_offssets{get_instance_offsets(model)}; + const InstancesPrintability original_printability{get_instances_printability(model)}; + const int original_bed{s_multiple_beds.get_active_bed()}; + Slic3r::ScopeGuard guard([&]() { + restore_instance_offsets(model, original_offssets); + restore_instances_printability(model, original_printability); + s_multiple_beds.set_active_bed(original_bed); + }); + + s_multiple_beds.move_from_bed_to_first_bed(model, bed_index); + s_multiple_beds.set_instances_outside_outside_bed_unprintable(model, bed_index); + s_multiple_beds.set_active_bed(bed_index); + callable(); +} + +} + +bool MultipleBeds::is_instance_on_bed(const ObjectID id, const int bed_index) const +{ + auto it = m_inst_to_bed.find(id); + return (it != m_inst_to_bed.end() && it->second == bed_index); +} + +void MultipleBeds::remove_instances_outside_outside_bed(Model& model, const int bed_index) const { + for (ModelObject* mo : model.objects) { + mo->instances.erase(std::remove_if( + mo->instances.begin(), + mo->instances.end(), + [&](const ModelInstance* instance){ + return !this->is_instance_on_bed(instance->id(), bed_index); + } + ), mo->instances.end()); + } + + model.objects.erase(std::remove_if( + model.objects.begin(), + model.objects.end(), + [](const ModelObject *object){ + return object->instances.empty(); + } + ), model.objects.end()); +} + +void MultipleBeds::set_instances_outside_outside_bed_unprintable(Model& model, const int bed_index) const { + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + if (!this->is_instance_on_bed(mi->id(), bed_index)) { + mi->printable = false; + } + } + } +} + +void MultipleBeds::move_from_bed_to_first_bed(Model& model, const int bed_index) const +{ + if (bed_index < 0 || bed_index >= MAX_NUMBER_OF_BEDS) { + assert(false); + return; + } + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + if (this->is_instance_on_bed(mi->id(), bed_index)) { + mi->set_offset(mi->get_offset() - get_bed_translation(bed_index)); + } + } + } +} + +bool MultipleBeds::is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, int instance_idx) const +{ + if (m_bed_for_thumbnails_generation == -2) { + // Called from shape gallery, just render everything. + return true; + } + + if (obj_idx < 0 || instance_idx < 0 || obj_idx >= int(model.objects.size()) || instance_idx >= int(model.objects[obj_idx]->instances.size())) + return false; + + auto it = m_inst_to_bed.find(model.objects[obj_idx]->instances[instance_idx]->id()); + if (it == m_inst_to_bed.end()) + return false; + return (m_bed_for_thumbnails_generation < 0 || it->second == m_bed_for_thumbnails_generation); +} + +void MultipleBeds::update_shown_beds(Model& model, const BuildVolume& build_volume, bool only_remove /*=false*/) { + const int original_number_of_beds = m_number_of_beds; + const int stash_active = get_active_bed(); + if (! only_remove) + m_number_of_beds = get_max_beds(); + model.update_print_volume_state(build_volume); + const int max_bed{std::accumulate( + this->m_inst_to_bed.begin(), this->m_inst_to_bed.end(), 0, + [](const int max_so_far, const std::pair &value){ + return std::max(max_so_far, value.second); + } + )}; + m_number_of_beds = std::min(this->get_max_beds(), max_bed + 1); + model.update_print_volume_state(build_volume); + set_active_bed(m_number_of_beds != original_number_of_beds ? 0 : stash_active); + if (m_number_of_beds != original_number_of_beds) + request_next_bed(false); +} + +bool MultipleBeds::rearrange_after_load(Model& model, const BuildVolume& build_volume) +{ + int original_number_of_beds = m_number_of_beds; + int stash_active = get_active_bed(); + Slic3r::ScopeGuard guard([&]() { + m_legacy_layout = false; + m_number_of_beds = get_max_beds(); + model.update_print_volume_state(build_volume); + int max_bed = 0; + for (const auto& [oid, bed_id] : m_inst_to_bed) + max_bed = std::max(bed_id, max_bed); + m_number_of_beds = std::min(get_max_beds(), max_bed + 1); + model.update_print_volume_state(build_volume); + request_next_bed(false); + set_active_bed(m_number_of_beds != original_number_of_beds ? 0 : stash_active); + if (m_number_of_beds != original_number_of_beds) + request_next_bed(false); + }); + + m_legacy_layout = true; + int abs_max = get_max_beds(); + while (true) { + // This is to ensure that even objects on linear bed with higher than + // allowed index will be rearranged. + m_number_of_beds = abs_max; + model.update_print_volume_state(build_volume); + int max_bed = 0; + for (const auto& [oid, bed_id] : m_inst_to_bed) + max_bed = std::max(bed_id, max_bed); + if (max_bed + 1 < abs_max) + break; + abs_max += get_max_beds(); + } + m_number_of_beds = 1; + m_legacy_layout = false; + + int max_bed = 0; + + // Check that no instances are out of any bed. + std::map> id_to_ptr_and_bed; + for (ModelObject* mo : model.objects) { + for (ModelInstance* mi : mo->instances) { + auto it = m_inst_to_bed.find(mi->id()); + if (it == m_inst_to_bed.end()) { + // An instance is outside. Do not rearrange anything, + // that could create collisions. + return false; + } + id_to_ptr_and_bed[mi->id()] = std::make_pair(mi, it->second); + max_bed = std::max(max_bed, it->second); + } + } + + // Now do the rearrangement + m_number_of_beds = max_bed + 1; + assert(m_number_of_beds <= get_max_beds()); + if (m_number_of_beds == 1) + return false; + + // All instances are on some bed, at least two are used. + // Move everything as if its bed was in the first position. + for (auto& [oid, mi_and_bed] : id_to_ptr_and_bed) { + auto& [mi, bed_idx] = mi_and_bed; + m_legacy_layout = true; + mi->set_offset(mi->get_offset() - get_bed_translation(bed_idx)); + m_legacy_layout = false; + mi->set_offset(mi->get_offset() + get_bed_translation(bed_idx)); + } + return true; +} + + + +Vec2d MultipleBeds::bed_gap() const +{ + // This is the only function that defines how far apart should the beds be. Used in scene and arrange. + // Note that the spacing is momentarily switched to legacy value of 2/10 when a project is loaded. + // Slicers before 1.2.2 used this value for arrange, and there are existing projects with objects spaced that way (controlled by the m_legacy_layout flag). + + // TOUCHING THIS WILL BREAK LOADING OF EXISTING PROJECTS !!! + + double gap = std::min(100., m_build_volume_bb.size().norm() * (3./10.)); + return Vec2d::Ones() * gap; +} + +bool MultipleBeds::is_bed_occupied(int i) const +{ + return m_occupied_beds_cache[i]; +} + + +Vec2crd MultipleBeds::get_bed_gap() const { + return scaled(Vec2d{bed_gap() / 2.0}); +}; + +void MultipleBeds::ensure_wipe_towers_on_beds(Model& model, const std::vector>& prints) +{ + for (size_t bed_idx = 0; bed_idx < get_number_of_beds(); ++bed_idx) { + ModelWipeTower& mwt = model.get_wipe_tower_vector()[bed_idx]; + double depth = prints[bed_idx]->wipe_tower_data().depth; + double width = prints[bed_idx]->wipe_tower_data().width; + double brim = prints[bed_idx]->wipe_tower_data().brim_width; + + Polygon plg(Points{Point::new_scale(-brim,-brim), Point::new_scale(brim+width, -brim), Point::new_scale(brim+width, brim+depth), Point::new_scale(-brim, brim+depth)}); + plg.rotate(Geometry::deg2rad(mwt.rotation)); + plg.translate(scaled(mwt.position)); + if (std::all_of(plg.points.begin(), plg.points.end(), [this](const Point& pt) { return !m_build_volume_bb.contains(unscale(pt)); })) + mwt.position = 2*brim*Vec2d(1.,1.); + } +} + +#ifdef SLIC3R_GUI + +void MultipleBeds::start_autoslice(std::function select_bed_fn) +{ + if (is_autoslicing()) + return; + m_select_bed_fn = select_bed_fn; + + m_autoslicing_original_bed = get_active_bed(); + + m_autoslicing = true; +} + + + +bool MultipleBeds::stop_autoslice(bool restore_original) +{ + if (! is_autoslicing()) + return false; + m_autoslicing = false; + + if (restore_original) + m_select_bed_fn(m_autoslicing_original_bed, false); + return true; +} + + + +void MultipleBeds::autoslice_next_bed() +{ + if (! is_autoslicing()) + return; + int next_bed = s_multiple_beds.get_active_bed() + 1; + if (next_bed >= s_multiple_beds.get_number_of_beds()) + next_bed = 0; + m_select_bed_fn(next_bed, false); +} +#endif // SLIC3R_GUI + + +} + diff --git a/src/libslic3r/MultipleBeds.hpp b/src/libslic3r/MultipleBeds.hpp new file mode 100644 index 0000000..8f58e98 --- /dev/null +++ b/src/libslic3r/MultipleBeds.hpp @@ -0,0 +1,138 @@ +#ifndef libslic3r_MultipleBeds_hpp_ +#define libslic3r_MultipleBeds_hpp_ + +#include "libslic3r/Model.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/BoundingBox.hpp" + +#include + +namespace Slic3r { + +class Model; +class BuildVolume; +class PrintBase; +class Print; + +extern bool s_reload_preview_after_switching_beds; +extern bool s_beds_just_switched; +extern bool s_beds_switched_since_last_gcode_load; + +namespace BedsGrid { +using GridCoords = Vec2crd; +using Index = int; +Index grid_coords2index(const GridCoords &coords); +GridCoords index2grid_coords(Index index); +} + +inline std::vector s_bed_selector_thumbnail_texture_ids; +inline std::array s_bed_selector_thumbnail_changed; +inline bool bed_selector_updated{false}; + +enum class PrintStatus { + idle, + running, + finished, + outside, + invalid, + empty, + toolpath_outside +}; + +bool is_sliceable(const PrintStatus status); + +inline std::array s_print_statuses; + +class MultipleBeds { +public: + MultipleBeds() = default; + + static constexpr int get_max_beds() { return MAX_NUMBER_OF_BEDS; }; + Vec3d get_bed_translation(int id) const; + + void clear_inst_map(); + void set_instance_bed(ObjectID id, bool printable, int bed_idx); + void inst_map_updated(); + const std::map &get_inst_map() const { return m_inst_to_bed; } + bool is_bed_occupied(int bed_idx) const; + + int get_number_of_beds() const { return m_number_of_beds; } + bool should_show_next_bed() const { return m_show_next_bed; } + + void request_next_bed(bool show); + int get_active_bed() const { return m_active_bed; } + + void set_active_bed(int i); + + void remove_instances_outside_outside_bed(Model& model, const int bed) const; + void set_instances_outside_outside_bed_unprintable(Model& model, const int bed_index) const; + + // Sets !printable to all instances outside the active bed. + void move_from_bed_to_first_bed(Model& model, const int bed) const; + + void set_thumbnail_bed_idx(int bed_idx) { m_bed_for_thumbnails_generation = bed_idx; } + int get_thumbnail_bed_idx() const { return m_bed_for_thumbnails_generation; } + bool is_glvolume_on_thumbnail_bed(const Model& model, int obj_idx, int instance_idx) const; + + void set_last_hovered_bed(int i) { m_last_hovered_bed = i; } + int get_last_hovered_bed() const { return m_last_hovered_bed; } + + void update_shown_beds(Model& model, const BuildVolume& build_volume, bool only_remove = false); + bool rearrange_after_load(Model& model, const BuildVolume& build_volume); + void set_loading_project_flag(bool project) { m_loading_project = project; } + bool get_loading_project_flag() const { return m_loading_project; } + + void update_build_volume(const BoundingBoxf& build_volume_bb) { + m_build_volume_bb = build_volume_bb; + } + Vec2d bed_gap() const; + Vec2crd get_bed_gap() const; + void ensure_wipe_towers_on_beds(Model& model, const std::vector>& prints); + + void start_autoslice(std::function); + bool stop_autoslice(bool restore_original); + bool is_autoslicing() const { return m_autoslicing; } + void autoslice_next_bed(); + +private: + bool is_instance_on_bed(const ObjectID id, const int bed_index) const; + + int m_number_of_beds = 1; + int m_active_bed = 0; + int m_bed_for_thumbnails_generation = -1; + bool m_show_next_bed = false; + std::map m_inst_to_bed; + std::map m_printbase_to_texture; + std::array m_occupied_beds_cache; + int m_last_hovered_bed = -1; + BoundingBoxf m_build_volume_bb; + bool m_legacy_layout = false; + bool m_loading_project = false; + + bool m_autoslicing = false; + int m_autoslicing_original_bed = 0; + std::function m_select_bed_fn; +}; + +extern MultipleBeds s_multiple_beds; + +namespace MultipleBedsUtils { + +using InstanceOffsets = std::vector; +// The bool is true if the instance is printable. +// The order is from 'for o in objects; for i in o.instances. +InstanceOffsets get_instance_offsets(Model& model); + +using ObjectInstances = std::vector>; +ObjectInstances get_object_instances(const Model& model); +void restore_instance_offsets(Model& model, const InstanceOffsets &offsets); +void restore_object_instances(Model& model, const ObjectInstances &object_instances); + +void with_single_bed_model_fff(Model &model, const int bed_index, const std::function &callable); +void with_single_bed_model_sla(Model &model, const int bed_index, const std::function &callable); +} + +} // namespace Slic3r + +#endif // libslic3r_MultipleBeds_hpp_ diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp index 7177c47..136aea2 100644 --- a/src/libslic3r/ObjectID.cpp +++ b/src/libslic3r/ObjectID.cpp @@ -4,17 +4,19 @@ namespace Slic3r { size_t ObjectBase::s_last_id = 0; -// Unique object / instance ID for the wipe tower. -ObjectID wipe_tower_object_id() -{ - static ObjectBase mine; - return mine.id(); -} +struct WipeTowerId : public ObjectBase { + // Need to inherit because ObjectBase + // destructor is protected. + using ObjectBase::ObjectBase; +}; -ObjectID wipe_tower_instance_id() +ObjectID wipe_tower_instance_id(size_t bed_idx) { - static ObjectBase mine; - return mine.id(); + static std::vector mine; + if (bed_idx >= mine.size()) { + mine.resize(bed_idx + 1); + } + return mine[bed_idx].id(); } ObjectWithTimestamp::Timestamp ObjectWithTimestamp::s_last_timestamp = 1; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index b3ce985..69172f5 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -2,12 +2,6 @@ #define slic3r_ObjectID_hpp_ #include -#include -#include -#include -#include -#include -#include namespace Slic3r { @@ -88,9 +82,6 @@ private: static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); } static size_t s_last_id; - - friend ObjectID wipe_tower_object_id(); - friend ObjectID wipe_tower_instance_id(); friend class cereal::access; friend class Slic3r::UndoRedo::StackImpl; @@ -136,67 +127,8 @@ private: template void serialize(Archive &ar) { ar(m_timestamp); } }; -class CutObjectBase : public ObjectBase -{ - // check sum of CutParts in initial Object - size_t m_check_sum{ 1 }; - // connectors count - size_t m_connectors_cnt{ 0 }; - -public: - // Default Constructor to assign an invalid ID - CutObjectBase() : ObjectBase(-1) {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - CutObjectBase(int) : ObjectBase(-1) {} - // Constructor to initialize full information from 3mf - CutObjectBase(ObjectID id, size_t check_sum, size_t connectors_cnt) : ObjectBase(id), m_check_sum(check_sum), m_connectors_cnt(connectors_cnt) {} - // The class tree will have virtual tables and type information. - virtual ~CutObjectBase() = default; - - bool operator<(const CutObjectBase& other) const { return other.id() > this->id(); } - bool operator==(const CutObjectBase& other) const { return other.id() == this->id(); } - - void copy(const CutObjectBase& rhs) { - this->copy_id(rhs); - this->m_check_sum = rhs.check_sum(); - this->m_connectors_cnt = rhs.connectors_cnt() ; - } - CutObjectBase& operator=(const CutObjectBase& other) { - this->copy(other); - return *this; - } - - void invalidate() { - set_invalid_id(); - m_check_sum = 1; - m_connectors_cnt = 0; - } - - void init() { this->set_new_unique_id(); } - bool has_same_id(const CutObjectBase& rhs) { return this->id() == rhs.id(); } - bool is_equal(const CutObjectBase& rhs) { return this->id() == rhs.id() && - this->check_sum() == rhs.check_sum() && - this->connectors_cnt() == rhs.connectors_cnt() ; } - - size_t check_sum() const { return m_check_sum; } - void set_check_sum(size_t cs) { m_check_sum = cs; } - void increase_check_sum(size_t cnt) { m_check_sum += cnt; } - - size_t connectors_cnt() const { return m_connectors_cnt; } - void increase_connectors_cnt(size_t connectors_cnt) { m_connectors_cnt += connectors_cnt; } - -private: - friend class cereal::access; - template void serialize(Archive& ar) { - ar(cereal::base_class(this)); - ar(m_check_sum, m_connectors_cnt); - } -}; - // Unique object / instance ID for the wipe tower. -extern ObjectID wipe_tower_object_id(); -extern ObjectID wipe_tower_instance_id(); +ObjectID wipe_tower_instance_id(size_t bed_idx);; } // namespace Slic3r diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp index 1327848..b405d1a 100644 --- a/src/libslic3r/PNGReadWrite.cpp +++ b/src/libslic3r/PNGReadWrite.cpp @@ -183,6 +183,31 @@ fopen_failed: return result; } +bool decode_png(const std::string& png_data, std::vector& image_data, unsigned& width, unsigned& height) +{ + png_image image; + memset(&image, 0, sizeof(image)); + image.version = PNG_IMAGE_VERSION; + + if (!png_image_begin_read_from_memory(&image, png_data.data(), png_data.size())) + return false; + + image.format = PNG_FORMAT_RGBA; + + // Allocate memory for the image data + image_data.resize(PNG_IMAGE_SIZE(image)); + if (!png_image_finish_read(&image, nullptr, image_data.data(), 0, nullptr)) { + png_image_free(&image); + return false; + } + + width = image.width; + height = image.height; + + png_image_free(&image); + return true; +} + bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb) { return write_rgb_or_gray_to_file(file_name_utf8, width, height, PNG_COLOR_TYPE_RGB, data_rgb); diff --git a/src/libslic3r/PNGReadWrite.hpp b/src/libslic3r/PNGReadWrite.hpp index fc9d6ea..50099b8 100644 --- a/src/libslic3r/PNGReadWrite.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -69,7 +69,7 @@ template bool decode_png(const ReadBuf &in_buf, Img &out_img) // TODO: std::istream of FILE* could be similarly adapted in case its needed... - +bool decode_png(const std::string& png_data, std::vector& image_data, unsigned& width, unsigned& height); // Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index ac188f2..cc29454 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -20,6 +20,7 @@ #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" +#include "Feature/FuzzySkin/FuzzySkin.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -34,7 +35,9 @@ #include "Arachne/utils/ExtrusionJunction.hpp" #include "libslic3r.h" #include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" #include "libslic3r/Line.hpp" +#include "libslic3r/Print.hpp" //#define ARACHNE_DEBUG @@ -169,13 +172,11 @@ public: bool is_contour; // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. unsigned short depth; - // Should this contur be fuzzyfied on path generation? - bool fuzzify; // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; - - PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify) : - polygon(polygon), is_contour(is_contour), depth(depth), fuzzify(fuzzify) {} + + PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour) : + polygon(polygon), is_contour(is_contour), depth(depth) {} // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. @@ -190,95 +191,15 @@ public: } }; -// Thanks Cura developers for this function. -static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_dist) -{ - const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_dist / 2.; - double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point - Point* p0 = &poly.points.back(); - Points out; - out.reserve(poly.points.size()); - for (Point &p1 : poly.points) - { // 'a' is the (next) new point between p0 and p1 - Vec2d p0p1 = (p1 - *p0).cast(); - double p0p1_size = p0p1.norm(); - // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size - double dist_last_point = dist_left_over + p0p1_size * 2.; - for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; - p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) - { - double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; - out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast()); - dist_last_point = p0pa_dist; - } - dist_left_over = p0p1_size - dist_last_point; - p0 = &p1; - } - while (out.size() < 3) { - size_t point_idx = poly.size() - 2; - out.emplace_back(poly[point_idx]); - if (point_idx == 0) - break; - -- point_idx; - } - if (out.size() >= 3) - poly.points = std::move(out); -} - -// Thanks Cura developers for this function. -static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist) -{ - const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value - const double range_random_point_dist = fuzzy_skin_point_dist / 2.; - double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point - - auto *p0 = &ext_lines.front(); - std::vector out; - out.reserve(ext_lines.size()); - for (auto &p1 : ext_lines) { - if (p0->p == p1.p) { // Connect endpoints. - out.emplace_back(p1.p, p1.w, p1.perimeter_index); - continue; - } - - // 'a' is the (next) new point between p0 and p1 - Vec2d p0p1 = (p1.p - p0->p).cast(); - double p0p1_size = p0p1.norm(); - // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size - double dist_last_point = dist_left_over + p0p1_size * 2.; - for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) { - double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness; - out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); - dist_last_point = p0pa_dist; - } - dist_left_over = p0p1_size - dist_last_point; - p0 = &p1; - } - - while (out.size() < 3) { - size_t point_idx = ext_lines.size() - 2; - out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); - if (point_idx == 0) - break; - -- point_idx; - } - - if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints. - out.front().p = out.back().p; - - if (out.size() >= 3) - ext_lines.junctions = std::move(out); -} - using PerimeterGeneratorLoops = std::vector; static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) { + using namespace Slic3r::Feature::FuzzySkin; + // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object - ExtrusionEntityCollection coll; - Polygon fuzzified; + ExtrusionEntityCollection coll; for (const PerimeterGeneratorLoop &loop : loops) { bool is_external = loop.is_external(); @@ -293,17 +214,15 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator } else { loop_role = elrDefault; } - - // detect overhanging/bridging perimeters + + // Apply fuzzy skin if it is enabled for at least some part of the polygon. + const Polygon polygon = apply_fuzzy_skin(loop.polygon, params.config, params.perimeter_regions, params.layer_id, loop.depth, loop.is_contour); + ExtrusionPaths paths; - const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon; - if (loop.fuzzify) { - fuzzified = loop.polygon; - fuzzy_polygon(fuzzified, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); - } - if (params.config.overhangs && params.layer_id > params.object_config.raft_layers - && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && - params.object_config.support_material_contact_distance.value == 0)) { + if (params.config.overhangs && params.layer_id > params.object_config.raft_layers && + !((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && + params.object_config.support_material_contact_distance.value == 0)) { + // Detect overhanging/bridging perimeters. BoundingBox bbox(polygon.points); bbox.offset(SCALED_EPSILON); Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox); @@ -328,7 +247,11 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator role_overhang, ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() } }); - + + if (paths.empty()) { + continue; + } + // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); @@ -486,9 +409,11 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions) { + using namespace Slic3r::Feature::FuzzySkin; + ExtrusionEntityCollection extrusion_coll; for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) { - Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion; + Arachne::ExtrusionLine extrusion = pg_extrusion.extrusion; if (extrusion.empty()) continue; @@ -496,8 +421,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter; ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge; - if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); + // Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine. + extrusion = apply_fuzzy_skin(extrusion, params.config, params.perimeter_regions, params.layer_id, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour()); ExtrusionPaths paths; // detect overhanging/bridging perimeters @@ -1200,46 +1125,6 @@ void PerimeterGenerator::process_arachne( Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params.config.external_perimeters_first); - if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { - std::vector closed_loop_extrusions; - for (Arachne::PerimeterOrder::PerimeterExtrusion &extrusion : ordered_extrusions) - if (extrusion.extrusion.inset_idx == 0) { - if (extrusion.extrusion.is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } else { - extrusion.fuzzify = true; - } - } - - if (params.config.fuzzy_skin == FuzzySkinType::External) { - ClipperLib_Z::Paths loops_paths; - loops_paths.reserve(closed_loop_extrusions.size()); - for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion.junctions.front() == cl_extrusion->extrusion.junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion.junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion.junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion.junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; - } - } - } - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) out_loops.append(extrusion_coll); @@ -1434,20 +1319,18 @@ void PerimeterGenerator::process_classic( break; } { - const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0; - const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All; for (const ExPolygon &expolygon : offsets) { // Outer contour may overlap with an inner contour, // inner contour may overlap with another inner contour, // outer contour may overlap with itself. //FIXME evaluate the overlaps, annotate each point with an overlap depth, // compensate for the depth of intersection. - contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); + contours[i].emplace_back(expolygon.contour, i, true); if (! expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); for (const Polygon &hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes); + holes[i].emplace_back(hole, i, false); } } } @@ -1694,4 +1577,43 @@ void PerimeterGenerator::process_classic( append(out_fill_expolygons, std::move(infill_areas)); } +PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region()) +{ + this->expolygons = to_expolygons(layer_region.slices().surfaces); + this->bbox = get_extents(this->expolygons); +} + +bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config) +{ + return config.fuzzy_skin == other_config.fuzzy_skin && + config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && + config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist; +} + +void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions) +{ + if (perimeter_regions.size() <= 1) { + return; + } + + PerimeterRegions perimeter_regions_merged; + for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) { + PerimeterRegion current_merge = *it_curr_region; + auto it_next_region = std::next(it_curr_region); + for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); ++it_next_region) { + Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons)); + current_merge.bbox.merge(it_next_region->bbox); + } + + if (std::distance(it_curr_region, it_next_region) > 1) { + current_merge.expolygons = union_ex(current_merge.expolygons); + } + + perimeter_regions_merged.emplace_back(std::move(current_merge)); + it_curr_region = it_next_region; + } + + perimeter_regions = perimeter_regions_merged; +} + } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 606afbd..9c2087c 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -16,11 +16,31 @@ namespace Slic3r { class ExtrusionEntityCollection; +class LayerRegion; class Surface; +class PrintRegion; struct ThickPolyline; -namespace PerimeterGenerator +struct PerimeterRegion { + const PrintRegion *region; + ExPolygons expolygons; + BoundingBox bbox; + + explicit PerimeterRegion(const LayerRegion &layer_region); + + // If there is any incompatibility, we don't need to create separate LayerRegions. + // Because it is enough to split perimeters by PerimeterRegions. + static bool has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config); + + static void merge_compatible_perimeter_regions(std::vector &perimeter_regions); +}; + +using PerimeterRegions = std::vector; + +} // namespace Slic3r + +namespace Slic3r::PerimeterGenerator { struct Parameters { Parameters( @@ -33,6 +53,7 @@ struct Parameters { const PrintRegionConfig &config, const PrintObjectConfig &object_config, const PrintConfig &print_config, + const PerimeterRegions &perimeter_regions, const bool spiral_vase) : layer_height(layer_height), layer_id(layer_id), @@ -43,6 +64,7 @@ struct Parameters { config(config), object_config(object_config), print_config(print_config), + perimeter_regions(perimeter_regions), spiral_vase(spiral_vase), scaled_resolution(scaled(print_config.gcode_resolution.value)), mm3_per_mm(perimeter_flow.mm3_per_mm()), @@ -61,6 +83,7 @@ struct Parameters { const PrintRegionConfig &config; const PrintObjectConfig &object_config; const PrintConfig &print_config; + const PerimeterRegions &perimeter_regions; // Derived parameters bool spiral_vase; @@ -111,7 +134,6 @@ void process_arachne( ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance); -} // namespace PerimeterGenerator -} // namespace Slic3r +} // namespace Slic3r::PerimeterGenerator #endif diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 0bcd6c9..5fa4b24 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -24,11 +24,14 @@ namespace Slic3r { class Polygon; class BoundingBox; +class ColorPolygon; using Polygons = std::vector>; using PolygonPtrs = std::vector>; using ConstPolygonPtrs = std::vector>; +using ColorPolygons = std::vector; + // Returns true if inside. Returns border_result if on boundary. bool contains(const Polygon& polygon, const Point& p, bool border_result = true); bool contains(const Polygons& polygons, const Point& p, bool border_result = true); @@ -318,12 +321,41 @@ template IntegerOnly reserve_polygons(I cap) return reserve_vector(cap); } -} // Slic3r +class ColorPolygon : public Polygon +{ +public: + using Color = uint8_t; + using Colors = std::vector; + + Colors colors; + + ColorPolygon() = default; + explicit ColorPolygon(const Points &points, const Colors &colors) : Polygon(points), colors(colors) {} + ColorPolygon(std::initializer_list points, std::initializer_list colors) : Polygon(points), colors(colors) {} + ColorPolygon(const ColorPolygon &other) : ColorPolygon(other.points, other.colors) {} + ColorPolygon(ColorPolygon &&other) noexcept : ColorPolygon(std::move(other.points), std::move(other.colors)) {} + ColorPolygon(Points &&points, Colors &&colors) : Polygon(std::move(points)), colors(std::move(colors)) {} + + void reverse() override { + Polygon::reverse(); + std::reverse(this->colors.begin(), this->colors.end()); + } + + ColorPolygon &operator=(const ColorPolygon &other) { + this->points = other.points; + this->colors = other.colors; + return *this; + } +}; + +using ColorPolygons = std::vector; + +} // namespace Slic3r // start Boost #include -namespace boost { namespace polygon { +namespace boost::polygon { template <> struct geometry_concept{ typedef polygon_concept type; }; @@ -401,7 +433,7 @@ namespace boost { namespace polygon { polygons.assign(input_begin, input_end); } }; -} } +} // namespace boost::polygon // end Boost #endif diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 59f418c..b36e0ac 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -443,11 +443,31 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) } } +std::string Preset::trim_vendor_repo_prefix(const std::string& id) const +{ + return Preset::trim_vendor_repo_prefix(id, this->vendor); +} +std::string Preset::trim_vendor_repo_prefix(const std::string& id, const VendorProfile* vendor_profile) const +{ + if (!vendor_profile) { + return id; + } + std::string res = id; + if (boost::algorithm::starts_with(res, vendor_profile->repo_prefix)) { + boost::algorithm::erase_head(res, vendor_profile->repo_prefix.size()); + boost::algorithm::trim_left(res); + } + return res; +} + static std::vector s_Preset_print_options { "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode", "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", - "extra_perimeters", "extra_perimeters_on_overhangs", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs", - "seam_position","staggered_inner_seams", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", + "ensure_vertical_shell_thickness", "extra_perimeters", "extra_perimeters_on_overhangs", + "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs", + "seam_position", "staggered_inner_seams", "seam_gap_distance", + "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", + "scarf_seam_placement", "scarf_seam_only_on_smooth", "scarf_seam_start_height", "scarf_seam_entire_loop", "scarf_seam_length", "scarf_seam_max_segment_length", "scarf_seam_on_inner_perimeters", "infill_every_layers", /*"infill_only_where_needed",*/ "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing", @@ -476,10 +496,10 @@ static std::vector s_Preset_print_options { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "elefant_foot_compensation", "xy_size_compensation", "resolution", "gcode_resolution", "arc_fitting", - "wipe_tower", "wipe_tower_x", "wipe_tower_y", + "wipe_tower", + "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", //w12 "xy_contour_compensation", "xy_hole_compensation", - "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_flow", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", @@ -490,8 +510,6 @@ static std::vector s_Preset_print_options { "first_layer_infill_speed", //w11 "detect_narrow_internal_solid_infill", - //Y21 - "seam_gap", //w21 "filter_top_gap_infill", //w25 @@ -509,7 +527,8 @@ static std::vector s_Preset_print_options { //w39 "precise_outer_wall", //Y27 - "resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed" + "resonance_avoidance", "min_resonance_avoidance_speed", "max_resonance_avoidance_speed", + "automatic_extrusion_widths", "automatic_infill_combination", "automatic_infill_combination_max_layer_height", }; static std::vector s_Preset_filament_options { @@ -530,6 +549,8 @@ static std::vector s_Preset_filament_options { "filament_vendor", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", // Shrinkage compensation "filament_shrinkage_compensation_xy", "filament_shrinkage_compensation_z", + // Seams overrides + "filament_seam_gap_distance", //B15 "enable_auxiliary_fan", //Y26 diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 4d182f7..31b5eaa 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -122,7 +122,6 @@ public: TYPE_PHYSICAL_PRINTER, // This type is here to support search through the Preferences TYPE_PREFERENCES, - TYPE_WEBVIEW, }; Type type = TYPE_INVALID; @@ -220,6 +219,10 @@ public: // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const Preset &other) const { return this->name < other.name; } + // Returns id without trimmed prefix if present and vendor has any. + std::string trim_vendor_repo_prefix(const std::string& id) const; + std::string trim_vendor_repo_prefix(const std::string& id, const VendorProfile* vendor_profile) const; + static const std::vector& print_options(); static const std::vector& filament_options(); // Printer options contain the nozzle options. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 316e9ac..639c07a 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1529,12 +1529,6 @@ std::pair PresetBundle::load_configbundle( if (dst) unescape_strings_cstyle(kvp.second.data(), *dst); } - } else if (section.first == "settings") { - // Load the settings. - for (auto &kvp : section.second) { - if (kvp.first == "autocenter") { - } - } } else // Ignore an unknown section. continue; @@ -2042,13 +2036,6 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst if (export_physical_printers && this->physical_printers.get_selected_idx() >= 0) c << "physical_printer = " << this->physical_printers.get_selected_printer_name() << std::endl; -#if 0 - // Export the following setting values from the provided setting repository. - static const char *settings_keys[] = { "autocenter" }; - c << "[settings]" << std::endl; - for (size_t i = 0; i < sizeof(settings_keys) / sizeof(settings_keys[0]); ++ i) - c << settings_keys[i] << " = " << settings.serialize(settings_keys[i]) << std::endl; -#endif c.close(); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 699aee5..7ed8e30 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -104,6 +104,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_density", "filament_notes", "filament_cost", + "filament_seam_gap_distance", "filament_spool_weight", "first_layer_acceleration", "first_layer_acceleration_over_raft", @@ -145,8 +146,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "retract_restart_extra", "retract_restart_extra_toolchange", "retract_speed", - //Y21 - "seam_gap", + "seam_gap_distance", "single_extruder_multi_material_priming", "slowdown_below_layer_time", "solid_infill_acceleration", @@ -187,10 +187,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "draft_shield" || opt_key == "skirt_distance" || opt_key == "min_skirt_length" - || opt_key == "ooze_prevention" - || opt_key == "wipe_tower_x" - || opt_key == "wipe_tower_y" - || opt_key == "wipe_tower_rotation_angle") { + || opt_key == "ooze_prevention") { steps.emplace_back(psSkirtBrim); } else if ( opt_key == "first_layer_height" @@ -280,6 +277,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n steps.emplace_back(psSkirtBrim); } else if (opt_key == "avoid_crossing_curled_overhangs") { osteps.emplace_back(posEstimateCurledExtrusions); + } else if (opt_key == "automatic_extrusion_widths") { + osteps.emplace_back(posPerimeters); } else { // for legacy, if we can't handle this option let's invalidate all steps //FIXME invalidate all steps of all objects as well? @@ -625,15 +624,19 @@ std::string Print::validate(std::vector* warnings) const if (this->has_wipe_tower() && ! m_objects.empty()) { // Make sure all extruders use same diameter filament and have the same nozzle diameter // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments - double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front()); + double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front()); double first_filament_diam = m_config.filament_diameter.get_at(extruders.front()); + + bool allow_nozzle_diameter_differ_warning = (warnings != nullptr); for (const auto& extruder_idx : extruders) { - double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); + double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); double filament_diam = m_config.filament_diameter.get_at(extruder_idx); - if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam - || std::abs((filament_diam-first_filament_diam)/first_filament_diam) > 0.1) - return _u8L("The wipe tower is only supported if all extruders have the same nozzle diameter " - "and use filaments of the same diameter."); + if (allow_nozzle_diameter_differ_warning && (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam)) { + allow_nozzle_diameter_differ_warning = false; + warnings->emplace_back("_WIPE_TOWER_NOZZLE_DIAMETER_DIFFER"); + } else if (std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) { + return _u8L("The wipe tower is only supported if all extruders use filaments of the same diameter."); + } } if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware && @@ -736,13 +739,11 @@ std::string Print::validate(std::vector* warnings) const }; for (PrintObject *object : m_objects) { if (object->has_support_material()) { - if ((object->config().support_material_extruder == 0 || object->config().support_material_interface_extruder == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) { + if (warnings != nullptr && (object->config().support_material_extruder == 0 || object->config().support_material_interface_extruder == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) { // The object has some form of support and either support_material_extruder or support_material_interface_extruder - // will be printed with the current tool without a forced tool change. Play safe, assert that all object nozzles - // are of the same diameter. - return _u8L("Printing with multiple extruders of differing nozzle diameters. " - "If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), " - "all nozzles have to be of the same diameter."); + // will be printed with the current tool without a forced tool change. + // Notify the user that printing supports with different nozzle diameters is experimental and requires caution. + warnings->emplace_back("_SUPPORT_NOZZLE_DIAMETER_DIFFER"); } if (this->has_wipe_tower() && object->config().support_material_style != smsOrganic) { if (object->config().support_material_contact_distance == 0) { @@ -1046,8 +1047,8 @@ void Print::process() if (this->has_wipe_tower()) { // These values have to be updated here, not during wipe tower generation. // When the wipe tower is moved/rotated, it is not regenerated. - m_wipe_tower_data.position = { m_config.wipe_tower_x, m_config.wipe_tower_y }; - m_wipe_tower_data.rotation_angle = m_config.wipe_tower_rotation_angle; + m_wipe_tower_data.position = model().wipe_tower().position; + m_wipe_tower_data.rotation_angle = model().wipe_tower().rotation; } auto conflictRes = ConflictChecker::find_inter_of_lines_in_diff_objs(objects(), m_wipe_tower_data); @@ -1276,8 +1277,8 @@ Points Print::first_layer_wipe_tower_corners() const pts.emplace_back(center + r*Vec2d(std::cos(alpha)/cone_x_scale, std::sin(alpha))); for (Vec2d& pt : pts) { - pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; - pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); + pt = Eigen::Rotation2Dd(Geometry::deg2rad(model().wipe_tower().rotation)) * pt; + pt += model().wipe_tower().position; pts_scaled.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); } } @@ -1551,7 +1552,7 @@ void Print::_make_wipe_tower() this->throw_if_canceled(); // Initialize the wipe tower. - WipeTower wipe_tower(m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); + WipeTower wipe_tower(model().wipe_tower().position.cast(), model().wipe_tower().rotation, m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < m_config.nozzle_diameter.size(); ++ i) @@ -1760,5 +1761,21 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co return final_path; } +PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const +{ + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + + if (this->parent_type == FuzzySkinParentType::PaintedRegion) { + return layer_range.painted_regions[this->parent].region; + } + + assert(this->parent_type == FuzzySkinParentType::VolumeRegion); + return layer_range.volume_regions[this->parent].region; +} + +int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const +{ + return this->parent_print_object_region(layer_range)->print_object_region_id(); +} } // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index d64bc54..feeee43 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -130,8 +130,6 @@ using SpanOfConstPtrs = tcb::span; using LayerPtrs = std::vector; using SupportLayerPtrs = std::vector; -class BoundingBoxf3; // TODO: for temporary constructor parameter - // Single instance of a PrintObject. // As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z), // ModelObject's instancess will be distributed among these multiple PrintObjects. @@ -184,6 +182,22 @@ public: PrintRegion *region { nullptr }; }; + struct LayerRangeRegions; + + struct FuzzySkinPaintedRegion + { + enum class ParentType { VolumeRegion, PaintedRegion }; + + ParentType parent_type { ParentType::VolumeRegion }; + // Index of a parent VolumeRegion or PaintedRegion. + int parent { -1 }; + // Pointer to PrintObjectRegions::all_regions. + PrintRegion *region { nullptr }; + + PrintRegion *parent_print_object_region(const LayerRangeRegions &layer_range) const; + int parent_print_object_region_id(const LayerRangeRegions &layer_range) const; + }; + // One slice over the PrintObject (possibly the whole PrintObject) and a list of ModelVolumes and their bounding boxes // possibly clipped by the layer_height_range. struct LayerRangeRegions @@ -196,8 +210,9 @@ public: std::vector volumes; // Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc. - std::vector volume_regions; - std::vector painted_regions; + std::vector volume_regions; + std::vector painted_regions; + std::vector fuzzy_skin_painted_regions; bool has_volume(const ObjectID id) const { auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &l) { return l.volume_id < id; }); @@ -320,6 +335,8 @@ public: bool has_support_material() const { return this->has_support() || this->has_raft(); } // Checks if the model object is painted using the multi-material painting gizmo. bool is_mm_painted() const { return this->model_object()->is_mm_painted(); } + // Checks if the model object is painted using the fuzzy skin painting gizmo. + bool is_fuzzy_skin_painted() const { return this->model_object()->is_fuzzy_skin_painted(); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) std::vector object_extruders() const; @@ -485,6 +502,8 @@ bool is_toolchange_required( struct PrintStatistics { PrintStatistics() { clear(); } + float normal_print_time_seconds; + float silent_print_time_seconds; std::string estimated_normal_print_time; std::string estimated_silent_print_time; double total_used_filament; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index ce538f1..6846bd5 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -101,6 +101,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, mv_dst.seam_facets.assign(mv_src.seam_facets); assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id()); mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets); + assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id()); + mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -677,7 +679,6 @@ bool verify_update_print_object_regions( ModelVolumePtrs model_volumes, const PrintRegionConfig &default_region_config, size_t num_extruders, - const std::vector &painting_extruders, PrintObjectRegions &print_object_regions, const std::function &callback_invalidate) { @@ -751,7 +752,7 @@ bool verify_update_print_object_regions( } } - // Verify and / or update PrintRegions produced by color painting. + // Verify and / or update PrintRegions produced by color painting. for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) { const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent]; @@ -775,6 +776,29 @@ bool verify_update_print_object_regions( print_region_ref_inc(*region.region); } + // Verify and / or update PrintRegions produced by fuzzy skin painting. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + for (const PrintObjectRegions::FuzzySkinPaintedRegion ®ion : layer_range.fuzzy_skin_painted_regions) { + const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range); + PrintRegionConfig cfg = parent_print_region.config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + } + // Lastly verify, whether some regions were not merged. { std::vector regions; @@ -877,9 +901,9 @@ static PrintObjectRegions* generate_print_object_regions( const PrintRegionConfig &default_region_config, const Transform3d &trafo, size_t num_extruders, - //w12 - const float xy_contour_compensation, - const std::vector &painting_extruders) + const float xy_size_compensation, + const std::vector &painting_extruders, + const bool has_painted_fuzzy_skin) { // Reuse the old object or generate a new one. auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); @@ -901,6 +925,7 @@ static PrintObjectRegions* generate_print_object_regions( r.config = range.config; r.volume_regions.clear(); r.painted_regions.clear(); + r.fuzzy_skin_painted_regions.clear(); } } else { out->trafo_bboxes = trafo; @@ -909,9 +934,8 @@ static PrintObjectRegions* generate_print_object_regions( layer_ranges_regions.push_back({ range.layer_height_range, range.config }); } - //w12 const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); - update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_contour_compensation)); + update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); std::vector region_set; auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { @@ -983,13 +1007,42 @@ static PrintObjectRegions* generate_print_object_regions( cfg.infill_extruder.value = painted_extruder_id; layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); } - // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation. + // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MM segmentation. std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) { int lid = layer_range.volume_regions[l.parent].region->print_object_region_id(); int rid = layer_range.volume_regions[r.parent].region->print_object_region_id(); return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); } + if (has_painted_fuzzy_skin) { + using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType; + + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { + // FuzzySkinPaintedRegion can override different parts of the Layer than PaintedRegions, + // so FuzzySkinPaintedRegion has to point to both VolumeRegion and PaintedRegion. + for (int parent_volume_region_id = 0; parent_volume_region_id < int(layer_range.volume_regions.size()); ++parent_volume_region_id) { + if (const PrintObjectRegions::VolumeRegion &parent_volume_region = layer_range.volume_regions[parent_volume_region_id]; parent_volume_region.model_volume->is_model_part() || parent_volume_region.model_volume->is_modifier()) { + PrintRegionConfig cfg = parent_volume_region.region->config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::VolumeRegion, parent_volume_region_id, get_create_region(std::move(cfg))}); + } + } + + for (int parent_painted_regions_id = 0; parent_painted_regions_id < int(layer_range.painted_regions.size()); ++parent_painted_regions_id) { + const PrintObjectRegions::PaintedRegion &parent_painted_region = layer_range.painted_regions[parent_painted_regions_id]; + + PrintRegionConfig cfg = parent_painted_region.region->config(); + cfg.fuzzy_skin.value = FuzzySkinType::All; + layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::PaintedRegion, parent_painted_regions_id, get_create_region(std::move(cfg))}); + } + + // Sort the regions by parent region::print_object_region_id() to help the slicing algorithm when applying fuzzy skin segmentation. + std::sort(layer_range.fuzzy_skin_painted_regions.begin(), layer_range.fuzzy_skin_painted_regions.end(), [&layer_range](auto &l, auto &r) { + return l.parent_print_object_region_id(layer_range) < r.parent_print_object_region_id(layer_range); + }); + } + } + return out.release(); } @@ -1011,7 +1064,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config); // If just a physical printer was changed, but printer preset is the same, then there is no need to apply whole print - // see https://github.com/QIDITECH/QIDISlicer/issues/8800 if (full_config_diff.size() == 1 && full_config_diff[0] == "physical_printer_settings_id") full_config_diff.clear(); @@ -1064,7 +1116,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ num_extruders_changed = true; } } - + + // Check the position and rotation of the wipe tower. + if (model.wipe_tower() != m_model.wipe_tower()) { + update_apply_status(this->invalidate_step(psSkirtBrim)); + } + m_model.wipe_tower() = model.wipe_tower(); + ModelObjectStatusDB model_object_status_db; // 1) Synchronize model objects. @@ -1085,18 +1143,18 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (const ModelObject *model_object : m_model.objects) model_object_status_db.add(*model_object, ModelObjectStatus::New); } else { - if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - const CustomGCode::Mode current_mode = m_model.custom_gcode_per_print_z.mode; - const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z.mode; + if (m_model.custom_gcode_per_print_z() != model.custom_gcode_per_print_z()) { + const CustomGCode::Mode current_mode = m_model.custom_gcode_per_print_z().mode; + const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z().mode; const bool multi_extruder_differ = (current_mode == next_mode) && (current_mode == CustomGCode::MultiExtruder || next_mode == CustomGCode::MultiExtruder); // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. // FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. - const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ToolChange); + const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z().gcodes, model.custom_gcode_per_print_z().gcodes, CustomGCode::ToolChange); // For multi-extruder printers, we perform a tool change before a color change. // So, in that case, we must invalidate tool ordering and wipe tower even if custom color change g-codes differ. - const bool color_change_differ = num_extruders > 1 && (next_mode == CustomGCode::MultiExtruder) && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ColorChange); + const bool color_change_differ = num_extruders > 1 && (next_mode == CustomGCode::MultiExtruder) && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z().gcodes, model.custom_gcode_per_print_z().gcodes, CustomGCode::ColorChange); update_apply_status( (num_extruders_changed || tool_change_differ || multi_extruder_differ || color_change_differ) ? @@ -1104,7 +1162,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ this->invalidate_steps({ psWipeTower, psGCodeExport }) : // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. this->invalidate_step(psGCodeExport)); - m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + m_model.custom_gcode_per_print_z() = model.custom_gcode_per_print_z(); } if (model_object_list_equal(m_model, model)) { // The object list did not change. @@ -1189,7 +1247,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || model_mmu_segmentation_data_changed(model_object, model_object_new) || - (model_object_new.is_mm_painted() && num_extruders_changed); + (model_object_new.is_mm_painted() && num_extruders_changed) || + model_fuzzy_skin_data_changed(model_object, model_object_new); bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); @@ -1396,22 +1455,34 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_object_regions->ref_cnt_inc(); } std::vector painting_extruders; - if (const auto &volumes = print_object.model_object()->volumes; - num_extruders > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) { - + if (const auto &volumes = print_object.model_object()->volumes; num_extruders > 1 && print_object.model_object()->is_mm_painted()) { std::array(TriangleStateType::Count)> used_facet_states{}; for (const ModelVolume *volume : volumes) { - const std::vector &volume_used_facet_states = volume->mm_segmentation_facets.get_data().used_states; + if (volume->is_mm_painted()) { + const std::vector &volume_used_facet_states = volume->mm_segmentation_facets.get_data().used_states; - assert(volume_used_facet_states.size() == used_facet_states.size()); - for (size_t state_idx = 0; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx) - used_facet_states[state_idx] |= volume_used_facet_states[state_idx]; + assert(volume_used_facet_states.size() == used_facet_states.size()); + for (size_t state_idx = 1; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx) { + used_facet_states[state_idx] |= volume_used_facet_states[state_idx]; + } +#if 0 + // When the default facet state (TriangleStateType::NONE) is used, then we mark the volume extruder also as the used extruder. + const bool used_volume_extruder = !volume_used_facet_states.empty() && volume_used_facet_states[static_cast(TriangleStateType::NONE)]; + if (const int volume_extruder_id = volume->extruder_id(); used_volume_extruder && volume_extruder_id >= 0) { + used_facet_states[volume_extruder_id] |= true; + } + } else if (const int volume_extruder_id = volume->extruder_id(); volume_extruder_id >= 0) { + used_facet_states[volume_extruder_id] |= true; + } +#else + } +#endif } for (size_t state_idx = static_cast(TriangleStateType::Extruder1); state_idx < used_facet_states.size(); ++state_idx) { - if (used_facet_states[state_idx]) + if (used_facet_states[state_idx]) { painting_extruders.emplace_back(state_idx); + } } } if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { @@ -1431,7 +1502,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_object.model_object()->volumes, m_default_region_config, num_extruders, - painting_extruders, *print_object_regions, [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { for (auto it = it_print_object; it != it_print_object_end; ++it) @@ -1457,9 +1527,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_default_region_config, model_object_status.print_instances.front().trafo, num_extruders, - //w12 - print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value), - painting_extruders); + print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), + painting_extruders, + print_object.is_fuzzy_skin_painted()); } for (auto it = it_print_object; it != it_print_object_end; ++it) if ((*it)->m_shared_regions) { @@ -1522,7 +1592,7 @@ void Print::cleanup() auto this_objects = SpanOfConstPtrs(const_cast(&(*it_begin)), it - it_begin); if (! Print::is_shared_print_object_step_valid_unguarded(this_objects, posSupportSpotsSearch)) shared_regions->generated_support_points.reset(); - } + } } bool Print::is_shared_print_object_step_valid_unguarded(SpanOfConstPtrs print_objects, PrintObjectStep print_object_step) diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index acb620d..ba80385 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -103,8 +103,10 @@ void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel /* auto status = print_object ? SlicingStatus(*print_object, step) : SlicingStatus(*this, step); m_status_callback(status); } - else if (! message.empty()) + else if (! message.empty()) { printf("%s warning: %s\n", print_object ? "print_object" : "print", message.c_str()); + std::fflush(stdout); + } } std::mutex& PrintObjectBase::state_mutex(PrintBase *print) diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 600a6cb..c810662 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -491,7 +491,7 @@ public: // Calls a registered callback to update the status, or print out the default message. void set_status(int percent, const std::string &message, unsigned int flags = SlicingStatus::DEFAULT) { if (m_status_callback) m_status_callback(SlicingStatus(percent, message, flags)); - else printf("%d => %s\n", percent, message.c_str()); + else { printf("%d => %s\n", percent, message.c_str()); std::fflush(stdout); } } typedef std::function cancel_callback_type; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 14503b7..6fe0fa8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -178,8 +178,17 @@ static const t_config_enum_values s_keys_map_SeamPosition { { "aligned", spAligned }, { "rear", spRear } }; + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamPosition) +static const t_config_enum_values s_keys_map_ScarfSeamPlacement { + { "nowhere", static_cast(ScarfSeamPlacement::nowhere) }, + { "contours", static_cast(ScarfSeamPlacement::countours) }, + { "everywhere", static_cast(ScarfSeamPlacement::everywhere) } +}; + +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ScarfSeamPlacement) + static const t_config_enum_values s_keys_map_SLADisplayOrientation = { { "landscape", sladoLandscape}, { "portrait", sladoPortrait} @@ -292,6 +301,13 @@ static const t_config_enum_values s_keys_map_TiltSpeeds{ }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TiltSpeeds) +static const t_config_enum_values s_keys_map_EnsureVerticalShellThickness { + { "disabled", int(EnsureVerticalShellThickness::Disabled) }, + { "partial", int(EnsureVerticalShellThickness::Partial) }, + { "enabled", int(EnsureVerticalShellThickness::Enabled) }, +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsureVerticalShellThickness) + static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -497,6 +513,20 @@ void PrintConfigDef::init_common_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionEnum(atKeyPassword)); + def = this->add("profile_vendor", coString); + def->label = L("Profile vendor"); + def->tooltip = L("Name of profile vendor"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("profile_version", coString); + def->label = L("Profile version"); + def->tooltip = L("Version of profile"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("")); + // temporary workaround for compatibility with older Slicer { def = this->add("preset_name", coString); @@ -519,6 +549,31 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(ArcFittingType::Disabled)); + def = this->add("automatic_extrusion_widths", coBool); + def->label = L("Automatic extrusion widths calculation"); + def->category = L("Extrusion Width"); + def->tooltip = L("Automatically calculates extrusion widths based on the nozzle diameter of the currently used extruder. " + "This setting is essential for printing with different nozzle diameters."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("automatic_infill_combination", coBool); + def->label = L("Automatic infill combination"); + def->category = L("Infill"); + def->tooltip = L("This feature automatically combines infill of several layers and speeds up your print by extruding thicker " + "infill layers while preserving thin perimeters, thus maintaining accuracy."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("automatic_infill_combination_max_layer_height", coFloatOrPercent); + def->label = L("Automatic infill combination - Max layer height"); + def->category = L("Infill"); + def->tooltip = L("Maximum layer height for combining infill when automatic infill combining is enabled. " + "Maximum layer height could be specified either as an absolute in millimeters value or as a percentage of nozzle diameter. " + "For printing with different nozzle diameters, it is recommended to use percentage value over absolute value."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(100., true)); + // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; def = this->add("avoid_crossing_curled_overhangs", coBool); @@ -1050,6 +1105,20 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" }); + def = this->add("ensure_vertical_shell_thickness", coEnum); + def->label = L("Ensure vertical shell thickness"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " + "(top+bottom solid layers)."); + def->set_enum({ + { "disabled", L("Disabled") }, + // TRN: This is a drop-down option for 'Ensure vertical shell thickness' parameter. + { "partial", L("Partial") }, + { "enabled", L("Enabled") }, + }); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(EnsureVerticalShellThickness::Enabled)); + auto def_top_fill_pattern = def = this->add("top_fill_pattern", coEnum); def->label = L("Top fill pattern"); def->category = L("Infill"); @@ -2849,6 +2918,17 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("seam_gap_distance", coFloatOrPercent); + def->label = L("Seam gap distance"); + def->tooltip = L("The distance between the endpoints of a closed loop perimeter. " + "Positive values will shorten and interrupt the loop slightly to reduce the seam. " + "Negative values will extend the loop, causing the endpoints to overlap slightly. " + "When percents are used, the distance is derived from the nozzle diameter. " + "Set to zero to disable this feature."); + def->sidetext = L("mm or %"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent{ 15., true }); + def = this->add("seam_position", coEnum); def->label = L("Seam position"); def->category = L("Layers and Perimeters"); @@ -2862,15 +2942,6 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(spAligned)); -//Y21 - def = this->add("seam_gap", coPercent); - def->label = L("Seam gap"); - def->tooltip = L("In order to reduce the visibility of the seam in a closed loop extrusion, the loop is interrupted and shortened by a specified amount.\n" "This amount as a percentage of the current extruder diameter. The default value for this parameter is 15"); - def->sidetext = L("%"); - def->min = 0; - def->mode = comExpert; - def->set_default_value(new ConfigOptionPercent(15)); - def = this->add("staggered_inner_seams", coBool); def->label = L("Staggered inner seams"); // TRN PrintSettings: "Staggered inner seams" @@ -2878,6 +2949,69 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("scarf_seam_placement", coEnum); + def->label = L("Scarf joint placement"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Where to place scarf joint seam."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(ScarfSeamPlacement::nowhere)); + def->set_enum({ + // TRN: Drop-down option for 'Scarf joint placement' parameter. + { "nowhere", L("Nowhere") }, + // TRN: Drop-down option for 'Scarf joint placement' parameter. + { "contours", L("Contours") }, + { "everywhere", L("Everywhere") } + }); + + def = this->add("scarf_seam_only_on_smooth", coBool); + def->label = L("Scarf joint only on smooth perimeters"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Only use the scarf joint when the perimeter is smooth."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("scarf_seam_start_height", coPercent); + def->label = L("Scarf start height"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Start height of the scarf joint specified as fraction of the current layer height."); + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(0)); + + def = this->add("scarf_seam_entire_loop", coBool); + def->label = L("Scarf joint around entire perimeter"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Extend the scarf around entire length of the perimeter."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("scarf_seam_length", coFloat); + def->label = L("Scarf joint length"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Length of the scarf joint."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(20)); + + def = this->add("scarf_seam_max_segment_length", coFloat); + def->label = L("Max scarf joint segment length"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Maximum length of any scarf joint segment."); + def->sidetext = L("mm"); + def->min = 0.15f; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("scarf_seam_on_inner_perimeters", coBool); + def->label = L("Scarf joint on inner perimeters"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Use scarf joint on inner perimeters."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + #if 0 def = this->add("seam_preferred_direction", coFloat); // def->gui_type = ConfigOptionDef::GUIType::slider; @@ -3717,20 +3851,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = ""; def->set_default_value(new ConfigOptionBool{ false }); - def = this->add("wipe_tower_x", coFloat); - def->label = L("Position X"); - def->tooltip = L("X coordinate of the left front corner of a wipe tower"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(180.)); - - def = this->add("wipe_tower_y", coFloat); - def->label = L("Position Y"); - def->tooltip = L("Y coordinate of the left front corner of a wipe tower"); - def->sidetext = L("mm"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(140.)); - def = this->add("wipe_tower_width", coFloat); def->label = L("Width"); def->tooltip = L("Width of a wipe tower"); @@ -3738,13 +3858,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(60.)); - def = this->add("wipe_tower_rotation_angle", coFloat); - def->label = L("Wipe tower rotation angle"); - def->tooltip = L("Wipe tower rotation angle with respect to x-axis."); - def->sidetext = L("°"); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.)); - def = this->add("wipe_tower_brim_width", coFloat); def->label = L("Wipe tower brim width"); def->tooltip = L("Wipe tower brim width"); @@ -3967,9 +4080,9 @@ void PrintConfigDef::init_fff_params() auto it_opt = options.find(opt_key); assert(it_opt != options.end()); def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); - def->label = it_opt->second.label; + def->label = it_opt->second.label; def->full_label = it_opt->second.full_label; - def->tooltip = it_opt->second.tooltip; + def->tooltip = it_opt->second.tooltip; def->sidetext = it_opt->second.sidetext; def->mode = it_opt->second.mode; switch (def->type) { @@ -3979,6 +4092,46 @@ void PrintConfigDef::init_fff_params() default: assert(false); } } + + + // Declare values for filament profile, overriding printer's profile. + for (const char *opt_key : { + // Floats or Percents + "seam_gap_distance"}) { + + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + + switch (it_opt->second.type) { + case coFloatOrPercent: { + def = this->add_nullable(std::string("filament_") + opt_key, coFloatsOrPercents); + break; + } + default: { + assert(false); + break; + } + } + + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->mode = it_opt->second.mode; + + switch (def->type) { + case coFloatsOrPercents: { + const auto &default_value = *static_cast(it_opt->second.default_value.get()); + def->set_default_value(new ConfigOptionFloatsOrPercentsNullable{{default_value.value, default_value.percent}}); + break; + } + default: { + assert(false); + break; + } + } + } + //w11 def = this->add("detect_narrow_internal_solid_infill", coBool); def->label = L("Detect narrow internal solid infill"); @@ -5053,16 +5206,12 @@ static std::set PrintConfigDef_ignore = { "seal_position", "vibration_limit", "bed_size", "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe", "serial_port", "serial_speed", - // Introduced in some QIDISlicer 2.3.1 alpha, later renamed or removed. "fuzzy_skin_perimeter_mode", "fuzzy_skin_shape", - // Introduced in QIDISlicer 2.3.0-alpha2, later replaced by automatic calculation based on extrusion width. "wall_add_middle_threshold", "wall_split_middle_threshold", - // Replaced by new concentric ensuring in 2.6.0-alpha5 - "ensure_vertical_shell_thickness", - // Disabled in 2.6.0-alpha6, this option is problematic "infill_only_where_needed", - "gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode). - "wiping_volumes_extruders" // Removed in 2.7.3-alpha1. + "gcode_binary", + "wiping_volumes_extruders", + "wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle" }; void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) @@ -5133,16 +5282,17 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = "printhost_apikey"; } else if (opt_key == "preset_name") { opt_key = "preset_names"; - } /*else if (opt_key == "material_correction" || opt_key == "relative_correction") { - ConfigOptionFloats p; - p.deserialize(value); - - if (p.values.size() < 3) { - double firstval = p.values.front(); - p.values.emplace(p.values.begin(), firstval); - value = p.serialize(); + } else if (opt_key == "ensure_vertical_shell_thickness") { + if (value == "1") { + value = "enabled"; + } else if (value == "0") { + value = "partial"; + } else if (const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); enum_keys_map.find(value) == enum_keys_map.end()) { + assert(value == "0" || value == "1"); + // Values other than 0/1 are replaced with "partial" for handling values from different slicers. + value = "partial"; } - }*/ + } // In QIDISlicer 2.3.0-alpha0 the "monotonous" infill was introduced, which was later renamed to "monotonic". if (value == "monotonous" && (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern")) @@ -5767,11 +5917,6 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->tooltip = L("Export the model(s) as 3MF."); def->set_default_value(new ConfigOptionBool(false)); - def = this->add("export_amf", coBool); - def->label = L("Export AMF"); - def->tooltip = L("Export the model(s) as AMF."); - def->set_default_value(new ConfigOptionBool(false)); - def = this->add("export_stl", coBool); def->label = L("Export STL"); def->tooltip = L("Export the model(s) as STL."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6016658..707c59e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -131,6 +131,12 @@ enum SeamPosition { spRandom, spNearest, spAligned, spRear }; +enum class ScarfSeamPlacement { + nowhere, + countours, + everywhere +}; + enum SLAMaterial { slamTough, slamFlex, @@ -214,6 +220,12 @@ enum TiltSpeeds : int { tsMove8000, }; +enum class EnsureVerticalShellThickness { + Disabled, + Partial, + Enabled, +}; + #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -232,6 +244,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ScarfSeamPlacement) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLASupportTreeType) @@ -242,6 +255,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopOnePerimeterType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(EnsureVerticalShellThickness) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -672,11 +686,14 @@ PRINT_CONFIG_CLASS_DEFINE( PRINT_CONFIG_CLASS_DEFINE( PrintRegionConfig, + ((ConfigOptionBool, automatic_infill_combination)) + ((ConfigOptionFloatOrPercent, automatic_infill_combination_max_layer_height)) ((ConfigOptionFloat, bridge_angle)) ((ConfigOptionInt, bottom_solid_layers)) ((ConfigOptionFloat, bottom_solid_min_thickness)) ((ConfigOptionFloat, bridge_flow_ratio)) ((ConfigOptionFloat, bridge_speed)) + ((ConfigOptionEnum, ensure_vertical_shell_thickness)) ((ConfigOptionEnum, top_fill_pattern)) ((ConfigOptionEnum, bottom_fill_pattern)) ((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width)) @@ -745,6 +762,14 @@ PRINT_CONFIG_CLASS_DEFINE( // Single perimeter. ((ConfigOptionEnum, top_one_perimeter_type)) ((ConfigOptionBool, only_one_perimeter_first_layer)) + + ((ConfigOptionEnum, scarf_seam_placement)) + ((ConfigOptionBool, scarf_seam_only_on_smooth)) + ((ConfigOptionPercent, scarf_seam_start_height)) + ((ConfigOptionBool, scarf_seam_entire_loop)) + ((ConfigOptionFloat, scarf_seam_length)) + ((ConfigOptionFloat, scarf_seam_max_segment_length)) + ((ConfigOptionBool, scarf_seam_on_inner_perimeters)) ) PRINT_CONFIG_CLASS_DEFINE( @@ -820,6 +845,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionFloats, filament_stamping_loading_speed)) ((ConfigOptionFloats, filament_stamping_distance)) + ((ConfigOptionFloatsOrPercentsNullable, filament_seam_gap_distance)) ((ConfigOptionPercents, filament_shrinkage_compensation_xy)) ((ConfigOptionPercents, filament_shrinkage_compensation_z)) ((ConfigOptionBool, gcode_comments)) @@ -850,6 +876,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, retract_restart_extra)) ((ConfigOptionFloats, retract_restart_extra_toolchange)) ((ConfigOptionFloats, retract_speed)) + ((ConfigOptionFloatOrPercent, seam_gap_distance)) ((ConfigOptionString, start_gcode)) ((ConfigOptionStrings, start_filament_gcode)) ((ConfigOptionBool, single_extruder_multi_material)) @@ -896,6 +923,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( PrintConfig, (MachineEnvelopeConfig, GCodeConfig), + ((ConfigOptionBool, automatic_extrusion_widths)) ((ConfigOptionBool, avoid_crossing_curled_overhangs)) ((ConfigOptionBool, avoid_crossing_perimeters)) ((ConfigOptionFloatOrPercent, avoid_crossing_perimeters_max_detour)) @@ -998,11 +1026,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionBools, wipe)) ((ConfigOptionBool, wipe_tower)) ((ConfigOptionFloat, wipe_tower_acceleration)) - ((ConfigOptionFloat, wipe_tower_x)) - ((ConfigOptionFloat, wipe_tower_y)) ((ConfigOptionFloat, wipe_tower_width)) ((ConfigOptionFloat, wipe_tower_per_color_wipe)) - ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, wipe_tower_brim_width)) ((ConfigOptionFloat, wipe_tower_cone_angle)) ((ConfigOptionPercent, wipe_tower_extra_spacing)) @@ -1012,8 +1037,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloats, wiping_volumes_matrix)) ((ConfigOptionBool, wiping_volumes_use_custom_matrix)) ((ConfigOptionFloat, z_offset)) - //Y21 - ((ConfigOptionPercent, seam_gap)) ) PRINT_CONFIG_CLASS_DERIVED_DEFINE0( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c076f13..ac8401e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -808,7 +808,10 @@ bool PrintObject::invalidate_state_by_config_options( opt_key == "interface_shells" || opt_key == "infill_only_where_needed" || opt_key == "infill_every_layers" + || opt_key == "automatic_infill_combination" + || opt_key == "automatic_infill_combination_max_layer_height" || opt_key == "solid_infill_every_layers" + || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bottom_solid_min_thickness" || opt_key == "top_solid_layers" || opt_key == "top_solid_min_thickness" @@ -877,8 +880,13 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posSlice); } else if ( opt_key == "seam_position" - //Y21 - || opt_key == "seam_gap" + || opt_key == "scarf_seam_placement" + || opt_key == "scarf_seam_only_on_smooth" + || opt_key == "scarf_seam_start_height" + || opt_key == "scarf_seam_entire_loop" + || opt_key == "scarf_seam_length" + || opt_key == "scarf_seam_max_segment_length" + || opt_key == "scarf_seam_on_inner_perimeters" || opt_key == "seam_preferred_direction" || opt_key == "seam_preferred_direction_jitter" || opt_key == "support_material_speed" @@ -1293,6 +1301,21 @@ void PrintObject::discover_vertical_shells() if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. + // Is the "ensure vertical wall thickness" applicable to any region? + bool has_extra_layers = false; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + const PrintRegionConfig &config = this->printing_region(region_id).config(); + if (config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Enabled || config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Partial) { + has_extra_layers = true; + break; + } + } + + if (!has_extra_layers) { + // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. + return; + } + BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -1362,7 +1385,13 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + const PrintRegion ®ion = this->printing_region(region_id); + if (region.config().ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Enabled && region.config().ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Partial) { + // This region will be handled by discover_horizontal_shells(). + continue; + } + //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -1482,7 +1511,10 @@ void PrintObject::discover_vertical_shells() ++ i) { at_least_one_top_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Partial) { + combine_holes(cache.holes); + } + combine_shells(cache.top_surfaces); } if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { @@ -1511,7 +1543,10 @@ void PrintObject::discover_vertical_shells() -- i) { at_least_one_bottom_projected = true; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Partial) { + combine_holes(cache.holes); + } + combine_shells(cache.bottom_surfaces); } @@ -2358,7 +2393,10 @@ void PrintObject::bridge_over_infill() Polygons lightning_area; Polygons expansion_area; Polygons total_fill_area; + Polygons total_top_area; for (const LayerRegion *region : layer->regions()) { + Polygons top_polys = to_polygons(region->fill_surfaces().filter_by_types({stTop})); + total_top_area.insert(total_top_area.end(), top_polys.begin(), top_polys.end()); Polygons internal_polys = to_polygons(region->fill_surfaces().filter_by_types({stInternal, stInternalSolid})); expansion_area.insert(expansion_area.end(), internal_polys.begin(), internal_polys.end()); Polygons fill_polys = to_polygons(region->fill_expolygons()); @@ -2446,6 +2484,7 @@ void PrintObject::bridge_over_infill() bridging_area = closing(bridging_area, flow.scaled_spacing()); bridging_area = intersection(bridging_area, limiting_area); bridging_area = intersection(bridging_area, total_fill_area); + bridging_area = diff(bridging_area, total_top_area); expansion_area = diff(expansion_area, bridging_area); #ifdef DEBUG_BRIDGE_OVER_INFILL @@ -2831,9 +2870,175 @@ void PrintObject::discover_horizontal_shells() if (surface.surface_type == stInternal) surface.surface_type = type; } - // The rest has already been performed by discover_vertical_shells(). + + // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). + if (region_config.ensure_vertical_shell_thickness.value != EnsureVerticalShellThickness::Disabled) + continue; + + assert(region_config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Disabled); + + coordf_t print_z = layer->print_z; + coordf_t bottom_z = layer->bottom_z(); + for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { + m_print->throw_if_canceled(); + SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; + int num_solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; + if (num_solid_layers == 0) + continue; + // Find slices of current type for current layer. + // Use slices instead of fill_surfaces, because they also include the perimeter area, + // which needs to be propagated in shells; we need to grow slices like we did for + // fill_surfaces though. Using both ungrown slices and grown fill_surfaces will + // not work in some situations, as there won't be any grown region in the perimeter + // area (this was seen in a model where the top layer had one extra perimeter, thus + // its fill_surfaces were thinner than the lower layer's infill), however it's the best + // solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put + // too much solid infill inside nearly-vertical slopes. + + // Surfaces including the area of perimeters. Everything, that is visible from the top / bottom + // (not covered by a layer above / below). + // This does not contain the areas covered by perimeters! + Polygons solid; + for (const Surface &surface : layerm->slices()) + if (surface.surface_type == type) + polygons_append(solid, to_polygons(surface.expolygon)); + // Infill areas (slices without the perimeters). + for (const Surface &surface : layerm->fill_surfaces()) + if (surface.surface_type == type) + polygons_append(solid, to_polygons(surface.expolygon)); + if (solid.empty()) + continue; + + // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. + for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1; + (type == stTop) ? + (n >= 0 && (int(i) - n < num_solid_layers || + print_z - m_layers[n]->print_z < region_config.top_solid_min_thickness.value - EPSILON)) : + (n < int(m_layers.size()) && (n - int(i) < num_solid_layers || + m_layers[n]->bottom_z() - bottom_z < region_config.bottom_solid_min_thickness.value - EPSILON)); + (type == stTop) ? -- n : ++ n) + { + // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. + LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id]; + + // find intersection between neighbor and current layer's surfaces + // intersections have contours and holes + // we update $solid so that we limit the next neighbor layer to the areas that were + // found on this one - in other words, solid shells on one layer (for a given external surface) + // are always a subset of the shells found on the previous shell layer + // this approach allows for DWIM in hollow sloping vases, where we want bottom + // shells to be generated in the base but not in the walls (where there are many + // narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the + // upper perimeter as an obstacle and shell will not be propagated to more upper layers + //FIXME How does it work for stInternalBRIDGE? This is set for sparse infill. Likely this does not work. + Polygons new_internal_solid; + { + Polygons internal; + for (const Surface &surface : neighbor_layerm->fill_surfaces()) + if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid) + polygons_append(internal, to_polygons(surface.expolygon)); + new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes); + } + if (new_internal_solid.empty()) { + // No internal solid needed on this layer. In order to decide whether to continue + // searching on the next neighbor (thus enforcing the configured number of solid + // layers, use different strategies according to configured infill density: + if (region_config.fill_density.value == 0 || region_config.ensure_vertical_shell_thickness.value == EnsureVerticalShellThickness::Disabled) { + // If user expects the object to be void (for example a hollow sloping vase), + // don't continue the search. In this case, we only generate the external solid + // shell if the object would otherwise show a hole (gap between perimeters of + // the two layers), and internal solid shells are a subset of the shells found + // on each previous layer. + goto EXTERNAL; + } else { + // If we have internal infill, we can generate internal solid shells freely. + continue; + } + } + + const float factor = (region_config.fill_density.value == 0) ? 1.f : 0.5f; + if (factor > 0.0f) { + // if we're printing a hollow object we discard any solid shell thinner + // than a perimeter width, since it's probably just crossing a sloping wall + // and it's not wanted in a hollow print even if it would make sense when + // obeying the solid shell count option strictly (DWIM!) + + // Also use the same strategy if the user has selected to reduce + // the amount of solid infill on walls. However reduce the margin to 20% overhang + // as we want to generate infill on sloped vertical surfaces but still keep a small amount of + // filtering. This is an arbitrary value to make this option safe + // by ensuring that top surfaces, especially slanted ones dont go **completely** unsupported + // especially when using single perimeter top layers. + float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()) * factor; + Polygons too_narrow = diff(new_internal_solid, + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, jtMiter, 5)); + // Trim the regularized region by the original region. + if (!too_narrow.empty()) + new_internal_solid = solid = diff(new_internal_solid, too_narrow); + } + + // make sure the new internal solid is wide enough, as it might get collapsed + // when spacing is added in Fill.pm + { + //FIXME Vojtech: Disable this and you will be sorry. + float margin = layerm->flow(frSolidInfill).scaled_width(); // require at least this size + // we use a higher miterLimit here to handle areas with acute angles + // in those cases, the default miterLimit would cut the corner and we'd + // get a triangle in $too_narrow; if we grow it below then the shell + // would have a different shape from the external surface and we'd still + // have the same angle, so the next shell would be grown even more and so on. + Polygons too_narrow = diff( + new_internal_solid, + opening(new_internal_solid, margin, margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); + if (! too_narrow.empty()) { + // grow the collapsing parts and add the extra area to the neighbor layer + // as well as to our original surfaces so that we support this + // additional area in the next shell too + // make sure our grown surfaces don't exceed the fill area + Polygons internal; + for (const Surface &surface : neighbor_layerm->fill_surfaces()) + if (surface.is_internal() && !surface.is_bridge()) + polygons_append(internal, to_polygons(surface.expolygon)); + polygons_append(new_internal_solid, + intersection( + expand(too_narrow, +margin), + // Discard bridges as they are grown for anchoring and we can't + // remove such anchors. (This may happen when a bridge is being + // anchored onto a wall where little space remains after the bridge + // is grown, and that little space is an internal solid shell so + // it triggers this too_narrow logic.) + internal)); + // solid = new_internal_solid; + } + } + + // internal-solid are the union of the existing internal-solid surfaces + // and new ones + SurfaceCollection backup = std::move(neighbor_layerm->m_fill_surfaces); + polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid))); + ExPolygons internal_solid = union_ex(new_internal_solid); + // assign new internal-solid surfaces to layer + neighbor_layerm->m_fill_surfaces.set(internal_solid, stInternalSolid); + // subtract intersections from layer surfaces to get resulting internal surfaces + Polygons polygons_internal = to_polygons(std::move(internal_solid)); + ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes); + // assign resulting internal surfaces to layer + neighbor_layerm->m_fill_surfaces.append(internal, stInternal); + polygons_append(polygons_internal, to_polygons(std::move(internal))); + // assign top and bottom surfaces to layer + backup.keep_types({ stTop, stBottom, stBottomBridge }); + std::vector top_bottom_groups; + backup.group(&top_bottom_groups); + for (SurfacesPtr &group : top_bottom_groups) + neighbor_layerm->m_fill_surfaces.append( + diff_ex(group, polygons_internal), + // Use an existing surface as a template, it carries the bridge angle etc. + *group.front()); + } + EXTERNAL:; + } // foreach type (stTop, stBottom, stBottomBridge) } // for each layer - } // for each region + } // for each region #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { @@ -2842,8 +3047,8 @@ void PrintObject::discover_horizontal_shells() layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells"); layerm->export_region_fill_surfaces_to_svg_debug("5_discover_horizontal_shells"); } // for each layer - } // for each region -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + } // for each region +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // void PrintObject::discover_horizontal_shells() // combine fill surfaces across layers to honor the "infill every N layers" option @@ -2852,16 +3057,24 @@ void PrintObject::discover_horizontal_shells() void PrintObject::combine_infill() { // Work on each region separately. - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - const PrintRegion ®ion = this->printing_region(region_id); - const size_t every = region.config().infill_every_layers.value; - if (every < 2 || region.config().fill_density == 0.) + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + const PrintRegion ®ion = this->printing_region(region_id); + const size_t combine_infill_every_n_layers = region.config().infill_every_layers.value; + const bool automatic_infill_combination = region.config().automatic_infill_combination; + const bool enable_combine_infill = automatic_infill_combination || combine_infill_every_n_layers >= 2; + + if (!enable_combine_infill || region.config().fill_density == 0.) { continue; + } + // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. //FIXME limit the layer height to max_layer_height - double nozzle_diameter = std::min( - this->print()->config().nozzle_diameter.get_at(region.config().infill_extruder.value - 1), - this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_extruder.value - 1)); + const double nozzle_diameter = std::min(this->print()->config().nozzle_diameter.get_at(region.config().infill_extruder.value - 1), + this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_extruder.value - 1)); + + const double automatic_infill_combination_max_layer_height = region.config().automatic_infill_combination_max_layer_height.get_abs_value(nozzle_diameter); + const double max_combine_layer_height = automatic_infill_combination ? std::min(automatic_infill_combination_max_layer_height, nozzle_diameter) : nozzle_diameter; + // define the combinations std::vector combine(m_layers.size(), 0); { @@ -2869,19 +3082,21 @@ void PrintObject::combine_infill() size_t num_layers = 0; for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { m_print->throw_if_canceled(); - const Layer *layer = m_layers[layer_idx]; - if (layer->id() == 0) + const Layer &layer = *m_layers[layer_idx]; + if (layer.id() == 0) // Skip first print layer (which may not be first layer in array because of raft). continue; + // Check whether the combination of this layer with the lower layers' buffer // would exceed max layer height or max combined layer count. - if (current_height + layer->height >= nozzle_diameter + EPSILON || num_layers >= every) { + if (current_height + layer.height >= max_combine_layer_height + EPSILON || (!automatic_infill_combination && num_layers >= combine_infill_every_n_layers)) { // Append combination to lower layer. combine[layer_idx - 1] = num_layers; current_height = 0.; num_layers = 0; } - current_height += layer->height; + + current_height += layer.height; ++ num_layers; } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index fe22eb0..e9a0bc0 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -35,7 +35,6 @@ #include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/libslic3r.h" -#include "tcbspan/span.hpp" namespace Slic3r { @@ -579,34 +578,37 @@ void PrintObject::slice() template void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) { - // Returns MMU segmentation based on painting in MMU segmentation gizmo + // Returns MM segmentation based on painting in MM segmentation gizmo std::vector> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel); assert(segmentation.size() == print_object.layer_count()); tbb::parallel_for( tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { const auto &layer_ranges = print_object.shared_regions()->layer_ranges; - double z = print_object.get_layer(range.begin())->slice_z; + double z = print_object.get_layer(int(range.begin()))->slice_z; auto it_layer_range = layer_range_first(layer_ranges, z); const size_t num_extruders = print_object.print()->config().nozzle_diameter.size(); + struct ByExtruder { ExPolygons expolygons; BoundingBox bbox; }; - std::vector by_extruder; + struct ByRegion { - ExPolygons expolygons; - bool needs_merge { false }; + ExPolygons expolygons; + bool needs_merge { false }; }; - std::vector by_region; - for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + + std::vector by_extruder; + std::vector by_region; + for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { throw_on_cancel(); - Layer *layer = print_object.get_layer(layer_id); - it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer->slice_z); + Layer &layer = *print_object.get_layer(int(layer_id)); + it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; // Gather per extruder expolygons. by_extruder.assign(num_extruders, ByExtruder()); - by_region.assign(layer->region_count(), ByRegion()); + by_region.assign(layer.region_count(), ByRegion()); bool layer_split = false; for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) { ByExtruder ®ion = by_extruder[extruder_id]; @@ -616,92 +618,229 @@ void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_can layer_split = true; } } - if (! layer_split) + + if (!layer_split) continue; + // Split LayerRegions by by_extruder regions. // layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID. - auto it_painted_region = layer_range.painted_regions.begin(); - for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id) - if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) { - assert(layerm.region().print_object_region_id() == region_id); - const BoundingBox bbox = get_extents(layerm.slices().surfaces); - assert(it_painted_region < layer_range.painted_regions.end()); - // Find the first it_painted_region which overrides this region. - for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region) - assert(it_painted_region != layer_range.painted_regions.end()); - assert(it_painted_region != layer_range.painted_regions.end()); - assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region()); - // 1-based extruder ID - bool self_trimmed = false; - int self_extruder_id = -1; - for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++ extruder_id) - if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) { - // Find the target region. - for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region) - assert(it_painted_region != layer_range.painted_regions.end()); - assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id); - //FIXME Don't trim by self, it is not reliable. - if (&layerm.region() == it_painted_region->region) { - self_extruder_id = extruder_id; - continue; - } - // Steal from this region. - int target_region_id = it_painted_region->region->print_object_region_id(); - ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons); - if (! stolen.empty()) { - ByRegion &dst = by_region[target_region_id]; - if (dst.expolygons.empty()) { - dst.expolygons = std::move(stolen); - } else { - append(dst.expolygons, std::move(stolen)); - dst.needs_merge = true; - } - } -#if 0 - if (&layerm.region() == it_painted_region->region) - // Slices of this LayerRegion were trimmed by a MMU region of the same PrintRegion. - self_trimmed = true; -#endif - } - if (! self_trimmed) { - // Trim slices of this LayerRegion with all the MMU regions. - Polygons mine = to_polygons(std::move(layerm.slices().surfaces)); - for (auto &segmented : by_extruder) - if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) { - mine = diff(mine, segmented.expolygons); - if (mine.empty()) - break; - } - // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region(). - // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region() - // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from - // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder. - // This could, on some models, produce bulges with the model's base color (#7109). - if (! mine.empty()) - mine = opening(union_ex(mine), float(scale_(5 * EPSILON)), float(scale_(5 * EPSILON))); - if (! mine.empty()) { - ByRegion &dst = by_region[layerm.region().print_object_region_id()]; - if (dst.expolygons.empty()) { - dst.expolygons = union_ex(mine); - } else { - append(dst.expolygons, union_ex(mine)); - dst.needs_merge = true; - } + auto it_painted_region_begin = layer_range.painted_regions.cbegin(); + for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { + if (it_painted_region_begin == layer_range.painted_regions.cend()) + continue; + + const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); + const PrintRegion &parent_print_region = parent_layer_region.region(); + assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); + if (parent_layer_region.slices().empty()) + continue; + + // Find the first PaintedRegion, which overrides the parent PrintRegion. + auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) { + return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id(); + }); + + if (it_first_painted_region == layer_range.painted_regions.cend()) + continue; // This LayerRegion isn't overrides by any PaintedRegion. + + assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region); + + // Update the beginning PaintedRegion iterator for the next iteration. + it_painted_region_begin = it_first_painted_region; + + const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces); + bool self_trimmed = false; + int self_extruder_id = -1; // 1-based extruder ID + for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) { + const ByExtruder &segmented = by_extruder[extruder_id - 1]; + if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox)) + continue; + + // Find the first target region iterator. + auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [extruder_id](const auto &painted_region) { + return int(painted_region.extruder_id) >= extruder_id; + }); + + assert(it_target_region != layer_range.painted_regions.end()); + assert(layer_range.volume_regions[it_target_region->parent].region == &parent_print_region && int(it_target_region->extruder_id) == extruder_id); + + // Update the beginning PaintedRegion iterator for the next iteration. + it_painted_region_begin = it_target_region; + + // FIXME: Don't trim by self, it is not reliable. + if (it_target_region->region == &parent_print_region) { + self_extruder_id = extruder_id; + continue; + } + + // Steal from this region. + int target_region_id = it_target_region->region->print_object_region_id(); + ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, segmented.expolygons); + if (!stolen.empty()) { + ByRegion &dst = by_region[target_region_id]; + if (dst.expolygons.empty()) { + dst.expolygons = std::move(stolen); + } else { + append(dst.expolygons, std::move(stolen)); + dst.needs_merge = true; } } } + + if (!self_trimmed) { + // Trim slices of this LayerRegion with all the MM regions. + Polygons mine = to_polygons(parent_layer_region.slices().surfaces); + for (auto &segmented : by_extruder) { + if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) { + mine = diff(mine, segmented.expolygons); + if (mine.empty()) + break; + } + } + + // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region(). + // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region() + // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from + // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder. + // This could, on some models, produce bulges with the model's base color (#7109). + if (!mine.empty()) { + mine = opening(union_ex(mine), scaled(5. * EPSILON), scaled(5. * EPSILON)); + } + + if (!mine.empty()) { + ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; + if (dst.expolygons.empty()) { + dst.expolygons = union_ex(mine); + } else { + append(dst.expolygons, union_ex(mine)); + dst.needs_merge = true; + } + } + } + } + // Re-create Surfaces of LayerRegions. - for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) { + for (int region_id = 0; region_id < layer.region_count(); ++region_id) { ByRegion &src = by_region[region_id]; - if (src.needs_merge) + if (src.needs_merge) { // Multiple regions were merged into one. - src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); - layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); + src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); + } + + layer.get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); } } }); } +template +void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) +{ + // Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo. + std::vector> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel); + assert(segmentation.size() == print_object.layer_count()); + + struct ByRegion + { + ExPolygons expolygons; + bool needs_merge { false }; + }; + + tbb::parallel_for(tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) { + const auto &layer_ranges = print_object.shared_regions()->layer_ranges; + auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z); + + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + throw_on_cancel(); + + Layer &layer = *print_object.get_layer(int(layer_idx)); + it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z); + const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range; + + assert(segmentation[layer_idx].size() == 1); + const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][0]; + const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation); + if (fuzzy_skin_segmentation.empty()) + continue; + + // Split LayerRegions by painted fuzzy skin regions. + // layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID. + std::vector by_region(layer.region_count()); + auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin(); + for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) { + if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend()) + continue; + + const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx); + const PrintRegion &parent_print_region = parent_layer_region.region(); + assert(parent_print_region.print_object_region_id() == parent_layer_region_idx); + if (parent_layer_region.slices().empty()) + continue; + + // Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion. + auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) { + return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id(); + }); + + if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend()) + continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion. + + assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region); + + // Update the beginning FuzzySkinPaintedRegion iterator for the next iteration. + it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region); + + const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces); + Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices().surfaces); + // Don't trim by self, it is not reliable. + if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) { + // Steal from this region. + const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id(); + ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, fuzzy_skin_segmentation); + if (!stolen.empty()) { + ByRegion &dst = by_region[target_region_id]; + if (dst.expolygons.empty()) { + dst.expolygons = std::move(stolen); + } else { + append(dst.expolygons, std::move(stolen)); + dst.needs_merge = true; + } + } + + // Trim slices of this LayerRegion by the fuzzy skin region. + layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation); + + // Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation. + if (!layer_region_remaining_polygons.empty()) { + layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled(5. * EPSILON), scaled(5. * EPSILON)); + } + } + + if (!layer_region_remaining_polygons.empty()) { + ByRegion &dst = by_region[parent_print_region.print_object_region_id()]; + if (dst.expolygons.empty()) { + dst.expolygons = union_ex(layer_region_remaining_polygons); + } else { + append(dst.expolygons, union_ex(layer_region_remaining_polygons)); + dst.needs_merge = true; + } + } + } + + // Re-create Surfaces of LayerRegions. + for (int region_id = 0; region_id < layer.region_count(); ++region_id) { + ByRegion &src = by_region[region_id]; + if (src.needs_merge) { + // Multiple regions were merged into one. + src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON)); + } + + layer.get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); + } + } + }); // end of parallel_for +} + // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions // 3) Slices the object meshes @@ -751,11 +890,8 @@ void PrintObject::slice_volumes() m_layers.back()->upper_layer = nullptr; m_print->throw_if_canceled(); - // Is any ModelVolume MMU painted? - if (const auto& volumes = this->model_object()->volumes; - m_print->config().nozzle_diameter.size() > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mm_segmentation_facets.empty(); }) != volumes.end()) { - + // Is any ModelVolume multi-material painted? + if (m_print->config().nozzle_diameter.size() > 1 && this->model_object()->is_mm_painted()) { // If XY Size compensation is also enabled, notify the user that XY Size compensation // would not be used because the object is multi-material painted. //w12 @@ -772,6 +908,21 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + // Is any ModelVolume fuzzy skin painted? + if (this->model_object()->is_fuzzy_skin_painted()) { + // If XY Size compensation is also enabled, notify the user that XY Size compensation + // would not be used because the object has custom fuzzy skin painted. + if (m_config.xy_size_compensation.value != 0.f) { + this->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, + _u8L("An object has enabled XY Size compensation which will not be used because it is also fuzzy skin painted.\nXY Size " + "compensation cannot be combined with fuzzy skin painting.") + + "\n" + (_u8L("Object name")) + ": " + this->model_object()->name); + } + + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - Fuzzy skin segmentation"; + apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); }); + } BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { diff --git a/src/libslic3r/ProfilesSharingUtils.cpp b/src/libslic3r/ProfilesSharingUtils.cpp index 52cdfa3..40fee16 100644 --- a/src/libslic3r/ProfilesSharingUtils.cpp +++ b/src/libslic3r/ProfilesSharingUtils.cpp @@ -572,6 +572,8 @@ std::string load_full_print_config(const std::string& print_preset_name, extruders_filaments.clear(); for (size_t i = 0; i < material_preset_names.size(); ++i) extruders_filaments.emplace_back(ExtruderFilaments(&preset_bundle.filaments, i, material_preset_names[i])); + if (extruders_filaments.size() == 1) + preset_bundle.filaments.select_preset_by_name(material_preset_names[0], false); } config = preset_bundle.full_config(); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 8d14b73..d3c8fed 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -17,6 +17,8 @@ #include #include +#include "libslic3r/MultipleBeds.hpp" + // #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK @@ -293,6 +295,16 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con if (! material_diff.empty()) update_apply_status(this->invalidate_state_by_config_options(material_diff, invalidate_all_model_objects)); + // Multiple beds hack: We currently use one SLAPrint for all beds. It must be invalidated + // when beds are switched. If not done explicitly, supports from previously sliced object + // might end up with wrong offset. + static int last_bed_idx = s_multiple_beds.get_active_bed(); + int current_bed = s_multiple_beds.get_active_bed(); + if (current_bed != last_bed_idx) { + invalidate_all_model_objects = true; + last_bed_idx = current_bed; + } + // Apply variables to placeholder parser. The placeholder parser is currently used // only to generate the output file name. if (! placeholder_parser_diff.empty()) { @@ -696,6 +708,16 @@ void SLAPrint::export_print(const std::string &fname, const ThumbnailsList &thum } } +bool SLAPrint::is_qidi_print(const std::string& printer_model) +{ + static const std::vector qidi_printer_models = { "SL1", "SL1S", "M1", "SLX" }; + for (const std::string& model : qidi_printer_models) + if (model == printer_model) + return true; + + return false; +} + bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 2577515..e9aa7a3 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -577,7 +577,9 @@ public: void export_print(const std::string &fname, const ThumbnailsList &thumbnails, const std::string &projectname = ""); - + + static bool is_qidi_print(const std::string& printer_model); + private: // Implement same logic as in SLAPrintObject diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index efdbead..412a52f 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1144,8 +1144,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { ExposureProfile above(material_config, 1); const int first_slow_layers = fade_layers_cnt + first_extra_slow_layers; - const std::string printer_model = printer_config.printer_model; - const bool is_qidi_print = printer_model == "SL1" || printer_model == "SL1S" || printer_model == "M1"; + const bool is_qidi_print = SLAPrint::is_qidi_print(printer_config.printer_model); const auto width = scaled(printer_config.display_width.getFloat()); const auto height = scaled(printer_config.display_height.getFloat()); diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 9ed66ac..473183d 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1953,14 +1953,15 @@ static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polyl for (const FlipEdge &edge : edges) { Polyline &pl = polylines[edge.source_index]; out.emplace_back(std::move(pl)); - if (edge.p2 == pl.first_point().cast()) { + if (edge.p2 == out.back().first_point().cast()) { // Polyline is flipped. out.back().reverse(); } else { // Polyline is not flipped. - assert(edge.p1 == pl.first_point().cast()); + assert(edge.p1 == out.back().first_point().cast()); } } + polylines = out; #ifndef NDEBUG double cost_final = cost(); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 5fe05ff..c4b48e6 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -553,9 +553,10 @@ struct EdgeToFace { bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); } }; -template -static std::vector create_edge_map( - const indexed_triangle_set &its, FaceFilter face_filter, ThrowOnCancelCallback throw_on_cancel) +template +static std::vector create_edge_map(const typename IndexedTriangleSetType::type &its, + FaceFilter face_filter, + ThrowOnCancelCallback throw_on_cancel) { std::vector edges_map; edges_map.reserve(its.indices.size() * 3); @@ -584,12 +585,14 @@ static std::vector create_edge_map( // Map from a face edge to a unique edge identifier or -1 if no neighbor exists. // Two neighbor faces share a unique edge identifier even if they are flipped. -template -static inline std::vector its_face_edge_ids_impl(const indexed_triangle_set &its, FaceFilter face_filter, ThrowOnCancelCallback throw_on_cancel) +template +static inline std::vector its_face_edge_ids_impl(const typename IndexedTriangleSetType::type &its, + FaceFilter face_filter, + ThrowOnCancelCallback throw_on_cancel) { std::vector out(its.indices.size(), Vec3i(-1, -1, -1)); - std::vector edges_map = create_edge_map(its, face_filter, throw_on_cancel); + std::vector edges_map = create_edge_map(its, face_filter, throw_on_cancel); // Assign a unique common edge id to touching triangle edges. int num_edges = 0; @@ -609,8 +612,8 @@ static inline std::vector its_face_edge_ids_impl(const indexed_triangle_s } if (! found) { //FIXME Vojtech: Trying to find an edge with equal orientation. This smells. - // admesh can assign the same edge ID to more than two facets (which is - // still topologically correct), so we have to search for a duplicate of + // admesh can assign the same edge ID to more than two facets (which is + // still topologically correct), so we have to search for a duplicate of // this edge too in case it was already seen in this orientation for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j) if (edges_map[j].face != -1) { @@ -635,9 +638,16 @@ static inline std::vector its_face_edge_ids_impl(const indexed_triangle_s return out; } -std::vector its_face_edge_ids(const indexed_triangle_set &its) +// Explicit template instantiation. +template std::vector its_face_edge_ids(const IndexedTriangleSetType::type &); +template std::vector its_face_edge_ids(const IndexedTriangleSetType::type &); +template std::vector its_face_edge_ids(const IndexedTriangleSetType::type &, const std::vector &); +template std::vector its_face_edge_ids(const IndexedTriangleSetType::type &, const std::vector &); + +template +std::vector its_face_edge_ids(const typename IndexedTriangleSetType::type &its) { - return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, [](){}); + return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, [](){}); } std::vector its_face_edge_ids(const indexed_triangle_set &its, std::function throw_on_cancel_callback) @@ -645,9 +655,10 @@ std::vector its_face_edge_ids(const indexed_triangle_set &its, std::funct return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, throw_on_cancel_callback); } -std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask) +template +std::vector its_face_edge_ids(const typename IndexedTriangleSetType::type &its, const std::vector &face_mask) { - return its_face_edge_ids_impl(its, [&face_mask](const uint32_t idx){ return face_mask[idx]; }, [](){}); + return its_face_edge_ids_impl(its, [&face_mask](const uint32_t idx){ return face_mask[idx]; }, [](){}); } // Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices. diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 20f4c9a..01385e7 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -28,6 +28,30 @@ namespace Slic3r { class TriangleMesh; class TriangleMeshSlicer; +struct indexed_triangle_set_with_color +{ + std::vector indices; + std::vector vertices; + std::vector colors; +}; + +enum class AdditionalMeshInfo { + None, + Color +}; + +template struct IndexedTriangleSetType; + +template<> struct IndexedTriangleSetType +{ + using type = indexed_triangle_set; +}; + +template<> struct IndexedTriangleSetType +{ + using type = indexed_triangle_set_with_color; +}; + struct RepairedMeshErrors { // How many edges were united by merging their end points with some other end points in epsilon neighborhood? int edges_fixed = 0; @@ -199,9 +223,14 @@ private: // Map from a face edge to a unique edge identifier or -1 if no neighbor exists. // Two neighbor faces share a unique edge identifier even if they are flipped. // Used for chaining slice lines into polygons. -std::vector its_face_edge_ids(const indexed_triangle_set &its); +template +std::vector its_face_edge_ids(const typename IndexedTriangleSetType::type &its); + std::vector its_face_edge_ids(const indexed_triangle_set &its, std::function throw_on_cancel_callback); -std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask); + +template +std::vector its_face_edge_ids(const typename IndexedTriangleSetType::type &its, const std::vector &face_mask); + // Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices. std::vector its_face_edge_ids(const indexed_triangle_set &its, std::vector &face_neighbors, bool assign_unbound_edges = false, int *num_edges = nullptr); @@ -383,7 +412,7 @@ inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its, const Transfo return {bmin.cast(), bmax.cast()}; } -} +} // namespace Slic3r // Serialization through the Cereal library #include diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index eaca55e..6d15242 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -54,6 +54,39 @@ namespace Slic3r { +template struct PolygonsType; + +template<> struct PolygonsType +{ + using type = Polygons; +}; + +template<> struct PolygonsType +{ + using type = ColorPolygons; +}; + +template struct FacetColorFunctor; + +template<> struct FacetColorFunctor +{ + constexpr ColorPolygon::Color operator()(size_t facet_idx) const { return 0; } +}; + +template<> struct FacetColorFunctor +{ + FacetColorFunctor() = delete; + explicit FacetColorFunctor(const ColorPolygon::Colors &colors) : colors(colors) {} + + ColorPolygon::Color operator()(size_t facet_idx) const { + assert(facet_idx < this->colors.size()); + return this->colors[facet_idx]; + } + +private: + const ColorPolygon::Colors &colors; +}; + class IntersectionReference { public: @@ -137,7 +170,10 @@ public: NO_SEED = 0x100, SKIP = 0x200, }; - uint32_t flags { 0 }; + + uint16_t flags { 0 }; + // Color id of sliced facet. + uint8_t color { 0 }; #ifdef DEBUG_INTERSECTIONLINE enum class Source { @@ -189,6 +225,7 @@ inline FacetSliceType slice_facet( const Vec3i &edge_ids, const int idx_vertex_lowest, const bool horizontal, + const ColorPolygon::Color facet_color, IntersectionLine &line_out) { using Vector = Eigen::Matrix; @@ -252,6 +289,7 @@ inline FacetSliceType slice_facet( line_out.b = v3f_scaled_to_contour_point(*b); line_out.a_id = a_id; line_out.b_id = b_id; + line_out.color = facet_color; assert(line_out.a != line_out.b); return result; } @@ -329,6 +367,7 @@ inline FacetSliceType slice_facet( line_out.b_id = points[0].point_id; line_out.edge_a_id = points[1].edge_id; line_out.edge_b_id = points[0].edge_id; + line_out.color = facet_color; // Not a zero lenght edge. //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. //assert(line_out.a != line_out.b); @@ -383,6 +422,7 @@ void slice_facet_at_zs( const TransformVertex &transform_vertex_fn, const stl_triangle_vertex_indices &indices, const Vec3i &edge_ids, + const ColorPolygon::Color facet_color, // Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well. const std::vector &zs, std::vector &lines, @@ -402,7 +442,7 @@ void slice_facet_at_zs( for (auto it = min_layer; it != max_layer; ++ it) { IntersectionLine il; // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. - if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) { + if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, facet_color, il) == FacetSliceType::Slicing) { assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal); size_t slice_id = it - zs.begin(); boost::lock_guard l(lines_mutex(slice_id)); @@ -411,41 +451,44 @@ void slice_facet_at_zs( } } -template +template static inline std::vector slice_make_lines( const std::vector &vertices, const TransformVertex &transform_vertex_fn, const std::vector &indices, const std::vector &face_edge_ids, + const FacetColorFunctor &facet_color_fn, const std::vector &zs, const ThrowOnCancel throw_on_cancel_fn) { - std::vector lines(zs.size(), IntersectionLines{}); - LinesMutexes lines_mutex; + std::vector lines(zs.size(), IntersectionLines{}); + LinesMutexes lines_mutex; tbb::parallel_for( tbb::blocked_range(0, int(indices.size())), - [&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { + [&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &facet_color_fn, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) { if ((face_idx & 0x0ffff) == 0) throw_on_cancel_fn(); - slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_edge_ids[face_idx], zs, lines, lines_mutex); + slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_edge_ids[face_idx], facet_color_fn(face_idx), zs, lines, lines_mutex); } } ); + return lines; } -template +template static inline IntersectionLines slice_make_lines( const std::vector &mesh_vertices, const TransformVertex &transform_vertex_fn, const std::vector &mesh_faces, const std::vector &face_edge_ids, - const float plane_z, + const FacetColorFunctor &facet_color_fn, + const float plane_z, FaceFilter face_filter) { IntersectionLines lines; - for (int face_idx = 0; face_idx < int(mesh_faces.size()); ++ face_idx) + for (int face_idx = 0; face_idx < int(mesh_faces.size()); ++ face_idx) { if (face_filter(face_idx)) { const Vec3i &indices = mesh_faces[face_idx]; stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; @@ -453,14 +496,16 @@ static inline IntersectionLines slice_make_lines( const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z())); const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z())); assert(min_z <= plane_z && max_z >= plane_z); - int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); IntersectionLine il; // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. - if (min_z != max_z && slice_facet(plane_z, vertices, indices, face_edge_ids[face_idx], idx_vertex_lowest, false, il) == FacetSliceType::Slicing) { + if (min_z != max_z && slice_facet(plane_z, vertices, indices, face_edge_ids[face_idx], idx_vertex_lowest, false, facet_color_fn(face_idx), il) == FacetSliceType::Slicing) { assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal); lines.emplace_back(il); } } + } + return lines; } @@ -602,7 +647,7 @@ void slice_facet_with_slabs( IntersectionLine il_prev; for (auto it = min_layer; it != max_layer; ++ it) { IntersectionLine il; - auto type = slice_facet(*it, vertices, indices, facet_edge_ids, idx_vertex_lowest, false, il); + auto type = slice_facet(*it, vertices, indices, facet_edge_ids, idx_vertex_lowest, false, 0, il); if (type == FacetSliceType::NoSlice) { // One and exactly one vertex is touching the slicing plane. } else { @@ -946,8 +991,8 @@ static inline void remove_tangent_edges(std::vector &lines) struct OpenPolyline { OpenPolyline() = default; - OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : - start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } + OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points, ColorPolygon::Colors &&colors) : + start(start), end(end), points(std::move(points)), colors(std::move(colors)), length(Slic3r::length(this->points)), consumed(false) {} void reverse() { std::swap(start, end); std::reverse(points.begin(), points.end()); @@ -955,13 +1000,17 @@ struct OpenPolyline { IntersectionReference start; IntersectionReference end; Points points; + ColorPolygon::Colors colors; double length; bool consumed; }; // called by make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. // Only connects segments crossing triangles of the same orientation. -static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polygons &loops, std::vector &open_polylines) +template +static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, + typename PolygonsType::type &loops, + std::vector &open_polylines) { // Build a map of lines by edge_a_id and a_id. std::vector by_edge_a_id; @@ -993,17 +1042,24 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg } if (first_line == nullptr) break; + first_line->set_skip(); Points loop_pts; loop_pts.emplace_back(first_line->a); + + ColorPolygon::Colors loop_colors; + if constexpr (mesh_info == AdditionalMeshInfo::Color) { + loop_colors.emplace_back(first_line->color); + } + IntersectionLine *last_line = first_line; - + /* printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); */ - + IntersectionLine key; for (;;) { // find a line starting where last one finishes @@ -1038,7 +1094,13 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { // The current loop is complete. Add it to the output. assert(first_line->a == last_line->b); - loops.emplace_back(std::move(loop_pts)); + + if constexpr (mesh_info == AdditionalMeshInfo::Color) { + loops.emplace_back(std::move(loop_pts), std::move(loop_colors)); + } else { + loops.emplace_back(std::move(loop_pts)); + } + #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); #endif @@ -1047,7 +1109,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg loop_pts.emplace_back(last_line->b); open_polylines.emplace_back(OpenPolyline( IntersectionReference(first_line->a_id, first_line->edge_a_id), - IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); + IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts), std::move(loop_colors))); } break; } @@ -1058,6 +1120,11 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg */ assert(last_line->b == next_line->a); loop_pts.emplace_back(next_line->a); + + if constexpr (mesh_info == AdditionalMeshInfo::Color) { + loop_colors.emplace_back(next_line->color); + } + last_line = next_line; next_line->set_skip(); } @@ -1080,7 +1147,10 @@ std::vector open_polylines_sorted(std::vector &open // called by make_loops() to connect remaining open polylines across shared triangle edges and vertices. // Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. -static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +template +static void chain_open_polylines_exact(std::vector &open_polylines, + typename PolygonsType::type &loops, + bool try_connect_reversed) { // Store the end points of open_polylines into vectors sorted struct OpenPolylineEnd { @@ -1105,7 +1175,7 @@ static void chain_open_polylines_exact(std::vector &open_polylines } std::sort(by_id.begin(), by_id.end(), by_id_lower); // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). - auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> typename std::vector::iterator { for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); it != by_id.end() && it->id() == end.id(); ++ it) if (*it == end) @@ -1131,15 +1201,20 @@ static void chain_open_polylines_exact(std::vector &open_polylines found: // Attach this polyline to the end of the initial polyline. if (it_next_start->start) { - auto it = it_next_start->polyline->points.begin(); - std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + auto pt_it = it_next_start->polyline->points.begin(); + auto color_it = it_next_start->polyline->colors.begin(); + std::copy(++pt_it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + std::copy(color_it, it_next_start->polyline->colors.end(), back_inserter(opl->colors)); } else { - auto it = it_next_start->polyline->points.rbegin(); - std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + auto pt_it = it_next_start->polyline->points.rbegin(); + auto color_it = it_next_start->polyline->colors.rbegin(); + std::copy(++pt_it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + std::copy(color_it, it_next_start->polyline->colors.rend(), back_inserter(opl->colors)); } opl->length += it_next_start->polyline->length; // Mark the next polyline as consumed. it_next_start->polyline->points.clear(); + it_next_start->polyline->colors.clear(); it_next_start->polyline->length = 0.; it_next_start->polyline->consumed = true; if (try_connect_reversed) { @@ -1159,16 +1234,26 @@ static void chain_open_polylines_exact(std::vector &open_polylines //assert(opl->points.front().point_id == opl->points.back().point_id); //assert(opl->points.front().edge_id == opl->points.back().edge_id); // Remove the duplicate last point. + // Contrary to the points, the assigned colors will not be duplicated, so we will not remove them. opl->points.pop_back(); if (opl->points.size() >= 3) { - if (try_connect_reversed && area(opl->points) < 0) + if (try_connect_reversed && area(opl->points) < 0) { // The closed polygon is patched from pieces with messed up orientation, therefore // the orientation of the patched up polygon is not known. // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. std::reverse(opl->points.begin(), opl->points.end()); - loops.emplace_back(std::move(opl->points)); + std::reverse(opl->colors.begin(), opl->colors.end()); + } + + if constexpr (mesh_info == AdditionalMeshInfo::Color) { + loops.emplace_back(std::move(opl->points), std::move(opl->colors)); + } else { + loops.emplace_back(std::move(opl->points)); + } } + opl->points.clear(); + opl->colors.clear(); break; } // Continue with the current loop. @@ -1176,10 +1261,41 @@ static void chain_open_polylines_exact(std::vector &open_polylines } } -// called by make_loops() to connect remaining open polylines across shared triangle edges and vertices, +// The midpoint is inserted when color differs on both endpoints. +// Return true when a midpoint is inserted. +template +bool handle_color_at_gap_between_open_polylines(OpenPolyline &opl, + const Point &next_polyline_first_pt, + const ColorPolygon::Color &next_polyline_first_color) +{ + if constexpr (mesh_info == AdditionalMeshInfo::Color) { + bool midpoint_inserted = false; + if (opl.colors.back() == next_polyline_first_color) { + // Both endpoints around the gap have the same color, so we also use the same color for the gap. + opl.colors.emplace_back(opl.colors.back()); + } else { + // Endpoints around the gap have different colors, so we split the gap into two pieces, + // each with a different color. + opl.points.emplace_back(line_alg::midpoint(opl.points.back(), next_polyline_first_pt)); + opl.colors.emplace_back(opl.colors.back()); + opl.colors.emplace_back(next_polyline_first_color); + midpoint_inserted = true; + } + + return midpoint_inserted; + } + + return false; +} + +// called by make_loops() to connect remaining open polylines across shared triangle edges and vertices, // possibly closing small gaps. // Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. -static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +template +static void chain_open_polylines_close_gaps(std::vector &open_polylines, + typename PolygonsType::type &loops, + double max_gap, + bool try_connect_reversed) { const coord_t max_gap_scaled = (coord_t)scale_(max_gap); @@ -1210,10 +1326,13 @@ static void chain_open_polylines_close_gaps(std::vector &open_poly for (OpenPolyline *opl : sorted_by_length) { if (opl->consumed) continue; + OpenPolylineEnd end(opl, false); - if (try_connect_reversed) + if (try_connect_reversed) { // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. closest_end_point_lookup.erase(end); + } + opl->consumed = true; size_t n_segments_joined = 1; for (;;) { @@ -1222,7 +1341,7 @@ static void chain_open_polylines_close_gaps(std::vector &open_poly const OpenPolylineEnd *next_start = next_start_and_dist.first; // Check whether we closed this loop. double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); - bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + bool loop_closed = current_loop_closing_distance2 < Slic3r::sqr(coordf_t(max_gap_scaled)); if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { // Heuristics to decide, whether to close the loop, or connect another polyline. // One should avoid closing loops shorter than max_gap_scaled. @@ -1233,21 +1352,35 @@ static void chain_open_polylines_close_gaps(std::vector &open_poly // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. opl->consumed = false; closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); + + bool midpoint_inserted = false; if (current_loop_closing_distance2 == 0.) { // Remove the duplicate last point. opl->points.pop_back(); } else { // The end points are different, keep both of them. + midpoint_inserted = handle_color_at_gap_between_open_polylines(*opl, opl->points.front(), opl->colors.front()); } - if (opl->points.size() >= 3) { - if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) + + // When we split the gap into two pieces by adding a midpoint, then a valid polygon has at least 4 points. + if (opl->points.size() >= (3 + size_t(midpoint_inserted))) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) { // The closed polygon is patched from pieces with messed up orientation, therefore // the orientation of the patched up polygon is not known. // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. std::reverse(opl->points.begin(), opl->points.end()); - loops.emplace_back(std::move(opl->points)); + std::reverse(opl->colors.begin(), opl->colors.end()); + } + + if constexpr (mesh_info == AdditionalMeshInfo::Color) { + loops.emplace_back(std::move(opl->points), std::move(opl->colors)); + } else { + loops.emplace_back(std::move(opl->points)); + } } + opl->points.clear(); + opl->colors.clear(); opl->consumed = true; break; } @@ -1259,36 +1392,56 @@ static void chain_open_polylines_close_gaps(std::vector &open_poly closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); break; } + // Attach this polyline to the end of the initial polyline. if (next_start->start) { - auto it = next_start->polyline->points.begin(); - if (*it == opl->points.back()) - ++ it; - std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); + auto pt_it = next_start->polyline->points.begin(); + auto color_it = next_start->polyline->colors.begin(); + if (*pt_it == opl->points.back()) { + ++pt_it; + } else { + handle_color_at_gap_between_open_polylines(*opl, *pt_it, *color_it); + } + + std::copy(pt_it, next_start->polyline->points.end(), back_inserter(opl->points)); + std::copy(color_it, next_start->polyline->colors.end(), back_inserter(opl->colors)); } else { - auto it = next_start->polyline->points.rbegin(); - if (*it == opl->points.back()) - ++ it; - std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); + auto pt_it = next_start->polyline->points.rbegin(); + auto color_it = next_start->polyline->colors.rbegin(); + if (*pt_it == opl->points.back()) { + ++pt_it; + } else { + handle_color_at_gap_between_open_polylines(*opl, *pt_it, *color_it); + } + + std::copy(pt_it, next_start->polyline->points.rend(), back_inserter(opl->points)); + std::copy(color_it, next_start->polyline->colors.rend(), back_inserter(opl->colors)); } - ++ n_segments_joined; + + ++n_segments_joined; // Remove the end points of the consumed polyline segment from the lookup. OpenPolyline *opl2 = next_start->polyline; closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); - if (try_connect_reversed) + if (try_connect_reversed) { closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); + } + opl2->points.clear(); + opl2->colors.clear(); opl2->consumed = true; // Continue with the current loop. } } } -static Polygons make_loops( +template +static typename PolygonsType::type make_loops( // Lines will have their flags modified. - IntersectionLines &lines) -{ - Polygons loops; + IntersectionLines &lines +) { + using PolygonsType = typename PolygonsType::type; + + PolygonsType loops; #if 0 //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. //#ifdef _DEBUG @@ -1316,7 +1469,7 @@ static Polygons make_loops( #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ std::vector open_polylines; - chain_lines_by_triangle_connectivity(lines, loops, open_polylines); + chain_lines_by_triangle_connectivity(lines, loops, open_polylines); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1332,8 +1485,8 @@ static Polygons make_loops( // Now process the open polylines. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. - chain_open_polylines_exact(open_polylines, loops, false); - chain_open_polylines_exact(open_polylines, loops, true); + chain_open_polylines_exact(open_polylines, loops, false); + chain_open_polylines_exact(open_polylines, loops, true); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1361,8 +1514,8 @@ static Polygons make_loops( } #else const double max_gap = 2.; //mm - chain_open_polylines_close_gaps(open_polylines, loops, max_gap, false); - chain_open_polylines_close_gaps(open_polylines, loops, max_gap, true); + chain_open_polylines_close_gaps(open_polylines, loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, loops, max_gap, true); #endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -1384,14 +1537,17 @@ static Polygons make_loops( return loops; } -template -static std::vector make_loops( +template +static std::vector::type> make_loops( // Lines will have their flags modified. std::vector &lines, const MeshSlicingParams ¶ms, ThrowOnCancel throw_on_cancel) { - std::vector layers; + using PolygonsType = typename PolygonsType::type; + using PolygonType = typename PolygonsType::value_type; + + std::vector layers; layers.resize(lines.size()); tbb::parallel_for( tbb::blocked_range(0, lines.size()), @@ -1400,31 +1556,33 @@ static std::vector make_loops( if ((line_idx & 0x0ffff) == 0) throw_on_cancel(); - Polygons &polygons = layers[line_idx]; - polygons = make_loops(lines[line_idx]); + PolygonsType &polygons = layers[line_idx]; + polygons = make_loops(lines[line_idx]); auto this_mode = line_idx < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; if (! polygons.empty()) { if (this_mode == MeshSlicingParams::SlicingMode::Positive) { // Reorient all loops to be CCW. - for (Polygon& p : polygons) + for (PolygonType &p : polygons) { p.make_counter_clockwise(); - } - else if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) { + } + } else if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) { // Keep just the largest polygon, make it CCW. - double max_area = 0.; - Polygon* max_area_polygon = nullptr; - for (Polygon& p : polygons) { - double a = p.area(); - if (std::abs(a) > std::abs(max_area)) { - max_area = a; + double max_area = 0.; + PolygonType *max_area_polygon = nullptr; + for (PolygonType &p : polygons) { + if (const double a = p.area(); std::abs(a) > std::abs(max_area)) { + max_area = a; max_area_polygon = &p; } } + assert(max_area_polygon != nullptr); - if (max_area < 0.) + if (max_area < 0.) { max_area_polygon->reverse(); - Polygon p(std::move(*max_area_polygon)); + } + + PolygonType p(std::move(*max_area_polygon)); polygons.clear(); polygons.emplace_back(std::move(p)); } @@ -1530,7 +1688,7 @@ static std::vector make_slab_loops( #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Polygons &loops = layers[line_idx]; std::vector open_polylines; - chain_lines_by_triangle_connectivity(in, loops, open_polylines); + chain_lines_by_triangle_connectivity(in, loops, open_polylines); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { SVG svg(debug_out_path("make_slab_loops-out-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg); @@ -1570,7 +1728,7 @@ static ExPolygons make_expolygons_simple(IntersectionLines &lines) ExPolygons slices; Polygons holes; - for (Polygon &loop : make_loops(lines)) + for (Polygon &loop : make_loops(lines)) if (loop.area() >= 0.) slices.emplace_back(std::move(loop)); else @@ -1741,7 +1899,8 @@ static inline bool is_identity(const Transform3d &trafo) return trafo.matrix() == Transform3d::Identity().matrix(); } -static std::vector transform_mesh_vertices_for_slicing(const indexed_triangle_set &mesh, const Transform3d &trafo) +template +static std::vector transform_mesh_vertices_for_slicing(const typename IndexedTriangleSetType::type &mesh, const Transform3d &trafo) { // Copy and scale vertices in XY, don't scale in Z. // Possibly apply the transformation. @@ -1765,13 +1924,23 @@ static std::vector transform_mesh_vertices_for_slicing(const indexed return out; } -std::vector slice_mesh( - const indexed_triangle_set &mesh, +template +std::vector::type> slice_mesh( + const typename IndexedTriangleSetType::type &mesh, // Unscaled Zs - const std::vector &zs, - const MeshSlicingParams ¶ms, - std::function throw_on_cancel) + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel) { + using PolygonsType = typename PolygonsType::type; + + const FacetColorFunctor facet_color_fn = [&] { + if constexpr (mesh_info == AdditionalMeshInfo::Color) + return FacetColorFunctor(mesh.colors); + else + return FacetColorFunctor(); + }(); + BOOST_LOG_TRIVIAL(debug) << "slice_mesh to polygons"; std::vector lines; @@ -1781,29 +1950,29 @@ std::vector slice_mesh( // Instead of edge identifiers, one shall use a sorted pair of edge vertex indices. // However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have // to make sure that no code relies on it. - std::vector face_edge_ids = its_face_edge_ids(mesh); + std::vector face_edge_ids = its_face_edge_ids(mesh); if (zs.size() <= 1) { // It likely is not worthwile to copy the vertices. Apply the transformation in place. if (is_identity(params.trafo)) { lines = slice_make_lines( mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled(p.x()), scaled(p.y()), p.z()); }, - mesh.indices, face_edge_ids, zs, throw_on_cancel); + mesh.indices, face_edge_ids, facet_color_fn, zs, throw_on_cancel); } else { // Transform the vertices, scale up in XY, not in Z. Transform3f tf = make_trafo_for_slicing(params.trafo); - lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel); + lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, face_edge_ids, facet_color_fn, zs, throw_on_cancel); } } else { // Copy and scale vertices in XY, don't scale in Z. Possibly apply the transformation. lines = slice_make_lines( - transform_mesh_vertices_for_slicing(mesh, params.trafo), - [](const Vec3f &p) { return p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel); + transform_mesh_vertices_for_slicing(mesh, params.trafo), + [](const Vec3f &p) { return p; }, mesh.indices, face_edge_ids, facet_color_fn, zs, throw_on_cancel); } } throw_on_cancel(); - std::vector layers = make_loops(lines, params, throw_on_cancel); + std::vector layers = make_loops(lines, params, throw_on_cancel); #ifdef SLIC3R_DEBUG { @@ -1841,13 +2010,43 @@ std::vector slice_mesh( return layers; } -// Specialized version for a single slicing plane only, running on a single thread. -Polygons slice_mesh( +std::vector slice_mesh( const indexed_triangle_set &mesh, // Unscaled Zs - const float plane_z, - const MeshSlicingParams ¶ms) + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel) { + return slice_mesh(mesh, zs, params, throw_on_cancel); +} + +std::vector slice_mesh( + const indexed_triangle_set_with_color &mesh, + // Unscaled Zs + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel) +{ + return slice_mesh(mesh, zs, params, throw_on_cancel); +} + +// Specialized version for a single slicing plane only, running on a single thread. +template +typename PolygonsType::type slice_mesh( + const typename IndexedTriangleSetType::type &mesh, + // Unscaled Zs + const float plane_z, + const MeshSlicingParams ¶ms) +{ + using PolygonsType = typename PolygonsType::type; + + const FacetColorFunctor facet_color_fn = [&] { + if constexpr (mesh_info == AdditionalMeshInfo::Color) + return FacetColorFunctor(mesh.colors); + else + return FacetColorFunctor(); + }(); + std::vector lines; { @@ -1883,27 +2082,45 @@ Polygons slice_mesh( } // 3) Calculate face neighbors for just the faces in face_mask. - std::vector face_edge_ids = its_face_edge_ids(mesh, face_mask); + std::vector face_edge_ids = its_face_edge_ids(mesh, face_mask); // 4) Slice "face_mask" triangles, collect line segments. // It likely is not worthwile to copy the vertices. Apply the transformation in place. if (trafo_identity) { - lines.emplace_back(slice_make_lines( + lines.emplace_back(slice_make_lines( mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled(p.x()), scaled(p.y()), p.z()); }, - mesh.indices, face_edge_ids, plane_z, [&face_mask](int face_idx) { return face_mask[face_idx]; })); + mesh.indices, face_edge_ids, facet_color_fn, plane_z, [&face_mask](int face_idx) { return face_mask[face_idx]; })); } else { // Transform the vertices, scale up in XY, not in Z. - lines.emplace_back(slice_make_lines(mesh.vertices, [tf](const Vec3f& p) { return tf * p; }, mesh.indices, face_edge_ids, plane_z, + lines.emplace_back(slice_make_lines(mesh.vertices, [tf](const Vec3f& p) { return tf * p; }, mesh.indices, face_edge_ids, facet_color_fn, plane_z, [&face_mask](int face_idx) { return face_mask[face_idx]; })); } } // 5) Chain the line segments. - std::vector layers = make_loops(lines, params, [](){}); + std::vector layers = make_loops(lines, params, [](){}); assert(layers.size() == 1); return layers.front(); } +Polygons slice_mesh( + const indexed_triangle_set &mesh, + // Unscaled Zs + const float plane_z, + const MeshSlicingParams ¶ms) +{ + return slice_mesh(mesh, plane_z, params); +} + +ColorPolygons slice_mesh( + const indexed_triangle_set_with_color &mesh, + // Unscaled Zs + const float plane_z, + const MeshSlicingParams ¶ms) +{ + return slice_mesh(mesh, plane_z, params); +} + std::vector slice_mesh_ex( const indexed_triangle_set &mesh, const std::vector &zs, @@ -2213,7 +2430,10 @@ Polygons project_mesh( std::vector top, bottom; std::vector zs { -1e10, 1e10 }; slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, throw_on_cancel); - return union_(top.front(), bottom.back()); + + // We typically perform a union operation on a lot of overlapping polygons, which can be slow in some cases. + // To address this, we use parallel reduction, which can be significantly faster in such cases. + return union_(union_parallel_reduce(top.front()), union_parallel_reduce(bottom.back())); } void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *upper, indexed_triangle_set *lower, bool triangulate_caps) @@ -2268,7 +2488,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u dst.y() = scaled(src.y()); dst.z() = src.z(); } - slice_type = slice_facet(double(z), vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line); + slice_type = slice_facet(double(z), vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, 0, line); } if (slice_type != FacetSliceType::NoSlice) { diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index c8ae0f4..d00480f 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -16,6 +16,8 @@ struct indexed_triangle_set; namespace Slic3r { +struct indexed_triangle_set_with_color; + struct MeshSlicingParams { enum class SlicingMode : uint32_t { @@ -33,6 +35,9 @@ struct MeshSlicingParams PositiveLargestContour, }; + MeshSlicingParams() = default; + explicit MeshSlicingParams(const Transform3d &trafo) : trafo(trafo) {} + SlicingMode mode { SlicingMode::Regular }; // For vase mode: below this layer a different slicing mode will be used to produce a single contour. // 0 = ignore. @@ -71,12 +76,23 @@ std::vector slice_mesh( const MeshSlicingParams ¶ms, std::function throw_on_cancel = []{}); +std::vector slice_mesh( + const indexed_triangle_set_with_color &mesh, + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel = []{}); + // Specialized version for a single slicing plane only, running on a single thread. Polygons slice_mesh( const indexed_triangle_set &mesh, - const float plane_z, + float plane_z, const MeshSlicingParams ¶ms); +ColorPolygons slice_mesh( + const indexed_triangle_set_with_color &mesh, + float plane_z, + const MeshSlicingParams ¶ms); + std::vector slice_mesh_ex( const indexed_triangle_set &mesh, const std::vector &zs, diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 019a1f5..ff6d54b 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -237,27 +237,21 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) { - assert(facet_start < m_orig_size_indices); + assert(this->is_original_triangle(facet_start)); // Save current cursor center, squared radius and camera direction, so we don't // have to pass it around. m_cursor = std::move(cursor); - // In case user changed cursor size since last time, update triangle edge limit. - // It is necessary to compare the internal radius in m_cursor! radius is in - // world coords and does not change after scaling. - if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) { - set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f); - m_old_cursor_radius_sqr = m_cursor->radius_sqr; - } + // In case user changed cursor parameters size since last time, update triangle edge limit. + set_edge_limit(m_cursor->get_edge_limit()); const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_check; + std::vector facets_to_check = m_cursor->get_facets_to_select(facet_start, m_vertices, m_triangles, m_orig_size_vertices, m_orig_size_indices); facets_to_check.reserve(16); - facets_to_check.emplace_back(facet_start); // Keep track of facets of the original mesh we already processed. std::vector visited(m_orig_size_indices, false); // Breadth-first search around the hit point. facets_to_check may grow significantly large. @@ -288,14 +282,28 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp) return false; } +bool TriangleSelector::is_any_neighbor_selected_by_seed_fill(const Triangle &triangle) { + size_t triangle_idx = &triangle - m_triangles.data(); + assert(triangle_idx < m_triangles.size()); + + for (int neighbor_idx: m_neighbors[triangle_idx]) { + assert(neighbor_idx >= -1); + + if (neighbor_idx >= 0 && m_triangles[neighbor_idx].is_selected_by_seed_fill()) + return true; + } + + return false; +} + void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate, - const ClippingPlane &clp, float seed_fill_angle, float highlight_by_angle_deg, - bool force_reselection) + const ClippingPlane &clp, float seed_fill_angle, float seed_fill_gap_area, + float highlight_by_angle_deg, ForceReselection force_reselection) { - assert(facet_start < m_orig_size_indices); + assert(this->is_original_triangle(facet_start)); // Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active. - if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active()) + if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active()) return; this->seed_fill_unselect_all_triangles(); @@ -304,10 +312,13 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st std::queue facet_queue; facet_queue.push(facet_start); - const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const float facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); + // Facets that need to be checked for gap filling. + std::vector gap_fill_candidate_facets; + // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. while (!facet_queue.empty()) { int current_facet = facet_queue.front(); @@ -319,14 +330,15 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.size())); - if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) + if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) { // Child triangle shares normal with its parent. Select it. facet_queue.push(child); + } } } else m_triangles[current_facet].select_by_seed_fill(); - if (current_facet < m_orig_size_indices) + if (this->is_original_triangle(current_facet)) { // Propagate over the original triangles. for (int neighbor_idx : m_neighbors[current_facet]) { assert(neighbor_idx >= -1); @@ -334,13 +346,102 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st // Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do. const Vec3f &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle]; const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle]; - if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) +if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) { facet_queue.push(neighbor_idx); + } else if (seed_fill_gap_area > 0. && get_triangle_area(m_triangles[neighbor_idx]) <= seed_fill_gap_area) { + gap_fill_candidate_facets.emplace_back(neighbor_idx); + } } } + } } + visited[current_facet] = true; } + + seed_fill_fill_gaps(gap_fill_candidate_facets, seed_fill_gap_area); +} + +void TriangleSelector::seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, const float seed_fill_gap_area) { + std::vector visited(m_triangles.size(), false); + + for (const int starting_facet_idx: gap_fill_candidate_facets) { + const Triangle &starting_facet = m_triangles[starting_facet_idx]; + + // If starting_facet_idx was visited from any facet, then we can skip it. + if (visited[starting_facet_idx]) + continue; + + // In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met. + // But both of those conditions are here to allow more general usage of this method. + if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) { + // Already selected by seed fill or split facet, so no additional actions are required. + visited[starting_facet_idx] = true; + continue; + } else if (!is_any_neighbor_selected_by_seed_fill(starting_facet)) { + // No neighbor triangles are selected by seed fill, so we will skip them for now. + continue; + } + + // Now we have a triangle that has at least one neighbor selected by seed fill. + // So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check + // if the total area of unselected triangles by seed fill meets the threshold. + double total_gap_area = 0.; + std::queue facet_queue; + std::vector gap_facets; + + facet_queue.push(starting_facet_idx); + while (!facet_queue.empty()) { + const int current_facet_idx = facet_queue.front(); + const Triangle ¤t_facet = m_triangles[current_facet_idx]; + facet_queue.pop(); + + if (visited[current_facet_idx]) + continue; + + if (this->is_original_triangle(current_facet_idx)) + total_gap_area += get_triangle_area(current_facet); + + // We exceed maximum gap area. + if (total_gap_area > seed_fill_gap_area) { + // It is necessary to set every facet inside gap_facets unvisited. + // Otherwise, we incorrectly select facets that are in a gap that is bigger + // than seed_fill_gap_area. + for (const int gap_facet_idx : gap_facets) + visited[gap_facet_idx] = false; + + gap_facets.clear(); + break; + } + + if (current_facet.is_split()) { + for (int split_triangle_idx = 0; split_triangle_idx <= current_facet.number_of_split_sides(); ++split_triangle_idx) { + assert(split_triangle_idx < int(current_facet.children.size())); + assert(current_facet.children[split_triangle_idx] < int(m_triangles.size())); + if (int child = current_facet.children[split_triangle_idx]; !visited[child]) + facet_queue.push(child); + } + } else if (total_gap_area < seed_fill_gap_area) { + gap_facets.emplace_back(current_facet_idx); + } + + if (this->is_original_triangle(current_facet_idx)) { + // Propagate over the original triangles. + for (int neighbor_idx: m_neighbors[current_facet_idx]) { + assert(neighbor_idx >= -1); + if (neighbor_idx >= 0 && !visited[neighbor_idx] && !m_triangles[neighbor_idx].is_selected_by_seed_fill()) + facet_queue.push(neighbor_idx); + } + } + + visited[current_facet_idx] = true; + } + + for (int to_seed_idx : gap_facets) + m_triangles[to_seed_idx].select_by_seed_fill(); + + gap_facets.clear(); + } } void TriangleSelector::precompute_all_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_propagated_out) const @@ -447,43 +548,59 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver process_subtriangle(touching.second, Partition::Second); } -void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, bool propagate, bool force_reselection) -{ +// Returns all triangles that are touching the given facet. +std::vector TriangleSelector::get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const { + assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); + assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); + + const Vec3i vertices = { m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2] }; + + std::vector touching_triangles; + append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles); + append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles); + append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles); + + for (int neighbor_idx: neighbors_propagated) { + if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split()) + touching_triangles.emplace_back(neighbor_idx); + } + + return touching_triangles; +} + +void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_start, const ClippingPlane &clp, + float bucket_fill_angle, float bucket_fill_gap_area, + BucketFillPropagate propagate, ForceReselection force_reselection) { int start_facet_idx = select_unsplit_triangle(hit, facet_start); assert(start_facet_idx != -1); // Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active. - if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active())) + if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active())) return; assert(!m_triangles[start_facet_idx].is_split()); TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state(); - this->seed_fill_unselect_all_triangles(); - if (!propagate) { + if (propagate == BucketFillPropagate::NO) { + if (m_triangle_selected_by_seed_fill != -1) + this->seed_fill_unselect_triangle(m_triangle_selected_by_seed_fill); + m_triangles[start_facet_idx].select_by_seed_fill(); + m_triangle_selected_by_seed_fill = start_facet_idx; return; + } else { + m_triangle_selected_by_seed_fill = -1; + this->seed_fill_unselect_all_triangles(); } - auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector { - assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); - assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); - std::vector touching_triangles; - Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]}; - append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles); - append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles); - append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles); - - for (int neighbor_idx : neighbors_propagated) - if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split()) - touching_triangles.emplace_back(neighbor_idx); - - return touching_triangles; - }; + const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON; auto [neighbors, neighbors_propagated] = this->precompute_all_neighbors(); std::vector visited(m_triangles.size(), false); std::queue facet_queue; + // Facets that need to be checked for gap filling. + std::vector gap_fill_candidate_facets; + facet_queue.push(start_facet_idx); while (!facet_queue.empty()) { int current_facet = facet_queue.front(); @@ -493,18 +610,98 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ if (!visited[current_facet]) { m_triangles[current_facet].select_by_seed_fill(); - std::vector touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]); - for(const int tr_idx : touching_triangles) { + std::vector touching_triangles = this->get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]); + for (const int tr_idx: touching_triangles) { if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || is_facet_clipped(tr_idx, clp)) continue; - assert(!m_triangles[tr_idx].is_split()); - facet_queue.push(tr_idx); + // Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do. + const Vec3f &n1 = m_face_normals[m_triangles[tr_idx].source_triangle]; + const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle]; + if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) { + assert(!m_triangles[tr_idx].is_split()); + facet_queue.push(tr_idx); + } else if (bucket_fill_gap_area > 0. && get_triangle_area(m_triangles[tr_idx]) <= bucket_fill_gap_area) { + gap_fill_candidate_facets.emplace_back(tr_idx); + } } } visited[current_facet] = true; } + + bucket_fill_fill_gaps(gap_fill_candidate_facets, bucket_fill_gap_area, start_facet_state, neighbors, neighbors_propagated); +} + +void TriangleSelector::bucket_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, const float bucket_fill_gap_area, + const TriangleStateType start_facet_state, const std::vector &neighbors, + const std::vector &neighbors_propagated) { + std::vector visited(m_triangles.size(), false); + + for (const int starting_facet_idx: gap_fill_candidate_facets) { + const Triangle &starting_facet = m_triangles[starting_facet_idx]; + + // If starting_facet_idx was visited from any facet, then we can skip it. + if (visited[starting_facet_idx]) + continue; + + // In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met. + // But both of those conditions are here to allow more general usage of this method. + if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) { + // Already selected by seed fill or split facet, so no additional actions are required. + visited[starting_facet_idx] = true; + continue; + } + + // In the way how bucket_fill_select_triangles() is implemented, all gap_fill_candidate_facets + // have at least one neighbor selected by seed fill. + // So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check + // if the total area of unselected triangles by seed fill meets the threshold. + double total_gap_area = 0.; + std::queue facet_queue; + std::vector gap_facets; + + facet_queue.push(starting_facet_idx); + while (!facet_queue.empty()) { + const int current_facet_idx = facet_queue.front(); + const Triangle ¤t_facet = m_triangles[current_facet_idx]; + facet_queue.pop(); + + if (visited[current_facet_idx]) + continue; + + total_gap_area += get_triangle_area(current_facet); + + // We exceed maximum gap area. + if (total_gap_area > bucket_fill_gap_area) { + // It is necessary to set every facet inside gap_facets unvisited. + // Otherwise, we incorrectly select facets that are in a gap that is bigger + // than bucket_fill_gap_area. + for (const int gap_facet_idx : gap_facets) + visited[gap_facet_idx] = false; + + gap_facets.clear(); + break; + } + + gap_facets.emplace_back(current_facet_idx); + + std::vector touching_triangles = this->get_all_touching_triangles(current_facet_idx, neighbors[current_facet_idx], neighbors_propagated[current_facet_idx]); + for (const int tr_idx: touching_triangles) { + if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || m_triangles[tr_idx].is_selected_by_seed_fill()) + continue; + + facet_queue.push(tr_idx); + } + + visited[current_facet_idx] = true; + } + + for (int to_seed_idx : gap_facets) + m_triangles[to_seed_idx].select_by_seed_fill(); + + gap_facets.clear(); + } } // Selects either the whole triangle (discarding any children it had), or divides @@ -874,7 +1071,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei if (num_of_inside_vertices == 0 && ! m_cursor->is_pointer_in_triangle(*tr, m_vertices) - && ! m_cursor->is_edge_inside_cursor(*tr, m_vertices)) + && ! m_cursor->is_any_edge_inside_cursor(*tr, m_vertices)) return false; if (num_of_inside_vertices == 3) { @@ -914,7 +1111,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei } void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) { - assert(facet_idx < m_orig_size_indices); + assert(this->is_original_triangle(facet_idx)); undivide_triangle(facet_idx); assert(! m_triangles[facet_idx].is_split()); m_triangles[facet_idx].set_state(state); @@ -945,8 +1142,8 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) // In case the object is non-uniformly scaled, transform the // points to world coords. - if (! m_cursor->uniform_scaling) { - for (size_t i=0; iuse_world_coordinates) { + for (size_t i = 0; i < pts.size(); ++i) { pts_transformed[i] = m_cursor->trafo * (*pts[i]); pts[i] = &pts_transformed[i]; } @@ -991,8 +1188,10 @@ bool TriangleSelector::Cursor::is_facet_visible(const Cursor &cursor, int facet_ { assert(facet_idx < int(face_normals.size())); Vec3f n = face_normals[facet_idx]; - if (!cursor.uniform_scaling) + if (cursor.use_world_coordinates) { n = cursor.trafo_normal * n; + } + return n.dot(cursor.dir) < 0.f; } @@ -1007,16 +1206,22 @@ int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vec return inside; } -// Is any edge inside Sphere cursor? -bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const -{ +inline std::array TriangleSelector::Cursor::transform_triangle(const Triangle &tr, const std::vector &vertices) const { std::array pts; - for (int i = 0; i < 3; ++i) { + for (size_t i = 0; i < 3; ++i) { pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) + if (this->use_world_coordinates) { pts[i] = this->trafo * pts[i]; + } } + return pts; +} + +// Is any edge inside Sphere cursor? +bool TriangleSelector::Sphere::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +{ + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -1027,16 +1232,10 @@ bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const s } // Is edge inside cursor? -bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Circle::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - - const Vec3f &p = this->center; + const std::array pts = this->transform_triangle(tr, vertices); + const Vec3f &p = this->center; for (int side = 0; side < 3; ++side) { const Vec3f &a = pts[side]; const Vec3f &b = pts[side < 2 ? side + 1 : 0]; @@ -1092,42 +1291,43 @@ void TriangleSelector::undivide_triangle(int facet_idx) } } -void TriangleSelector::remove_useless_children(int facet_idx) -{ +// Returns true when some triangle during recursive descending was removed (undivided). +bool TriangleSelector::remove_useless_children(int facet_idx) { // Check that all children are leafs of the same type. If not, try to - // make them (recursive call). Remove them if sucessful. + // make them (recursive call). Remove them if successful. assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid()); - Triangle& tr = m_triangles[facet_idx]; + Triangle &tr = m_triangles[facet_idx]; - if (! tr.is_split()) { + if (!tr.is_split()) { // This is a leaf, there nothing to do. This can happen during the // first (non-recursive call). Shouldn't otherwise. - return; + return false; } // Call this for all non-leaf children. - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + bool children_removed = false; + for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) { assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid()); if (m_triangles[tr.children[child_idx]].is_split()) - remove_useless_children(tr.children[child_idx]); + children_removed |= remove_useless_children(tr.children[child_idx]); } - // Return if a child is not leaf or two children differ in type. TriangleStateType first_child_type = TriangleStateType::NONE; - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) - return; + return children_removed; if (child_idx == 0) first_child_type = m_triangles[tr.children[0]].get_state(); else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) - return; + return children_removed; } // If we got here, the children can be removed. undivide_triangle(facet_idx); tr.set_state(first_child_type); + return true; } void TriangleSelector::garbage_collect() @@ -1212,7 +1412,7 @@ void TriangleSelector::reset() void TriangleSelector::set_edge_limit(float edge_limit) { - m_edge_limit_sqr = std::pow(edge_limit, 2.f); + m_edge_limit_sqr = Slic3r::sqr(edge_limit); } int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) { @@ -1329,13 +1529,16 @@ int TriangleSelector::num_facets(TriangleStateType state) const { return cnt; } -indexed_triangle_set TriangleSelector::get_facets(TriangleStateType state) const { - indexed_triangle_set out; +template +typename IndexedTriangleSetType::type TriangleSelector::get_facets(const std::function &facet_filter) const { + using IndexedTriangleSetType = typename IndexedTriangleSetType::type; + + IndexedTriangleSetType out; std::vector vertex_map(m_vertices.size(), -1); - for (const Triangle& tr : m_triangles) { - if (tr.valid() && ! tr.is_split() && tr.get_state() == state) { + for (const Triangle &tr : m_triangles) { + if (tr.valid() && !tr.is_split() && facet_filter(tr)) { stl_triangle_vertex_indices indices; - for (int i=0; i<3; ++i) { + for (int i = 0; i < 3; ++i) { int j = tr.verts_idxs[i]; if (vertex_map[j] == -1) { vertex_map[j] = int(out.vertices.size()); @@ -1344,55 +1547,105 @@ indexed_triangle_set TriangleSelector::get_facets(TriangleStateType state) const indices[i] = vertex_map[j]; } out.indices.emplace_back(indices); + + if constexpr (facet_info == AdditionalMeshInfo::Color) { + out.colors.emplace_back(static_cast(tr.get_state())); + } } } + return out; } -indexed_triangle_set TriangleSelector::get_facets_strict(TriangleStateType state) const { - indexed_triangle_set out; +indexed_triangle_set TriangleSelector::get_facets(TriangleStateType state) const { + return this->get_facets([state](const Triangle &tr) { return tr.get_state() == state; }); +} + +indexed_triangle_set TriangleSelector::get_all_facets() const { + return this->get_facets([](const Triangle &tr) { return true; }); +} + +indexed_triangle_set_with_color TriangleSelector::get_all_facets_with_colors() const { + return this->get_facets([](const Triangle &tr) { return true; }); +} + +template +typename IndexedTriangleSetType::type TriangleSelector::get_facets_strict(const std::function &facet_filter) const { + using IndexedTriangleSetType = typename IndexedTriangleSetType::type; + + auto get_vertices_count = [&vertices = std::as_const(m_vertices)]() -> size_t { + size_t vertices_cnt = 0; + for (const Vertex &v : vertices) { + if (v.ref_cnt > 0) + ++vertices_cnt; + } + + return vertices_cnt; + }; + + IndexedTriangleSetType out; + out.vertices.reserve(get_vertices_count()); - size_t num_vertices = 0; - for (const Vertex &v : m_vertices) - if (v.ref_cnt > 0) - ++ num_vertices; - out.vertices.reserve(num_vertices); std::vector vertex_map(m_vertices.size(), -1); - for (size_t i = 0; i < m_vertices.size(); ++ i) + for (size_t i = 0; i < m_vertices.size(); ++i) { if (const Vertex &v = m_vertices[i]; v.ref_cnt > 0) { vertex_map[i] = int(out.vertices.size()); out.vertices.emplace_back(v.v); } + } + std::vector out_colors; for (int itriangle = 0; itriangle < m_orig_size_indices; ++ itriangle) - this->get_facets_strict_recursive(m_triangles[itriangle], m_neighbors[itriangle], state, out.indices); + this->get_facets_strict_recursive(m_triangles[itriangle], m_neighbors[itriangle], facet_filter, out.indices, out_colors); - for (auto &triangle : out.indices) - for (int i = 0; i < 3; ++ i) + if constexpr (facet_info == AdditionalMeshInfo::Color) { + out.colors = std::move(out_colors); + } + + for (auto &triangle : out.indices) { + for (int i = 0; i < 3; ++i) { triangle(i) = vertex_map[triangle(i)]; + } + } return out; } +indexed_triangle_set TriangleSelector::get_facets_strict(TriangleStateType state) const { + return this->get_facets_strict([state](const Triangle &tr) { return tr.get_state() == state; }); +} + +indexed_triangle_set TriangleSelector::get_all_facets_strict() const { + return this->get_facets_strict([](const Triangle &tr) { return true; }); +} + +indexed_triangle_set_with_color TriangleSelector::get_all_facets_strict_with_colors() const { + return this->get_facets_strict([](const Triangle &tr) { return true; }); +} + +template void TriangleSelector::get_facets_strict_recursive( const Triangle &tr, const Vec3i &neighbors, - TriangleStateType state, - std::vector &out_triangles) const + const std::function &facet_filter, + std::vector &out_triangles, + std::vector &out_colors) const { if (tr.is_split()) { for (int i = 0; i <= tr.number_of_split_sides(); ++ i) - this->get_facets_strict_recursive( + this->get_facets_strict_recursive( m_triangles[tr.children[i]], this->child_neighbors(tr, neighbors, i), - state, out_triangles); - } else if (tr.get_state() == state) - this->get_facets_split_by_tjoints({tr.verts_idxs[0], tr.verts_idxs[1], tr.verts_idxs[2]}, neighbors, out_triangles); + facet_filter, out_triangles, out_colors); + } else if (facet_filter(tr)) { + const uint8_t facet_color = static_cast(tr.get_state()); + this->get_facets_split_by_tjoints({tr.verts_idxs[0], tr.verts_idxs[1], tr.verts_idxs[2]}, neighbors, facet_color, out_triangles, out_colors); + } } -void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector &out_triangles) const -{ -// Export this triangle, but first collect the T-joint vertices along its edges. +template +void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, const uint8_t color, std::vector &out_triangles, std::vector &out_colors) const { + // Export this triangle, but first collect the T-joint vertices along its edges. Vec3i midpoints( this->triangle_midpoint(neighbors(0), vertices(1), vertices(0)), this->triangle_midpoint(neighbors(1), vertices(2), vertices(1)), @@ -1402,6 +1655,11 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const case 0: // Just emit this triangle. out_triangles.emplace_back(vertices(0), vertices(1), vertices(2)); + + if constexpr (facet_info == AdditionalMeshInfo::Color) { + out_colors.emplace_back(color); + } + break; case 1: { @@ -1409,18 +1667,18 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const int i = midpoints(0) != -1 ? 2 : midpoints(1) != -1 ? 0 : 1; int j = next_idx_modulo(i, 3); int k = next_idx_modulo(j, 3); - this->get_facets_split_by_tjoints( + this->get_facets_split_by_tjoints( { vertices(i), vertices(j), midpoints(j) }, { neighbors(i), this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::Second), -1 }, - out_triangles); - this->get_facets_split_by_tjoints( + color, out_triangles, out_colors); + this->get_facets_split_by_tjoints( { midpoints(j), vertices(k), vertices(i) }, { this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::First), neighbors(k), -1 }, - out_triangles); + color, out_triangles, out_colors); break; } case 2: @@ -1429,47 +1687,53 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const int i = midpoints(0) == -1 ? 2 : midpoints(1) == -1 ? 0 : 1; int j = next_idx_modulo(i, 3); int k = next_idx_modulo(j, 3); - this->get_facets_split_by_tjoints( + this->get_facets_split_by_tjoints( { vertices(i), midpoints(i), midpoints(k) }, { this->neighbor_child(neighbors(i), vertices(j), vertices(i), Partition::Second), -1, this->neighbor_child(neighbors(k), vertices(i), vertices(k), Partition::First) }, - out_triangles); - this->get_facets_split_by_tjoints( + color, out_triangles, out_colors); + this->get_facets_split_by_tjoints( { midpoints(i), vertices(j), midpoints(k) }, { this->neighbor_child(neighbors(i), vertices(j), vertices(i), Partition::First), -1, -1 }, - out_triangles); - this->get_facets_split_by_tjoints( + color, out_triangles, out_colors); + this->get_facets_split_by_tjoints( { vertices(j), vertices(k), midpoints(k) }, { neighbors(j), this->neighbor_child(neighbors(k), vertices(i), vertices(k), Partition::Second), -1 }, - out_triangles); + color, out_triangles, out_colors); break; } default: assert(splits == 3); // Split to 4 triangles. - this->get_facets_split_by_tjoints( + this->get_facets_split_by_tjoints( { vertices(0), midpoints(0), midpoints(2) }, { this->neighbor_child(neighbors(0), vertices(1), vertices(0), Partition::Second), -1, this->neighbor_child(neighbors(2), vertices(0), vertices(2), Partition::First) }, - out_triangles); - this->get_facets_split_by_tjoints( + color, out_triangles, out_colors); + this->get_facets_split_by_tjoints( { midpoints(0), vertices(1), midpoints(1) }, { this->neighbor_child(neighbors(0), vertices(1), vertices(0), Partition::First), this->neighbor_child(neighbors(1), vertices(2), vertices(1), Partition::Second), -1 }, - out_triangles); - this->get_facets_split_by_tjoints( + color, out_triangles, out_colors); + this->get_facets_split_by_tjoints( { midpoints(1), vertices(2), midpoints(2) }, { this->neighbor_child(neighbors(1), vertices(2), vertices(1), Partition::First), this->neighbor_child(neighbors(2), vertices(0), vertices(2), Partition::Second), -1 }, - out_triangles); + color, out_triangles, out_colors); + out_triangles.emplace_back(midpoints); + + if constexpr (facet_info == AdditionalMeshInfo::Color) { + out_colors.emplace_back(color); + } + break; } } @@ -1590,6 +1854,9 @@ TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const { out.data.triangles_to_split.emplace_back(i, int(out.data.bitstream.size())); // out the triangle bits. out.serialize(i); + } else if (!tr.is_split()) { + assert(tr.get_state() == TriangleStateType::NONE); + out.data.used_states[static_cast(TriangleStateType::NONE)] = true; } // May be stored onto Undo / Redo stack, thus conserve memory. @@ -1789,7 +2056,22 @@ void TriangleSelector::seed_fill_unselect_all_triangles() triangle.unselect_by_seed_fill(); } +void TriangleSelector::seed_fill_unselect_triangle(const int facet_idx) { + assert(facet_idx > 0 && facet_idx < m_triangles.size()); + Triangle &triangle = m_triangles[facet_idx]; + + assert(!triangle.is_split()); + if (!triangle.is_split()) + triangle.unselect_by_seed_fill(); +} + void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) { + if (m_triangle_selected_by_seed_fill != -1) { + this->seed_fill_apply_on_single_triangle(new_state, m_triangle_selected_by_seed_fill); + m_triangle_selected_by_seed_fill = -1; + return; + } + for (Triangle &triangle : m_triangles) if (!triangle.is_split() && triangle.is_selected_by_seed_fill()) triangle.set_state(new_state); @@ -1801,24 +2083,42 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) } } +void TriangleSelector::seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx) { + assert(facet_idx > 0 && facet_idx < m_triangles.size()); + + if (Triangle &triangle = m_triangles[facet_idx]; !triangle.is_split() && triangle.is_selected_by_seed_fill()) { + triangle.set_state(new_state); + remove_useless_children(triangle.source_triangle); + } +} + +double TriangleSelector::get_triangle_area(const Triangle &triangle) const { + const stl_vertex &v0 = m_vertices[triangle.verts_idxs[0]].v; + const stl_vertex &v1 = m_vertices[triangle.verts_idxs[1]].v; + const stl_vertex &v2 = m_vertices[triangle.verts_idxs[2]].v; + return (v1 - v0).cross(v2 - v0).norm() / 2.; +} + TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) : source{source_}, trafo{trafo_.cast()}, clipping_plane{clipping_plane_} { Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor(); - if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) { - radius = float(radius_world / sf(0)); - radius_sqr = float(Slic3r::sqr(radius_world / sf(0))); - uniform_scaling = true; + if (is_approx(sf.x(), sf.y()) && is_approx(sf.y(), sf.z())) { + radius = float(radius_world / sf.x()); + radius_sqr = float(Slic3r::sqr(radius_world / sf.x())); + use_world_coordinates = false; } else { // In case that the transformation is non-uniform, all checks whether // something is inside the cursor should be done in world coords. // First transform source in world coords and remember that we did this. - source = trafo * source; - uniform_scaling = false; - radius = radius_world; - radius_sqr = Slic3r::sqr(radius_world); - trafo_normal = trafo.linear().inverse().transpose(); + source = trafo * source; + use_world_coordinates = true; + radius = radius_world; + radius_sqr = Slic3r::sqr(radius_world); + trafo_normal = trafo.linear().inverse().transpose(); } + + m_edge_limit = std::sqrt(radius_sqr) / 5.f; } TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_) @@ -1827,8 +2127,9 @@ TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, con // In case that the transformation is non-uniform, all checks whether // something is inside the cursor should be done in world coords. // Because of the center is transformed. - if (!uniform_scaling) + if (use_world_coordinates) { center = trafo * center; + } // Calculate dir, in whatever coords is appropriate. dir = (center - source).normalized(); @@ -1837,7 +2138,7 @@ TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, con TriangleSelector::DoublePointCursor::DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) : first_center{first_center_}, second_center{second_center_}, Cursor(source_, radius_world, trafo_, clipping_plane_) { - if (!uniform_scaling) { + if (use_world_coordinates) { first_center = trafo * first_center_; second_center = trafo * second_center_; } @@ -1855,7 +2156,7 @@ inline static bool is_mesh_point_not_clipped(const Vec3f &point, const TriangleS // Is a point (in mesh coords) inside a Sphere cursor? bool TriangleSelector::Sphere::is_mesh_point_inside(const Vec3f &point) const { - const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point); + const Vec3f transformed_point = use_world_coordinates ? Vec3f(trafo * point) : point; if ((center - transformed_point).squaredNorm() < radius_sqr) return is_mesh_point_not_clipped(point, clipping_plane); @@ -1865,7 +2166,7 @@ bool TriangleSelector::Sphere::is_mesh_point_inside(const Vec3f &point) const // Is a point (in mesh coords) inside a Circle cursor? bool TriangleSelector::Circle::is_mesh_point_inside(const Vec3f &point) const { - const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point); + const Vec3f transformed_point = use_world_coordinates ? Vec3f(trafo * point) : point; const Vec3f diff = center - transformed_point; if ((diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr) @@ -1877,7 +2178,7 @@ bool TriangleSelector::Circle::is_mesh_point_inside(const Vec3f &point) const // Is a point (in mesh coords) inside a Capsule3D cursor? bool TriangleSelector::Capsule3D::is_mesh_point_inside(const Vec3f &point) const { - const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point); + const Vec3f transformed_point = use_world_coordinates ? Vec3f(trafo * point) : point; const Vec3f first_center_diff = this->first_center - transformed_point; const Vec3f second_center_diff = this->second_center - transformed_point; if (first_center_diff.squaredNorm() < this->radius_sqr || second_center_diff.squaredNorm() < this->radius_sqr) @@ -1895,7 +2196,7 @@ bool TriangleSelector::Capsule3D::is_mesh_point_inside(const Vec3f &point) const // Is a point (in mesh coords) inside a Capsule2D cursor? bool TriangleSelector::Capsule2D::is_mesh_point_inside(const Vec3f &point) const { - const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point); + const Vec3f transformed_point = use_world_coordinates ? Vec3f(trafo * point) : point; const Vec3f first_center_diff = this->first_center - transformed_point; const Vec3f first_center_diff_projected = first_center_diff - first_center_diff.dot(this->dir) * this->dir; if (first_center_diff_projected.squaredNorm() < this->radius_sqr) @@ -1926,7 +2227,7 @@ bool TriangleSelector::Capsule2D::is_mesh_point_inside(const Vec3f &point) const } // p1, p2, p3 are in mesh coords! -static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_, const Vec3f ¢er, const Vec3f &dir, const bool uniform_scaling, const Transform3f &trafo) { +static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_, const Vec3f ¢er, const Vec3f &dir, const bool use_world_coordinates, const Transform3f &trafo) { const Vec3f& q1 = center + dir; const Vec3f& q2 = center - dir; @@ -1936,9 +2237,9 @@ static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_ }; // In case the object is non-uniformly scaled, do the check in world coords. - const Vec3f& p1 = uniform_scaling ? p1_ : Vec3f(trafo * p1_); - const Vec3f& p2 = uniform_scaling ? p2_ : Vec3f(trafo * p2_); - const Vec3f& p3 = uniform_scaling ? p3_ : Vec3f(trafo * p3_); + const Vec3f& p1 = use_world_coordinates ? Vec3f(trafo * p1_) : p1_; + const Vec3f& p2 = use_world_coordinates ? Vec3f(trafo * p2_) : p2_; + const Vec3f& p3 = use_world_coordinates ? Vec3f(trafo * p3_) : p3_; if (signed_volume_sign(q1,p1,p2,p3) == signed_volume_sign(q2,p1,p2,p3)) return false; @@ -1950,14 +2251,14 @@ static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_ // p1, p2, p3 are in mesh coords! bool TriangleSelector::SinglePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const { - return is_circle_pointer_inside_triangle(p1_, p2_, p3_, center, dir, uniform_scaling, trafo); + return is_circle_pointer_inside_triangle(p1_, p2_, p3_, center, dir, use_world_coordinates, trafo); } // p1, p2, p3 are in mesh coords! bool TriangleSelector::DoublePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const { - return is_circle_pointer_inside_triangle(p1_, p2_, p3_, first_center, dir, uniform_scaling, trafo) || - is_circle_pointer_inside_triangle(p1_, p2_, p3_, second_center, dir, uniform_scaling, trafo); + return is_circle_pointer_inside_triangle(p1_, p2_, p3_, first_center, dir, use_world_coordinates, trafo) || + is_circle_pointer_inside_triangle(p1_, p2_, p3_, second_center, dir, use_world_coordinates, trafo); } bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &plane_origin, const Vec3f &plane_normal, Vec3f &out_intersection) @@ -1977,15 +2278,9 @@ bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec return false; } -bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Capsule3D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -1997,15 +2292,8 @@ bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, cons } // Is edge inside cursor? -bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - const Vec3f centers_diff = this->second_center - this->first_center; // Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center. const Vec3f rectangle_da_dir = centers_diff.cross(this->dir); @@ -2024,6 +2312,7 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons return false; }; + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -2051,4 +2340,59 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons return false; } +TriangleSelector::HeightRange::HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane) + : Cursor(Vec3f::Zero(), 0.f, trafo, clipping_plane) { + const Vec3f mesh_hit_world = (trafo * mesh_hit.cast()).cast(); + + m_z_range_top = mesh_hit_world.z() + z_range / 2.f; + m_z_range_bottom = mesh_hit_world.z() - z_range / 2.f; + m_edge_limit = 0.1f; + + // Always calculate HeightRange in world coordinates. + use_world_coordinates = true; +} + +bool TriangleSelector::HeightRange::is_mesh_point_inside(const Vec3f &point) const { + const float transformed_point_z = Vec3f(this->trafo * point).z(); + return Slic3r::is_in_range(transformed_point_z, m_z_range_bottom, m_z_range_top); +} + +bool TriangleSelector::HeightRange::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { + const std::array pts = this->transform_triangle(tr, vertices); + // If all vertices are below m_z_range_bottom or all vertices are above m_z_range_top, then it means that no edge + // is inside the height range. Otherwise, there is at least one edge inside the height range. + return !((pts[0].z() < m_z_range_bottom && pts[1].z() < m_z_range_bottom && pts[2].z() < m_z_range_bottom) || + (pts[0].z() > m_z_range_top && pts[1].z() > m_z_range_top && pts[2].z() > m_z_range_top)); +} + +std::vector TriangleSelector::HeightRange::get_facets_to_select(const int facet_idx, const std::vector &vertices, const std::vector &triangles, const int orig_size_vertices, const int orig_size_indices) const { + std::vector facets_to_check; + + // Assigns each vertex a value of -1, 1, or 0. The value -1 indicates a vertex is below m_z_range_bottom, + // while 1 indicates a vertex is above m_z_range_top. The value of 0 indicates that the vertex between + // m_z_range_bottom and m_z_range_top. + std::vector vertex_side(orig_size_vertices, 0); + if (trafo.matrix() == Transform3f::Identity().matrix()) { + for (int i = 0; i < orig_size_vertices; ++i) { + const float z = vertices[i].v.z(); + vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0); + } + } else { + for (int i = 0; i < orig_size_vertices; ++i) { + const float z = Vec3f(this->trafo * vertices[i].v).z(); + vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0); + } + } + + // Determine if each triangle crosses m_z_range_bottom or m_z_range_top. + for (int i = 0; i < orig_size_indices; ++i) { + const std::array &face = triangles[i].verts_idxs; + const std::array sides = { vertex_side[face[0]], vertex_side[face[1]], vertex_side[face[2]] }; + if ((sides[0] * sides[1] <= 0) || (sides[1] * sides[2] <= 0) || (sides[0] * sides[2] <= 0)) + facets_to_check.emplace_back(i); + } + + return facets_to_check; +} + } // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index e59d3a5..c2a5430 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -33,6 +33,8 @@ enum class TriangleStateType : int8_t { NONE = 0, ENFORCER = 1, BLOCKER = 2, + // For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN). + FUZZY_SKIN = ENFORCER, // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. Extruder1 = ENFORCER, Extruder2 = BLOCKER, @@ -61,10 +63,21 @@ protected: struct Vertex; public: - enum CursorType { + enum class CursorType { CIRCLE, SPHERE, - POINTER + POINTER, + HEIGHT_RANGE + }; + + enum class ForceReselection { + NO, + YES + }; + + enum class BucketFillPropagate { + NO, + YES }; struct ClippingPlane @@ -85,14 +98,21 @@ public: Cursor() = delete; virtual ~Cursor() = default; + float get_edge_limit() { return m_edge_limit; }; + bool is_pointer_in_triangle(const Triangle &tr, const std::vector &vertices) const; + std::array transform_triangle(const Triangle &tr, const std::vector &vertices) const; virtual bool is_mesh_point_inside(const Vec3f &point) const = 0; virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0; virtual int vertices_inside(const Triangle &tr, const std::vector &vertices) const; - virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; + virtual bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; virtual bool is_facet_visible(int facet_idx, const std::vector &face_normals) const = 0; + virtual std::vector get_facets_to_select(int facet_idx, const std::vector &vertices, const std::vector &triangles, int orig_size_vertices, int orig_size_indices) const { + return { facet_idx }; + }; + static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector &face_normals); protected: @@ -101,7 +121,7 @@ public: Transform3f trafo; Vec3f source; - bool uniform_scaling; + bool use_world_coordinates; Transform3f trafo_normal; float radius; float radius_sqr; @@ -109,6 +129,8 @@ public: ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only + float m_edge_limit; + friend TriangleSelector; }; @@ -168,7 +190,7 @@ public: ~Sphere() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } }; @@ -181,9 +203,8 @@ public: ~Circle() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; - bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override - { + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); } }; @@ -198,7 +219,7 @@ public: ~Capsule3D() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } }; @@ -212,13 +233,32 @@ public: ~Capsule2D() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; - bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override - { + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); } }; + class HeightRange : public Cursor + { + public: + HeightRange() = delete; + + explicit HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane); + ~HeightRange() override = default; + + bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override { return false; } + bool is_mesh_point_inside(const Vec3f &point) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } + + std::vector get_facets_to_select(int facet_idx, const std::vector &vertices, const std::vector &triangles, int orig_size_vertices, int orig_size_indices) const override; + + private: + float m_z_range_top; + float m_z_range_bottom; + }; + struct TriangleBitStreamMapping { // Index of the triangle to which we assign the bitstream containing splitting information. @@ -292,27 +332,46 @@ public: bool triangle_splitting, // If triangles will be split base on the cursor or not float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - void seed_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - float seed_fill_angle, // the maximal angle between two facets to be painted by the same color - float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void seed_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float seed_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle - void bucket_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void bucket_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + float bucket_fill_angle, // the maximal angle between two facets to be painted by the same color + float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + BucketFillPropagate propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. + ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle bool has_facets(TriangleStateType state) const; static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state); int num_facets(TriangleStateType state) const; + // Get facets that pass the filter. Don't triangulate T-joints. + template + typename IndexedTriangleSetType::type get_facets(const std::function &facet_filter) const; // Get facets at a given state. Don't triangulate T-joints. indexed_triangle_set get_facets(TriangleStateType state) const; + // Get all facets. Don't triangulate T-joints. + indexed_triangle_set get_all_facets() const; + // Get all facets with information about the colors of the facets. Don't triangulate T-joints. + indexed_triangle_set_with_color get_all_facets_with_colors() const; + + // Get facets that pass the filter. Triangulate T-joints. + template + typename IndexedTriangleSetType::type get_facets_strict(const std::function &facet_filter) const; // Get facets at a given state. Triangulate T-joints. indexed_triangle_set get_facets_strict(TriangleStateType state) const; + // Get all facets. Triangulate T-joints. + indexed_triangle_set get_all_facets_strict() const; + // Get all facets with information about the colord of the facetd. Triangulate T-joints. + indexed_triangle_set_with_color get_all_facets_strict_with_colors() const; + // Get edges around the selected area by seed fill. std::vector get_seed_fill_contour() const; @@ -338,10 +397,20 @@ public: // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles(); + // Remove the flag indicating that the triangle was selected by seed fill. + void seed_fill_unselect_triangle(int facet_idx); + // For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill. // The operation may merge split triangles if they are being assigned the same color. void seed_fill_apply_on_triangles(TriangleStateType new_state); + // For the triangle selected by seed fill, set a new TriangleStateType and remove the flag indicating that the triangle was selected by seed fill. + // The operation may merge a split triangle if it is being assigned the same color. + void seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx); + + // Compute total area of the triangle. + double get_triangle_area(const Triangle &triangle) const; + protected: // Triangle and info about how it's split. class Triangle { @@ -428,8 +497,9 @@ protected: int m_orig_size_indices = 0; std::unique_ptr m_cursor; - // Zero indicates an uninitialized state. - float m_old_cursor_radius_sqr = 0; + + // Single triangle selected by seed fill. It is used to optimize painting using a single triangle brush. + int m_triangle_selected_by_seed_fill = -1; // Private functions: private: @@ -437,7 +507,7 @@ private: bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting); void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i &neighbors); - void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const; int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE); void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state); @@ -461,20 +531,41 @@ private: void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector &touching_subtriangles_out) const; void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector &touching_edges_out) const; + // Returns all triangles that are touching the given facet. + std::vector get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const; + + // Check if the triangle index is the original triangle from mesh, or it was additionally created by splitting. + bool is_original_triangle(int triangle_idx) const { return triangle_idx < m_orig_size_indices; } + #ifndef NDEBUG bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const; bool verify_triangle_midpoints(const Triangle& tr) const; #endif // NDEBUG + template void get_facets_strict_recursive( const Triangle &tr, const Vec3i &neighbors, - TriangleStateType state, - std::vector &out_triangles) const; - void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector &out_triangles) const; + const std::function &facet_filter, + std::vector &out_triangles, + std::vector &out_colors) const; + + template + void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, uint8_t color, std::vector &out_triangles, std::vector &out_colors) const; void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &edges_out) const; + bool is_any_neighbor_selected_by_seed_fill(const Triangle &triangle); + + void seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the original mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). + float seed_fill_gap_area); // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + + void bucket_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). + float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + TriangleStateType start_facet_state, // The state of the starting facet that determines which neighbors to consider. + const std::vector &neighbors, + const std::vector &neighbors_propagate); + int m_free_triangles_head { -1 }; int m_free_vertices_head { -1 }; }; diff --git a/src/libslic3r/Utils/DirectoriesUtils.cpp b/src/libslic3r/Utils/DirectoriesUtils.cpp index 3c3f07a..6842573 100644 --- a/src/libslic3r/Utils/DirectoriesUtils.cpp +++ b/src/libslic3r/Utils/DirectoriesUtils.cpp @@ -51,34 +51,61 @@ static std::string GetDataDir() #include #include -static std::string GetDataDir() -{ - std::string dir; +std::optional get_env(std::string_view key) { + const char* result{getenv(key.data())}; + if(result == nullptr) { + return std::nullopt; + } + return std::string{result}; +} - char* ptr; - if ((ptr = getenv("XDG_CONFIG_HOME"))) - dir = std::string(ptr); - else { - if ((ptr = getenv("HOME"))) - dir = std::string(ptr); - else { - struct passwd* who = (struct passwd*)NULL; - if ((ptr = getenv("USER")) || (ptr = getenv("LOGNAME"))) - who = getpwnam(ptr); - // make sure the user exists! - if (!who) - who = getpwuid(getuid()); - - dir = std::string(who ? who->pw_dir : 0); +namespace { +std::optional get_home_dir(const std::string& subfolder) { + if (auto result{get_env("HOME")}) { + return *result + subfolder; + } else { + std::optional user_name{get_env("USER")}; + if (!user_name) { + user_name = get_env("LOGNAME"); } - if (! dir.empty()) - dir += "/.config"; + struct passwd* who{ + user_name ? + getpwnam(user_name->data()) : + (struct passwd*)NULL + }; + // make sure the user exists! + if (!who) { + who = getpwuid(getuid()); + } + if (who) { + return std::string{who->pw_dir} + subfolder; + } + } + return std::nullopt; +} +} + +namespace Slic3r { +std::optional get_home_config_dir() { + return get_home_dir("/.config"); +} + +std::optional get_home_local_dir() { + return get_home_dir("/.local"); +} +} + +std::string GetDataDir() +{ + if (auto result{get_env("XDG_CONFIG_HOME")}) { + return *result; + } else if (auto result{Slic3r::get_home_config_dir()}) { + return result->string(); } - if (dir.empty()) - BOOST_LOG_TRIVIAL(error) << "GetDataDir() > unsupported file layout"; + BOOST_LOG_TRIVIAL(error) << "GetDataDir() > unsupported file layout"; - return dir; + return {}; } #endif @@ -88,7 +115,7 @@ namespace Slic3r { std::string get_default_datadir() { const std::string config_dir = GetDataDir(); - const std::string data_dir = (boost::filesystem::path(config_dir) / SLIC3R_APP_FULL_NAME).make_preferred().string(); + std::string data_dir = (boost::filesystem::path(config_dir) / SLIC3R_APP_FULL_NAME).make_preferred().string(); return data_dir; } diff --git a/src/libslic3r/Utils/DirectoriesUtils.hpp b/src/libslic3r/Utils/DirectoriesUtils.hpp index cafc054..81dccd6 100644 --- a/src/libslic3r/Utils/DirectoriesUtils.hpp +++ b/src/libslic3r/Utils/DirectoriesUtils.hpp @@ -2,6 +2,8 @@ #define slic3r_DirectoriesUtils_hpp_ #include +#include +#include #if __APPLE__ //implemented at MacUtils.mm @@ -10,6 +12,10 @@ std::string GetDataDir(); namespace Slic3r { +// Only defined on linux. +std::optional get_home_config_dir(); +std::optional get_home_local_dir(); + std::string get_default_datadir(); } // namespace Slic3r diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 19f08b6..d41e773 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -4,9 +4,9 @@ #include "libslic3r_version.h" // Profiles for the alpha are stored into the QIDISlicer-alpha directory to not mix with the current release. -#define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY -//#define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY "-alpha" -//#define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY "-beta" + #define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY +// #define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY "-alpha" +// #define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY "-beta" #define GCODEVIEWER_APP_NAME "QIDISlicer G-code Viewer" #define GCODEVIEWER_APP_KEY "QIDISlicerGcodeViewer" @@ -102,6 +102,8 @@ using deque = template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } +constexpr size_t MAX_NUMBER_OF_BEDS = 9; + enum Axis { X=0, Y, @@ -474,6 +476,11 @@ Fn for_each_in_tuple(Fn fn, Tup &&tup) return fn; } +template +inline bool is_in_range(const T &value, const T &low, const T &high) { + return low <= value && value <= high; +} + } // namespace Slic3r #endif // _libslic3r_h_