Scarf seam implementation.

This commit is contained in:
Martin Šach 2024-07-11 09:49:14 +02:00 committed by Lukas Matena
parent 7042c1a34e
commit aa6132ebb6
25 changed files with 1151 additions and 163 deletions

View File

@ -195,6 +195,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

View File

@ -1273,7 +1273,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();
@ -2267,6 +2269,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);
@ -2276,33 +2280,86 @@ 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 std::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 (std::holds_alternative<Point>(seam)) {
const auto seam_point = std::get<Point>(seam);
return {
smooth_path_cache.resolve_or_fit_split_with_seam(
loop, flipped, scaled_resolution, seam_point, seam_point_merge_distance_threshold
),
0};
} else if (std::holds_alternative<Seams::Scarf::Scarf>(seam)) {
const auto scarf{std::get<Seams::Scarf::Scarf>(seam)};
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
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) {
std::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
@ -2310,19 +2367,21 @@ struct SmoothPathGenerator {
const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)};
if (enable_loop_clipping) {
clip_end(
smooth_path,
scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER,
result, scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER,
scaled<double>(GCode::ExtrusionOrder::min_gcode_segment_length)
);
}
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()) {
@ -2331,9 +2390,8 @@ struct SmoothPathGenerator {
}
}
return result;
return {result, wipe_offset};
}
};
std::vector<GCode::ExtrusionOrder::ExtruderExtrusions> GCodeGenerator::get_sorted_extrusions(
@ -2458,6 +2516,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)};
@ -2465,7 +2525,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);
@ -2479,7 +2540,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";
@ -2499,7 +2559,7 @@ 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
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);
@ -2590,9 +2650,7 @@ LayerResult GCodeGenerator::process_layer(
}
if (!this->m_moved_to_first_layer_point) {
const Vec3crd point{to_3d(first_point, scaled(print_z))};
gcode += this->travel_to_first_position(point, print_z, ExtrusionRole::Mixed, [this]() {
gcode += this->travel_to_first_position(first_point, print_z, ExtrusionRole::Mixed, [this]() {
if (m_writer.multiple_extruders) {
return std::string{""};
}
@ -2875,7 +2933,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;
@ -2913,8 +2975,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));
@ -2971,7 +3038,7 @@ std::string GCodeGenerator::extrude_perimeters(
// Apply the small perimeter speed.
if (perimeter.extrusion_entity->length() <= SMALL_PERIMETER_LENGTH)
speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
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
);
@ -3083,8 +3150,9 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
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{
@ -3170,7 +3238,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;
@ -3352,7 +3422,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;
@ -3373,8 +3443,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);
@ -3528,19 +3605,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);
@ -3556,7 +3635,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 {
@ -3568,7 +3647,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);
@ -3577,7 +3655,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(
@ -3589,6 +3667,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

@ -284,9 +284,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.
@ -334,8 +340,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) {

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

@ -387,7 +387,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>();
@ -399,6 +399,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

@ -92,6 +92,7 @@ public:
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);
@ -211,6 +212,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

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

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

@ -164,7 +164,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 +174,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

@ -10,6 +10,7 @@
#include "SeamPlacer.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/GCode/SeamShells.hpp"
#include "libslic3r/GCode/SeamAligned.hpp"
#include "libslic3r/GCode/SeamRear.hpp"
@ -216,68 +217,116 @@ 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;
}
std::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]{
auto [loop_line_index, loop_point]{
project_to_extrusion_loop(seam_choice, perimeter, loop_lines)};
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_loop_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, loop_lines);
}
}
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 (place_scarf_seam) {
Scarf::Scarf scarf{};
scarf.length = region->config().scarf_seam_length;
scarf.entire_loop = region->config().scarf_seam_entire_loop;
scarf.max_segment_length = region->config().scarf_seam_max_segment_length;
scarf.start_height = region->config().scarf_seam_start_height.get_abs_value(1.0);
if (loop.role() == ExtrusionRole::Perimeter) { // Inner perimeter
const double offset{scarf.entire_loop ? 0 : scarf.length};
const ExPolygons shrank_polygons{shrink_ex({perimeter_polygon}, scaled(loop_width / 2.0))};
if (shrank_polygons.empty()) {
return scaled(loop_point);
}
const std::optional<Geometry::PointOnLine> outter_scarf_start_point{Geometry::offset_along_loop_lines(
seam_choice.position, seam_choice.previous_index, to_unscaled_linesf({shrank_polygons.front()}), offset, offset_direction
)};
if (!outter_scarf_start_point) {
return scaled(loop_point);
}
const auto [end_point_previous_index, end_point]{project_to_extrusion_loop(
to_seam_choice(*outter_scarf_start_point, perimeter), perimeter, loop_lines
)};
if (!region->config().scarf_seam_on_inner_perimeters) {
return scaled(end_point);
}
scarf.end_point = scaled(end_point);
scarf.end_point_previous_index = end_point_previous_index;
} else { // Outter perimeter
scarf.end_point = scaled(loop_point);
scarf.end_point_previous_index = loop_line_index;
}
return scarf;
}
return scaled(loop_point);
}
@ -354,7 +403,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 {
std::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);
@ -362,16 +413,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]};
@ -387,14 +437,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

@ -12,6 +12,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"
@ -37,7 +38,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);
@ -53,7 +54,9 @@ public:
const std::function<void(void)> &throw_if_canceled
);
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const;
std::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

@ -0,0 +1,334 @@
#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)};
const auto path_iterator{result.begin() + path_point.path_index};
result.erase(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;
}
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) {
start_point = Impl::get_point_offset_from_end(paths, scaled(scarf.length));
}
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,85 @@
#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 end_point;
std::size_t end_point_previous_index{};
double length{};
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);
} // 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

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

@ -63,17 +63,18 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|| will_go_down); // don't dig into the print
if (should_travel_to_tower) {
const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos);
const Vec3crd to{to_3d(xy_point, scaled(z))};
gcode += gcodegen.m_label_objects.maybe_stop_instance();
gcode += gcodegen.retract_and_wipe();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
const std::string comment{"Travel to a Wipe Tower"};
if (!gcodegen.m_moved_to_first_layer_point) {
const Vec3crd point = to_3d(xy_point, scaled(z));
gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";});
gcode += gcodegen.travel_to_first_position(to, current_z, ExtrusionRole::Mixed, [](){return "";});
} else {
if (gcodegen.last_position) {
const Vec3crd from{to_3d(*gcodegen.last_position, scaled(z))};
gcode += gcodegen.travel_to(
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";}
from, to, ExtrusionRole::Mixed, comment, [](){return "";}
);
} else {
gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment);

View File

@ -98,8 +98,6 @@ static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt
static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance)
{
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(end - begin >= 3);
// Test the 1st point.

View File

@ -424,6 +424,9 @@ struct Segment
// CCW or CW. Ignored for zero radius (linear segment).
Orientation orientation{ Orientation::CCW };
float height_fraction{ 1.f };
float e_fraction{ 1.f };
bool linear() const { return radius == 0; }
bool ccw() const { return orientation == Orientation::CCW; }
bool cw() const { return orientation == Orientation::CW; }

View File

@ -20,6 +20,7 @@ add_executable(${_TEST_NAME}_tests
test_seam_aligned.cpp
test_seam_rear.cpp
test_seam_random.cpp
test_seam_scarf.cpp
benchmark_seams.cpp
test_gcodefindreplace.cpp
test_gcodewriter.cpp

View File

@ -105,13 +105,14 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm
using namespace Slic3r;
Placer placer;
placer.init(print->objects(), params, [](){});
std::vector<std::pair<const Layer*, const ExtrusionLoop*>> loops;
std::vector<std::tuple<const Layer*, const ExtrusionLoop*, const PrintRegion *>> loops;
const PrintObject* object{print->objects().front()};
for (const Layer* layer :object->layers()) {
for (const LayerSlice& lslice : layer->lslices_ex) {
for (const LayerIsland &island : lslice.islands) {
const LayerRegion &layer_region = *layer->get_region(island.perimeters.region());
const PrintRegion &region = print->get_print_region(layer_region.region().print_region_id());
for (uint32_t perimeter_id : island.perimeters) {
const auto *entity_collection{static_cast<const ExtrusionEntityCollection*>(layer_region.perimeters().entities[perimeter_id])};
if (entity_collection != nullptr) {
@ -120,7 +121,7 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm
if (loop == nullptr) {
continue;
}
loops.emplace_back(layer, loop);
loops.emplace_back(layer, loop, &region);
}
}
}
@ -129,8 +130,8 @@ TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchm
}
BENCHMARK_ADVANCED("Place seam benchy")(Catch::Benchmark::Chronometer meter) {
meter.measure([&] {
for (const auto &[layer, loop] : loops) {
placer.place_seam(layer, *loop, {0, 0});
for (const auto &[layer, loop, region] : loops) {
placer.place_seam(layer, region, *loop, false, {0, 0});
}
});
};

View File

@ -143,3 +143,30 @@ TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") {
0.0, M_PI / 4.0, M_PI / 4.0, 0.0
}));
}
const Linesf lines{to_unscaled_linesf({ExPolygon{
scaled(Vec2d{0.0, 0.0}),
scaled(Vec2d{1.0, 0.0}),
scaled(Vec2d{1.0, 1.0}),
scaled(Vec2d{0.0, 1.0})
}})};
TEST_CASE("Offset along loop lines forward", "[Seams][SeamGeometry]") {
const std::optional<Seams::Geometry::PointOnLine> result{Seams::Geometry::offset_along_loop_lines(
{0.5, 0.0}, 0, lines, 3.9, Seams::Geometry::Direction1D::forward
)};
REQUIRE(result);
const auto &[point, line_index] = *result;
CHECK((scaled(point) - Point::new_scale(0.4, 0.0)).norm() < scaled(EPSILON));
CHECK(line_index == 0);
}
TEST_CASE("Offset along loop lines backward", "[Seams][SeamGeometry]") {
const std::optional<Seams::Geometry::PointOnLine> result{Seams::Geometry::offset_along_loop_lines(
{1.0, 0.5}, 1, lines, 1.8, Seams::Geometry::Direction1D::backward
)};
REQUIRE(result);
const auto &[point, line_index] = *result;
CHECK((scaled(point) - Point::new_scale(0.0, 0.3)).norm() < scaled(EPSILON));
CHECK(line_index == 3);
}

View File

@ -0,0 +1,295 @@
#include <catch2/catch.hpp>
#include <libslic3r/GCode/SeamScarf.hpp>
#include <libslic3r/GCode/SmoothPath.hpp>
using namespace Slic3r;
using Seams::Scarf::Impl::PathPoint;
TEST_CASE("Get path point", "[Seams][Scarf]") {
using Seams::Scarf::Impl::get_path_point;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(0, 1),
Point::new_scale(0, 2),
Point::new_scale(0, 3),
Point::new_scale(0, 4),
};
const ExtrusionPaths paths{
{{points[0], points[1]}, {}},
{{points[1], points[2]}, {}},
{{points[2], points[3], points[4]}, {}},
};
const std::size_t global_index{5}; // Index if paths are flattened.
const Point point{Point::new_scale(0, 3.5)};
const PathPoint path_point{get_path_point(paths, point, global_index)};
CHECK(path_point.path_index == 2);
CHECK(path_point.previous_point_on_path_index == 1);
CHECK(path_point.point == point);
}
TEST_CASE("Split path", "[Seams][Scarf]") {
using Seams::Scarf::Impl::split_path;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(1, 0),
Point::new_scale(2, 0),
};
const auto split_point{Point::new_scale(1.5, 0)};
const ExtrusionPath path{Polyline{points}, {}};
const auto[path_before, path_after]{split_path(
path, split_point, 1
)};
REQUIRE(path_before.size() == 3);
CHECK(path_before.first_point() == points.front());
CHECK(path_before.last_point() == split_point);
REQUIRE(path_after.size() == 2);
CHECK(path_after.first_point() == split_point);
CHECK(path_after.last_point() == points.back());
}
TEST_CASE("Split paths", "[Seams][Scarf]") {
using Seams::Scarf::Impl::split_paths;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(0, 1),
Point::new_scale(0, 2),
};
ExtrusionPaths paths{
{{points[0], points[1]}, {}},
{{points[1], points[2]}, {}},
};
const auto split_point{Point::new_scale(0, 1.5)};
PathPoint path_point{};
path_point.point = split_point;
path_point.previous_point_on_path_index = 0;
path_point.path_index = 1;
const ExtrusionPaths result{split_paths(std::move(paths), path_point)};
REQUIRE(result.size() == 3);
CHECK(result[1].last_point() == split_point);
CHECK(result[2].first_point() == split_point);
}
TEST_CASE("Get length", "[Seams][Scarf]") {
using Seams::Scarf::Impl::get_length;
using Seams::Scarf::Impl::convert_to_smooth;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(0, 1),
Point::new_scale(0, 2.2),
};
ExtrusionPaths paths{
{{points[0], points[1]}, {}},
{{points[1], points[2]}, {}},
};
CHECK(get_length(convert_to_smooth(paths)) == scaled(2.2));
}
TEST_CASE("Linspace", "[Seams][Scarf]") {
using Seams::Scarf::Impl::linspace;
const auto from{Point::new_scale(1, 0)};
const auto to{Point::new_scale(3, 0)};
Points points{linspace(from, to, 3)};
REQUIRE(points.size() == 3);
CHECK(points[1] == Point::new_scale(2, 0));
points = linspace(from, to, 5);
REQUIRE(points.size() == 5);
CHECK(points[1] == Point::new_scale(1.5, 0));
CHECK(points[2] == Point::new_scale(2.0, 0));
CHECK(points[3] == Point::new_scale(2.5, 0));
}
TEST_CASE("Ensure max distance", "[Seams][Scarf]") {
using Seams::Scarf::Impl::ensure_max_distance;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(0, 1),
};
Points result{ensure_max_distance(points, scaled(0.5))};
REQUIRE(result.size() == 3);
CHECK(result[1] == Point::new_scale(0, 0.5));
result = ensure_max_distance(points, scaled(0.49));
REQUIRE(result.size() == 4);
}
TEST_CASE("Lineary increase extrusion height", "[Seams][Scarf]") {
using Seams::Scarf::Impl::lineary_increase_extrusion_height;
using GCode::SmoothPath, GCode::SmoothPathElement;
SmoothPath path{
{{}, {{Point::new_scale(0, 0)}, {Point::new_scale(1, 0)}}},
{{}, {{Point::new_scale(1, 0)}, {Point::new_scale(2, 0)}}},
};
SmoothPath result{lineary_increase_extrusion_height(std::move(path), 0.5)};
CHECK(result[0].path[0].height_fraction == Approx(0.5));
CHECK(result[0].path[0].e_fraction == Approx(0.0));
CHECK(result[0].path[1].height_fraction == Approx(0.75));
CHECK(result[0].path[1].e_fraction == Approx(0.5));
CHECK(result[1].path[0].height_fraction == Approx(0.75));
CHECK(result[1].path[0].e_fraction == Approx(0.5));
CHECK(result[1].path[1].height_fraction == Approx(1.0));
CHECK(result[1].path[1].e_fraction == Approx(1.0));
}
TEST_CASE("Lineary reduce extrusion amount", "[Seams][Scarf]") {
using Seams::Scarf::Impl::lineary_readuce_extrusion_amount;
using GCode::SmoothPath, GCode::SmoothPathElement;
SmoothPath path{
{{}, {{Point::new_scale(0, 0)}, {Point::new_scale(1, 0)}}},
{{}, {{Point::new_scale(1, 0)}, {Point::new_scale(2, 0)}}},
};
SmoothPath result{lineary_readuce_extrusion_amount(std::move(path))};
CHECK(result[0].path[0].e_fraction == Approx(1.0));
CHECK(result[0].path[1].e_fraction == Approx(0.5));
CHECK(result[1].path[0].e_fraction == Approx(0.5));
CHECK(result[1].path[1].e_fraction == Approx(0.0));
}
TEST_CASE("Elevate scarf", "[Seams][Scarf]") {
using Seams::Scarf::Impl::elevate_scarf;
using Seams::Scarf::Impl::convert_to_smooth;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(1, 0),
Point::new_scale(2, 0),
Point::new_scale(3, 0),
};
const ExtrusionPaths paths{
{{points[0], points[1]}, {}},
{{points[1], points[2]}, {}},
{{points[2], points[3]}, {}},
};
const GCode::SmoothPath result{elevate_scarf(
paths,
1,
convert_to_smooth,
0.5
)};
REQUIRE(result.size() == 3);
REQUIRE(result[0].path.size() == 2);
CHECK(result[0].path[0].e_fraction == Approx(0.0));
CHECK(result[0].path[0].height_fraction == Approx(0.5));
CHECK(result[0].path[1].e_fraction == Approx(1.0));
CHECK(result[0].path[1].height_fraction == Approx(1.0));
REQUIRE(result[1].path.size() == 2);
CHECK(result[1].path[0].e_fraction == Approx(1.0));
CHECK(result[1].path[0].height_fraction == Approx(1.0));
CHECK(result[1].path[1].e_fraction == Approx(1.0));
CHECK(result[1].path[1].height_fraction == Approx(1.0));
REQUIRE(result[2].path.size() == 2);
CHECK(result[2].path[0].e_fraction == Approx(1.0));
CHECK(result[2].path[0].height_fraction == Approx(1.0));
CHECK(result[2].path[1].e_fraction == Approx(0.0));
CHECK(result[2].path[1].height_fraction == Approx(1.0));
}
TEST_CASE("Get point offset from the path end", "[Seams][Scarf]") {
using Seams::Scarf::Impl::get_point_offset_from_end;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(1, 0),
Point::new_scale(2, 0),
Point::new_scale(3, 0),
};
const ExtrusionPaths paths{
{{points[0], points[1]}, {}},
{{points[1], points[2]}, {}},
{{points[2], points[3]}, {}},
};
std::optional<PathPoint> result{get_point_offset_from_end(paths, scaled(1.6))};
REQUIRE(result);
CHECK(result->point == Point::new_scale(1.4, 0));
CHECK(result->previous_point_on_path_index == 0);
CHECK(result->path_index == 1);
}
TEST_CASE("Add scarf seam", "[Seams][Scarf]") {
using Seams::Scarf::add_scarf_seam;
using Seams::Scarf::Impl::convert_to_smooth;
using Seams::Scarf::Impl::get_length;
using Seams::Scarf::Scarf;
const Points points{
Point::new_scale(0, 0),
Point::new_scale(1, 0),
Point::new_scale(1, 1),
Point::new_scale(0, 1),
Point::new_scale(0, 0),
};
const ExtrusionPaths paths{
{Polyline{points}, {}},
};
Scarf scarf{};
scarf.end_point = Point::new_scale(1, 0.5);
scarf.start_height = 0.2;
scarf.length = 1.0;
scarf.max_segment_length = 0.1;
scarf.end_point_previous_index = 1;
scarf.entire_loop = false;
const auto start_point{Point::new_scale(0.5, 0)};
const auto [path, wipe_offset]{add_scarf_seam(ExtrusionPaths{paths}, scarf, convert_to_smooth, false)};
REQUIRE(path.size() == 4);
CHECK(wipe_offset == 1);
REQUIRE(path.back().path.size() >= scarf.length / scarf.max_segment_length);
CHECK(path.back().path.back().point == scarf.end_point);
CHECK(path.back().path.front().point == start_point);
CHECK(path.back().path.back().e_fraction == Approx(0));
REQUIRE(path.front().path.size() >= scarf.length / scarf.max_segment_length);
CHECK(path.front().path.back().point == scarf.end_point);
CHECK(path.front().path.front().point == start_point);
CHECK(path.front().path.front().e_fraction == Approx(0));
CHECK(path.front().path.front().height_fraction == Approx(scarf.start_height));
CHECK(path.front().path[5].point == points[1]);
CHECK(path.front().path[5].e_fraction == Approx(0.5));
CHECK(path.front().path[5].height_fraction == Approx(0.6));
CHECK(path.back().path[5].e_fraction == Approx(0.5));
CHECK(path.back().path[5].height_fraction == Approx(1.0));
scarf.entire_loop = true;
const auto [loop_path, _]{add_scarf_seam(ExtrusionPaths{paths}, scarf, convert_to_smooth, false)};
CHECK(get_length(loop_path) == scaled(8.0));
REQUIRE(!loop_path.empty());
REQUIRE(!loop_path.front().path.empty());
CHECK(loop_path.front().path.front().point == scarf.end_point);
CHECK(loop_path.front().path.front().e_fraction == Approx(0));
REQUIRE(!loop_path.back().path.empty());
CHECK(loop_path.back().path.back().point == scarf.end_point);
CHECK(loop_path.front().path.at(20).e_fraction == Approx(0.5));
CHECK(loop_path.front().path.at(20).point == Point::new_scale(0, 0.5));
}