update libslic3r

This commit is contained in:
QIDI TECH
2025-02-08 16:06:54 +08:00
parent 629a8a4ec7
commit e0c8038c88
150 changed files with 7190 additions and 11691 deletions

View File

@@ -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)
{

View File

@@ -0,0 +1,574 @@
#include <cassert>
#include <limits>
#include <vector>
#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<coord_t>(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<LineRegionRange>;
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<false>(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<double>();
const Vec2d query_vec = (query_pt - line_from_pt).template cast<double>();
const double line_length_sqr = line_vec.squaredNorm();
if (line_length_sqr <= 0.) {
return { std::numeric_limits<double>::max(), std::numeric_limits<double>::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<double>::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<double>::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<LineRegionRange> 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<double>().squaredNorm();
const double curr_dist = (curr_pt - subject_pt).cast<double>().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<double>::max() || end_t == std::numeric_limits<double>::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<LineRegionRange> 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> &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 &region_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 &region_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> &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> &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> &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<ExPolygons> to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips)
{
std::vector<ExPolygons> 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

View File

@@ -0,0 +1,69 @@
#ifndef libslic3r_LineSegmentation_hpp_
#define libslic3r_LineSegmentation_hpp_
#include <vector>
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
namespace Slic3r {
class ExPolygon;
class Polyline;
class Polygon;
class PrintRegionConfig;
struct PerimeterRegion;
using ExPolygons = std::vector<ExPolygon>;
using PerimeterRegions = std::vector<PerimeterRegion>;
} // 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<PolylineSegment>;
using ExtrusionSegments = std::vector<ExtrusionSegment>;
using PolylineRegionSegments = std::vector<PolylineRegionSegment>;
using ExtrusionRegionSegments = std::vector<ExtrusionRegionSegment>;
PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector<ExPolygons> &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_

View File

@@ -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");

View File

@@ -32,9 +32,6 @@ struct PerimeterExtrusion
size_t depth = std::numeric_limits<size_t>::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(); }

View File

@@ -55,7 +55,8 @@ inline const Point& make_point(const ExtrusionJunction& ej)
return ej.p;
}
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
using ExtrusionJunctions = std::vector<ExtrusionJunction>;
}
#endif // UTILS_EXTRUSION_JUNCTION_H

View File

@@ -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())

View File

@@ -81,7 +81,8 @@ struct ExtrusionLine
*/
std::vector<ExtrusionJunction> 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) {}

View File

@@ -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 ArrItem> class Arranger
{
public:
class Ctl : public ArrangeTaskCtl {
public:
virtual void on_packed(ArrItem &item) {};
};
virtual ~Arranger() = default;
virtual void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
Ctl &ctl) = 0;
void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangeTaskCtl &ctl);
void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
Ctl &&ctl)
{
arrange(items, fixed, bed, ctl);
}
void arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangeTaskCtl &&ctl)
{
arrange(items, fixed, bed, ctl);
}
static std::unique_ptr<Arranger> create(const ArrangeSettingsView &settings);
};
template<class ArrItem> using ArrangerCtl = typename Arranger<ArrItem>::Ctl;
template<class ArrItem>
class DefaultArrangerCtl : public Arranger<ArrItem>::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<class ArrItem>
void Arranger<ArrItem>::arrange(std::vector<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangeTaskCtl &ctl)
{
arrange(items, fixed, bed, DefaultArrangerCtl<ArrItem>{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 ArrItem> 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<ArrangeableToItemConverter> create(
ArrangeSettingsView::GeometryHandling geometry_handling,
coord_t safety_d);
static std::unique_ptr<ArrangeableToItemConverter> create(
const Scene &sc)
{
return create(sc.settings().get_geometry_handling(),
scaled(sc.settings().get_distance_from_objects()));
}
};
template<class DStore, class = WritableDataStoreOnly<DStore>>
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 ArrItem>
class BasicItemConverter : public ArrangeableToItemConverter<ArrItem>
{
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 ArrItem>
class ConvexItemConverter : public BasicItemConverter<ArrItem>
{
public:
using BasicItemConverter<ArrItem>::BasicItemConverter;
ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override;
};
template<class ArrItem>
class AdvancedItemConverter : public BasicItemConverter<ArrItem>
{
protected:
virtual ArrItem get_arritem(const Arrangeable &arrbl, coord_t eps) const;
public:
using BasicItemConverter<ArrItem>::BasicItemConverter;
ArrItem convert(const Arrangeable &arrbl, coord_t offs) const override;
};
template<class ArrItem>
class BalancedItemConverter : public AdvancedItemConverter<ArrItem>
{
protected:
ArrItem get_arritem(const Arrangeable &arrbl, coord_t offs) const override;
public:
using AdvancedItemConverter<ArrItem>::AdvancedItemConverter;
};
template<class ArrItem, class En = void> 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<ObjectID> retrieve_id(const ArrItem &itm)
{
std::optional<ObjectID> ret;
auto idptr = get_data<const ObjectID>(itm, Key);
if (idptr)
ret = *idptr;
return ret;
}
};
template<class ArrItem>
using ImbueableItemTraits = ImbueableItemTraits_<StripCVRef<ArrItem>>;
template<class ArrItem>
void imbue_id(ArrItem &itm, const ObjectID &id)
{
ImbueableItemTraits<ArrItem>::imbue_id(itm, id);
}
template<class ArrItem>
std::optional<ObjectID> retrieve_id(const ArrItem &itm)
{
return ImbueableItemTraits<ArrItem>::retrieve_id(itm);
}
template<class ArrItem>
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<class ArrItem>
double get_min_area_bounding_box_rotation(const ArrItem &itm)
{
return MinAreaBoundigBox{envelope_convex_hull(itm),
MinAreaBoundigBox::pcConvex}
.angle_to_X();
}
template<class ArrItem>
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<class ArrItem>
auto get_corrected_bed(const ExtendedBed &bed,
const ArrangeableToItemConverter<ArrItem> &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

View File

@@ -1,498 +0,0 @@
#ifndef ARRANGEIMPL_HPP
#define ARRANGEIMPL_HPP
#include <random>
#include <map>
#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<class It,
class ConstIt,
class SelectionStrategy,
class PackStrategy, class...SBedArgs>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const Range<ConstIt> &fixed,
const SegmentedRectangleBed<SBedArgs...> &bed)
{
// Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, fixed,
RectangleBed{bed.bb}, SelStrategyTag<SelectionStrategy>{});
std::vector<int> bed_indices = get_bed_indices(items, fixed);
std::map<int, BoundingBox> pilebb;
std::map<int, bool> 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<int>(bedidx) && !is_wipe_tower(itm))
translate(itm, d);
}
}
using VariantKernel =
boost::variant<TMArrangeKernel, GravityKernel>;
template<> struct KernelTraits_<VariantKernel> {
template<class ArrItem>
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<class ArrItem, class Bed, class Ctx, class RemIt>
static bool on_start_packing(VariantKernel &kernel,
ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> &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<class ArrItem>
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<class ArrItem>
struct firstfit::ItemArrangedVisitor<ArrItem, DataStoreOnly<ArrItem>> {
template<class Bed, class PIt, class RIt>
static void on_arranged(ArrItem &itm,
const Bed &bed,
const Range<PIt> &packed,
const Range<RIt> &remaining)
{
using OnArrangeCb = std::function<void(StripCVRef<ArrItem> &)>;
auto cb = get_data<OnArrangeCb>(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<std::mt19937::result_type>
dist(0, arr2::ArrangeSettingsView::xlpRandom - 1);
xlpivot = static_cast<ArrangeSettingsView::XLPivots>(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<class It, class Bed>
void fill_rotations(const Range<It> &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<double> 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<Bed, RectangleBed>) {
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 ArrItem>
class DefaultArranger: public Arranger<ArrItem> {
ArrangeSettings m_settings;
static constexpr auto Accuracy = 1.;
template<class It, class FixIt, class Bed>
void arrange_(
const Range<It> &items,
const Range<FixIt> &fixed,
const Bed &bed,
ArrangerCtl<ArrItem> &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<Bed, CircleBed>){
basekernel = GravityKernel{};
} else {
basekernel = TMArrangeKernel{items.size(), area(bed)};
}
break;
case ArrangeSettingsView::asPullToCenter:
basekernel = GravityKernel{};
break;
}
#ifndef NDEBUG
SVGDebugOutputKernelWrapper<VariantKernel> 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<Bed>) {
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<ArrItem> &items,
const std::vector<ArrItem> &fixed,
const ExtendedBed &bed,
ArrangerCtl<ArrItem> &ctl) override
{
visit_bed([this, &items, &fixed, &ctl](auto rawbed) {
if constexpr (IsSegmentedBed<decltype(rawbed)>)
rawbed.pivot = xlpivots_to_rect_pivots(
m_settings.get_xl_alignment());
arrange_(range(items), crange(fixed), rawbed, ctl);
}, bed);
}
};
template<class ArrItem>
std::unique_ptr<Arranger<ArrItem>> Arranger<ArrItem>::create(
const ArrangeSettingsView &settings)
{
// Currently all that is needed is handled by DefaultArranger
return std::make_unique<DefaultArranger<ArrItem>>(settings);
}
template<class ArrItem>
ArrItem ConvexItemConverter<ArrItem>::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<ArrItem>)
arrbl.imbue_data(AnyWritableDataStore{ret});
return ret;
}
template<class ArrItem>
ArrItem AdvancedItemConverter<ArrItem>::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<ArrItem>)
arrbl.imbue_data(AnyWritableDataStore{ret});
return ret;
}
template<class ArrItem>
ArrItem AdvancedItemConverter<ArrItem>::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<double>(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<class ArrItem>
ArrItem BalancedItemConverter<ArrItem>::get_arritem(const Arrangeable &arrbl,
coord_t offs) const
{
ArrItem ret = AdvancedItemConverter<ArrItem>::get_arritem(arrbl, offs);
set_convex_envelope(ret, envelope_convex_hull(ret));
return ret;
}
template<class ArrItem>
std::unique_ptr<ArrangeableToItemConverter<ArrItem>>
ArrangeableToItemConverter<ArrItem>::create(
ArrangeSettingsView::GeometryHandling gh,
coord_t safety_d)
{
std::unique_ptr<ArrangeableToItemConverter<ArrItem>> ret;
constexpr coord_t SimplifyTol = scaled(.2);
switch(gh) {
case arr2::ArrangeSettingsView::ghConvex:
ret = std::make_unique<ConvexItemConverter<ArrItem>>(safety_d);
break;
case arr2::ArrangeSettingsView::ghBalanced:
ret = std::make_unique<BalancedItemConverter<ArrItem>>(safety_d, SimplifyTol);
break;
case arr2::ArrangeSettingsView::ghAdvanced:
ret = std::make_unique<AdvancedItemConverter<ArrItem>>(safety_d, SimplifyTol);
break;
default:
;
}
return ret;
}
}} // namespace Slic3r::arr2
#endif // ARRANGEIMPL_HPP

View File

@@ -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

View File

@@ -1,97 +0,0 @@
#ifndef ARRANGESETTINGSDB_APPCFG_HPP
#define ARRANGESETTINGSDB_APPCFG_HPP
#include <string>
#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<class Self>
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<class Self> static auto &get_slot(Self *self)
{
return get_slot(self, self->m_current_slot);
}
template<class Self>
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

View File

@@ -1,235 +0,0 @@
#ifndef ARRANGESETTINGSVIEW_HPP
#define ARRANGESETTINGSVIEW_HPP
#include <string_view>
#include <array>
#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<class EnumType, size_t N>
using EnumMap = StaticMap<std::string_view, EnumType, N>;
template<class EnumType, size_t N>
static constexpr std::optional<EnumType> get_enumval(std::string_view str,
const EnumMap<EnumType, N> &emap)
{
std::optional<EnumType> ret;
if (auto v = query(emap, str); v.has_value()) {
ret = *v;
}
return ret;
}
public:
static constexpr std::optional<GeometryHandling> to_geometry_handling(std::string_view str)
{
return get_enumval(str, GeometryHandlingLabels);
}
static constexpr std::optional<ArrangeStrategy> to_arrange_strategy(std::string_view str)
{
return get_enumval(str, ArrangeStrategyLabels);
}
static constexpr std::optional<XLPivots> to_xl_pivots(std::string_view str)
{
return get_enumval(str, XLPivotsLabels);
}
private:
static constexpr const auto GeometryHandlingLabels = make_staticmap<std::string_view, GeometryHandling>({
{"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<std::string_view, ArrangeStrategy>({
{"auto"sv, asAuto},
{"pulltocenter"sv, asPullToCenter},
{"0"sv, asAuto},
{"1"sv, asPullToCenter}
});
static constexpr const auto XLPivotsLabels = make_staticmap<std::string_view, XLPivots>({
{"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

View File

@@ -1,295 +0,0 @@
#ifndef ARRANGEBASE_HPP
#define ARRANGEBASE_HPP
#include <iterator>
#include <type_traits>
#include "ArrangeItemTraits.hpp"
#include "PackingContext.hpp"
#include "libslic3r/Point.hpp"
namespace Slic3r { namespace arr2 {
namespace detail_is_const_it {
template<class It, class En = void>
struct IsConstIt_ { static constexpr bool value = false; };
template<class It>
using iterator_category_t = typename std::iterator_traits<It>::iterator_category;
template<class It>
using iterator_reference_t = typename std::iterator_traits<It>::reference;
template<class It>
struct IsConstIt_ <It, std::enable_if_t<std::is_class_v<iterator_category_t<It>>> >
{
static constexpr bool value =
std::is_const_v<std::remove_reference_t<iterator_reference_t<It>>>;
};
} // namespace detail_is_const_it
template<class It>
static constexpr bool IsConstIterator = detail_is_const_it::IsConstIt_<It>::value;
template<class It>
constexpr bool is_const_iterator(const It &it) noexcept { return IsConstIterator<It>; }
// 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<class PackStrategy> struct PackStrategyTag_ {
using Tag = UnimplementedPacking;
};
// Helper metafunc to derive packing strategy tag from a strategy object.
template<class Strategy>
using PackStrategyTag =
typename PackStrategyTag_<remove_cvref_t<Strategy>>::Tag;
template<class PackStrategy, class En = void> struct PackStrategyTraits_ {
template<class ArrItem> using Context = DefaultPackingContext<ArrItem>;
template<class ArrItem, class Bed>
static Context<ArrItem> create_context(PackStrategy &ps,
const Bed &bed,
int bed_index)
{
return {};
}
};
template<class PS> using PackStrategyTraits = PackStrategyTraits_<StripCVRef<PS>>;
template<class PS, class ArrItem>
using PackStrategyContext =
typename PackStrategyTraits<PS>::template Context<StripCVRef<ArrItem>>;
template<class ArrItem, class PackStrategy, class Bed>
PackStrategyContext<PackStrategy, ArrItem> create_context(PackStrategy &&ps,
const Bed &bed,
int bed_index)
{
return PackStrategyTraits<PackStrategy>::template create_context<
StripCVRef<ArrItem>>(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<class Strategy, class Bed, class ArrItem, class RemIt>
bool pack(Strategy &&strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &context,
const Range<RemIt> &remaining_items)
{
static_assert(IsConstIterator<RemIt>, "Remaining item iterator is not const!");
// Dispatch:
return pack(std::forward<Strategy>(strategy), bed, item, context,
remaining_items, PackStrategyTag<Strategy>{});
}
// Overload without fixed items:
template<class Strategy, class Bed, class ArrItem>
bool pack(Strategy &&strategy, const Bed &bed, ArrItem &item)
{
std::vector<ArrItem> dummy;
auto context = create_context<ArrItem>(strategy, bed, PhysicalBedId);
return pack(std::forward<Strategy>(strategy), bed, item, context,
crange(dummy));
}
// Overload when strategy is unkown, yields compile error:
template<class Strategy, class Bed, class ArrItem, class RemIt>
bool pack(Strategy &&strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &context,
const Range<RemIt> &remaining_items,
const UnimplementedPacking &)
{
static_assert(always_false<Strategy>::value,
"Packing unimplemented for this placement strategy");
return false;
}
// Helper function to remove unpackable items from the input container.
template<class PackStrategy, class Container, class Bed, class StopCond>
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<decltype(*it)> &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<class SelStrategy> struct SelStrategyTag_ {
using Tag = UnimplementedSelection;
};
// Helper metafunc to derive the selection strategy tag from a strategy object.
template<class Strategy>
using SelStrategyTag = typename SelStrategyTag_<remove_cvref_t<Strategy>>::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<class It,
class ConstIt,
class TBed,
class SelectionStrategy,
class PackStrategy>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const Range<ConstIt> &fixed,
const TBed &bed)
{
static_assert(IsConstIterator<ConstIt>, "Fixed item iterator is not const!");
// Dispatch:
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, fixed, bed,
SelStrategyTag<SelectionStrategy>{});
}
template<class It, class TBed, class SelectionStrategy, class PackStrategy>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const TBed &bed)
{
std::vector<typename std::iterator_traits<It>::value_type> dummy;
arrange(std::forward<SelectionStrategy>(selstrategy),
std::forward<PackStrategy>(packingstrategy), items, crange(dummy),
bed);
}
// Overload for unimplemented selection strategy, yields compile error:
template<class It,
class ConstIt,
class TBed,
class SelectionStrategy,
class PackStrategy>
void arrange(SelectionStrategy &&selstrategy,
PackStrategy &&packingstrategy,
const Range<It> &items,
const Range<ConstIt> &fixed,
const TBed &bed,
const UnimplementedSelection &)
{
static_assert(always_false<SelectionStrategy>::value,
"Arrange unimplemented for this selection strategy");
}
template<class It>
std::vector<int> get_bed_indices(const Range<It> &items)
{
auto bed_indices = reserve_vector<int>(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<class It, class CIt>
std::vector<int> get_bed_indices(const Range<It> &items, const Range<CIt> &fixed)
{
std::vector<int> 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<class It>
size_t get_bed_count(const Range<It> &items)
{
return get_bed_indices(items).size();
}
template<class It> int get_max_bed_index(const Range<It> &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

View File

@@ -1,166 +0,0 @@
#ifndef ARRANGEFIRSTFIT_HPP
#define ARRANGEFIRSTFIT_HPP
#include <iterator>
#include <map>
#include <libslic3r/Arrange/Core/ArrangeBase.hpp>
namespace Slic3r { namespace arr2 { namespace firstfit {
struct SelectionTag {};
// Can be specialized by Items
template<class ArrItem, class En = void>
struct ItemArrangedVisitor {
template<class Bed, class PIt, class RIt>
static void on_arranged(ArrItem &itm,
const Bed &bed,
const Range<PIt> &packed_items,
const Range<RIt> &remaining_items)
{}
};
// Use the the visitor baked into the ArrItem type by default
struct DefaultOnArrangedFn {
template<class ArrItem, class Bed, class PIt, class RIt>
void operator()(ArrItem &itm,
const Bed &bed,
const Range<PIt> &packed,
const Range<RIt> &remaining)
{
ItemArrangedVisitor<StripCVRef<ArrItem>>::on_arranged(itm, bed, packed,
remaining);
}
};
struct DefaultItemCompareFn {
template<class ArrItem>
bool operator() (const ArrItem &ia, const ArrItem &ib)
{
return get_priority(ia) > get_priority(ib);
}
};
template<class CompareFn = DefaultItemCompareFn,
class OnArrangedFn = DefaultOnArrangedFn,
class StopCondition = DefaultStopCondition>
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<class... Args> struct SelStrategyTag_<firstfit::SelectionStrategy<Args...>> {
using Tag = firstfit::SelectionTag;
};
template<class It,
class ConstIt,
class TBed,
class SelStrategy,
class PackStrategy>
void arrange(
SelStrategy &&sel,
PackStrategy &&ps,
const Range<It> &items,
const Range<ConstIt> &fixed,
const TBed &bed,
const firstfit::SelectionTag &)
{
using ArrItem = typename std::iterator_traits<It>::value_type;
using ArrItemRef = std::reference_wrapper<ArrItem>;
auto sorted_items = reserve_vector<ArrItemRef>(items.size());
for (auto &itm : items) {
set_bed_index(itm, Unarranged);
sorted_items.emplace_back(itm);
}
using Context = PackStrategyContext<PackStrategy, ArrItem>;
std::map<int, Context> 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<ArrItem>(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<decltype(sel.cmpfn)>) {
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<ArrItemRef>::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<SConstIt>(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<SConstIt>(it)};
sel.on_arranged_fn(*it, bed, packed_range, remaining);
} else {
set_bed_index(*it, Unarranged);
}
}
}
++it;
}
}
}} // namespace Slic3r::arr2
#endif // ARRANGEFIRSTFIT_HPP

View File

@@ -1,114 +0,0 @@
#ifndef ARRANGE_ITEM_TRAITS_HPP
#define ARRANGE_ITEM_TRAITS_HPP
#include <libslic3r/Point.hpp>
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<class ArrItem, class En = void> 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<class T> using ArrangeItemTraits = ArrangeItemTraits_<StripCVRef<T>>;
// Getters:
template<class T> Vec2crd get_translation(const T &itm)
{
return ArrangeItemTraits<T>::get_translation(itm);
}
template<class T> double get_rotation(const T &itm)
{
return ArrangeItemTraits<T>::get_rotation(itm);
}
template<class T> int get_bed_index(const T &itm)
{
return ArrangeItemTraits<T>::get_bed_index(itm);
}
template<class T> int get_priority(const T &itm)
{
return ArrangeItemTraits<T>::get_priority(itm);
}
// Setters:
template<class T> void set_translation(T &itm, const Vec2crd &v)
{
ArrangeItemTraits<T>::set_translation(itm, v);
}
template<class T> void set_rotation(T &itm, double v)
{
ArrangeItemTraits<T>::set_rotation(itm, v);
}
template<class T> void set_bed_index(T &itm, int v)
{
ArrangeItemTraits<T>::set_bed_index(itm, v);
}
// Helper functions for arrange items
template<class ArrItem> bool is_arranged(const ArrItem &ap)
{
return get_bed_index(ap) > Unarranged;
}
template<class ArrItem> bool is_fixed(const ArrItem &ap)
{
return get_bed_index(ap) >= PhysicalBedId;
}
template<class ArrItem> bool is_on_physical_bed(const ArrItem &ap)
{
return get_bed_index(ap) == PhysicalBedId;
}
template<class ArrItem> void translate(ArrItem &ap, const Vec2crd &t)
{
set_translation(ap, get_translation(ap) + t);
}
template<class ArrItem> void rotate(ArrItem &ap, double rads)
{
set_rotation(ap, get_rotation(ap) + rads);
}
}} // namespace Slic3r::arr2
#endif // ARRANGE_ITEM_HPP

View File

@@ -1,136 +0,0 @@
#include "Beds.hpp"
#include <cstdlib>
#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<C>::lowest() + 2 * bed.center.x()) / 4.01);
C My = C((std::numeric_limits<C>::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<coord_t>(bed.radius() * std::cos(angle));
auto y = bed.center().y() + static_cast<coord_t>(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 &center, const Points &points)
{
std::vector<double> 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<class Fn> 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

View File

@@ -1,203 +0,0 @@
#ifndef BEDS_HPP
#define BEDS_HPP
#include <libslic3r/Point.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <numeric>
#include <cmath>
#include <limits>
#include <type_traits>
#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<coord_t>(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<InfiniteBed, RectangleBed, CircleBed, IrregularBed>;
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<double>::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<class Bed, class En = void> struct IsRectangular_ : public std::false_type {};
template<> struct IsRectangular_<RectangleBed>: public std::true_type {};
template<> struct IsRectangular_<BoundingBox>: public std::true_type {};
template<class Bed> static constexpr bool IsRectangular = IsRectangular_<Bed>::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

View File

@@ -1,79 +0,0 @@
#ifndef DATASTORETRAITS_HPP
#define DATASTORETRAITS_HPP
#include <string_view>
#include "libslic3r/libslic3r.h"
namespace Slic3r { namespace arr2 {
// Some items can be containers of arbitrary data stored under string keys.
template<class ArrItem, class En = void> struct DataStoreTraits_
{
static constexpr bool Implemented = false;
template<class T> static const T *get(const ArrItem &, const std::string &key)
{
return nullptr;
}
// Same as above just not const.
template<class T> static T *get(ArrItem &, const std::string &key)
{
return nullptr;
}
static bool has_key(const ArrItem &itm, const std::string &key)
{
return false;
}
};
template<class ArrItem, class En = void> struct WritableDataStoreTraits_
{
static constexpr bool Implemented = false;
template<class T> static void set(ArrItem &, const std::string &key, T &&data)
{
}
};
template<class T> using DataStoreTraits = DataStoreTraits_<StripCVRef<T>>;
template<class T> constexpr bool IsDataStore = DataStoreTraits<StripCVRef<T>>::Implemented;
template<class T, class TT = T> using DataStoreOnly = std::enable_if_t<IsDataStore<T>, TT>;
template<class T, class ArrItem>
const T *get_data(const ArrItem &itm, const std::string &key)
{
return DataStoreTraits<ArrItem>::template get<T>(itm, key);
}
template<class ArrItem>
bool has_key(const ArrItem &itm, const std::string &key)
{
return DataStoreTraits<ArrItem>::has_key(itm, key);
}
template<class T, class ArrItem>
T *get_data(ArrItem &itm, const std::string &key)
{
return DataStoreTraits<ArrItem>::template get<T>(itm, key);
}
template<class T> using WritableDataStoreTraits = WritableDataStoreTraits_<StripCVRef<T>>;
template<class T> constexpr bool IsWritableDataStore = WritableDataStoreTraits<StripCVRef<T>>::Implemented;
template<class T, class TT = T> using WritableDataStoreOnly = std::enable_if_t<IsWritableDataStore<T>, TT>;
template<class T, class ArrItem>
void set_data(ArrItem &itm, const std::string &key, T &&data)
{
WritableDataStoreTraits<ArrItem>::template set(itm, key, std::forward<T>(data));
}
template<class T> constexpr bool IsReadWritableDataStore = IsDataStore<T> && IsWritableDataStore<T>;
template<class T, class TT = T> using ReadWritableDataStoreOnly = std::enable_if_t<IsReadWritableDataStore<T>, TT>;
}} // namespace Slic3r::arr2
#endif // DATASTORETRAITS_HPP

View File

@@ -1,111 +0,0 @@
#ifndef CIRCULAR_EDGEITERATOR_HPP
#define CIRCULAR_EDGEITERATOR_HPP
#include <libslic3r/Polygon.hpp>
#include <libslic3r/Line.hpp>
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<bool flip_lines = false>
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_<true>;
inline Range<CircularEdgeIterator> line_range(const Polygon &poly)
{
return Range{CircularEdgeIterator{0, poly}, CircularEdgeIterator{poly.size(), poly}};
}
inline Range<CircularReverseEdgeIterator> line_range_flp(const Polygon &poly)
{
return Range{CircularReverseEdgeIterator{0, poly}, CircularReverseEdgeIterator{poly.size(), poly}};
}
} // namespace Slic3r
#endif // CIRCULAR_EDGEITERATOR_HPP

View File

@@ -1,105 +0,0 @@
#include "EdgeCache.hpp"
#include <iterator>
#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<ContourLocation> &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<double> &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<double>() + t * n).cast<coord_t>();
return ret;
}
void fill_distances(const Polygon &poly, std::vector<double> &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

View File

@@ -1,81 +0,0 @@
#ifndef EDGECACHE_HPP
#define EDGECACHE_HPP
#include <libslic3r/ExPolygon.hpp>
#include <assert.h>
#include <stddef.h>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cassert>
#include <cstddef>
#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<double> &distances);
Vec2crd coords(const Polygon &poly, const std::vector<double>& 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<double> distances;
} m_contour;
std::vector<ContourCache> 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<coord_t>(
std::round(N / std::pow(n, std::pow(accuracy, 1./3.)))
);
}
void sample_contour(double accuracy, std::vector<ContourLocation> &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

View File

@@ -1,62 +0,0 @@
#ifndef COMPACTIFYKERNEL_HPP
#define COMPACTIFYKERNEL_HPP
#include <numeric>
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <libslic3r/Geometry/ConvexHull.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include "KernelUtils.hpp"
namespace Slic3r { namespace arr2 {
struct CompactifyKernel {
ExPolygons merged_pile;
template<class ArrItem>
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<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> & /*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<class ArrItem>
bool on_item_packed(ArrItem &itm) { return true; }
};
}} // namespace Slic3r::arr2
#endif // COMPACTIFYKERNEL_HPP

View File

@@ -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<Vec2crd> sink;
std::optional<Vec2crd> item_sink;
Vec2d active_sink;
GravityKernel(Vec2crd gravity_center) :
sink{gravity_center}, active_sink{unscaled(gravity_center)} {}
GravityKernel() = default;
template<class ArrItem>
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<class ArrItem, class Bed, class Ctx, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> & /*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<class ArrItem> bool on_item_packed(ArrItem &itm) { return true; }
};
}} // namespace Slic3r::arr2
#endif // GRAVITYKERNEL_HPP

View File

@@ -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<class Kernel, class En = void> 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<class ArrItem>
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<class ArrItem, class Bed, class Ctx, class RemIt>
static bool on_start_packing(Kernel &k,
ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> &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<class ArrItem>
static bool on_item_packed(Kernel &k, ArrItem &itm)
{
return k.on_item_packed(itm);
}
};
template<class K> using KernelTraits = KernelTraits_<StripCVRef<K>>;
}} // namespace Slic3r::arr2
#endif // KERNELTRAITS_HPP

View File

@@ -1,77 +0,0 @@
#ifndef ARRANGEKERNELUTILS_HPP
#define ARRANGEKERNELUTILS_HPP
#include <type_traits>
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include "libslic3r/Arrange/Core/DataStoreTraits.hpp"
namespace Slic3r { namespace arr2 {
template<class Itm, class Bed, class Context>
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<Bed, RectangleBed> ||
std::is_convertible_v<Bed, InfiniteBed> ||
std::is_convertible_v<Bed, CircleBed>)
{
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<class ArrItem> std::optional<Vec2crd> get_gravity_sink(const ArrItem &itm)
{
constexpr const char * SinkKey = "sink";
std::optional<Vec2crd> ret;
auto ptr = get_data<Vec2crd>(itm, SinkKey);
if (ptr)
ret = *ptr;
return ret;
}
template<class ArrItem> 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

View File

@@ -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<class Kernel>
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<class ArrItem>
double placement_fitness(const ArrItem &item, const Vec2crd &transl) const
{
double score = KernelTraits<Kernel>::placement_fitness(k, item, transl);
auto itmbb = envelope_bounding_box(item);
itmbb.translate(transl);
double miss = overfit(itmbb);
score -= miss * miss;
return score;
}
template<class ArrItem, class Bed, class Ctx, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Ctx &packing_context,
const Range<RemIt> &remaining_items)
{
pilebb = BoundingBox{};
for (auto &fitm : all_items_range(packing_context))
pilebb.merge(fixed_bounding_box(fitm));
return KernelTraits<Kernel>::on_start_packing(k, itm, RectangleBed{binbb},
packing_context,
remaining_items);
}
template<class ArrItem>
bool on_item_packed(ArrItem &itm)
{
bool ret = KernelTraits<Kernel>::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

View File

@@ -1,97 +0,0 @@
#ifndef SVGDEBUGOUTPUTKERNELWRAPPER_HPP
#define SVGDEBUGOUTPUTKERNELWRAPPER_HPP
#include <memory>
#include "KernelTraits.hpp"
#include "libslic3r/Arrange/Core/PackingContext.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include "libslic3r/Arrange/Core/Beds.hpp"
#include <libslic3r/SVG.hpp>
namespace Slic3r { namespace arr2 {
template<class Kernel>
struct SVGDebugOutputKernelWrapper {
Kernel &k;
std::unique_ptr<Slic3r::SVG> svg;
BoundingBox drawbounds;
template<class... Args>
SVGDebugOutputKernelWrapper(const BoundingBox &bounds, Kernel &kern)
: k{kern}, drawbounds{bounds}
{}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> &rem)
{
using namespace Slic3r;
bool ret = KernelTraits<Kernel>::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<SVG>(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<class ArrItem>
double placement_fitness(const ArrItem &item, const Vec2crd &transl) const
{
return KernelTraits<Kernel>::placement_fitness(k, item, transl);
}
template<class ArrItem>
bool on_item_packed(ArrItem &itm)
{
using namespace Slic3r;
using namespace Slic3r::arr2;
bool ret = KernelTraits<Kernel>::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

View File

@@ -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 <boost/geometry/index/rtree.hpp>
#include <libslic3r/BoostAdapter.hpp>
namespace Slic3r { namespace arr2 {
// Summon the spatial indexing facilities from boost
namespace bgi = boost::geometry::index;
using SpatElement = std::pair<BoundingBox, unsigned>;
using SpatIndex = bgi::rtree<SpatElement, bgi::rstar<16, 4> >;
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<ItemStats> m_itemstats;
// A coefficient used in separating bigger items and smaller items.
static constexpr double BigItemTreshold = 0.02;
template<class T> ArithmeticOnly<T, double> 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<Point> sink;
std::optional<Point> 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<class ArrItem>
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<double>().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<SpatElement> 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<double>().norm());
break;
}
}
return -score;
}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> &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<double>(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<class ArrItem>
bool on_item_packed(ArrItem &itm) { return true; }
};
}} // namespace Slic3r::arr2
#endif // TMARRANGEKERNEL_HPP

View File

@@ -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 <boost/multiprecision/integer.hpp>
namespace Slic3r { using LargeInt = boost::multiprecision::int128_t; }
#endif
#include <boost/rational.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <limits>
#include <utility>
#include <vector>
#include <cassert>
namespace Slic3r {
static bool line_cmp(const Line& e1, const Line& e2)
{
using Ratio = boost::rational<LargeInt>;
const Vec<2, int64_t> ax(1, 0); // Unit vector for the X axis
Vec<2, int64_t> p1 = (e1.b - e1.a).cast<int64_t>();
Vec<2, int64_t> p2 = (e2.b - e2.a).cast<int64_t>();
// 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<int, 4> quadrants {0, 3, 1, 2 };
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
using TDots = std::array<int64_t, 2>;
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<coord_t>::min(),
std::numeric_limits<coord_t>::min()};
auto it = std::max_element(poly.points.begin(), poly.points.end(), vsort);
if (it != poly.points.end())
ret = std::max(ret, static_cast<const Vec2crd &>(*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<coord_t>::min(),
std::numeric_limits<coord_t>::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<coord_t>::min(),
std::numeric_limits<coord_t>::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<coord_t>::max(),
std::numeric_limits<coord_t>::max()};
auto it = std::min_element(poly.points.begin(), poly.points.end(), vsort);
if (it != poly.points.end())
ret = std::min(ret, static_cast<const Vec2crd&>(*it), vsort);
return ret;
}
// Find the vertex corresponding to the edge with minimum angle to X axis.
// Only usable with CircularEdgeIterator<> template.
template<class It> 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<Line>& 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<Line> 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<int64_t>().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

View File

@@ -1,57 +0,0 @@
#ifndef NFP_HPP
#define NFP_HPP
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Arrange/Core/Beds.hpp>
#include <stdint.h>
#include <boost/variant.hpp>
#include <cinttypes>
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
namespace Slic3r {
template<class Unit = int64_t, class T>
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

View File

@@ -1,197 +0,0 @@
#ifndef NFPARRANGEITEMTRAITS_HPP
#define NFPARRANGEITEMTRAITS_HPP
#include <numeric>
#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<class ArrItem, class En = void> struct NFPArrangeItemTraits_
{
template<class Context, class Bed, class StopCond = DefaultStopCondition>
static ExPolygons calculate_nfp(const ArrItem &item,
const Context &packing_context,
const Bed &bed,
StopCond stop_condition = {})
{
static_assert(always_false<ArrItem>::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<class T>
using NFPArrangeItemTraits = NFPArrangeItemTraits_<StripCVRef<T>>;
template<class ArrItem,
class Context,
class Bed,
class StopCond = DefaultStopCondition>
ExPolygons calculate_nfp(const ArrItem &itm,
const Context &context,
const Bed &bed,
StopCond stopcond = {})
{
return NFPArrangeItemTraits<ArrItem>::calculate_nfp(itm, context, bed,
std::move(stopcond));
}
template<class ArrItem> Vec2crd reference_vertex(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::reference_vertex(itm);
}
template<class ArrItem> BoundingBox envelope_bounding_box(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_bounding_box(itm);
}
template<class ArrItem> BoundingBox fixed_bounding_box(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_bounding_box(itm);
}
template<class ArrItem> decltype(auto) envelope_convex_hull(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_convex_hull(itm);
}
template<class ArrItem> decltype(auto) fixed_convex_hull(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_convex_hull(itm);
}
template<class ArrItem> decltype(auto) envelope_outline(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_outline(itm);
}
template<class ArrItem> decltype(auto) fixed_outline(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_outline(itm);
}
template<class ArrItem> double envelope_area(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_area(itm);
}
template<class ArrItem> double fixed_area(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_area(itm);
}
template<class ArrItem> Vec2crd fixed_centroid(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::fixed_centroid(itm);
}
template<class ArrItem> Vec2crd envelope_centroid(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::envelope_centroid(itm);
}
template<class ArrItem>
auto allowed_rotations(const ArrItem &itm)
{
return NFPArrangeItemTraits<ArrItem>::allowed_rotations(itm);
}
template<class It>
BoundingBox bounding_box(const Range<It> &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<class It>
BoundingBox bounding_box_on_bedidx(const Range<It> &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

View File

@@ -1,118 +0,0 @@
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/partition_2.h>
#include <CGAL/Partition_traits_2.h>
#include <CGAL/property_map.h>
#include <CGAL/Polygon_vertical_decomposition_2.h>
#include <iterator>
#include <utility>
#include <vector>
#include <cstddef>
#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<K, CGAL::Pointer_property_map<K::Point_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<Vec2crd>(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<K> decomp;
CGAL::Polygon_2<K> contour;
for (auto &p : expoly.contour.points)
contour.push_back({unscaled(p.x()), unscaled(p.y())});
CGAL::Polygon_with_holes_2<K> cgalpoly{contour};
for (const Polygon &h : expoly.holes) {
CGAL::Polygon_2<K> hole;
for (auto &p : h.points)
hole.push_back({unscaled(p.x()), unscaled(p.y())});
cgalpoly.add_hole(hole);
}
std::vector<CGAL::Polygon_2<K>> 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<K::Point_2>(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<Polygon_2> 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

View File

@@ -1,17 +0,0 @@
#ifndef NFPCONCAVE_CGAL_HPP
#define NFPCONCAVE_CGAL_HPP
#include <libslic3r/ExPolygon.hpp>
#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

View File

@@ -1,78 +0,0 @@
#include "NFPConcave_Tesselate.hpp"
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Tesselate.hpp>
#include <algorithm>
#include <iterator>
#include <vector>
#include <cstddef>
#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<Vec2d> 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<Vec2crd>(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

View File

@@ -1,18 +0,0 @@
#ifndef NFPCONCAVE_TESSELATE_HPP
#define NFPCONCAVE_TESSELATE_HPP
#include <libslic3r/ExPolygon.hpp>
#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

View File

@@ -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<class ArrItem>
double placement_fitness(const ArrItem &itm, const Vec2crd &dest_pos) const
{
return NaNd;
}
template<class ArrItem, class Bed, class Context, class RemIt>
bool on_start_packing(ArrItem &itm,
const Bed &bed,
const Context &packing_context,
const Range<RemIt> &remaining_items)
{
return true;
}
template<class ArrItem> bool on_item_packed(ArrItem &itm) { return true; }
};
template<class Strategy> using OptAlg = typename Strategy::OptAlg;
template<class ArrangeKernel = DummyArrangeKernel,
class ExecPolicy = ExecutionSeq,
class OptMethod = opt::AlgNLoptSubplex,
class StopCond = DefaultStopCondition>
struct PackStrategyNFP {
using OptAlg = OptMethod;
ArrangeKernel kernel;
ExecPolicy ep;
double accuracy = 1.;
opt::Optimizer<OptMethod> solver;
StopCond stop_condition;
PackStrategyNFP(opt::Optimizer<OptMethod> 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<OptMethod>{}, std::move(k),
std::move(execpolicy), accur, std::move(stop_cond)}
{
// Defaults for AlgNLoptSubplex
auto iters = static_cast<unsigned>(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<class...Args>
struct PackStrategyTag_<PackStrategyNFP<Args...>>
{
using Tag = NFPPackingTag;
};
template<class ArrItem, class Bed, class PStrategy>
double pick_best_spot_on_nfp_verts_only(ArrItem &item,
const ExPolygons &nfp,
const Bed &bed,
const PStrategy &strategy)
{
using KernelT = KernelTraits<decltype(strategy.kernel)>;
auto score = -std::numeric_limits<double>::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<class ArrItem, class Bed, class... Args>
double pick_best_spot_on_nfp(ArrItem &item,
const ExPolygons &nfp,
const Bed &bed,
const PackStrategyNFP<Args...> &strategy)
{
auto &ex_policy = strategy.ep;
using KernelT = KernelTraits<decltype(strategy.kernel)>;
auto score = -std::numeric_limits<double>::infinity();
Vec2crd orig_tr = get_translation(item);
Vec2crd translation{0, 0};
Vec2crd ref_v = reference_vertex(item);
auto edge_caches = reserve_vector<EdgeCache>(nfp.size());
auto sample_sets = reserve_vector<std::vector<ContourLocation>>(
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<CornerResult> 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<CornerResult> 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<class Strategy, class ArrItem, class Bed, class RemIt>
bool pack(Strategy &strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &packing_context,
const Range<RemIt> &remaining_items,
const NFPPackingTag &)
{
using KernelT = KernelTraits<decltype(strategy.kernel)>;
// 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<double>::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

View File

@@ -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<Vec2crd(const BoundingBox &bedbb,
const BoundingBox &pilebb)>;
struct CenterAlignmentFn {
Vec2crd operator() (const BoundingBox &bedbb,
const BoundingBox &pilebb)
{
return bedbb.center() - pilebb.center();
}
};
template<class ArrItem>
struct RectangleOverfitPackingContext : public DefaultPackingContext<ArrItem>
{
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<class ...Args>
struct RectangleOverfitPackingStrategy {
PackStrategyNFP<Args...> base_strategy;
PostAlignmentFn post_alignment_fn = CenterAlignmentFn{};
template<class ArrItem>
using Context = RectangleOverfitPackingContext<ArrItem>;
RectangleOverfitPackingStrategy(PackStrategyNFP<Args...> s,
PostAlignmentFn post_align_fn)
: base_strategy{std::move(s)}, post_alignment_fn{post_align_fn}
{}
RectangleOverfitPackingStrategy(PackStrategyNFP<Args...> s)
: base_strategy{std::move(s)}
{}
};
struct RectangleOverfitPackingStrategyTag {};
template<class... Args>
struct PackStrategyTag_<RectangleOverfitPackingStrategy<Args...>> {
using Tag = RectangleOverfitPackingStrategyTag;
};
template<class... Args>
struct PackStrategyTraits_<RectangleOverfitPackingStrategy<Args...>> {
template<class ArrItem>
using Context = typename RectangleOverfitPackingStrategy<
Args...>::template Context<StripCVRef<ArrItem>>;
template<class ArrItem, class Bed>
static Context<ArrItem> create_context(
RectangleOverfitPackingStrategy<Args...> &ps,
const Bed &bed,
int bed_index)
{
return Context<ArrItem>{bounding_box(bed), bed_index,
ps.post_alignment_fn};
}
};
template<class ArrItem>
struct PackingContextTraits_<RectangleOverfitPackingContext<ArrItem>>
: public PackingContextTraits_<DefaultPackingContext<ArrItem>>
{
static void add_packed_item(RectangleOverfitPackingContext<ArrItem> &ctx, ArrItem &itm)
{
ctx.add_packed_item(itm);
// to prevent coords going out of range
ctx.align_pile();
}
};
template<class Strategy, class ArrItem, class Bed, class RemIt>
bool pack(Strategy &strategy,
const Bed &bed,
ArrItem &item,
const PackStrategyContext<Strategy, ArrItem> &packing_context,
const Range<RemIt> &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

View File

@@ -1,125 +0,0 @@
#ifndef PACKINGCONTEXT_HPP
#define PACKINGCONTEXT_HPP
#include "ArrangeItemTraits.hpp"
namespace Slic3r { namespace arr2 {
template<class Ctx, class En = void>
struct PackingContextTraits_ {
template<class ArrItem>
static void add_fixed_item(Ctx &ctx, const ArrItem &itm)
{
ctx.add_fixed_item(itm);
}
template<class ArrItem>
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<class Ctx, class ArrItem>
void add_fixed_item(Ctx &ctx, const ArrItem &itm)
{
PackingContextTraits_<StripCVRef<Ctx>>::add_fixed_item(ctx, itm);
}
template<class Ctx, class ArrItem>
void add_packed_item(Ctx &ctx, ArrItem &itm)
{
PackingContextTraits_<StripCVRef<Ctx>>::add_packed_item(ctx, itm);
}
template<class Ctx>
auto all_items_range(const Ctx &ctx)
{
return PackingContextTraits_<StripCVRef<Ctx>>::all_items_range(ctx);
}
template<class Ctx>
auto fixed_items_range(const Ctx &ctx)
{
return PackingContextTraits_<StripCVRef<Ctx>>::fixed_items_range(ctx);
}
template<class Ctx>
auto packed_items_range(Ctx &&ctx)
{
return PackingContextTraits_<StripCVRef<Ctx>>::packed_items_range(ctx);
}
template<class ArrItem>
class DefaultPackingContext {
using ArrItemRaw = StripCVRef<ArrItem>;
std::vector<std::reference_wrapper<const ArrItemRaw>> m_fixed;
std::vector<std::reference_wrapper<ArrItemRaw>> m_packed;
std::vector<std::reference_wrapper<const ArrItemRaw>> m_items;
public:
DefaultPackingContext() = default;
template<class It>
explicit DefaultPackingContext(const Range<It> &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<class It>
auto default_context(const Range<It> &items)
{
using ArrItem = StripCVRef<typename std::iterator_traits<It>::value_type>;
return DefaultPackingContext<ArrItem>{items};
}
template<class Cont, class ArrItem = typename Cont::value_type>
auto default_context(const Cont &container)
{
return DefaultPackingContext<ArrItem>{crange(container)};
}
}} // namespace Slic3r::arr2
#endif // PACKINGCONTEXT_HPP

View File

@@ -1,92 +0,0 @@
#ifndef ARBITRARYDATASTORE_HPP
#define ARBITRARYDATASTORE_HPP
#include <string>
#include <map>
#include <any>
#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<std::string, std::any> m_data;
public:
template<class T> void add(const std::string &key, T &&data)
{
m_data[key] = std::any{std::forward<T>(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<class T> const T *get(const std::string &key) const
{
auto it = m_data.find(key);
return it != m_data.end() ? std::any_cast<T>(&(it->second)) :
nullptr;
}
// Same as above just not const.
template<class T> T *get(const std::string &key)
{
auto it = m_data.find(key);
return it != m_data.end() ? std::any_cast<T>(&(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_<ArbitraryDataStore>
{
static constexpr bool Implemented = true;
template<class T>
static const T *get(const ArbitraryDataStore &s, const std::string &key)
{
return s.get<T>(key);
}
// Same as above just not const.
template<class T>
static T *get(ArbitraryDataStore &s, const std::string &key)
{
return s.get<T>(key);
}
template<class T>
static bool has_key(ArbitraryDataStore &s, const std::string &key)
{
return s.has_key(key);
}
};
template<> struct WritableDataStoreTraits_<ArbitraryDataStore>
{
static constexpr bool Implemented = true;
template<class T>
static void set(ArbitraryDataStore &store,
const std::string &key,
T &&data)
{
store.add(key, std::forward<T>(data));
}
};
}} // namespace Slic3r::arr2
#endif // ARBITRARYDATASTORE_HPP

View File

@@ -1,206 +0,0 @@
#include "ArrangeItem.hpp"
#include <numeric>
#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<double>(1.) * scaled<double>(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<double>(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<DecomposedShape>(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<DecomposedShape>(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_<ArrangeItem>;
template class ArrangeableToItemConverter<ArrangeItem>;
template struct ArrangeTask<ArrangeItem>;
template struct FillBedTask<ArrangeItem>;
template struct MultiplySelectionTask<ArrangeItem>;
template class Arranger<ArrangeItem>;
}} // namespace Slic3r::arr2

View File

@@ -1,494 +0,0 @@
#ifndef ARRANGEITEM_HPP
#define ARRANGEITEM_HPP
#include <boost/variant.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <assert.h>
#include <stddef.h>
#include <optional>
#include <algorithm>
#include <initializer_list>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <cassert>
#include <cstddef>
#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<Point> m_refs;
mutable std::vector<Point> 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<Point> 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<DecomposedShape> 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<DecomposedShape>(std::move(envelope))}
{}
explicit ArrangeItem(const ExPolygons &shape);
explicit ArrangeItem(Polygon shape);
explicit ArrangeItem(std::initializer_list<Point> 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_<ArrangeItem>
{
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_<ArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static const T *get(const ArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
// Same as above just not const.
template<class T>
static T *get(ArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
static bool has_key(const ArrangeItem &itm, const std::string &key)
{
return itm.datastore().has_key(key);
}
};
template<> struct WritableDataStoreTraits_<ArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static void set(ArrangeItem &itm,
const std::string &key,
T &&data)
{
itm.datastore().add(key, std::forward<T>(data));
}
};
template<class FixedIt, class StopCond = DefaultStopCondition>
static Polygons calculate_nfp_unnormalized(const ArrangeItem &item,
const Range<FixedIt> &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_<ArrangeItem> {
template<class Context, class Bed, class StopCond>
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<Bed, InfiniteBed>) {
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<double>(1.) *
scaled<double>(1.);
}
static double fixed_area(const ArrangeItem &itm)
{
return itm.shape().area_unscaled() * scaled<double>(1.) *
scaled<double>(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<double>& allowed_rotations(const ArrangeItem &itm)
{
static const std::vector<double> ret_zero = {0.};
const std::vector<double> * ret_ptr = &ret_zero;
auto rots = get_data<std::vector<double>>(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_<ArrangeItem>: public std::true_type {};
template<>
struct MutableItemTraits_<ArrangeItem> {
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<class T>
static void set_arbitrary_data(ArrangeItem &itm, const std::string &key, T &&data)
{
set_data(itm, key, std::forward<T>(data));
}
static void set_allowed_rotations(ArrangeItem &itm, const std::vector<double> &rotations)
{
set_data(itm, "rotations", rotations);
}
};
extern template struct ImbueableItemTraits_<ArrangeItem>;
extern template class ArrangeableToItemConverter<ArrangeItem>;
extern template struct ArrangeTask<ArrangeItem>;
extern template struct FillBedTask<ArrangeItem>;
extern template struct MultiplySelectionTask<ArrangeItem>;
extern template class Arranger<ArrangeItem>;
}} // namespace Slic3r::arr2
#endif // ARRANGEITEM_HPP

View File

@@ -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<class Itm> 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<class Itm, class En = void> struct MutableItemTraits_
{
static_assert(IsMutableItem_<Itm>::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<class T>
static void set_arbitrary_data(Itm &itm, const std::string &key, T &&data)
{
if constexpr (IsWritableDataStore<Itm>)
set_data(itm, key, std::forward<T>(data));
}
static void set_allowed_rotations(Itm &itm,
const std::vector<double> &rotations)
{
itm.set_allowed_rotations(rotations);
}
};
template<class T>
using MutableItemTraits = MutableItemTraits_<StripCVRef<T>>;
template<class T> constexpr bool IsMutableItem = IsMutableItem_<T>::value;
template<class T, class TT = T>
using MutableItemOnly = std::enable_if_t<IsMutableItem<T>, TT>;
template<class Itm> void set_priority(Itm &itm, int p)
{
MutableItemTraits<Itm>::set_priority(itm, p);
}
template<class Itm> void set_convex_shape(Itm &itm, const Polygon &shape)
{
MutableItemTraits<Itm>::set_convex_shape(itm, shape);
}
template<class Itm> void set_shape(Itm &itm, const ExPolygons &shape)
{
MutableItemTraits<Itm>::set_shape(itm, shape);
}
template<class Itm>
void set_convex_envelope(Itm &itm, const Polygon &envelope)
{
MutableItemTraits<Itm>::set_convex_envelope(itm, envelope);
}
template<class Itm> void set_envelope(Itm &itm, const ExPolygons &envelope)
{
MutableItemTraits<Itm>::set_envelope(itm, envelope);
}
template<class T, class Itm>
void set_arbitrary_data(Itm &itm, const std::string &key, T &&data)
{
MutableItemTraits<Itm>::set_arbitrary_data(itm, key, std::forward<T>(data));
}
template<class Itm>
void set_allowed_rotations(Itm &itm, const std::vector<double> &rotations)
{
MutableItemTraits<Itm>::set_allowed_rotations(itm, rotations);
}
template<class ArrItem> int raise_priority(ArrItem &itm)
{
int ret = get_priority(itm) + 1;
set_priority(itm, ret);
return ret;
}
template<class ArrItem> int reduce_priority(ArrItem &itm)
{
int ret = get_priority(itm) - 1;
set_priority(itm, ret);
return ret;
}
template<class It> int lowest_priority(const Range<It> &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

View File

@@ -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<SimpleArrangeItem>;
template struct ArrangeTask<SimpleArrangeItem>;
template struct FillBedTask<SimpleArrangeItem>;
template struct MultiplySelectionTask<SimpleArrangeItem>;
template class Arranger<SimpleArrangeItem>;
}} // namespace Slic3r::arr2

View File

@@ -1,227 +0,0 @@
#ifndef SIMPLEARRANGEITEM_HPP
#define SIMPLEARRANGEITEM_HPP
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#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<double> 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<double> 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_<SimpleArrangeItem>
{
template<class Context, class Bed, class StopCond>
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<Bed, InfiniteBed>) {
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_<SimpleArrangeItem>: public std::true_type {};
template<>
struct MutableItemTraits_<SimpleArrangeItem> {
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<class T>
static void set_data(SimpleArrangeItem &itm, const std::string &key, T &&data)
{}
static void set_allowed_rotations(SimpleArrangeItem &itm, const std::vector<double> &rotations)
{
itm.set_allowed_rotations(rotations);
}
};
template<> struct ImbueableItemTraits_<SimpleArrangeItem>
{
static void imbue_id(SimpleArrangeItem &itm, const ObjectID &id)
{
itm.set_object_id(id);
}
static std::optional<ObjectID> retrieve_id(const SimpleArrangeItem &itm)
{
std::optional<ObjectID> ret;
if (itm.get_object_id().valid())
ret = itm.get_object_id();
return ret;
}
};
extern template class ArrangeableToItemConverter<SimpleArrangeItem>;
extern template struct ArrangeTask<SimpleArrangeItem>;
extern template struct FillBedTask<SimpleArrangeItem>;
extern template struct MultiplySelectionTask<SimpleArrangeItem>;
extern template class Arranger<SimpleArrangeItem>;
}} // namespace Slic3r::arr2
#endif // SIMPLEARRANGEITEM_HPP

View File

@@ -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<class ArrItm>
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_<TrafoOnlyArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static const T *get(const TrafoOnlyArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
template<class T>
static T *get(TrafoOnlyArrangeItem &itm, const std::string &key)
{
return itm.datastore().get<T>(key);
}
static bool has_key(const TrafoOnlyArrangeItem &itm, const std::string &key)
{
return itm.datastore().has_key(key);
}
};
template<> struct IsMutableItem_<TrafoOnlyArrangeItem>: public std::true_type {};
template<> struct WritableDataStoreTraits_<TrafoOnlyArrangeItem>
{
static constexpr bool Implemented = true;
template<class T>
static void set(TrafoOnlyArrangeItem &itm,
const std::string &key,
T &&data)
{
set_data(itm.datastore(), key, std::forward<T>(data));
}
};
} // namespace arr2
} // namespace Slic3r
#endif // TRAFOONLYARRANGEITEM_HPP

View File

@@ -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<ObjectID> Scene::selected_ids() const
{
auto items = reserve_vector<ObjectID>(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> ArrangeTaskBase::create(Tasks task_type, const Scene &sc)
{
std::unique_ptr<ArrangeTaskBase> ret;
switch(task_type) {
case Tasks::Arrange:
ret = ArrangeTask<ArrangeItem>::create(sc);
break;
case Tasks::FillBed:
ret = FillBedTask<ArrangeItem>::create(sc);
break;
default:
;
}
return ret;
}
std::set<ObjectID> selected_geometry_ids(const Scene &sc)
{
std::set<ObjectID> result;
std::vector<ObjectID> 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

View File

@@ -1,429 +0,0 @@
#ifndef ARR2_SCENE_HPP
#define ARR2_SCENE_HPP
#include <stddef.h>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <any>
#include <string_view>
#include <algorithm>
#include <functional>
#include <memory>
#include <set>
#include <type_traits>
#include <utility>
#include <vector>
#include <cstddef>
#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<void(Arrangeable &)>) = 0;
virtual void for_each_arrangeable(std::function<void(const Arrangeable&)>) const = 0;
// Visit a specific arrangeable identified by it's id
virtual void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)>) const = 0;
virtual void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)>) = 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<size_t, 4>,
std::integral_constant<size_t, 4>>;
// ExtendedBed is a variant type holding all bed types supported by the
// arrange core and the additional XLBed
template<class... Args> struct ExtendedBed_
{
using Type =
boost::variant<XLBed, /* insert other types if needed*/ Args...>;
};
template<class... Args> struct ExtendedBed_<boost::variant<Args...>>
{
using Type = boost::variant<XLBed, Args...>;
};
using ExtendedBed = typename ExtendedBed_<ArrangeBed>::Type;
template<class BedFn> void visit_bed(BedFn &&fn, const ExtendedBed &bed)
{
boost::apply_visitor(fn, bed);
}
template<class BedFn> 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 Subclass>
class SceneBuilderBase
{
protected:
AnyPtr<ArrangeableModel> m_arrangeable_model;
AnyPtr<const ArrangeSettingsView> 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<const ArrangeSettingsView> settings)
{
m_settings = std::move(settings);
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_arrange_settings(const ArrangeSettingsView &settings)
{
m_settings = std::make_unique<ArrangeSettings>(settings);
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_bed(const Points &pts)
{
m_bed = arr2::to_arrange_bed(pts);
return std::move(static_cast<Subclass&>(*this));
}
Subclass && set_bed(const arr2::ArrangeBed &bed)
{
m_bed = bed;
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_bed(const XLBed &bed)
{
m_bed = bed;
return std::move(static_cast<Subclass&>(*this));
}
Subclass &&set_arrangeable_model(AnyPtr<ArrangeableModel> model)
{
m_arrangeable_model = std::move(model);
return std::move(static_cast<Subclass&>(*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<BasicSceneBuilder> {};
// The Scene class captures all data needed to do an arrangement.
class Scene
{
template <class Sub> friend class SceneBuilderBase;
// These fields always need to be initialized to valid objects after
// construction of Scene which is ensured by the SceneBuilder
AnyPtr<ArrangeableModel> m_amodel;
AnyPtr<const ArrangeSettingsView> 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<class Sub>
explicit Scene(SceneBuilderBase<Sub> &&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<class BedFn> void visit_bed(BedFn &&fn) const
{
arr2::visit_bed(fn, m_bed);
}
const ExtendedBed & bed() const { return m_bed; }
std::vector<ObjectID> selected_ids() const;
};
// Get all the ObjectIDs of Arrangeables which are in selected state
std::set<ObjectID> 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<void(Arrangeable &)>) override {}
void for_each_arrangeable(std::function<void(const Arrangeable&)>) const override {}
void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)>) const override {}
void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)>) override {}
ObjectID add_arrangeable(const ObjectID &prototype_id) override { return {}; }
};
template<class Subclass>
void SceneBuilderBase<Subclass>::build_scene(Scene &sc) &&
{
if (!m_arrangeable_model)
m_arrangeable_model = std::make_unique<EmptyArrangeableModel>();
if (!m_settings)
m_settings = std::make_unique<arr2::ArrangeSettings>();
// 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<ArrangeResult> process(Ctl &ctl) = 0;
[[nodiscard]] virtual int item_count_to_process() const = 0;
[[nodiscard]] static std::unique_ptr<ArrangeTaskBase> create(
Tasks task_type, const Scene &sc);
[[nodiscard]] std::unique_ptr<ArrangeResult> process(Ctl &&ctl)
{
return process(ctl);
}
[[nodiscard]] std::unique_ptr<ArrangeResult> 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<class Builder, class Ctl = DummyCtl>
bool arrange(SceneBuilderBase<Builder> &&builder, Ctl &&ctl = {})
{
return arrange(Scene{std::move(builder)}, ctl);
}
} // namespace arr2
} // namespace Slic3r
#endif // ARR2_SCENE_HPP

View File

@@ -1,942 +0,0 @@
#ifndef SCENEBUILDER_CPP
#define SCENEBUILDER_CPP
#include "SceneBuilder.hpp"
#include <cmath>
#include <limits>
#include <numeric>
#include <cstdlib>
#include <iterator>
#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<decltype(rawbed), InfiniteBed>;
},
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<ArrangeableSLAPrint>(m_sla_print.get(), *this);
} else {
m_arrangeable_model = std::make_unique<ArrangeableSlicerModel>(*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<SceneBuilder>::build_scene(sc);
}
void SceneBuilder::build_arrangeable_slicer_model(ArrangeableSlicerModel &amodel)
{
if (!m_model)
m_model = std::make_unique<Model>();
if (!m_selection)
m_selection = std::make_unique<FixedSelection>(*m_model);
if (!m_vbed_handler) {
m_vbed_handler = VirtualBedHandler::create(m_bed);
}
if (!m_wipetower_handler) {
m_wipetower_handler = std::make_unique<MissingWipeTowerHandler>();
}
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<int>::min())
bedidx = std::numeric_limits<int>::min();
else if (bedidx_d > std::numeric_limits<int>::max())
bedidx = std::numeric_limits<int>::max();
else
bedidx = static_cast<int>(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<int>::min())
bedidx = std::numeric_limits<int>::min();
else if (bedidx_d > std::numeric_limits<int>::max())
bedidx = std::numeric_limits<int>::max();
else
bedidx = static_cast<int>(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<int>(std::sqrt(std::numeric_limits<int>::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<int>(obj_sel.size()); ++oidx)
m_seldata.emplace_back(other.selected_instances(oidx));
}
std::vector<bool> FixedSelection::selected_objects() const
{
auto ret = Slic3r::reserve_vector<bool>(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<size_t> find_true_indices(const std::vector<bool> &v)
{
auto ret = reserve_vector<size_t>(v.size());
for (size_t i = 0; i < v.size(); ++i)
if (v[i])
ret.emplace_back(i);
return ret;
}
std::vector<size_t> selected_object_indices(const SelectionMask &sm)
{
auto sel = sm.selected_objects();
return find_true_indices(sel);
}
std::vector<size_t> 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<Model> 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<const Print> mdl_print)
{
m_fff_print = std::move(mdl_print);
return std::move(*this);
}
SceneBuilder &&SceneBuilder::set_sla_print(AnyPtr<const SLAPrint> 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<void(Arrangeable &)> fn)
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
void ArrangeableSlicerModel::for_each_arrangeable(
std::function<void(const Arrangeable &)> 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<class Self, class Fn>
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<class Self, class Fn>
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<void(const Arrangeable &)> fn) const
{
visit_arrangeable_(*this, id, fn);
}
void ArrangeableSlicerModel::visit_arrangeable(
const ObjectID &id, std::function<void(Arrangeable &)> fn)
{
visit_arrangeable_(*this, id, fn);
}
template<class Self, class Fn>
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<void(Arrangeable &)> fn)
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
void ArrangeableSLAPrint::for_each_arrangeable(
std::function<void(const Arrangeable &)> fn) const
{
for_each_arrangeable_(*this, fn);
m_wth->visit(fn);
}
template<class Self, class Fn>
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<void(const Arrangeable &)> fn) const
{
visit_arrangeable_(*this, id, fn);
}
void ArrangeableSLAPrint::visit_arrangeable(
const ObjectID &id, std::function<void(Arrangeable &)> fn)
{
visit_arrangeable_(*this, id, fn);
}
template<class InstPtr, class VBedHPtr>
ExPolygons ArrangeableModelInstance<InstPtr, VBedHPtr>::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<class InstPtr, class VBedHPtr>
Polygon ArrangeableModelInstance<InstPtr, VBedHPtr>::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<class InstPtr, class VBedHPtr>
bool ArrangeableModelInstance<InstPtr, VBedHPtr>::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<class InstPtr, class VBedHPtr>
void ArrangeableModelInstance<InstPtr, VBedHPtr>::transform(const Vec2d &transl, double rot)
{
if constexpr (!std::is_const_v<InstPtr> && !std::is_const_v<VBedHPtr>) {
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<class InstPtr, class VBedHPtr>
bool ArrangeableModelInstance<InstPtr, VBedHPtr>::assign_bed(int bed_idx)
{
bool ret = false;
if constexpr (!std::is_const_v<InstPtr> && !std::is_const_v<VBedHPtr>)
ret = m_vbedh->assign_bed(*this, bed_idx);
return ret;
}
template class ArrangeableModelInstance<ModelInstance, VirtualBedHandler>;
template class ArrangeableModelInstance<const ModelInstance, const VirtualBedHandler>;
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<float>();
trafo_instance = trafo_instance * m_po->trafo().cast<float>().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<float>();
trafo_instance = trafo_instance * m_po->trafo().cast<float>().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<Model> mdl, AnyPtr<VirtualBedHandler> 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<class Mdl, class Dup, class VBH>
ObjectID ArrangeableFullModel<Mdl, Dup, VBH>::geometry_id() const { return m_mdl->id(); }
template<class Mdl, class Dup, class VBH>
ExPolygons ArrangeableFullModel<Mdl, Dup, VBH>::full_outline() const
{
auto ret = reserve_vector<ExPolygon>(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<class Mdl, class Dup, class VBH>
Polygon ArrangeableFullModel<Mdl, Dup, VBH>::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<Model, ModelDuplicate, VirtualBedHandler>;
template class ArrangeableFullModel<const Model, const ModelDuplicate, const VirtualBedHandler>;
std::unique_ptr<VirtualBedHandler> VirtualBedHandler::create(const ExtendedBed &bed)
{
std::unique_ptr<VirtualBedHandler> ret;
if (is_infinite_bed(bed)) {
ret = std::make_unique<PhysicalOnlyVBedHandler>();
} 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<GridStriderVBedHandler>(bedbb, xgap);
}
return ret;
}
}} // namespace Slic3r::arr2
#endif // SCENEBUILDER_CPP

View File

@@ -1,714 +0,0 @@
#ifndef SCENEBUILDER_HPP
#define SCENEBUILDER_HPP
#include <assert.h>
#include <stddef.h>
#include <algorithm>
#include <functional>
#include <initializer_list>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include <cassert>
#include <cstddef>
#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<bool()>;
// 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<void(Arrangeable &)>) = 0;
virtual void visit(std::function<void(const Arrangeable &)>) 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<VirtualBedHandler> create(const ExtendedBed &bed);
};
// Holds the info about which object (ID) is selected/unselected
class SelectionMask
{
public:
virtual ~SelectionMask() = default;
virtual std::vector<bool> selected_objects() const = 0;
virtual std::vector<bool> selected_instances(int obj_id) const = 0;
virtual bool is_wipe_tower() const = 0;
};
class FixedSelection : public Slic3r::arr2::SelectionMask
{
std::vector<std::vector<bool>> m_seldata;
bool m_wp = false;
public:
FixedSelection() = default;
explicit FixedSelection(std::initializer_list<std::vector<bool>> seld,
bool wp = false)
: m_seldata{std::move(seld)}, m_wp{wp}
{}
explicit FixedSelection(const Model &m);
explicit FixedSelection(const SelectionMask &other);
std::vector<bool> selected_objects() const override;
std::vector<bool> selected_instances(int obj_id) const override
{
return obj_id < int(m_seldata.size()) ? m_seldata[obj_id] :
std::vector<bool>{};
}
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<Model> m_model;
AnyPtr<WipeTowerHandler> m_wth; // Determines how wipe tower is handled
AnyPtr<VirtualBedHandler> m_vbed_handler; // Determines how virtual beds are handled
AnyPtr<const SelectionMask> m_selmask; // Determines which objects are selected/unselected
private:
friend class SceneBuilder;
template<class Self, class Fn>
static void for_each_arrangeable_(Self &&self, Fn &&fn);
template<class Self, class Fn>
static void visit_arrangeable_(Self &&self, const ObjectID &id, Fn &&fn);
public:
explicit ArrangeableSlicerModel(SceneBuilder &builder);
~ArrangeableSlicerModel();
void for_each_arrangeable(std::function<void(Arrangeable &)>) override;
void for_each_arrangeable(std::function<void(const Arrangeable&)>) const override;
void visit_arrangeable(const ObjectID &id, std::function<void(const Arrangeable &)>) const override;
void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)>) 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<SceneBuilder>
{
protected:
AnyPtr<Model> m_model;
AnyPtr<WipeTowerHandler> m_wipetower_handler;
AnyPtr<VirtualBedHandler> m_vbed_handler;
AnyPtr<const SelectionMask> m_selection;
AnyPtr<const SLAPrint> m_sla_print;
AnyPtr<const Print> 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<Model> mdl);
SceneBuilder && set_model(Model &mdl);
SceneBuilder && set_fff_print(AnyPtr<const Print> fffprint);
SceneBuilder && set_sla_print(AnyPtr<const SLAPrint> mdl_print);
using SceneBuilderBase<SceneBuilder>::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<WipeTowerHandler> wth)
{
m_wipetower_handler = std::move(wth);
return std::move(*this);
}
SceneBuilder && set_virtual_bed_handler(AnyPtr<VirtualBedHandler> vbedh)
{
m_vbed_handler = std::move(vbedh);
return std::move(*this);
}
SceneBuilder && set_sla_print(const SLAPrint *slaprint);
SceneBuilder && set_selection(AnyPtr<const SelectionMask> 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<void(Arrangeable &)>) override {}
void visit(std::function<void(const Arrangeable &)>) const override {}
void set_selection_predicate(std::function<bool()>) 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<size_t> selected_object_indices(const SelectionMask &sm);
std::vector<size_t> 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 InstPtr, class VBedHPtr>
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<InstPtr>)
transform_instance(*m_mi, transl, rot);
}
};
extern template class ArrangeableModelInstance<ModelInstance, VirtualBedHandler>;
extern template class ArrangeableModelInstance<const ModelInstance, const VirtualBedHandler>;
// 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<class Self, class Fn>
static void for_each_arrangeable_(Self &&self, Fn &&fn);
template<class Self, class Fn>
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<void(Arrangeable &)>) override;
void for_each_arrangeable(
std::function<void(const Arrangeable &)>) const override;
void visit_arrangeable(
const ObjectID &id,
std::function<void(const Arrangeable &)>) const override;
void visit_arrangeable(const ObjectID &id,
std::function<void(Arrangeable &)>) override;
};
template<class Mdl>
auto find_instance_by_id(Mdl &&model, const ObjectID &id)
{
std::remove_reference_t<
decltype(std::declval<Mdl>().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 Mdl, class Dup, class VBH>
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<Mdl> && !std::is_const_v<Dup>) {
m_dup->tr += tr;
m_dup->rot += rot;
}
}
bool assign_bed(int bed_idx) override
{
bool ret = false;
if constexpr (!std::is_const_v<VBH> && !std::is_const_v<Dup>) {
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<Model, ModelDuplicate, VirtualBedHandler>;
extern template class ArrangeableFullModel<const Model, const ModelDuplicate, const VirtualBedHandler>;
// An implementation of the ArrangeableModel to be used for the full model 'duplicate' feature
// accessible from CLI
class DuplicableModel: public ArrangeableModel {
AnyPtr<Model> m_model;
AnyPtr<VirtualBedHandler> m_vbh;
std::vector<ModelDuplicate> m_duplicates;
BoundingBox m_bedbb;
template<class Self, class Fn>
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<Model> mdl,
AnyPtr<VirtualBedHandler> vbh,
const BoundingBox &bedbb);
~DuplicableModel();
void for_each_arrangeable(std::function<void(Arrangeable &)> fn) override
{
for (ModelDuplicate &md : m_duplicates) {
ArrangeableFullModel arrbl{m_model.get(), &md, m_vbh.get()};
fn(arrbl);
}
}
void for_each_arrangeable(std::function<void(const Arrangeable&)> 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<void(const Arrangeable &)> fn) const override
{
visit_arrangeable_(*this, id, fn);
}
void visit_arrangeable(const ObjectID &id, std::function<void(Arrangeable &)> 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

View File

@@ -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<class T> struct IsSegmentedBed_ : public std::false_type {};
template<class T> constexpr bool IsSegmentedBed = IsSegmentedBed_<StripCVRef<T>>::value;
template<class SegX = void, class SegY = void, class Pivot = void>
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<size_t SegX, size_t SegY>
struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<size_t, SegY>>
{
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<size_t SegX, size_t SegY, RectPivots pivot>
struct SegmentedRectangleBed<std::integral_constant<size_t, SegX>,
std::integral_constant<size_t, SegY>,
std::integral_constant<RectPivots, pivot>>
{
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<class... Args>
struct IsSegmentedBed_<SegmentedRectangleBed<Args...>>
: public std::true_type {};
template<class... Args>
auto offset(const SegmentedRectangleBed<Args...> &bed, coord_t val_scaled)
{
auto cpy = bed;
cpy.bb.offset(val_scaled);
return cpy;
}
template<class...Args>
auto bounding_box(const SegmentedRectangleBed<Args...> &bed)
{
return bed.bb;
}
template<class...Args>
auto area(const SegmentedRectangleBed<Args...> &bed)
{
return arr2::area(bed.bb);
}
template<class...Args>
ExPolygons to_expolygons(const SegmentedRectangleBed<Args...> &bed)
{
return to_expolygons(RectangleBed{bed.bb});
}
template<class SegB>
struct IsRectangular_<SegB, std::enable_if_t<IsSegmentedBed<SegB>, void>> : public std::true_type
{};
}} // namespace Slic3r::arr2
#endif // SEGMENTEDRECTANGLEBED_HPP

View File

@@ -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<TrafoOnlyArrangeItem> 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<class ArrItem>
void add_item(const ArrItem &itm)
{
items.emplace_back(itm);
if (auto id = retrieve_id(itm))
imbue_id(items.back(), *id);
}
template<class It>
void add_items(const Range<It> &items_range)
{
for (auto &itm : items_range)
add_item(itm);
}
};
template<class ArrItem> struct ArrangeTask : public ArrangeTaskBase
{
struct ArrangeSet
{
std::vector<ArrItem> selected, unselected;
} printable, unprintable;
ExtendedBed bed;
ArrangeSettings settings;
static std::unique_ptr<ArrangeTask> create(
const Scene &sc,
const ArrangeableToItemConverter<ArrItem> &converter);
static std::unique_ptr<ArrangeTask> create(const Scene &sc)
{
auto conv = ArrangeableToItemConverter<ArrItem>::create(sc);
return create(sc, *conv);
}
std::unique_ptr<ArrangeResult> process(Ctl &ctl) override
{
return process_native(ctl);
}
std::unique_ptr<ArrangeTaskResult> process_native(Ctl &ctl);
std::unique_ptr<ArrangeTaskResult> process_native(Ctl &&ctl)
{
return process_native(ctl);
}
int item_count_to_process() const override
{
return static_cast<int>(printable.selected.size() +
unprintable.selected.size());
}
};
} // namespace arr2
} // namespace Slic3r
#endif // ARRANGETASK_HPP

View File

@@ -1,151 +0,0 @@
#ifndef ARRANGETASK_IMPL_HPP
#define ARRANGETASK_IMPL_HPP
#include <random>
#include <boost/log/trivial.hpp>
#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<class ArrItem>
void extract_selected(ArrangeTask<ArrItem> &task,
const ArrangeableModel &mdl,
const ArrangeableToItemConverter<ArrItem> &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<class ArrItem>
std::unique_ptr<ArrangeTask<ArrItem>> ArrangeTask<ArrItem>::create(
const Scene &sc, const ArrangeableToItemConverter<ArrItem> &converter)
{
auto task = std::make_unique<ArrangeTask<ArrItem>>();
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<class ItemCont>
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<int>& 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<class ArrItem>
std::unique_ptr<ArrangeTaskResult>
ArrangeTask<ArrItem>::process_native(Ctl &ctl)
{
auto result = std::make_unique<ArrangeTaskResult>();
auto arranger = Arranger<ArrItem>::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<int> 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

View File

@@ -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<class ArrItem>
struct FillBedTask: public ArrangeTaskBase
{
std::optional<ArrItem> prototype_item;
std::vector<ArrItem> selected, unselected;
// For workaround regarding "holes" when filling the bed with the same
// item's copies
std::vector<ArrItem> selected_fillers;
ArrangeSettings settings;
ExtendedBed bed;
size_t selected_existing_count = 0;
std::unique_ptr<FillBedTaskResult> process_native(Ctl &ctl);
std::unique_ptr<FillBedTaskResult> process_native(Ctl &&ctl)
{
return process_native(ctl);
}
std::unique_ptr<ArrangeResult> process(Ctl &ctl) override
{
return process_native(ctl);
}
int item_count_to_process() const override
{
return selected.size();
}
static std::unique_ptr<FillBedTask> create(
const Scene &sc,
const ArrangeableToItemConverter<ArrItem> &converter);
static std::unique_ptr<FillBedTask> create(const Scene &sc)
{
auto conv = ArrangeableToItemConverter<ArrItem>::create(sc);
return create(sc, *conv);
}
};
} // namespace arr2
} // namespace Slic3r
#endif // FILLBEDTASK_HPP

View File

@@ -1,208 +0,0 @@
#ifndef FILLBEDTASKIMPL_HPP
#define FILLBEDTASKIMPL_HPP
#include "FillBedTask.hpp"
#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace arr2 {
template<class ArrItem>
int calculate_items_needed_to_fill_bed(const ExtendedBed &bed,
const ArrItem &prototype_item,
size_t prototype_count,
const std::vector<ArrItem> &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<int>(
std::ceil((bed_area - fixed_area) / poly_area));
return needed_items;
}
template<class ArrItem>
void extract(FillBedTask<ArrItem> &task,
const Scene &scene,
const ArrangeableToItemConverter<ArrItem> &itm_conv)
{
task.prototype_item = {};
auto selected_ids = scene.selected_ids();
if (selected_ids.empty())
return;
std::set<ObjectID> 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<class ArrItem>
std::unique_ptr<FillBedTask<ArrItem>> FillBedTask<ArrItem>::create(
const Scene &sc, const ArrangeableToItemConverter<ArrItem> &converter)
{
auto task = std::make_unique<FillBedTask<ArrItem>>();
task->settings.set_from(sc.settings());
task->bed = get_corrected_bed(sc.bed(), converter);
extract(*task, sc, converter);
return task;
}
template<class ArrItem>
std::unique_ptr<FillBedTaskResult> FillBedTask<ArrItem>::process_native(
Ctl &ctl)
{
auto result = std::make_unique<FillBedTaskResult>();
if (!prototype_item)
return result;
result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{});
class FillBedCtl: public ArrangerCtl<ArrItem>
{
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<ArrItem>::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

View File

@@ -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<TrafoOnlyArrangeItem> arranged_items;
std::vector<TrafoOnlyArrangeItem> 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<class ArrItem>
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<class It>
void add_arranged_items(const Range<It> &items_range)
{
arranged_items.reserve(items_range.size());
for (auto &itm : items_range)
add_arranged_item(itm);
}
template<class ArrItem> void add_new_item(const ArrItem &itm)
{
to_add.emplace_back(itm);
}
template<class It> void add_new_items(const Range<It> &items_range)
{
to_add.reserve(items_range.size());
for (auto &itm : items_range) {
to_add.emplace_back(itm);
}
}
};
template<class ArrItem>
struct MultiplySelectionTask: public ArrangeTaskBase
{
std::optional<ArrItem> prototype_item;
std::vector<ArrItem> selected, unselected;
ArrangeSettings settings;
ExtendedBed bed;
size_t selected_existing_count = 0;
std::unique_ptr<MultiplySelectionTaskResult> process_native(Ctl &ctl);
std::unique_ptr<MultiplySelectionTaskResult> process_native(Ctl &&ctl)
{
return process_native(ctl);
}
std::unique_ptr<ArrangeResult> process(Ctl &ctl) override
{
return process_native(ctl);
}
int item_count_to_process() const override
{
return selected.size();
}
static std::unique_ptr<MultiplySelectionTask> create(
const Scene &sc,
size_t multiply_count,
const ArrangeableToItemConverter<ArrItem> &converter);
static std::unique_ptr<MultiplySelectionTask> create(const Scene &sc,
size_t multiply_count)
{
auto conv = ArrangeableToItemConverter<ArrItem>::create(sc);
return create(sc, multiply_count, *conv);
}
};
}} // namespace Slic3r::arr2
#endif // MULTIPLYSELECTIONTASK_HPP

View File

@@ -1,128 +0,0 @@
#ifndef MULTIPLYSELECTIONTASKIMPL_HPP
#define MULTIPLYSELECTIONTASKIMPL_HPP
#include "MultiplySelectionTask.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace arr2 {
template<class ArrItem>
std::unique_ptr<MultiplySelectionTask<ArrItem>> MultiplySelectionTask<ArrItem>::create(
const Scene &scene, size_t count, const ArrangeableToItemConverter<ArrItem> &itm_conv)
{
auto task_ptr = std::make_unique<MultiplySelectionTask<ArrItem>>();
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<ObjectID> 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<class ArrItem>
std::unique_ptr<MultiplySelectionTaskResult>
MultiplySelectionTask<ArrItem>::process_native(Ctl &ctl)
{
auto result = std::make_unique<MultiplySelectionTaskResult>();
if (!prototype_item)
return result;
result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{});
class MultiplySelectionCtl: public ArrangerCtl<ArrItem>
{
ArrangeTaskCtl &parent;
MultiplySelectionTask<ArrItem> &self;
public:
MultiplySelectionCtl(ArrangeTaskCtl &p, MultiplySelectionTask<ArrItem> &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<ArrItem>::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

View File

@@ -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<float>());
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<float>(m_circle.center), unscaled<float>(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<double>()); }) :
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<double>()); });
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<double>()); }) :
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<double>()); });
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<Vec3d> 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<double>::max();
if (ignore_bottom)
build_volume.min.z() = -std::numeric_limits<double>::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

View File

@@ -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;

View File

@@ -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)

View File

@@ -9,6 +9,9 @@
#include "libslic3r/Surface.hpp"
#include "libslic3r/libslic3r.h"
#include <oneapi/tbb/blocked_range.h>
#include <oneapi/tbb/parallel_reduce.h>
// #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::Paths>(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<size_t>(0, subject.size()), Polygons(),
[&subject](tbb::blocked_range<size_t> 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);

View File

@@ -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

View File

@@ -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<bool Open>
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<Open>(expoly.contour.points, z));
for (const Polygon &hole : expoly.holes) {
out.emplace_back(to_zpath<Open>(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<bool Open = false>

View File

@@ -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<class Archive> 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<class Archive> 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<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
if (this->nullable) {
switch (this->type) {
case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break;
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(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<const ConfigOptionFloatNullable*>(opt)); break;
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
case coFloatsOrPercents: archive(*static_cast<const ConfigOptionFloatsOrPercentsNullable*>(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<const ConfigOptionFloat*>(opt)); break;

View File

@@ -1,4 +1,5 @@
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/log/trivial.hpp>
#include <numeric>
#include <cstdlib>
@@ -1086,7 +1087,7 @@ std::unique_ptr<FontFile> Emboss::create_font_file(
std::unique_ptr<FontFile> 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.";

View File

@@ -0,0 +1,230 @@
#include <random>
#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::thread::id>()(std::this_thread::get_id()));
thread_local std::uniform_real_distribution<double> 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>();
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<double>().normalized() * r).cast<coord_t>());
}
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>();
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<double>().normalized() * r).cast<coord_t>(), 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<double>(config.fuzzy_skin_thickness.value), scaled<double>(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<double>(config.fuzzy_skin_thickness.value), scaled<double>(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<double>(base_config.fuzzy_skin_thickness.value), scaled<double>(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<double>(config.fuzzy_skin_thickness.value), scaled<double>(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

View File

@@ -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_

View File

@@ -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<float>::max();
for (const double nozzle_diameter : nozzle_diameters.values) {
min_nozzle_diameter = std::min(min_nozzle_diameter, static_cast<float>(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<float>(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<float>(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<float>(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));
}

View File

@@ -42,6 +42,8 @@ namespace pt = boost::property_tree;
#include "libslic3r/NSVGUtils.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include <fast_float.h>
// 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<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mm_segmentation;
std::vector<std::string> 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<Connector> 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<double>::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<double>::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<CutObjectInfo::Connector> 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<size_t>("<xmlattr>.id")),
cut_id_tree.get<size_t>("<xmlattr>.check_sum"),
cut_id_tree.get<size_t>("<xmlattr>.connectors_cnt"));
cut_id = CutId(cut_id_tree.get<size_t>("<xmlattr>.id"),
cut_id_tree.get<size_t>("<xmlattr>.check_sum"),
cut_id_tree.get<size_t>("<xmlattr>.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<std::string>("<xmlattr>.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<int>("<xmlattr>.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<double> ("<xmlattr>.print_z" );
int extruder = tree.get<int> ("<xmlattr>.extruder");
std::string color = tree.get<std::string> ("<xmlattr>.color" );
pt::ptree code_tree = bed_block.second;
CustomGCode::Type type;
std::string extra;
pt::ptree attr_tree = tree.find("<xmlattr>")->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<std::string> ("<xmlattr>.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<std::string>("<xmlattr>.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<double> ("<xmlattr>.print_z" );
int extruder = tree.get<int> ("<xmlattr>.extruder");
std::string color = tree.get<std::string> ("<xmlattr>.color" );
CustomGCode::Type type;
std::string extra;
pt::ptree attr_tree = tree.find("<xmlattr>")->second;
if (attr_tree.find("type") == attr_tree.not_found()) {
// read old data ...
std::string gcode = tree.get<std::string> ("<xmlattr>.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<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
extra = tree.get<std::string>("<xmlattr>.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<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
extra = tree.get<std::string>("<xmlattr>.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<int>("<xmlattr>.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<double>("<xmlattr>.position_x");
double pos_y = bed_block.second.get<double>("<xmlattr>.position_y");
double rot_deg = bed_block.second.get<double>("<xmlattr>.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; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size());
assert(index < geometry.mm_segmentation.size());
if (! geometry.custom_supports[index].empty())
volume->supported_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("<xmlattr>.id", object->cut_id.id().id);
cut_id_tree.put("<xmlattr>.id", object->cut_id.id());
cut_id_tree.put("<xmlattr>.check_sum", object->cut_id.check_sum());
cut_id_tree.put("<xmlattr>.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<model.get_custom_gcode_per_print_z_vector().size(); ++bed_idx) {
if (bed_idx != 0 && model.get_custom_gcode_per_print_z_vector()[bed_idx].gcodes.empty()) {
// Always save the first bed so older slicers are able to tell
// that there are no color changes on it.
continue;
}
for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
pt::ptree& code_tree = main_tree.add("code", "");
pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
main_tree.put("<xmlattr>.bed_idx" , bed_idx);
// store data of custom_gcode_per_print_z
code_tree.put("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.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("<xmlattr>.gcode" , gcode );
// store data of custom_gcode_per_print_z
code_tree.put("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.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("<xmlattr>.gcode" , gcode );
}
pt::ptree& mode_tree = main_tree.add("mode", "");
// store mode of a custom_gcode_per_print_z
mode_tree.put("<xmlattr>.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("<xmlattr>.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<Semver>& 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("<xmlattr>.bed_idx", bed_idx);
main_tree.put("<xmlattr>.position_x", wipe_tower.position.x());
main_tree.put("<xmlattr>.position_y", wipe_tower.position.y());
main_tree.put("<xmlattr>.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<Semver>& qidislicer_generator_version
) {
if (! config.has("brim_separation")) {
if (auto *opt_elephant_foot = config.option<ConfigOptionFloat>("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<Semver> &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;
}

View File

@@ -1,6 +1,8 @@
#ifndef slic3r_Format_3mf_hpp_
#define slic3r_Format_3mf_hpp_
#include "libslic3r/Semver.hpp"
#include <boost/optional/optional.hpp>
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<Semver> &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

View File

@@ -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<CustomGCode::Type>(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<float>::max_digits10);
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<amf unit=\"millimeter\">\n";
stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\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 << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(str_config) << "</metadata>\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 << " <material id=\"" << material.first << "\">\n";
for (const auto &attr : material.second->attributes)
stream << " <metadata type=\"" << attr.first << "\">" << attr.second << "</metadata>\n";
for (const std::string &key : material.second->config.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << material.second->config.opt_serialize(key) << "</metadata>\n";
stream << " </material>\n";
}
std::string instances;
for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) {
ModelObject *object = model->objects[object_id];
stream << " <object id=\"" << object_id << "\">\n";
for (const std::string &key : object->config.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << object->config.opt_serialize(key) << "</metadata>\n";
if (!object->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(object->name) << "</metadata>\n";
const std::vector<double> &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 << " <metadata type=\"slic3r.layer_height_profile\">";
stream << layer_height_profile.front();
for (size_t i = 1; i < layer_height_profile.size(); ++i)
stream << ";" << layer_height_profile[i];
stream << "\n </metadata>\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 << " <layer_config_ranges>\n";
size_t layer_counter = 0;
for (const auto &range : config_ranges) {
stream << " <range id=\"" << layer_counter << "\">\n";
stream << " <metadata type=\"slic3r.layer_height_range\">";
stream << range.first.first << ";" << range.first.second << "</metadata>\n";
for (const std::string& key : range.second.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << range.second.opt_serialize(key) << "</metadata>\n";
stream << " </range>\n";
layer_counter++;
}
stream << " </layer_config_ranges>\n";
}
const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
if (!sla_support_points.empty()) {
// Store the SLA supports as a single semicolon separated list.
stream << " <metadata type=\"slic3r.sla_support_points\">";
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 </metadata>\n";
}
stream << " <mesh>\n";
stream << " <vertices>\n";
std::vector<int> 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 << " <vertex>\n";
stream << " <coordinates>\n";
Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
stream << " <x>" << v(0) << "</x>\n";
stream << " <y>" << v(1) << "</y>\n";
stream << " <z>" << v(2) << "</z>\n";
stream << " </coordinates>\n";
stream << " </vertex>\n";
}
num_vertices += (int)its.vertices.size();
}
stream << " </vertices>\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 << " <volume>\n";
else
stream << " <volume materialid=\"" << volume->material_id() << "\">\n";
for (const std::string &key : volume->config.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.opt_serialize(key) << "</metadata>\n";
if (!volume->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
stream << " <metadata type=\"slic3r.matrix\">";
const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix();
stream << std::setprecision(std::numeric_limits<double>::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 << "</metadata>\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 << " <metadata type=\"slic3r.source_file\">" << input_file << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
}
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
if (volume->source.is_converted_from_inches)
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
else if (volume->source.is_converted_from_meters)
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
if (volume->source.is_from_builtin_objects)
stream << " <metadata type=\"slic3r.source_is_builtin_volume\">1</metadata>\n";
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
const indexed_triangle_set &its = volume->mesh().its;
for (size_t i = 0; i < its.indices.size(); ++i) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)
stream << " <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n";
stream << " </triangle>\n";
}
stream << " </volume>\n";
}
stream << " </mesh>\n";
stream << " </object>\n";
if (!object->instances.empty()) {
for (ModelInstance *instance : object->instances) {
std::stringstream buf;
buf << " <instance objectid=\"" << object_id << "\">\n"
<< " <deltax>" << instance->get_offset(X) << "</deltax>\n"
<< " <deltay>" << instance->get_offset(Y) << "</deltay>\n"
<< " <deltaz>" << instance->get_offset(Z) << "</deltaz>\n"
<< " <rx>" << instance->get_rotation(X) << "</rx>\n"
<< " <ry>" << instance->get_rotation(Y) << "</ry>\n"
<< " <rz>" << instance->get_rotation(Z) << "</rz>\n"
<< " <scalex>" << instance->get_scaling_factor(X) << "</scalex>\n"
<< " <scaley>" << instance->get_scaling_factor(Y) << "</scaley>\n"
<< " <scalez>" << instance->get_scaling_factor(Z) << "</scalez>\n"
<< " <mirrorx>" << instance->get_mirror(X) << "</mirrorx>\n"
<< " <mirrory>" << instance->get_mirror(Y) << "</mirrory>\n"
<< " <mirrorz>" << instance->get_mirror(Z) << "</mirrorz>\n"
<< " <printable>" << instance->printable << "</printable>\n"
<< " </instance>\n";
//FIXME missing instance->scaling_factor
instances.append(buf.str());
}
}
}
if (! instances.empty()) {
stream << " <constellation id=\"1\">\n";
stream << instances;
stream << " </constellation>\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("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.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("<xmlattr>.gcode" , gcode );
}
pt::ptree& mode_tree = main_tree.add("mode", "");
// store mode of a custom_gcode_per_print_z
mode_tree.put("<xmlattr>.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("<custom_gcodes_per_height");
if (del_header_pos != std::string::npos)
out.erase(out.begin(), out.begin() + del_header_pos);
// Post processing("beautification") of the output string
boost::replace_all(out, "><code", ">\n <code");
boost::replace_all(out, "><mode", ">\n <mode");
boost::replace_all(out, "><", ">\n<");
stream << out << "\n";
}
}
stream << "</amf>\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

View File

@@ -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

View File

@@ -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.");
}
}

View File

@@ -24,6 +24,7 @@
#include "libslic3r.h"
#include "LocalesUtils.hpp"
#include "format.hpp"
#include "Time.hpp"
#include <algorithm>
#include <cstdlib>
@@ -443,9 +444,11 @@ namespace DoExport {
static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector<Extruder>& 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<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time);
print_statistics.normal_print_time_seconds = result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time;
print_statistics.silent_print_time_seconds = result.print_statistics.modes[static_cast<size_t>(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<size_t>(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<std::pair<coordf_t, ObjectsLayerToPrint>> 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<GCode::WipeTowerIntegration>(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<GCode::WipeTowerIntegration>(print.model().wipe_tower().position.cast<float>(), 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<GCode::SmoothPath, std::size_t> split_with_seam(
const ExtrusionLoop &loop,
const boost::variant<Point, Seams::Scarf::Scarf> &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<Point>(&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<Seams::Scarf::Scarf>(&seam)}; scarf != nullptr) {
ExtrusionPaths paths{loop.paths};
const auto apply_smoothing{[&](tcb::span<const ExtrusionPath> paths){
return smooth_path_cache.resolve_or_fit(paths, false, scaled<double>(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<InstancePoint> &previous_position) {
GCode::ExtrusionOrder::PathSmoothingResult operator()(
const Layer *layer,
const PrintRegion *region,
const ExtrusionEntityReference &extrusion_reference,
const unsigned extruder_id,
std::optional<InstancePoint> &previous_position
) {
const ExtrusionEntity *extrusion_entity{&extrusion_reference.extrusion_entity()};
GCode::SmoothPath result;
std::size_t wipe_offset{0};
if (auto loop = dynamic_cast<const ExtrusionLoop *>(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<double>(0.0015)
const auto seam_point_merge_distance_threshold{scaled<double>(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<Point, Seams::Scarf::Scarf> 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<double>(extrusion_clipping),
scaled<double>(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<double>(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<double>(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<const ExtrusionMultiPath *>(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<const ExtrusionPath *>(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<GCode::ExtrusionOrder::ExtruderExtrusions> GCodeGenerator::get_sorted_extrusions(
@@ -2480,6 +2555,8 @@ LayerResult GCodeGenerator::process_layer(
m_enable_loop_clipping = !enable;
}
const float height = first_layer ? static_cast<float>(print_z) : static_cast<float>(print_z) - m_last_layer_z;
using GCode::ExtrusionOrder::ExtruderExtrusions;
const std::vector<ExtruderExtrusions> 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<float>(print_z) : static_cast<float>(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<unsigned int>(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<Point> 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<double>(), p.cast<double>(), 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<std::string()>& 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);
}

View File

@@ -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<InstanceToPrint> 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<std::string()>& insert_gcode

View File

@@ -129,10 +129,10 @@ std::vector<Perimeter> 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, &region, 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<InfillRange> extract_infill_ranges(
std::vector<SmoothPath> paths;
for (const ExtrusionEntityReference &extrusion_reference : sorted_extrusions) {
std::optional<InstancePoint> last_position{get_instance_point(previous_position, offset)};
SmoothPath path{smooth_path(&layer, extrusion_reference, extruder_id, last_position)};
auto [path, _]{smooth_path(&layer, &region, extrusion_reference, extruder_id, last_position)};
if (!path.empty()) {
paths.push_back(std::move(path));
}
@@ -367,7 +367,7 @@ std::vector<SupportPath> get_support_extrusions(
if (collection != nullptr) {
for (const ExtrusionEntity * sub_entity : *collection) {
std::optional<InstancePoint> 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<SupportPath> get_support_extrusions(
}
} else {
std::optional<InstancePoint> 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<ExtruderExtrusions> get_extrusions(
}
const ExtrusionEntityReference entity{*print.skirt().entities[i], reverse};
std::optional<InstancePoint> 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<ExtruderExtrusions> get_extrusions(
const ExtrusionEntityReference entity_reference{*entity, reverse};
std::optional<InstancePoint> 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<ExtruderExtrusions> get_extrusions(
return extrusions;
}
std::optional<InstancePoint> get_first_point(const SmoothPath &path) {
std::optional<Geometry::ArcWelder::Segment> 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<InstancePoint> get_first_point(const std::vector<SmoothPath> &smooth_paths) {
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<SmoothPath> &smooth_paths) {
for (const SmoothPath &path : smooth_paths) {
if (auto result = get_first_point(path)) {
return result;
@@ -625,7 +625,7 @@ std::optional<InstancePoint> get_first_point(const std::vector<SmoothPath> &smoo
return std::nullopt;
}
std::optional<InstancePoint> get_first_point(const std::vector<InfillRange> &infill_ranges) {
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<InfillRange> &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<InstancePoint> get_first_point(const std::vector<InfillRange> &inf
return std::nullopt;
}
std::optional<InstancePoint> get_first_point(const std::vector<Perimeter> &perimeters) {
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<Perimeter> &perimeters) {
for (const Perimeter &perimeter : perimeters) {
if (auto result = get_first_point(perimeter.smooth_path)) {
return result;
@@ -643,7 +643,7 @@ std::optional<InstancePoint> get_first_point(const std::vector<Perimeter> &perim
return std::nullopt;
}
std::optional<InstancePoint> get_first_point(const std::vector<IslandExtrusions> &extrusions) {
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<IslandExtrusions> &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<InstancePoint> get_first_point(const std::vector<IslandExtrusions>
return std::nullopt;
}
std::optional<InstancePoint> get_first_point(const std::vector<SliceExtrusions> &extrusions) {
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<SliceExtrusions> &extrusions) {
for (const SliceExtrusions &slice : extrusions) {
if (auto result = get_first_point(slice.common_extrusions)) {
return result;
@@ -673,44 +673,47 @@ std::optional<InstancePoint> get_first_point(const std::vector<SliceExtrusions>
return std::nullopt;
}
std::optional<Point> get_first_point(const ExtruderExtrusions &extrusions) {
std::optional<Geometry::ArcWelder::Segment> 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<Point> get_first_point(const std::vector<ExtruderExtrusions> &extrusions) {
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<ExtruderExtrusions> &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<ExtruderExtrusions> &extrusions,
const std::vector<InstanceToPrint> &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];
}
}

View File

@@ -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<SmoothPath(
const Layer *, const ExtrusionEntityReference &, const unsigned extruder_id, std::optional<InstancePoint> &previous_position
using PathSmoothingResult = std::pair<GCode::SmoothPath, std::size_t>;
using PathSmoothingFunction = std::function<PathSmoothingResult(
const Layer *, const PrintRegion *, const ExtrusionEntityReference &, const unsigned extruder_id, std::optional<InstancePoint> &previous_position
)>;
struct BrimPath {
@@ -158,7 +160,7 @@ std::vector<ExtruderExtrusions> get_extrusions(
std::optional<Point> previous_position
);
std::optional<Point> get_first_point(const std::vector<ExtruderExtrusions> &extrusions);
std::optional<Geometry::ArcWelder::Segment> get_first_point(const std::vector<ExtruderExtrusions> &extrusions);
const PrintInstance * get_first_instance(
const std::vector<ExtruderExtrusions> &extrusions,

View File

@@ -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<std::optional<double>, 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<Vec3f> 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

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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);

View File

@@ -79,8 +79,26 @@ void LabelObjects::init(const SpanOfConstPtrs<PrintObject>& 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);

View File

@@ -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);
}
}

View File

@@ -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<GCodeLine>;

View File

@@ -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<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {

View File

@@ -122,8 +122,8 @@ std::optional<std::size_t> 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<std::size_t> snap_to_angle(
min_distance = std::numeric_limits<double>::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;

View File

@@ -66,7 +66,7 @@ Vec2d get_polygon_normal(
std::optional<std::size_t> previous_index;
std::optional<std::size_t> 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<Vec2d> 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<bool(std::size_t)> &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<bool(std::size_t)> &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<double> get_vertex_angles(const std::vector<Vec2d> &points, const do
std::optional<std::size_t> previous_index;
std::optional<std::size_t> 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<double> get_vertex_angles(const std::vector<Vec2d> &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<PointOnLine> 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<PointOnLine> 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

View File

@@ -147,12 +147,12 @@ void iterate_nested(const NestedVector &nested_vector, const std::function<void(
});
}
void visit_near_forward(
void visit_forward(
const std::size_t start_index,
const std::size_t loop_size,
const std::function<bool(std::size_t)> &visitor
);
void visit_near_backward(
void visit_backward(
const std::size_t start_index,
const std::size_t loop_size,
const std::function<bool(std::size_t)> &visitor
@@ -192,6 +192,24 @@ std::pair<std::size_t, double> 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<PointOnLine> 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_

View File

@@ -58,10 +58,7 @@ std::pair<std::vector<Vec2d>, std::vector<PointType>> 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<Vec2d> simplification_result;
douglas_peucker<double>(
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<AngleType> 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<AngleType> 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;

View File

@@ -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<std::size_t, Vec2d> 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<Linef> &distancer
) {
const AABBTreeLines::LinesDistancer<Linef> 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<std::size_t, Vec2d> project_to_extrusion_loop(
return {loop_line_index, loop_point};
}
std::optional<Vec2d> 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<Vec2d> 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<Point, Scarf::Scarf> 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<Linef> 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<Vec2d> staggered_point{
offset_along_loop_lines(loop_point, loop_line_index, loop_lines, staggering_offset)};
std::optional<Geometry::PointOnLine> 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<double>::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<Geometry::PointOnLine> 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<Geometry::PointOnLine> 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<Point, Scarf::Scarf> 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<const SupportLayer *>(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::BoundedPerimeter> &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::BoundedPerimeter> &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<SeamPerimeterChoice> &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

View File

@@ -7,6 +7,7 @@
#include <atomic>
#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<void(void)> &throw_if_canceled
);
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const;
boost::variant<Point, Scarf::Scarf> place_seam(
const Layer *layer, const PrintRegion *region, const ExtrusionLoop &loop, const bool flipped, const Point &last_pos
) const;
private:
Params params;

View File

@@ -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<false>(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<false>(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<false>(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};
}
};

View File

@@ -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<ExtrusionPath, ExtrusionPath> split_path(
const ExtrusionPath &path, const Point &point, const std::size_t point_previous_index
) {
if (static_cast<int>(point_previous_index) >= static_cast<int>(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<ExtrusionPath, ExtrusionPath> 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<const GCode::SmoothPathElement> 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<double>().norm();
previous_point = segment.point;
}
}
return result;
}
GCode::SmoothPath convert_to_smooth(tcb::span<const ExtrusionPath> 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 &current_point{points[i]};
const Point &previous_point{points[i - 1]};
const double distance = (current_point - previous_point).cast<double>().norm();
if (distance > max_distance) {
const std::size_t points_count{
static_cast<std::size_t>(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<Point> 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<double>().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<Point> 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<double>().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<GCode::SmoothPath(tcb::span<const ExtrusionPath>)> &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<PathPoint> 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<int>(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<int>(path.polyline.size() - 2)}; point_index >= 0;
--point_index) {
const Point &previous_point{path.polyline[point_index + 1]};
const Point &current_point{path.polyline[point_index]};
const Line line{previous_point, current_point};
if (is_on_line(point, line, tolerance)) {
return PathPoint{
point,
static_cast<size_t>(path_index), static_cast<size_t>(point_index)
};
}
}
}
return std::nullopt;
}
std::optional<PathPoint> 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<int>(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<int>(path.polyline.size() - 2)}; point_index >= 0;
--point_index) {
const Point &previous_point{path.polyline[point_index + 1]};
const Point &current_point{path.polyline[point_index]};
const Vec2d edge{(current_point - previous_point).cast<double>()};
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<int>(),
static_cast<size_t>(path_index), static_cast<size_t>(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<GCode::SmoothPath, std::size_t> add_scarf_seam(
ExtrusionPaths &&paths,
const Scarf &scarf,
const std::function<GCode::SmoothPath(tcb::span<const ExtrusionPath>)> &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<int>(path.size()) - 1) {
// Last point of the path is picked. This is invalid for splitting.
if (static_cast<int>(end_point.path_index) < static_cast<int>(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<Impl::PathPoint> 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

View File

@@ -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<SmoothPathElement>;
}
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<GCode::SmoothPath(tcb::span<const ExtrusionPath>)>;
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<ExtrusionPath, ExtrusionPath> 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<const GCode::SmoothPathElement> smooth_path);
GCode::SmoothPath convert_to_smooth(tcb::span<const ExtrusionPath> 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<PathPoint> get_point_offset_from_end(const ExtrusionPaths &paths, const double length);
std::optional<PathPoint> find_path_point_from_end(
const ExtrusionPaths &paths,
const Point &point,
const double tolerance
);
} // namespace Impl
std::pair<GCode::SmoothPath, std::size_t> add_scarf_seam(
ExtrusionPaths &&paths,
const Scarf &scarf,
const SmoothingFunction &apply_smoothing,
const bool flipped
);
} // namespace Slic3r::Seams::Scarf
#endif // libslic3r_SeamScarf_hpp_

View File

@@ -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<const ExtrusionPath> 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.

View File

@@ -4,6 +4,7 @@
#include <ankerl/unordered_dense.h>
#include <optional>
#include <vector>
#include <tcbspan/span.hpp>
#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<const ExtrusionPath> 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; }

View File

@@ -25,7 +25,7 @@ using ThumbnailsList = std::vector<ThumbnailData>;
struct ThumbnailsParams
{
const Vec2ds sizes;
Vec2ds sizes;
bool printable_only;
bool parts_only;
bool show_bed;

View File

@@ -160,17 +160,17 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
std::vector<std::pair<double, unsigned int>> 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<std::pair<double, unsigned int>> 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<unsigned char> 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

View File

@@ -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

View File

@@ -534,11 +534,10 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer,
WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default_region_config, const std::vector<std::vector<float>>& 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<std::vector<float>>& 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<Vec2d>& 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)

Some files were not shown because too many files have changed in this diff Show More