update libslic3r

This commit is contained in:
QIDI TECH
2025-07-08 20:00:47 +08:00
parent bafe693d0a
commit e0d447172c
64 changed files with 2958 additions and 712 deletions

View File

@@ -416,6 +416,17 @@ static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary
return Direction::Backward;
}
Polyline ConvertBBoxToPolyline(const BoundingBoxf &bbox)
{
Point left_bottom = bbox.min.cast<coord_t>();
Point left_up(bbox.min.cast<coord_t>()[0], bbox.max.cast<coord_t>()[1]);
Point right_up = bbox.max.cast<coord_t>();
Point right_bottom(bbox.max.cast<coord_t>()[0], bbox.min.cast<coord_t>()[1]);
return Polyline({left_bottom, right_bottom, right_up, left_up, left_bottom});
}
// Straighten the travel path as long as it does not collide with the contours stored in edge_grid.
static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector<TravelPoint> &travel)
{
@@ -423,7 +434,6 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
std::vector<TravelPoint> simplified_path;
simplified_path.reserve(travel.size());
simplified_path.emplace_back(travel.front());
// Try to skip some points in the path.
//FIXME maybe use a binary search to trim the line?
//FIXME how about searching tangent point at long segments?
@@ -507,8 +517,10 @@ static float get_perimeter_spacing_external(const Layer &layer)
return perimeter_spacing;
}
// Called by avoid_perimeters() and by simplify_travel_heuristics().
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &init_boundary,
const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start_point,
const Point &end_point,
const Layer &layer,
@@ -630,8 +642,10 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
if (! intersections.empty())
result = simplify_travel(boundary, result);
if (! intersections.empty()) {
if (!init_boundary.boundaries.empty()) result = simplify_travel(init_boundary, result);
else result = simplify_travel(boundary, result);
}
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
@@ -645,6 +659,172 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
return intersections.size();
}
static size_t avoid_perimeters_inner(
const AvoidCrossingPerimeters::Boundary &boundary, const Point &start_point, const Point &end_point, const Layer &layer, std::vector<TravelPoint> &result_out)
{
const Polygons &boundaries = boundary.boundaries;
const EdgeGrid::Grid &edge_grid = boundary.grid;
Point start = start_point, end = end_point;
// Find all intersections between boundaries and the line segment, sort them along the line segment.
std::vector<Intersection> intersections;
{
intersections.reserve(boundaries.size());
AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end));
edge_grid.visit_cells_intersecting_line(start, end, visitor);
Vec2d dir = (end - start).cast<double>();
// if do not intersect due to the boundaries inner-offset, try to find the closest point to do intersect again!
if (intersections.empty()) {
// try to find the closest point on boundaries to start/end with distance less than extend_distance, which is noted as new start_point/end_point
auto search_radius = 1.5 * get_perimeter_spacing(layer);
const std::vector<ClosestLine> closest_line_to_start = get_closest_lines_in_radius(boundary.grid, start, search_radius);
const std::vector<ClosestLine> closest_line_to_end = get_closest_lines_in_radius(boundary.grid, end, search_radius);
if (!(closest_line_to_start.empty() && closest_line_to_end.empty())) {
auto new_start_point = closest_line_to_start.empty() ? start : closest_line_to_start.front().point;
auto new_end_point = closest_line_to_end.empty() ? end : closest_line_to_end.front().point;
dir = (new_end_point - new_start_point).cast<double>();
auto unit_direction = dir.normalized();
// out-offset new_start_point/new_end_point epsilon along the Line(new_start_point, new_end_point) for right intersection!
new_start_point = new_start_point - (unit_direction * double(coord_t(SCALED_EPSILON))).cast<coord_t>();
new_end_point = new_end_point + (unit_direction * double(coord_t(SCALED_EPSILON))).cast<coord_t>();
AllIntersectionsVisitor visitor(edge_grid, intersections, Line(new_start_point, new_end_point));
edge_grid.visit_cells_intersecting_line(new_start_point, new_end_point, visitor);
if (!intersections.empty()) {
start = new_start_point;
end = new_end_point;
}
}
}
for (Intersection &intersection : intersections) {
float dist_from_line_begin = (intersection.point - boundary.boundaries[intersection.border_idx][intersection.line_idx]).cast<float>().norm();
intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx] + dist_from_line_begin;
}
std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast<double>().dot(dir) > 0.; });
// Search radius should always be at least equals to the value of offset used for computing boundaries.
const float search_radius = 2.f * get_perimeter_spacing(layer);
// When the offset is too big, then original travel doesn't have to cross created boundaries.
// These cases are fixed by calling extend_for_closest_lines.
intersections = extend_for_closest_lines(intersections, boundary, start, end, search_radius);
}
std::vector<TravelPoint> result;
result.push_back({start, -1});
#if 0
auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) {
const Polygon &poly = boundary.boundaries[intersection.border_idx];
Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast<double>();
Vec2d intersection_vec = (intersection.point - start).cast<double>();
return poly_line.normalized().dot(intersection_vec.normalized()) >= 0;
};
#endif
for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) {
// The entry point to the boundary polygon
const Intersection &intersection_first = *it_first;
// if(!crossing_boundary_from_inside(start, intersection_first))
// continue;
// Skip the it_first from the search for the farthest exit point from the boundary polygon
auto it_last_item = std::make_reverse_iterator(it_first) - 1;
// Search for the farthest intersection different from it_first but with the same border_idx
auto it_second_r = std::find_if(intersections.rbegin(), it_last_item,
[&intersection_first](const Intersection &intersection) { return intersection_first.border_idx == intersection.border_idx; });
// Append the first intersection into the path
size_t left_idx = intersection_first.line_idx;
size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1;
// Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the
// boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the
// appended point will be inside the polygon and not on the polygon border.
result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)),
int(intersection_first.border_idx)});
// Check if intersection line also exit the boundary polygon
if (it_second_r != it_last_item) {
// Transform reverse iterator to forward
auto it_second = it_second_r.base() - 1;
// The exit point from the boundary polygon
const Intersection &intersection_second = *it_second;
Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second,
boundary.boundaries_params[intersection_first.border_idx].back());
// Append the path around the border into the path
if (shortest_direction == Direction::Forward)
for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx);
line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0)
result.push_back(
{get_polygon_vertex_offset(boundaries[intersection_first.border_idx],
(line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)),
int(intersection_first.border_idx)});
else
for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx);
line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1)
result.push_back(
{get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)});
// Append the farthest intersection into the path
left_idx = intersection_second.line_idx;
right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1);
result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)),
int(intersection_second.border_idx)});
// Skip intersections in between
it_first = it_second;
}
}
result.push_back({end, -1});
auto result_polyline = to_polyline(result);
(void) result_polyline;
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0;
export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-initial-%d-%d.svg", layer.id(), iRun++));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
if (!intersections.empty()) result = simplify_travel(boundary, result);
auto simplified_result_polyline = to_polyline(result);
(void) simplified_result_polyline;
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0;
export_travel_to_svg(boundaries, Line(start, end), result, intersections, debug_out_path("AvoidCrossingPerimetersInner-final-%d-%d.svg", layer.id(), iRun++));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
append(result_out, std::move(result));
return intersections.size();
}
// Called by AvoidCrossingPerimeters::travel_to()
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &init_boundary, const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start,
const Point &end,
const Layer &layer,
Polyline &result_out)
{
// Travel line is completely or partially inside the bounding box.
std::vector<TravelPoint> path;
size_t num_intersections = avoid_perimeters_inner(init_boundary, boundary, start, end, layer, path);
result_out = to_polyline(path);
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0;
export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d-%d.svg", layer.id(), iRun ++));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
return num_intersections;
}
// Called by AvoidCrossingPerimeters::travel_to()
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start,
@@ -1028,9 +1208,9 @@ static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset)
//#define INCLUDE_SUPPORTS_IN_BOUNDARY
// called by AvoidCrossingPerimeters::travel_to()
static ExPolygons get_boundary(const Layer &layer)
static ExPolygons get_boundary(const Layer &layer, float perimeter_spacing)
{
const float perimeter_spacing = get_perimeter_spacing(layer);
// const float perimeter_spacing = get_perimeter_spacing(layer);
const float perimeter_offset = perimeter_spacing / 2.f;
auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer);
ExPolygons boundary = union_ex(inner_offset(layer.lslices, 1.5 * perimeter_spacing));
@@ -1064,6 +1244,43 @@ static ExPolygons get_boundary(const Layer &layer)
return boundary;
}
// called by AvoidCrossingPerimeters::travel_to()
static ExPolygons get_slice_boundary_internal(const Layer &layer)
{
auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer);
ExPolygons boundary = layer.lslices;
if(support_layer) {
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
append(boundary, support_layer->support_islands);
#endif
auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON);
if (layer_below)
append(boundary, layer_below->lslices);
// After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons
boundary = union_ex(boundary);
}
// Collect all top layers that will not be crossed.
size_t polygons_count = 0;
for (const LayerRegion *layer_region : layer.regions())
for (const Surface &surface : layer_region->fill_surfaces.surfaces)
if (surface.is_top()) ++polygons_count;
if (polygons_count > 0) {
ExPolygons top_layer_polygons;
top_layer_polygons.reserve(polygons_count);
for (const LayerRegion *layer_region : layer.regions())
for (const Surface &surface : layer_region->fill_surfaces.surfaces)
if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon);
top_layer_polygons = union_ex(top_layer_polygons);
return diff_ex(boundary, top_layer_polygons);
// return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset));
}
return boundary;
}
// called by AvoidCrossingPerimeters::travel_to()
static Polygons get_boundary_external(const Layer &layer)
{
@@ -1129,7 +1346,7 @@ static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary)
precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]);
}
static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons)
void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons)
{
boundary->clear();
boundary->boundaries = std::move(boundary_polygons);
@@ -1143,6 +1360,41 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons
init_boundary_distances(boundary);
}
static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons, const std::vector<Point>& merge_poins)
{
boundary->clear();
boundary->boundaries = std::move(boundary_polygons);
BoundingBox bbox(get_extents(boundary->boundaries));
for (const auto& merge_point : merge_poins) {
bbox.merge(merge_point);
}
bbox.offset(SCALED_EPSILON);
boundary->bbox = BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>());
boundary->grid.set_bbox(bbox);
// FIXME 1mm grid?
boundary->grid.create(boundary->boundaries, coord_t(scale_(1.)));
init_boundary_distances(boundary);
}
void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons, BoundingBox&& ref_bbox, const std::vector<Point>& merge_poins)
{
boundary->clear();
boundary->boundaries = std::move(boundary_polygons);
BoundingBox bbox(ref_bbox);
for (const auto& merge_point : merge_poins) {
bbox.merge(merge_point);
}
bbox.offset(SCALED_EPSILON);
boundary->bbox = BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>());
boundary->grid.set_bbox(bbox);
// FIXME 1mm grid?
boundary->grid.create(boundary->boundaries, coord_t(scale_(1.)));
init_boundary_distances(boundary);
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
@@ -1162,29 +1414,53 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes;
bool is_support_layer = (dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr);
if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) {
// Initialize m_internal only when it is necessary.
if (m_internal.boundaries.empty())
init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer())));
if (m_lslice_internal.boundaries.empty()) {
init_boundary(&m_lslice_internal, to_polygons(get_slice_boundary_internal(*gcodegen.layer())), {start, end});
} else if (!(m_lslice_internal.bbox.contains(startf) && m_lslice_internal.bbox.contains(endf))) {
// check if start and end are in bbox
m_lslice_internal.clear();
init_boundary(&m_lslice_internal, to_polygons(get_slice_boundary_internal(*gcodegen.layer())), {start, end});
}
// Trim the travel line by the bounding box.
if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
// Initialize m_internal only when it is necessary.
if (m_internal.boundaries.empty()) {
init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer(), get_perimeter_spacing(*gcodegen.layer()))), get_extents(m_lslice_internal.boundaries),
{start, end});
} else if (!(m_internal.bbox.contains(startf) && m_internal.bbox.contains(endf))) {
// check if start and end are in bbox, if not, merge start and end points to bbox
m_internal.clear();
init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer(), get_perimeter_spacing(*gcodegen.layer()))), get_extents(m_lslice_internal.boundaries),
{start, end});
}
if (!m_internal.boundaries.empty()) {
travel_intersection_count = avoid_perimeters(m_lslice_internal, m_internal, start, end, *gcodegen.layer(), result_pl);
result_pl.points.front() = start;
result_pl.points.back() = end;
}
} else if(use_external) {
}
else if(use_external) {
// Initialize m_external only when exist any external travel for the current layer.
if (m_external.boundaries.empty())
init_boundary(&m_external, get_boundary_external(*gcodegen.layer()));
if (m_external.boundaries.empty()) {
init_boundary(&m_external, get_boundary_external(*gcodegen.layer()), {start, end});
} else if (!(m_external.bbox.contains(startf) && m_external.bbox.contains(endf))) {
// check if start and end are in bbox
m_external.clear();
init_boundary(&m_external, get_boundary_external(*gcodegen.layer()), {start, end});
}
// Trim the travel line by the bounding box.
if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) {
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), *gcodegen.layer(), result_pl);
if (!m_external.boundaries.empty())
{
travel_intersection_count = avoid_perimeters(m_external, start, end, *gcodegen.layer(), result_pl);
result_pl.points.front() = start;
result_pl.points.back() = end;
}
}
if(result_pl.empty()) {
// Travel line is completely outside the bounding box.
result_pl = {start, end};
@@ -1221,6 +1497,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
void AvoidCrossingPerimeters::init_layer(const Layer &layer)
{
m_internal.clear();
m_lslice_internal.clear();
m_external.clear();
BoundingBox bbox_slice(get_extents(layer.lslices));

View File

@@ -62,6 +62,8 @@ private:
EdgeGrid::Grid m_grid_lslice;
// Store all needed data for travels inside object
Boundary m_internal;
// Store all needed data for travels inside object without inner offset
Boundary m_lslice_internal;
// Store all needed data for travels outside object
Boundary m_external;
};

View File

@@ -2778,7 +2778,7 @@ bool GCodeProcessor::get_last_z_from_gcode(const std::string& gcode_str, double&
line_str.erase(line_str.find_last_not_of(" ") + 1);
//command which may have z movement
if (line_str.size() > 5 && (line_str.find("G0 ") == 0
if (line_str.size() > 4 && (line_str.find("G0 ") == 0
|| line_str.find("G1 ") == 0
|| line_str.find("G2 ") == 0
|| line_str.find("G3 ") == 0))
@@ -5450,6 +5450,16 @@ void GCodeProcessor::process_T(const std::string_view command)
}
void GCodeProcessor::init_filament_maps_and_nozzle_type_when_import_only_gcode()
{
if (m_filament_maps.empty()) {
m_filament_maps.assign((int) EnforcerBlockerType::ExtruderMax, 1);
}
if (m_result.nozzle_type.empty()) {
m_result.nozzle_type.assign((int) EnforcerBlockerType::ExtruderMax, NozzleType::ntUndefine);
}
}
void GCodeProcessor::process_filament_change(int id)
{
assert(id < m_result.filaments_count);
@@ -5872,7 +5882,7 @@ void GCodeProcessor::update_slice_warnings()
warning.params.clear();
warning.level=1;
std::vector<int>nozzle_hrc_lists(m_result.nozzle_type.size(), 0);
std::vector<int> nozzle_hrc_lists(m_result.nozzle_type.size(), 0);
// store the nozzle hrc of each extruder
for (size_t idx = 0; idx < m_result.nozzle_type.size(); ++idx)
nozzle_hrc_lists[idx] = Print::get_hrc_by_nozzle_type(m_result.nozzle_type[idx]);

View File

@@ -1104,7 +1104,7 @@ namespace Slic3r {
public:
GCodeProcessor();
void init_filament_maps_and_nozzle_type_when_import_only_gcode();
// check whether the gcode path meets the filament_map grouping requirements
bool check_multi_extruder_gcode_valid(const std::vector<Polygons> &unprintable_areas,
const std::vector<double> &printable_heights,

View File

@@ -0,0 +1,583 @@
#include "TimelapsePosPicker.hpp"
#include "Layer.hpp"
constexpr int FILTER_THRESHOLD = 5;
constexpr int MAX_CANDIDATE_SIZE = 5;
namespace Slic3r {
void TimelapsePosPicker::init(const Print* print_, const Point& plate_offset)
{
reset();
m_plate_offset = plate_offset;
print = print_;
m_nozzle_height_to_rod = print_->config().extruder_clearance_height_to_rod;
m_nozzle_clearance_radius = print_->config().extruder_clearance_max_radius;
if (print_->config().nozzle_diameter.size() > 1) {
m_extruder_height_gap = std::abs(print_->config().extruder_printable_height.values[0] - print_->config().extruder_printable_height.values[1]);
m_liftable_extruder_id = print_->config().extruder_printable_height.values[0] < print_->config().extruder_printable_height.values[1] ? 0 : 1;
}
m_print_seq = print_->config().print_sequence.value;
m_based_on_all_layer = print_->config().timelapse_type == TimelapseType::tlSmooth;
construct_printable_area_by_printer();
}
void TimelapsePosPicker::reset()
{
print = nullptr;
m_bed_polygon.clear();
m_extruder_printable_area.clear();
m_all_layer_pos = std::nullopt;
bbox_cache.clear();
m_print_seq = PrintSequence::ByObject;
m_nozzle_height_to_rod = 0;
m_nozzle_clearance_radius = 0;
m_liftable_extruder_id = std::nullopt;
m_extruder_height_gap = std::nullopt;
m_based_on_all_layer = false;
}
/**
* @brief Retrieves a list of print objects based on the provided optional set of printed objects.
*
* If the optional set of printed objects is provided, it converts the set into a vector.
* Otherwise, it retrieves all objects from the print instance.
*/
std::vector<const PrintObject*> TimelapsePosPicker::get_object_list(const std::optional<std::vector<const PrintObject*>>& printed_objects)
{
std::vector<const PrintObject*> object_list;
if (printed_objects.has_value()) {
object_list = std::vector<const PrintObject*>(printed_objects->begin(), printed_objects->end());
}
else {
object_list = std::vector<const PrintObject*>(print->objects().begin(), print->objects().end());
}
return object_list;
}
/**
* @brief Constructs the printable area based on printer configuration.
*
* This function initializes the bed polygon, excludes specific areas, accounts for wipe towers,
* and calculates the printable area for each extruder.
*/
void TimelapsePosPicker::construct_printable_area_by_printer()
{
auto config = print->config();
size_t extruder_count = config.nozzle_diameter.size();
m_extruder_printable_area.clear();
m_extruder_printable_area.resize(extruder_count);
for (size_t idx = 0; idx < config.printable_area.values.size(); ++idx)
m_bed_polygon.points.emplace_back(coord_t(scale_(config.printable_area.values[idx].x())), coord_t(scale_(config.printable_area.values[idx].y())));
auto bed_bbox = get_extents(m_bed_polygon);
m_plate_height = unscale_(bed_bbox.max.y());
m_plate_width = unscale_(bed_bbox.max.x());
Polygon bed_exclude_area;
for (size_t idx = 0; idx < config.bed_exclude_area.values.size(); ++idx)
bed_exclude_area.points.emplace_back(coord_t(scale_(config.bed_exclude_area.values[idx].x())), coord_t(scale_(config.bed_exclude_area.values[idx].y())));
Point base_wp_pt = print->get_fake_wipe_tower().pos.cast<coord_t>();
base_wp_pt = Point{ scale_(base_wp_pt.x()),scale_(base_wp_pt.y()) };
auto transform_wt_pt = [base_wp_pt](const Point& pt) -> Point {
Point out =pt;
out += base_wp_pt;
return out;
};
auto wt_box = print->wipe_tower_data().bbx;
Polygon wipe_tower_area{
{transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.min.y())})},
{transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.min.y())})},
{transform_wt_pt({scale_(wt_box.max.x()),scale_(wt_box.max.y())})},
{transform_wt_pt({scale_(wt_box.min.x()),scale_(wt_box.max.y())})}
};
wipe_tower_area = expand_object_projection(wipe_tower_area, m_print_seq == PrintSequence::ByObject);
for (size_t idx = 0; idx < extruder_count; ++idx) {
ExPolygons printable_area = diff_ex(diff(m_bed_polygon, bed_exclude_area), { wipe_tower_area });
if (idx < config.extruder_printable_area.size()) {
Polygon extruder_printable_area;
for (size_t j = 0; j < config.extruder_printable_area.values[idx].size(); ++j)
extruder_printable_area.points.emplace_back(coord_t(scale_(config.extruder_printable_area.values[idx][j].x())), coord_t(scale_(config.extruder_printable_area.values[idx][j].y())));
printable_area = intersection_ex(printable_area, Polygons{ extruder_printable_area });
}
m_extruder_printable_area[idx] = printable_area;
}
}
/**
* @brief Collects object slice data within a specified height range for a given layer.
*
* @param layer The layer for which slices are being collected.
* @param height_range The height range to consider for collecting slices.
* @param object_list List of print objects to process.
* @param by_object Decides the expand length of polygon
* @return ExPolygons representing the collected slice data.
*/
ExPolygons TimelapsePosPicker::collect_object_slices_data(const Layer* layer, float height_range, const std::vector<const PrintObject*>& object_list, bool by_object)
{
auto range_intersect = [](int left1, int right1, int left2, int right2) {
if (left1 <= left2 && left2 <= right1)
return true;
if (left2 <= left1 && left1 <= right2)
return true;
return false;
};
ExPolygons ret;
float z_target = layer->print_z;
float z_low = height_range < 0 ? layer->print_z + height_range : layer->print_z;
float z_high = height_range < 0 ? layer->print_z : layer->print_z + height_range;
if (z_low <= 0)
return to_expolygons({ m_bed_polygon });
for (auto& obj : object_list) {
for (auto& instance : obj->instances()) {
auto instance_bbox = get_real_instance_bbox(instance);
if(range_intersect(instance_bbox.min.z(), instance_bbox.max.z(), z_low, z_high)){
ExPolygon expoly;
expoly.contour = {
{scale_(instance_bbox.min.x()), scale_(instance_bbox.min.y())},
{scale_(instance_bbox.max.x()), scale_(instance_bbox.min.y())},
{scale_(instance_bbox.max.x()), scale_(instance_bbox.max.y())},
{scale_(instance_bbox.min.x()), scale_(instance_bbox.max.y())}
};
expoly.contour = expand_object_projection(expoly.contour, by_object);
ret.emplace_back(std::move(expoly));
}
}
}
ret = union_ex(ret);
return ret;
}
Polygons TimelapsePosPicker::collect_limit_areas_for_camera(const std::vector<const PrintObject*>& object_list)
{
Polygons ret;
for (auto& obj : object_list)
ret.emplace_back(get_limit_area_for_camera(obj));
ret = union_(ret);
return ret;
}
// scaled data
Polygons TimelapsePosPicker::collect_limit_areas_for_rod(const std::vector<const PrintObject*>& object_list, const PosPickCtx& ctx)
{
double rod_limit_height = m_nozzle_height_to_rod + ctx.curr_layer->print_z;
std::vector<const PrintObject*> rod_collision_candidates;
for(auto& obj : object_list){
if(ctx.printed_objects && obj == ctx.printed_objects->back())
continue;
auto bbox = get_real_instance_bbox(obj->instances().front());
if(bbox.max.z() >= rod_limit_height)
rod_collision_candidates.push_back(obj);
}
if (rod_collision_candidates.empty())
return {};
std::vector<BoundingBoxf3> collision_obj_bboxs;
for (auto obj : rod_collision_candidates) {
collision_obj_bboxs.emplace_back(
expand_object_bbox(
get_real_instance_bbox(obj->instances().front()),
m_print_seq == PrintSequence::ByObject
)
);
}
std::sort(collision_obj_bboxs.begin(), collision_obj_bboxs.end(), [&](const auto& lbbox, const auto& rbbox) {
if (lbbox.min.y() == rbbox.min.y())
return lbbox.max.y() < rbbox.max.y();
return lbbox.min.y() < rbbox.min.y();
});
std::vector<std::pair<int,int>> object_y_ranges = {{0,0}};
for(auto& bbox : collision_obj_bboxs){
if( object_y_ranges.back().second >= bbox.min.y())
object_y_ranges.back().second = bbox.max.y();
else
object_y_ranges.emplace_back(bbox.min.y(), bbox.max.y());
}
if (object_y_ranges.back().second < m_plate_height)
object_y_ranges.emplace_back(m_plate_height, m_plate_height);
int lower_y_pos = -1, upper_y_pos =-1;
Point unscaled_curr_pos = {unscale_(ctx.curr_pos.x())-m_plate_offset.x(), unscale_(ctx.curr_pos.y()) - m_plate_offset.y()};
for (size_t idx = 1; idx < object_y_ranges.size(); ++idx) {
if (unscaled_curr_pos.y() >= object_y_ranges[idx - 1].second && unscaled_curr_pos.y() <= object_y_ranges[idx].first) {
lower_y_pos = object_y_ranges[idx - 1].second;
upper_y_pos = object_y_ranges[idx].first;
break;
}
}
if(lower_y_pos == -1 && upper_y_pos == -1)
return { m_bed_polygon };
Polygons ret;
ret.emplace_back(
Polygon{
Point{scale_(0), scale_(0)},
Point{scale_(m_plate_width), scale_(0)},
Point{scale_(m_plate_width), scale_(lower_y_pos)},
Point{scale_(0), scale_(lower_y_pos)}
}
);
ret.emplace_back(
Polygon{
Point{scale_(0), scale_(upper_y_pos)},
Point{scale_(m_plate_width), scale_(upper_y_pos)},
Point{scale_(m_plate_width), scale_(m_plate_height)},
Point{scale_(0), scale_(m_plate_height)}
}
);
return ret;
}
// expand the object expolygon by safe distance, scaled data
Polygon TimelapsePosPicker::expand_object_projection(const Polygon& poly, bool by_object)
{
float radius = 0;
if (by_object)
radius = scale_(print->config().extruder_clearance_max_radius.value);
else
radius = scale_(print->config().extruder_clearance_max_radius.value / 2);
// the input poly is bounding box, so we get the first offseted polygon is ok
auto ret = offset(poly, radius);
if (ret.empty())
return {};
return ret[0];
}
// unscaled data
BoundingBoxf3 TimelapsePosPicker::expand_object_bbox(const BoundingBoxf3& bbox, bool by_object)
{
float radius = 0;
if (by_object)
radius = print->config().extruder_clearance_max_radius.value;
else
radius = print->config().extruder_clearance_max_radius.value / 2;
BoundingBoxf3 ret = bbox;
ret.min.x() -= radius;
ret.min.y() -= radius;
ret.max.x() += radius;
ret.max.y() += radius;
return ret;
}
double TimelapsePosPicker::get_raft_height(const PrintObject* obj)
{
if (!obj || !obj->has_raft())
return 0;
auto slice_params = obj->slicing_parameters();
int base_raft_layers = slice_params.base_raft_layers;
double base_raft_height = slice_params.base_raft_layer_height;
int interface_raft_layers = slice_params.interface_raft_layers;
double interface_raft_height = slice_params.interface_raft_layer_height;
double contact_raft_layer_height = slice_params.contact_raft_layer_height;
double ret = print->config().initial_layer_print_height;
if (base_raft_layers - 1 > 0)
ret += (base_raft_layers - 1) * base_raft_height;
if (interface_raft_layers - 1 > 0)
ret += (interface_raft_layers - 1) * interface_raft_height;
if (obj->config().raft_layers > 1)
ret += contact_raft_layer_height;
return ret + slice_params.gap_raft_object;
}
// get the real instance bounding box, remove the plate offset and add raft height , unscaled data
BoundingBoxf3 TimelapsePosPicker::get_real_instance_bbox(const PrintInstance& instance)
{
auto iter = bbox_cache.find(&instance);
if (iter != bbox_cache.end())
return iter->second;
auto bbox = instance.get_bounding_box();
double raft_height =get_raft_height(instance.print_object);
bbox.max.z() += raft_height;
// remove plate offset
bbox.min.x() -= m_plate_offset.x();
bbox.max.x() -= m_plate_offset.x();
bbox.min.y() -= m_plate_offset.y();
bbox.max.y() -= m_plate_offset.y();
bbox_cache[&instance] = bbox;
return bbox;
}
Polygon TimelapsePosPicker::get_limit_area_for_camera(const PrintObject* obj)
{
if (!obj)
return {};
auto bbox = get_real_instance_bbox(obj->instances().front());
float radius = m_nozzle_clearance_radius / 2;
auto offset_bbox = bbox.inflated(sqrt(2) * radius);
// Constrain the coordinates to the first quadrant.
Polygon ret = {
DefaultCameraPos,
Point{std::max(scale_(offset_bbox.max.x()),0.),std::max(scale_(offset_bbox.min.y()),0.)},
Point{std::max(scale_(offset_bbox.max.x()),0.),std::max(scale_(offset_bbox.max.y()),0.)},
Point{std::max(scale_(offset_bbox.min.x()),0.),std::max(scale_(offset_bbox.max.y()),0.)}
};
return ret;
}
/**
* @brief Selects the nearest position within the given safe areas relative to the current position.
*
* This function determines the closest point in the safe areas to the provided current position.
* If the current position is already inside a safe area, it returns the current position.
* If no safe areas are defined, return default timelapse position.
*
* @param curr_pos The reference point representing the current position.
* @param safe_areas A collection of extended polygons defining the safe areas.
* @return Point The nearest point within the safe areas or the default timelapse position if no safe areas exist.
*/
Point pick_pos_internal(const Point& curr_pos, const ExPolygons& safe_areas, const ExPolygons& path_collision_area, bool detect_path_collision)
{
struct CandidatePoint
{
double dist;
Point point;
bool operator<(const CandidatePoint& other) const {
return dist < other.dist;
}
};
if (std::any_of(safe_areas.begin(), safe_areas.end(), [&curr_pos](const ExPolygon& p) { return p.contains(curr_pos); }))
return curr_pos;
if (safe_areas.empty())
return DefaultTimelapsePos;
std::priority_queue<CandidatePoint> max_heap;
double min_distance = std::numeric_limits<double>::max();
Point nearest_point = DefaultTimelapsePos;
for (const auto& expoly : safe_areas) {
Polygons polys = to_polygons(expoly);
for (auto& poly : polys) {
for (size_t idx = 0; idx < poly.points.size(); ++idx) {
Line line(poly.points[idx], poly.points[next_idx_modulo(idx, poly.points)]);
Point candidate;
double dist = line.distance_to_squared(curr_pos, &candidate);
max_heap.push({ dist,candidate });
if (max_heap.size() > MAX_CANDIDATE_SIZE)
max_heap.pop();
}
}
}
std::vector<Point> top_candidates;
while (!max_heap.empty()) {
top_candidates.push_back(max_heap.top().point);
max_heap.pop();
}
std::reverse(top_candidates.begin(), top_candidates.end());
for (auto& p : top_candidates) {
if (!detect_path_collision)
return p;
Polyline path(curr_pos, p);
if (intersection_pl(path, path_collision_area).empty())
return p;
}
return DefaultTimelapsePos;
}
Point TimelapsePosPicker::pick_pos(const PosPickCtx& ctx)
{
Point res;
if (m_based_on_all_layer)
res = pick_pos_for_all_layer(ctx);
else
res = pick_pos_for_curr_layer(ctx);
return { unscale_(res.x()), unscale_(res.y()) };
}
// get center point of curr object, scaled data
Point TimelapsePosPicker::get_object_center(const PrintObject* obj)
{
if (!obj)
return {};
// in qidi studio, each object only has one instance
const auto& instance = obj->instances().front();
auto instance_bbox = get_real_instance_bbox(instance);
Point min_p{ instance_bbox.min.x(),instance_bbox.min.y() };
Point max_p{ instance_bbox.max.x(),instance_bbox.max.y() };
return { scale_((min_p.x() + max_p.x()) / 2),scale_((min_p.y() + max_p.y()) / 2) };
}
// scaled data
Point TimelapsePosPicker::pick_nearest_object_center(const Point& curr_pos, const std::vector<const PrintObject*>& object_list)
{
if (object_list.empty())
return {};
const PrintObject* ptr = object_list.front();
double distance = std::numeric_limits<double>::max();
for (auto& obj : object_list) {
Point obj_center = get_object_center(obj);
double dist = (obj_center - curr_pos).cast<double>().norm();
if (distance > dist) {
distance = dist;
ptr = obj;
}
}
return get_object_center(ptr);
}
// scaled data
Point TimelapsePosPicker::pick_pos_for_curr_layer(const PosPickCtx& ctx)
{
float height_gap = 0;
if (ctx.curr_extruder_id != ctx.picture_extruder_id) {
if (m_liftable_extruder_id.has_value() && ctx.picture_extruder_id != m_liftable_extruder_id && m_extruder_height_gap.has_value())
height_gap = -*m_extruder_height_gap;
}
bool by_object = m_print_seq == PrintSequence::ByObject;
std::vector<const PrintObject*> object_list = get_object_list(ctx.printed_objects);
ExPolygons layer_slices = collect_object_slices_data(ctx.curr_layer, height_gap, object_list, by_object);
Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list);
Polygons rod_limit_areas;
if (by_object) {
rod_limit_areas = collect_limit_areas_for_rod(object_list, ctx);
}
ExPolygons unplacable_area = union_ex(union_ex(layer_slices, camera_limit_areas), rod_limit_areas);
ExPolygons extruder_printable_area = m_extruder_printable_area[ctx.picture_extruder_id];
ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area);
safe_area = opening_ex(safe_area, scale_(FILTER_THRESHOLD));
Point center_p;
if (by_object && ctx.printed_objects && !ctx.printed_objects->empty())
center_p = get_object_center(ctx.printed_objects->back());
else
center_p = get_objects_center(object_list);
ExPolygons path_collision_area;
if (by_object) {
auto object_without_curr = ctx.printed_objects;
if (object_without_curr && !object_without_curr->empty())
object_without_curr->pop_back();
ExPolygons layer_slices_without_curr = collect_object_slices_data(ctx.curr_layer, height_gap, get_object_list(object_without_curr), by_object);
path_collision_area = union_ex(layer_slices_without_curr, rod_limit_areas);
}
return pick_pos_internal(center_p, safe_area,path_collision_area, by_object);
}
/**
* @brief Calculates the center of multiple objects.
*
* This function computes the average center of all instances of the provided objects.
*
* @param object_list A vector of pointers to PrintObject instances.
* @return Point The average center of all objects.Scaled data
*/
Point TimelapsePosPicker::get_objects_center(const std::vector<const PrintObject*>& object_list)
{
if (object_list.empty())
return Point(0,0);
double sum_x = 0.0;
double sum_y = 0.0;
size_t total_instances = 0;
for (auto& obj : object_list) {
for (auto& instance : obj->instances()) {
const auto& bbox = get_real_instance_bbox(instance);
Point min_p{ bbox.min.x(),bbox.min.y() };
Point max_p{ bbox.max.x(),bbox.max.y() };
double center_x = (min_p.x() + max_p.x()) / 2.f;
double center_y = (min_p.y() + max_p.y()) / 2.f;
sum_x += center_x;
sum_y += center_y;
total_instances += 1;
}
}
return Point{ coord_t(scale_(sum_x / total_instances)),coord_t(scale_(sum_y / total_instances)) };
}
Point TimelapsePosPicker::pick_pos_for_all_layer(const PosPickCtx& ctx)
{
bool by_object = m_print_seq == PrintSequence::ByObject;
if (by_object)
return DefaultTimelapsePos;
float height_gap = 0;
if (ctx.curr_extruder_id != ctx.picture_extruder_id) {
if (m_liftable_extruder_id.has_value() && ctx.picture_extruder_id != m_liftable_extruder_id && m_extruder_height_gap.has_value())
height_gap = *m_extruder_height_gap;
}
if (ctx.curr_layer->print_z < height_gap)
return DefaultTimelapsePos;
if (m_all_layer_pos)
return *m_all_layer_pos;
Polygons object_projections;
auto object_list = get_object_list(std::nullopt);
for (auto& obj : object_list) {
for (auto& instance : obj->instances()) {
const auto& bbox = get_real_instance_bbox(instance);
Point min_p{ scale_(bbox.min.x()),scale_(bbox.min.y()) };
Point max_p{ scale_(bbox.max.x()),scale_(bbox.max.y()) };
Polygon obj_proj{ { min_p.x(),min_p.y() },
{ max_p.x(),min_p.y() },
{ max_p.x(),max_p.y() },
{ min_p.x(),max_p.y() }
};
object_projections.emplace_back(expand_object_projection(obj_proj,by_object));
}
};
object_projections = union_(object_projections);
Polygons camera_limit_areas = collect_limit_areas_for_camera(object_list);
Polygons unplacable_area = union_(object_projections, camera_limit_areas);
ExPolygons extruder_printable_area;
if (m_extruder_printable_area.size() > 1)
extruder_printable_area = intersection_ex(m_extruder_printable_area[0], m_extruder_printable_area[1]);
else if (m_extruder_printable_area.size() == 1)
extruder_printable_area = m_extruder_printable_area.front();
ExPolygons safe_area = diff_ex(extruder_printable_area, unplacable_area);
safe_area = opening_ex(safe_area, scale_(FILTER_THRESHOLD));
Point starting_pos = get_objects_center(object_list);
m_all_layer_pos = pick_pos_internal(starting_pos, safe_area, {}, by_object);
return *m_all_layer_pos;
}
}

View File

@@ -0,0 +1,81 @@
#ifndef TIMELAPSE_POS_PICKER_HPP
#define TIMELAPSE_POS_PICKER_HPP
#include <vector>
#include "libslic3r/Point.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
const Point DefaultTimelapsePos = Point(0, 0);
const Point DefaultCameraPos = Point(0, 0);
class Layer;
class Print;
struct PosPickCtx
{
Point curr_pos;
const Layer* curr_layer;
int picture_extruder_id; // the extruder id to take picture
int curr_extruder_id;
std::optional<std::vector<const PrintObject*>> printed_objects; // printed objects, only have value in by object mode
};
// data are stored without plate offset
class TimelapsePosPicker
{
public:
TimelapsePosPicker() = default;
~TimelapsePosPicker() = default;
Point pick_pos(const PosPickCtx& ctx);
void init(const Print* print, const Point& plate_offset);
void reset();
private:
void construct_printable_area_by_printer();
Point pick_pos_for_curr_layer(const PosPickCtx& ctx);
Point pick_pos_for_all_layer(const PosPickCtx& ctx);
ExPolygons collect_object_slices_data(const Layer* curr_layer, float height_range, const std::vector<const PrintObject*>& object_list,bool by_object);
Polygons collect_limit_areas_for_camera(const std::vector<const PrintObject*>& object_list);
Polygons collect_limit_areas_for_rod(const std::vector<const PrintObject*>& object_list, const PosPickCtx& ctx);
Polygon expand_object_projection(const Polygon& poly, bool by_object);
BoundingBoxf3 expand_object_bbox(const BoundingBoxf3& bbox, bool by_object);
Point pick_nearest_object_center(const Point& curr_pos, const std::vector<const PrintObject*>& object_list);
Point get_objects_center(const std::vector<const PrintObject*>& object_list);
Polygon get_limit_area_for_camera(const PrintObject* obj);
std::vector<const PrintObject*> get_object_list(const std::optional<std::vector<const PrintObject*>>& printed_objects);
double get_raft_height(const PrintObject* obj);
BoundingBoxf3 get_real_instance_bbox(const PrintInstance& instance);
Point get_object_center(const PrintObject* obj);
private:
const Print* print{ nullptr };
std::vector<ExPolygons> m_extruder_printable_area; //scaled data
Polygon m_bed_polygon; //scaled_data
Point m_plate_offset; // unscaled data
int m_plate_height; // unscaled data
int m_plate_width; // unscaled data
PrintSequence m_print_seq;
bool m_based_on_all_layer;
int m_nozzle_height_to_rod;
int m_nozzle_clearance_radius;
std::optional<int> m_liftable_extruder_id;
std::optional<int> m_extruder_height_gap;
std::unordered_map<const PrintInstance*, BoundingBoxf3> bbox_cache;
std::optional<Point> m_all_layer_pos;
};
}
#endif

View File

@@ -398,7 +398,8 @@ namespace Slic3r
int target_cost = std::numeric_limits<int>::max();
for (size_t k = 0; k < is_visited.size(); ++k) {
if (!is_visited[k]) {
if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost) {
if (wipe_volumes[*prev_filament][curr_layer_extruders[k]] < target_cost ||
(wipe_volumes[*prev_filament][curr_layer_extruders[k]] == target_cost && prev_filament == curr_layer_extruders[k])) {
target_idx = k;
target_cost = wipe_volumes[*prev_filament][curr_layer_extruders[k]];
}
@@ -426,8 +427,24 @@ namespace Slic3r
std::sort(curr_layer_extruders.begin(), curr_layer_extruders.end());
std::sort(next_layer_extruders.begin(), next_layer_extruders.end());
float best_cost = std::numeric_limits<float>::max();
int best_change = std::numeric_limits<int>::max(); // add filament change check in case flush volume between different filament is 0
std::vector<unsigned int>best_seq;
auto get_filament_change_count = [](const std::vector<unsigned int>& curr_seq, const std::vector<unsigned int>& next_seq,const std::optional<unsigned int>& start_extruder_id) {
int count = 0;
auto prev_extruder_id = start_extruder_id;
for (auto seq : { curr_seq,next_seq }) {
for (auto eid : seq) {
if (prev_extruder_id && prev_extruder_id != eid) {
count += 1;
}
prev_extruder_id = eid;
}
}
return count;
};
do {
std::optional<unsigned int>prev_extruder_1 = start_extruder_id;
float curr_layer_cost = 0;
@@ -441,6 +458,7 @@ namespace Slic3r
do {
std::optional<unsigned int>prev_extruder_2 = prev_extruder_1;
float total_cost = curr_layer_cost;
int total_change = get_filament_change_count(curr_layer_extruders, next_layer_extruders, start_extruder_id);
for (size_t idx = 0; idx < next_layer_extruders.size(); ++idx) {
if (prev_extruder_2)
@@ -448,9 +466,10 @@ namespace Slic3r
prev_extruder_2 = next_layer_extruders[idx];
}
if (total_cost < best_cost) {
if (total_cost < best_cost || (total_cost == best_cost && total_change < best_change)) {
best_cost = total_cost;
best_seq = curr_layer_extruders;
best_change = total_change;
}
} while (std::next_permutation(next_layer_extruders.begin(), next_layer_extruders.end()));
} while (std::next_permutation(curr_layer_extruders.begin(), curr_layer_extruders.end()));

View File

@@ -67,16 +67,12 @@ bool check_filament_printable_after_group(const std::vector<unsigned int> &used_
{
for (unsigned int filament_id : used_filaments) {
std::string filament_type = print_config->filament_type.get_at(filament_id);
for (size_t idx = 0; idx < print_config->unprintable_filament_types.values.size(); ++idx) {
if (filament_maps[filament_id] == idx) {
std::vector<std::string> limit_types = split_string(print_config->unprintable_filament_types.get_at(idx), ',');
auto iter = std::find(limit_types.begin(), limit_types.end(), filament_type);
if (iter != limit_types.end()) {
std::string extruder_name = idx == 0 ? _L("left") : _L("right");
std::string error_msg = _L("Grouping error: ") + filament_type + _L(" can not be placed in the ") + extruder_name + _L(" nozzle");
throw Slic3r::RuntimeError(error_msg);
}
}
int printable_status = print_config->filament_printable.get_at(filament_id);
int extruder_idx = filament_maps[filament_id];
if (!(printable_status >> extruder_idx & 1)) {
std::string extruder_name = extruder_idx == 0 ? _L("left") : _L("right");
std::string error_msg = _L("Grouping error: ") + filament_type + _L(" can not be placed in the ") + extruder_name + _L(" nozzle");
throw Slic3r::RuntimeError(error_msg);
}
}
return true;
@@ -679,33 +675,6 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
}
bool ToolOrdering::check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config)
{
int check_extruder_id = -1;
int master_extruder_id = config->master_extruder_id.value - 1; // transfer to 0 based idx
std::map<int, std::vector<int>> extruder_filament_nums;
for (unsigned int filament_id : used_filaments) {
int extruder_id = filament_maps[filament_id];
extruder_filament_nums[extruder_id].push_back(filament_id);
std::string filament_type = config->filament_type.get_at(filament_id);
if (filament_type == "TPU") {
// if we meet two TPU filaments, just return false
if(check_extruder_id==-1)
check_extruder_id = filament_maps[filament_id];
else
return false;
}
}
// TPU can only place in master extruder, and it should have no other filaments in the same extruder
if (check_extruder_id != -1 && (check_extruder_id != master_extruder_id || extruder_filament_nums[check_extruder_id].size() > 1)) {
return false;
}
return true;
}
void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height)
{
if (m_layer_tools.empty())
@@ -1161,13 +1130,6 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
check_filament_printable_after_group(used_filaments, filament_maps, print_config);
if (nozzle_nums > 1 && !check_tpu_group(used_filaments, filament_maps, print_config)) {
int master_extruder_id = print_config->master_extruder_id.value - 1; // to 0 based
std::string nozzle_name = master_extruder_id == 0 ? _L("left") : _L("right");
std::string exception_str = _L("TPU is incompatible with BOX and must be printed seperately in the ") + nozzle_name + _L(" nozzle.\nPlease adjust the filament group accordingly.");
throw Slic3r::RuntimeError(exception_str);
}
}
else {
// we just need to change the map to 0 based

View File

@@ -243,8 +243,6 @@ public:
*/
static std::vector<int> get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const Print* print,const FilamentMapMode mode, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables);
static bool check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config);
// should be called after doing reorder
FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode);
void cal_most_used_extruder(const PrintConfig &config);

View File

@@ -16,7 +16,6 @@
namespace Slic3r
{
bool flat_ironing = true; // Whether to enable flat ironing for the wipe tower
float flat_iron_area = 4.f;
constexpr float flat_iron_speed = 10.f * 60.f;
static const double wipe_tower_wall_infill_overlap = 0.0;
@@ -25,6 +24,7 @@ static constexpr double WT_SIMPLIFY_TOLERANCE_SCALED = 0.001 / SCALING_FACTOR;
static constexpr int arc_fit_size = 20;
#define SCALED_WIPE_TOWER_RESOLUTION (WIPE_TOWER_RESOLUTION / SCALING_FACTOR)
enum class LimitFlow { None, LimitPrintFlow, LimitRammingFlow };
static const std::map<float, float> nozzle_diameter_to_nozzle_change_width{{0.2f, 0.5f}, {0.4f, 1.0f}, {0.6f, 1.2f}, {0.8f, 1.4f}};
inline float align_round(float value, float base)
{
@@ -1596,10 +1596,12 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
m_extra_spacing((float)config.prime_tower_infill_gap.value/100.f),
m_tower_framework(config.prime_tower_enable_framework.value),
m_max_speed((float)config.prime_tower_max_speed.value*60.f),
m_printable_height(config.extruder_printable_height.values),
m_accel_to_decel_enable(config.accel_to_decel_enable.value),
m_accel_to_decel_factor(config.accel_to_decel_factor.value)
m_accel_to_decel_factor(config.accel_to_decel_factor.value),
m_printable_height(config.extruder_printable_height.values),
m_flat_ironing(config.prime_tower_flat_ironing.value)
{
m_flat_ironing = (m_flat_ironing && m_use_gap_wall);
m_normal_accels.clear();
for (auto value : config.default_acceleration.values) {
m_normal_accels.emplace_back((unsigned int) floor(value + 0.5));
@@ -1663,7 +1665,6 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
m_bed_bottom_left = m_bed_shape == RectangularBed
? Vec2f(bed_points.front().x(), bed_points.front().y())
: Vec2f::Zero();
flat_ironing = config.nozzle_diameter.values.size() > 1;//Only used for dual extrusion
m_last_layer_id.resize(config.nozzle_diameter.size(), -1);
}
@@ -1725,7 +1726,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
m_filpar[idx].ramming_travel_time = float(config.filament_ramming_travel_time.get_at(idx));
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
m_nozzle_change_perimeter_width = 2*m_perimeter_width;
m_nozzle_change_perimeter_width = nozzle_diameter_to_nozzle_change_width.at(nozzle_diameter);
// QDS: remove useless config
#if 0
if (m_semm) {
@@ -1767,7 +1768,7 @@ Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, fl
const float &xr = cleaning_box.rd.x();
int line_count = wipe_length / (xr - xl);
float dy = m_layer_info->extra_spacing * m_perimeter_width;
float dy = m_layer_info->extra_spacing * get_block_gap_width(m_current_tool,false);
float y_offset = float(line_count) * dy;
const Vec2f pos_offset = Vec2f(0.f, m_depth_traversed);
@@ -2607,6 +2608,33 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
return construct_tcr(writer, false, old_tool, true, false, 0.f);
}
WipeTower::WipeTowerInfo::ToolChange WipeTower::set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume)
{
float depth = 0.f;
float width = m_wipe_tower_width - 2 * m_perimeter_width;
float nozzle_change_width = m_wipe_tower_width - (m_nozzle_change_perimeter_width + m_perimeter_width);
float length_to_extrude = volume_to_length(wipe_volume, m_perimeter_width, layer_height);
float toolchange_gap_width = get_block_gap_width(new_tool,false);
float nozzlechange_gap_width = get_block_gap_width(old_tool,true);
depth += std::ceil(length_to_extrude / width) * toolchange_gap_width;
// depth *= m_extra_spacing;
float nozzle_change_depth = 0;
float nozzle_change_length = 0;
if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) {
double e_flow = nozzle_change_extrusion_flow(layer_height);
double length = m_filaments_change_length[old_tool] / e_flow;
int nozzle_change_line_count = std::ceil(length / nozzle_change_width);
nozzle_change_depth = nozzle_change_line_count * nozzlechange_gap_width;
depth += nozzle_change_depth;
nozzle_change_length = length;
}
WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume);
tool_change.nozzle_change_depth = nozzle_change_depth;
tool_change.nozzle_change_length = nozzle_change_length;
return tool_change;
}
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool,
unsigned int new_tool, float wipe_volume, float purge_volume)
@@ -2653,18 +2681,18 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
//depth *= m_extra_spacing;
float nozzle_change_depth = 0;
float nozzle_change_length = 0;
if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) {
double e_flow = nozzle_change_extrusion_flow(layer_height_par);
double length = m_filaments_change_length[old_tool] / e_flow;
int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width));
if (m_need_reverse_travel)
nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width;
else
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
depth += nozzle_change_depth;
nozzle_change_length = length;
}
WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume);
tool_change.nozzle_change_depth = nozzle_change_depth;
tool_change.nozzle_change_length = nozzle_change_length;
m_plan.back().tool_changes.push_back(tool_change);
#endif
}
@@ -2795,7 +2823,7 @@ bool WipeTower::is_tpu_filament(int filament_id) const
bool WipeTower::is_need_reverse_travel(int filament_id) const
{
return m_filpar[filament_id].ramming_travel_time > EPSILON;
return m_filpar[filament_id].ramming_travel_time > EPSILON && m_filaments_change_length[filament_id]>EPSILON;
}
// QDS: consider both soluable and support properties
@@ -2865,16 +2893,12 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer)
const WipeTowerInfo::ToolChange &tool_change = layer.tool_changes[i];
size_t old_filament = tool_change.old_tool;
size_t new_filament = tool_change.new_tool;
float spacing = m_layer_info->extra_spacing;
if (m_need_reverse_travel && m_layer_info->extra_spacing < m_tpu_fixed_spacing) spacing = 1;
else if (m_need_reverse_travel) spacing = spacing / m_tpu_fixed_spacing;
float nozzle_change_depth = tool_change.nozzle_change_depth * spacing;
//float nozzle_change_depth = tool_change.nozzle_change_depth * (has_tpu_filament() ? m_tpu_fixed_spacing : layer.extra_spacing);
float nozzle_change_depth = tool_change.nozzle_change_depth;
float wipe_depth = tool_change.required_depth - nozzle_change_depth;
if (!is_valid_last_layer(old_filament)) nozzle_change_depth = 0.f;
auto* block = get_block_by_category(m_filpar[new_filament].category, false);
if (!block)
continue;
//float wipe_depth = tool_change.required_depth - nozzle_change_depth;
float wipe_depth = ceil(tool_change.wipe_length / (m_wipe_tower_width - 2 * m_perimeter_width)) * m_perimeter_width*layer.extra_spacing;
float process_depth = 0.f;
if (!cur_block_depth.count(m_filpar[new_filament].category))
cur_block_depth[m_filpar[new_filament].category] = block->start_depth;
@@ -2892,20 +2916,21 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer)
cur_block_depth[m_filpar[old_filament].category] += nozzle_change_depth;
}
}
{
{
float infill_gap_width = get_block_gap_width(new_filament,false);
Vec2f res;
int index = m_cur_layer_id % 4;
switch (index % 4) {
case 0: res = Vec2f(0, process_depth); break;
case 1: res = Vec2f(m_wipe_tower_width, process_depth + wipe_depth - layer.extra_spacing*m_perimeter_width); break;
case 1: res = Vec2f(m_wipe_tower_width, process_depth + wipe_depth - m_layer_info->extra_spacing * infill_gap_width); break;
case 2: res = Vec2f(m_wipe_tower_width, process_depth); break;
case 3: res = Vec2f(0, process_depth + wipe_depth - layer.extra_spacing * m_perimeter_width); break;
case 3: res = Vec2f(0, process_depth + wipe_depth - m_layer_info->extra_spacing * infill_gap_width); break;
default: break;
}
m_wall_skip_points.emplace_back(res);
}
cur_block_depth[m_filpar[new_filament].category] = process_depth + tool_change.required_depth - tool_change.nozzle_change_depth * layer.extra_spacing;
cur_block_depth[m_filpar[new_filament].category] = process_depth + wipe_depth;
}
}
@@ -2932,21 +2957,17 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
wipe_depth = b.required_depth;
purge_volume = b.purge_volume;
nozzle_change_depth = b.nozzle_change_depth;
if (m_need_reverse_travel)
nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2;
else
nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width;
break;
}
}
m_current_tool = new_tool;
WipeTowerBlock* block = get_block_by_category(m_filpar[new_tool].category, false);
if (!block) {
assert(block != nullptr);
return WipeTower::ToolChangeResult();
}
m_cur_block = block;
box_coordinates cleaning_box(Vec2f(m_perimeter_width, block->cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, wipe_depth-m_layer_info->extra_spacing*nozzle_change_depth);
box_coordinates cleaning_box(Vec2f(m_perimeter_width, block->cur_depth), m_wipe_tower_width - 2 * m_perimeter_width, wipe_depth-nozzle_change_depth);
WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar);
writer.set_extrusion_flow(m_extrusion_flow)
@@ -3016,7 +3037,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
} else
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].nozzle_temperature);
block->cur_depth += (wipe_depth - nozzle_change_depth * m_layer_info->extra_spacing);
block->cur_depth += (wipe_depth - nozzle_change_depth);
block->last_filament_change_id = new_tool;
// QDS
@@ -3038,13 +3059,14 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_infill)
{
int nozzle_change_line_count = 0;
float x_offset = m_perimeter_width + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2;
float nozzle_change_box_width = m_wipe_tower_width - 2 * x_offset;
float nozzle_change_depth = 0.f;
if (new_filament_id != (unsigned int) (-1)) {
for (const auto &b : m_layer_info->tool_changes)
if (b.new_tool == new_filament_id) {
if (m_need_reverse_travel)
nozzle_change_line_count = ((b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width) / 2;
else
nozzle_change_line_count = (b.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width;
nozzle_change_line_count = std::ceil(b.nozzle_change_length / nozzle_change_box_width);
nozzle_change_depth = b.nozzle_change_depth;
break;
}
}
@@ -3077,14 +3099,10 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
return WipeTower::NozzleChangeResult();
}
m_cur_block = block;
float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width;
if (m_need_reverse_travel && m_extra_spacing < m_tpu_fixed_spacing)
dy = m_tpu_fixed_spacing * m_nozzle_change_perimeter_width;
float x_offset = m_perimeter_width + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2;
float dy = is_first_layer() ? m_nozzle_change_perimeter_width : m_layer_info->extra_spacing * get_block_gap_width(m_current_tool,true);
box_coordinates cleaning_box(Vec2f(x_offset,block->cur_depth + (m_nozzle_change_perimeter_width - m_perimeter_width) / 2),
m_wipe_tower_width - 2 * x_offset,
nozzle_change_line_count * dy - (m_nozzle_change_perimeter_width - m_perimeter_width) / 2);//top can not print
nozzle_change_box_width,
nozzle_change_depth); // top can not print
Vec2f initial_position = cleaning_box.ld;
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
@@ -3132,7 +3150,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
}
writer.set_extrusion_flow(nz_extrusion_flow); // Reset the extrusion flow.
block->cur_depth += nozzle_change_line_count * dy;
block->cur_depth += nozzle_change_depth;
block->last_nozzle_change_id = old_filament_id;
NozzleChangeResult result;
@@ -3165,7 +3183,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
}
} else {
result.wipe_path.push_back(writer.pos_rotated());
if (m_left_to_right) {
if (m_left_to_right) {
result.wipe_path.push_back(Vec2f(0, writer.pos_rotated().y()));
} else {
result.wipe_path.push_back(Vec2f(m_wipe_tower_width, writer.pos_rotated().y()));
@@ -3559,7 +3577,9 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
const float &xr = cleaning_box.rd.x();
float x_to_wipe = wipe_length;
float dy = solid_tool_toolchange ? m_perimeter_width :m_layer_info->extra_spacing * m_perimeter_width;
float dy = is_first_layer() ? m_perimeter_width : m_layer_info->extra_spacing * get_block_gap_width(m_current_tool,false);
if (solid_tool_toolchange)
dy = m_perimeter_width;
x_to_wipe = solid_tool_toolchange ? std::numeric_limits<float>::max(): x_to_wipe;
float target_speed = is_first_layer() ? std::min(m_first_layer_speed * 60.f, m_max_speed) : m_max_speed;
target_speed = solid_tool_toolchange ? 20.f * 60.f : target_speed;
@@ -3597,7 +3617,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed);
writer.retract(retract_length, retract_speed);
writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 600.);
if (flat_ironing) {
if (m_flat_ironing) {
writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.);
Vec2f pos{writer.x() + 1.f * ironing_length, writer.y()};
writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed);
@@ -3612,7 +3632,7 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
writer.extrude(writer.x() - ironing_length, writer.y(), wipe_speed);
writer.retract(retract_length, retract_speed);
writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 600.);
if (flat_ironing) {
if (m_flat_ironing) {
writer.travel(writer.x() - 0.5f * ironing_length, writer.y(), 240.);
Vec2f pos{writer.x() - 1.0f * ironing_length, writer.y()};
writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed);
@@ -3866,34 +3886,99 @@ void WipeTower::generate_wipe_tower_blocks()
}
}
void WipeTower::calc_block_infill_gap()
{
//1.calc block infill gap width
struct BlockInfo
{
bool has_ramming = false;
bool has_reverse_travel = false;
float depth = 0.f;
};
std::unordered_map<int, BlockInfo> block_info;
std::unordered_map<int, BlockInfo> high_block_info;
for (int i= (int)m_plan.size()-1;i>=0;i--)
{
for (auto &toolchange : m_plan[i].tool_changes) {
int new_tool =toolchange.new_tool;
int old_tool =toolchange.old_tool;
if (!m_filament_map.empty() && m_filament_map[old_tool] != m_filament_map[new_tool]) {
block_info[m_filpar[old_tool].category].has_ramming=true;
if (is_need_reverse_travel(old_tool)) block_info[m_filpar[old_tool].category].has_reverse_travel = true;
block_info[m_filpar[old_tool].category].depth += toolchange.nozzle_change_depth;
}
if (!block_info.count(m_filpar[new_tool].category)) block_info.insert({m_filpar[new_tool].category,BlockInfo{}});
block_info[m_filpar[new_tool].category].depth += toolchange.required_depth - toolchange.nozzle_change_depth;
}
for (auto &block : block_info) {
if (high_block_info.count(block.first) && high_block_info[block.first].depth > block.second.depth)
block.second.depth = high_block_info[block.first].depth;
}
high_block_info = block_info;
for (auto &block : block_info) { block.second.depth = 0.f;}
if (i == 0) block_info = high_block_info;
}
float max_depth = std::accumulate(block_info.begin(), block_info.end(), 0.f, [](float value, const std::pair<int,BlockInfo> &block) { return value + block.second.depth; });
float height_to_depth = get_limit_depth_by_height(m_wipe_tower_height);
float height_to_spacing = max_depth > height_to_depth ? 1.f : height_to_depth / max_depth;
float spacing_ratio = m_extra_spacing - 1.f;
float extra_width = spacing_ratio * m_perimeter_width;
float line_gap_tol = 2.f * m_nozzle_change_perimeter_width; //If the block's line_gap is greater than it, the block should be aligned.
for (auto &info : block_info) {
//case1: no ramming, it can always align
if (!info.second.has_ramming) {
m_block_infill_gap_width[info.first].first = m_block_infill_gap_width[info.first].second = extra_width + m_perimeter_width;
}
// case2: has ramming, but no reverse travel
//
else if (!info.second.has_reverse_travel) {
float line_gap = m_nozzle_change_perimeter_width + extra_width;
if (!m_use_rib_wall) line_gap *= height_to_spacing;
if (line_gap < line_gap_tol) {
m_block_infill_gap_width[info.first].first = m_perimeter_width + extra_width;
m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + extra_width;
} else {
m_block_infill_gap_width[info.first].first = m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + extra_width;
}
}
// case 3: has ramming and reverse travel
else {
float extra_tpu_fix_spacing = m_tpu_fixed_spacing - 1.f;
float line_gap = m_nozzle_change_perimeter_width + std::max(extra_tpu_fix_spacing * m_perimeter_width, extra_width);
if (!m_use_rib_wall) line_gap = height_to_spacing * line_gap;
if (line_gap < line_gap_tol) {
m_block_infill_gap_width[info.first].first = m_perimeter_width + extra_width;
m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width + std::max(extra_tpu_fix_spacing * m_perimeter_width, extra_width);
} else {
m_block_infill_gap_width[info.first].first = m_block_infill_gap_width[info.first].second = m_nozzle_change_perimeter_width +
std::max(extra_tpu_fix_spacing * m_perimeter_width, extra_width);
}
}
}
//2. recalculate toolchange depth
for (int idx = 0; idx < m_plan.size(); idx++) {
for (auto &toolchange : m_plan[idx].tool_changes) {
toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool, m_plan[idx].height, toolchange.wipe_volume, toolchange.purge_volume);
}
}
m_extra_spacing = 1.f;
}
void WipeTower::plan_tower_new()
{
if (m_wipe_tower_brim_width < 0) m_wipe_tower_brim_width = get_auto_brim_by_height(m_wipe_tower_height);
calc_block_infill_gap();
if (m_use_rib_wall) {
// recalculate wipe_tower_with and layer's depth
generate_wipe_tower_blocks();
float max_depth = std::accumulate(m_wipe_tower_blocks.begin(), m_wipe_tower_blocks.end(), 0.f, [](float a, const auto &t) { return a + t.depth; }) + m_perimeter_width;
float square_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width * m_extra_spacing), m_perimeter_width);
//std::cout << " before m_wipe_tower_width = " << m_wipe_tower_width << " max_depth = " << max_depth << std::endl;
m_wipe_tower_width = square_width;
float width = m_wipe_tower_width - 2 * m_perimeter_width;
for (int idx = 0; idx < m_plan.size(); idx++) {
for (auto &toolchange : m_plan[idx].tool_changes) {
float length_to_extrude = toolchange.wipe_length;
float depth = std::ceil(length_to_extrude / width) * m_perimeter_width;
float nozzle_change_depth = 0;
if (!m_filament_map.empty() && m_filament_map[toolchange.old_tool] != m_filament_map[toolchange.new_tool]) {
double e_flow = nozzle_change_extrusion_flow(m_plan[idx].height);
double length = m_filaments_change_length[toolchange.old_tool] / e_flow;
int nozzle_change_line_count = std::ceil(length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width));
if (m_need_reverse_travel)
nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width;
else
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
depth += nozzle_change_depth;
}
toolchange.nozzle_change_depth = nozzle_change_depth;
toolchange.required_depth = depth;
toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool, m_plan[idx].height, toolchange.wipe_volume, toolchange.purge_volume);
}
}
}
@@ -3925,28 +4010,29 @@ void WipeTower::plan_tower_new()
for (int idx = 0; idx < m_plan.size(); idx++) {
auto &info = m_plan[idx];
if (idx == 0 && m_extra_spacing > 1.f + EPSILON) {
if (idx == 0 /*&& m_extra_spacing > 1.f + EPSILON*/) {
// apply solid fill for the first layer
info.extra_spacing = 1.f;
for (auto &toolchange : info.tool_changes) {
float x_to_wipe = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height);
//float x_to_wipe = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height);
float line_len = m_wipe_tower_width - 2 * m_perimeter_width;
float x_to_wipe_new = x_to_wipe * m_extra_spacing;
x_to_wipe_new = std::floor(x_to_wipe_new / line_len) * line_len;
x_to_wipe_new = std::max(x_to_wipe_new, x_to_wipe);
float wipe_depth = (toolchange.required_depth - toolchange.nozzle_change_depth) * m_extra_spacing;
float wipe_line_count = wipe_depth / m_perimeter_width;
float nozzle_change_depth = toolchange.nozzle_change_depth * m_extra_spacing;
int line_count = std::ceil((x_to_wipe_new - WT_EPSILON) / line_len);
// nozzle change length
int nozzle_change_line_count = (toolchange.nozzle_change_depth + WT_EPSILON) / m_nozzle_change_perimeter_width;
int nozzle_change_line_count = (toolchange.nozzle_change_depth * m_extra_spacing + WT_EPSILON) / m_nozzle_change_perimeter_width;
toolchange.required_depth = line_count * m_perimeter_width + nozzle_change_line_count * m_nozzle_change_perimeter_width;
toolchange.wipe_volume = x_to_wipe_new / x_to_wipe * toolchange.wipe_volume;
toolchange.wipe_length = x_to_wipe_new;
toolchange.required_depth = wipe_depth + nozzle_change_depth;
toolchange.wipe_length = wipe_line_count * line_len;
toolchange.wipe_volume = length_to_volume(toolchange.wipe_length, m_perimeter_width, info.height);
toolchange.nozzle_change_length = nozzle_change_line_count * (m_wipe_tower_width - (m_nozzle_change_perimeter_width + m_perimeter_width));
toolchange.nozzle_change_depth = nozzle_change_depth;
}
} else {
info.extra_spacing = m_extra_spacing;
for (auto &toolchange : info.tool_changes) {
toolchange.required_depth *= m_extra_spacing;
toolchange.nozzle_change_depth *= m_extra_spacing;
toolchange.wipe_length = volume_to_length(toolchange.wipe_volume, m_perimeter_width, info.height);
}
}
@@ -4629,5 +4715,14 @@ bool WipeTower::is_valid_last_layer(int tool) const
if (m_last_layer_id[nozzle_id] == m_cur_layer_id && m_z_pos > m_printable_height[nozzle_id]) return false;
return true;
}
float WipeTower::get_block_gap_width(int tool,bool is_nozzlechangle)
{
assert(m_block_infill_gap_width.count(m_filpar[tool].category));
if (!m_block_infill_gap_width.count(m_filpar[tool].category)) {
return m_perimeter_width;
}
return is_nozzlechangle ? m_block_infill_gap_width[m_filpar[tool].category].second : m_block_infill_gap_width[m_filpar[tool].category].first;
}
} // namespace Slic3r

View File

@@ -184,6 +184,7 @@ public:
// to be used before building begins. The entries must be added ordered in z.
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<ToolChangeResult>> &result);
@@ -307,14 +308,13 @@ public:
void set_need_reverse_travel(const std::vector<unsigned int> & used_extruders)
{
for (unsigned int filament_id : used_extruders) {
if (m_filpar[filament_id].ramming_travel_time > EPSILON) {
if (m_filpar[filament_id].ramming_travel_time > EPSILON && m_filaments_change_length[filament_id]>EPSILON) {
m_need_reverse_travel = true;
return;
}
}
}
bool has_tpu_filament() const { return m_has_tpu_filament; }
struct FilamentParameters {
std::string material = "PLA";
int category;
@@ -393,7 +393,7 @@ public:
void generate_wipe_tower_blocks();
void update_all_layer_depth(float wipe_tower_depth);
void set_nozzle_last_layer_id();
void calc_block_infill_gap();
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
@@ -484,7 +484,7 @@ private:
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
std::unordered_map<int, std::pair<float,float>> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap
// Extruder specific parameters.
std::vector<FilamentParameters> m_filpar;
@@ -511,12 +511,16 @@ private:
std::vector<double> m_printable_height;
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
bool is_valid_last_layer(int tool) const;
bool m_flat_ironing=false;
// Calculates length of extrusion line to extrude given volume
float volume_to_length(float volume, float line_width, float layer_height) const {
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
}
// Calculates volume of extrusion line
float length_to_volume(float length,float line_width, float layer_height) const
{
return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
}
// Calculates depth for all layers and propagates them downwards
void plan_tower();
@@ -546,6 +550,7 @@ private:
float wipe_volume;
float wipe_length;
float nozzle_change_depth{0};
float nozzle_change_length{0};
// QDS
float purge_volume;
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
@@ -575,7 +580,7 @@ private:
// ot -1 if there is no such toolchange.
int first_toolchange_to_nonsoluble_nonsupport(
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume);
void toolchange_Unload(
WipeTowerWriter &writer,
const box_coordinates &cleaning_box,
@@ -597,6 +602,7 @@ private:
float wipe_volume);
void get_wall_skip_points(const WipeTowerInfo &layer);
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
float get_block_gap_width(int tool, bool is_nozzlechangle = false);
};