mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-07 12:21:50 +03:00
update libslic3r
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
583
src/libslic3r/GCode/TimelapsePosPicker.cpp
Normal file
583
src/libslic3r/GCode/TimelapsePosPicker.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
81
src/libslic3r/GCode/TimelapsePosPicker.hpp
Normal file
81
src/libslic3r/GCode/TimelapsePosPicker.hpp
Normal 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
|
||||
@@ -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()));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user