code cleanup

This commit is contained in:
SoftFever
2026-07-29 23:46:42 +08:00
parent 252df70ec4
commit 3ab9cf53d0
5 changed files with 120 additions and 445 deletions

View File

@@ -13,6 +13,7 @@
#include "GCode/PrintExtents.hpp"
#include "GCode/Thumbnails.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/WipeTower2.hpp"
#include "ShortestPath.hpp"
#include "Print.hpp"
#include "Utils.hpp"
@@ -888,18 +889,22 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
return res;
}
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
// entry (tcr.start_pos): route the approach around the tower's bounding box so the
// nozzle enters through that opening instead of dragging across the printed wall
// (append_tcr parity). Emits only the waypoints leading up to the opening — the
// caller still travels to start_wipe_pos itself. Returns an empty string when the
// option is off, the cone wall is active (it has no gap machinery), or the approach
// already starts inside the tower: such hops never cross the wall and must stay direct.
std::string WipeTowerIntegration::travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
// Type2 tower-local point -> bed frame. The rib-wall offset is tower-local, so it
// rotates with the tower (unlike the BBL tower in append_tcr, which never rotates).
Vec2f WipeTowerIntegration::transform_wt2_pt(const Vec2f &pt) const
{
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
}
// Printable-area bounds for tower-approach routing, in object coordinates (shared by
// the BBL avoid-perimeter path in append_tcr and the Type2 skip-points router).
// Multi-nozzle: clamp the travel bounds to the region every extruder can reach
// (get_extruder_shared_printable_polygon) instead of the full bed. Gated on the
// multi-nozzle predicate so every existing single/dual printer keeps the historic
// full-printable_area routing byte-identical.
BoundingBox WipeTowerIntegration::printer_travel_bounds(GCode &gcodegen) const
{
if (!gcodegen.m_config.prime_tower_skip_points.value
|| gcodegen.m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwCone)
return {};
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
BoundingBox printer_bbx;
if (is_multi_nozzle_printer(gcodegen.m_config)) {
@@ -912,19 +917,30 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d));
printer_bbx = BoundingBox(bed_points);
}
// Transform the tower-local bbx corners exactly like the tcr points (rib
// offset, rotation, tower position); a rotated tower gets a conservative
// axis-aligned envelope.
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
for (auto& p : avoid_points.points) {
Vec2f pp = Eigen::Rotation2Df(alpha) * (unscale(p).cast<float>() + m_rib_offset) + m_wipe_tower_pos;
p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d);
}
return printer_bbx;
}
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
// entry (tcr.start_pos): route the approach around the tower's bounding box so the
// nozzle enters through that opening instead of dragging across the printed wall
// (append_tcr parity). Emits only the waypoints leading up to the opening — the
// caller still travels to start_wipe_pos itself. Returns an empty string when the
// gap wall is off (option off or cone wall) or the approach already starts inside
// the tower: such hops never cross the wall and must stay direct.
std::string WipeTowerIntegration::travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
{
if (!WipeTower2::use_gap_wall(gcodegen.m_config))
return {};
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
// Transform the tower-local bbx corners exactly like the tcr points; a rotated
// tower gets a conservative axis-aligned envelope.
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
for (auto& p : avoid_points.points)
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
BoundingBox avoid_bbx(avoid_points.points);
if (avoid_bbx.contains(route_start))
return {};
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_bbx);
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_travel_bounds(gcodegen));
std::string gcode;
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
@@ -1305,24 +1321,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
BoundingBox avoid_bbx, printer_bbx;
{
// set printer_bbx
// Multi-nozzle: clamp the avoid-perimeter travel bounds to the region every
// extruder can reach (get_extruder_shared_printable_polygon) instead of the full
// bed. Gated on the multi-nozzle predicate so H2D and every existing single/dual
// printer keep the historic full-printable_area routing byte-identical.
if (is_multi_nozzle_printer(gcodegen.m_config)) {
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
} else {
Pointfs bed_pointsf = gcodegen.m_config.printable_area.values;
Points bed_points;
for (auto p : bed_pointsf) { bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d)); }
printer_bbx = BoundingBox(bed_points);
}
}
BoundingBox avoid_bbx, printer_bbx = printer_travel_bounds(gcodegen);
{
// set avoid_bbx
avoid_bbx = scaled(m_wipe_tower_bbx);
@@ -1466,19 +1465,13 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
// The rib-wall offset is tower-local, so it rotates with the tower (unlike the BBL
// tower in append_tcr, which never rotates). Priming lines are absolute bed moves.
auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f {
Vec2f out = Eigen::Rotation2Df(alpha) * (pt + m_rib_offset);
out += m_wipe_tower_pos;
return out;
};
// Priming lines are absolute bed moves; everything else is tower-local
// (transform_wt2_pt).
Vec2f start_pos = tcr.start_pos;
Vec2f end_pos = tcr.end_pos;
if (!tcr.priming) {
start_pos = transform_wt_pt(start_pos);
end_pos = transform_wt_pt(end_pos);
start_pos = transform_wt2_pt(start_pos);
end_pos = transform_wt2_pt(end_pos);
}
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
@@ -1511,13 +1504,13 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|| is_ramming
|| tool_change_on_wipe_tower);
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
const bool travel_to_tower_now = should_travel_to_tower || gcodegen.m_need_change_layer_lift_z;
if (travel_to_tower_now) {
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
// then we could simplify the condition and make it more readable.
gcode += gcodegen.retract();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
if (!tcr.priming && gcodegen.last_pos_defined())
gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
gcode += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
@@ -1549,9 +1542,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
toolchange_temp_override = interface_temp;
}
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
if (!travel_to_tower_now && !tcr.priming && needs_toolchange
&& gcodegen.m_config.prime_tower_skip_points.value
&& gcodegen.m_config.wipe_tower_wall_type.value != WipeTowerWallType::wtwCone) {
if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
// The tool changed in place (multi-tool printer without ramming), so the
// tower entry is the tcr's own positioning move — a straight line across
// the printed wall. Route it around the tower and in through the wall
@@ -1574,8 +1565,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
if (have_start) {
gcodegen.set_last_pos(route_start);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
travel += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
check_add_eol(travel);
toolchange_gcode_str += travel;
@@ -1767,7 +1757,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
// Prepare a future wipe.
gcodegen.m_wipe.reset_path();
for (const Vec2f& wipe_pt : tcr.wipe_path)
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d));
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(wipe_pt) + plate_origin_2d));
}
// Let the planner know we are traveling between objects.

View File

@@ -133,6 +133,8 @@ private:
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
Vec2f transform_wt2_pt(const Vec2f &pt) const;
BoundingBox printer_travel_bounds(GCode &gcodegen) const;
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;

View File

@@ -20,6 +20,11 @@ class WipeTowerWriter;
class PrintConfig;
enum GCodeFlavor : unsigned char;
// Cuts the tower wall polygon open at each skip point (a toolchange's entry position)
// so the entry travel can pass through instead of crossing the printed wall. Defined in
// WipeTower.cpp, shared by WipeTower and WipeTower2.
Polylines contrust_gap_for_skip_points(
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
class WipeTower
{

View File

@@ -233,24 +233,6 @@ static Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double
return res;
}
static std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, const Vec2f& b, const Vec2f& c)
{
const Vec2f v2 = c - b;
double denom = cross2(v1, v2);
if (fabs(denom) < EPSILON)
return {false, Vec2f(0, 0)};
const Vec2f v12 = (a - b);
double nume_a = cross2(v2, v12);
double nume_b = cross2(v1, v12);
double t1 = nume_a / denom;
double t2 = nume_b / denom;
if (t1 >= 0 && t2 >= 0 && t2 <= 1.) {
// Get the intersection point.
Vec2f res = a + t1 * v1;
return std::pair<bool, Vec2f>(true, res);
}
return std::pair<bool, Vec2f>(false, Vec2f{0, 0});
}
static Polygon scale_polygon(const std::vector<Vec2f>& points)
{
Polygon res;
@@ -295,6 +277,7 @@ static Polygon generate_rectange(const Line& line, coord_t offset)
return poly;
};
// Straight or arc-fitted wall segment used by WipeTowerWriter2::generate_path().
struct Segment
{
Vec2f start;
@@ -305,324 +288,6 @@ struct Segment
bool is_valid() const { return start.y() < end.y(); }
};
static std::vector<Segment> remove_points_from_segment(const Segment& segment, const std::vector<Vec2f>& skip_points, double range)
{
std::vector<Segment> result;
result.push_back(segment);
float x = segment.start.x();
for (const Vec2f& point : skip_points) {
std::vector<Segment> newResult;
for (const auto& seg : result) {
if (point.y() + range <= seg.start.y() || point.y() - range >= seg.end.y()) {
newResult.push_back(seg);
} else {
if (point.y() - range > seg.start.y()) {
newResult.push_back(Segment(Vec2f(x, seg.start.y()), Vec2f(x, point.y() - range)));
}
if (point.y() + range < seg.end.y()) {
newResult.push_back(Segment(Vec2f(x, point.y() + range), Vec2f(x, seg.end.y())));
}
}
}
result = newResult;
}
result.erase(std::remove_if(result.begin(), result.end(), [](const Segment& seg) { return !seg.is_valid(); }), result.end());
return result;
}
struct IntersectionInfo
{
Vec2f pos;
int idx;
int pair_idx; // gap_pair idx
float dis_from_idx;
bool is_forward;
};
struct PointWithFlag
{
Vec2f pos;
int pair_idx; // gap_pair idx
bool is_forward;
};
static IntersectionInfo move_point_along_polygon(
const std::vector<Vec2f>& points, const Vec2f& startPoint, int startIdx, float offset, bool forward, int pair_idx)
{
float remainingDistance = offset;
IntersectionInfo res;
int mod = points.size();
if (forward) {
int next = (startIdx + 1) % mod;
remainingDistance -= (points[next] - startPoint).norm();
if (remainingDistance <= 0) {
res.idx = startIdx;
res.pos = startPoint + (points[next] - startPoint).normalized() * offset;
res.pair_idx = pair_idx;
res.dis_from_idx = (points[startIdx] - res.pos).norm();
return res;
} else {
for (int i = (startIdx + 1) % mod; i != startIdx; i = (i + 1) % mod) {
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
if (remainingDistance <= segmentLength) {
float ratio = remainingDistance / segmentLength;
res.idx = i;
res.pos = points[i] + ratio * (points[(i + 1) % mod] - points[i]);
res.dis_from_idx = remainingDistance;
res.pair_idx = pair_idx;
return res;
}
remainingDistance -= segmentLength;
}
res.idx = (startIdx - 1 + mod) % mod;
res.pos = points[startIdx];
res.pair_idx = pair_idx;
res.dis_from_idx = (res.pos - points[res.idx]).norm();
}
} else {
int next = (startIdx + 1) % mod;
remainingDistance -= (points[startIdx] - startPoint).norm();
if (remainingDistance <= 0) {
res.idx = startIdx;
res.pos = startPoint - (points[next] - points[startIdx]).normalized() * offset;
res.dis_from_idx = (res.pos - points[startIdx]).norm();
res.pair_idx = pair_idx;
return res;
}
for (int i = (startIdx - 1 + mod) % mod; i != startIdx; i = (i - 1 + mod) % mod) {
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
if (remainingDistance <= segmentLength) {
float ratio = remainingDistance / segmentLength;
res.idx = i;
res.pos = points[(i + 1) % mod] - ratio * (points[(i + 1) % mod] - points[i]);
res.dis_from_idx = segmentLength - remainingDistance;
res.pair_idx = pair_idx;
return res;
}
remainingDistance -= segmentLength;
}
res.idx = startIdx;
res.pos = points[res.idx];
res.pair_idx = pair_idx;
res.dis_from_idx = 0;
}
return res;
};
static void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_idx, bool is_forward)
{
int next = (idx + 1) % pl.size();
Vec2f pos1 = pl[idx].pos;
Vec2f pos2 = pl[next].pos;
if ((pos - pos1).squaredNorm() < EPSILON) {
pl[idx].pair_idx = pair_idx;
pl[idx].is_forward = is_forward;
} else if ((pos - pos2).squaredNorm() < EPSILON) {
pl[next].pair_idx = pair_idx;
pl[next].is_forward = is_forward;
} else {
pl.insert(pl.begin() + idx + 1, PointWithFlag{pos, pair_idx, is_forward});
}
}
// For skip_point
// TODO: Optimize the skip_point algorithm itself instead of adding guards here
static Polygon add_extra_point(const Polygon& polygon, int scale_range)
{
Polygon res;
if (polygon.size() < 2) return polygon;
// Compute bounding box of the polygon
auto polygon_box = get_extents(polygon);
// Anchor point: X at bbox center, Y at bbox bottom
Vec2f anchor_point(float(polygon_box.center()[0]), float(polygon_box.min[1]));
// Find the edge whose midpoint is closest to the anchor point
size_t closest_edge_idx = 0;
float min_dist_sq = std::numeric_limits<float>::max();
for (size_t i = 0; i < polygon.size(); ++i) {
const Point &a_i = polygon[i];
const Point &b_i = polygon[(i + 1) % polygon.size()];
Vec2f a(float(a_i.x()), float(a_i.y()));
Vec2f b(float(b_i.x()), float(b_i.y()));
Vec2f mid = (a + b) * 0.5f;
float dist_sq = (anchor_point - mid).squaredNorm();
if (dist_sq < min_dist_sq) {
min_dist_sq = dist_sq;
closest_edge_idx = i;
}
}
// Edge endpoints (integer space)
const Point &a_i = polygon[closest_edge_idx];
const Point &b_i = polygon[(closest_edge_idx + 1) % polygon.size()];
// Convert to float for geometric computation
Vec2f a(float(a_i.x()), float(a_i.y()));
Vec2f b(float(b_i.x()), float(b_i.y()));
Vec2f mid = (a + b) * 0.5f;
// Direction vectors from midpoint towards A and B
Vec2f dir_to_a = a - mid;
Vec2f dir_to_b = b - mid;
float len_a = dir_to_a.norm();
float len_b = dir_to_b.norm();
// Guard against degenerated edges
if (len_a < EPSILON || len_b < EPSILON) return polygon;
dir_to_a /= len_a;
dir_to_b /= len_b;
// Clamp range to avoid overshooting the edge
float max_range = std::min(len_a, len_b) * 0.9f;
float range = std::min(float(scale_range), max_range);
// Offset points (float space)
Vec2f offset_to_a_f = mid + dir_to_a * range;
Vec2f offset_to_b_f = mid + dir_to_b * range;
// Safe cast back to scaled integer Point
auto to_int_point = [](const Vec2f &p) {
auto clamp = [](float v) -> coord_t {
constexpr float kMin = float(std::numeric_limits<coord_t>::min());
constexpr float kMax = float(std::numeric_limits<coord_t>::max());
v = std::clamp(v, kMin, kMax);
return static_cast<coord_t>(std::lround(v));
};
return Point(clamp(p.x()), clamp(p.y()));
};
Point mid_i = to_int_point(mid);
Point offset_to_a_i = to_int_point(offset_to_a_f);
Point offset_to_b_i = to_int_point(offset_to_b_f);
// Rebuild polygon with inserted points
for (size_t i = 0; i < polygon.size(); ++i) {
res.points.push_back(polygon[i]);
// Insert points right after the selected edge start vertex
if (i == closest_edge_idx) {
res.points.push_back(offset_to_a_i);
res.points.push_back(mid_i);
res.points.push_back(offset_to_b_i);
}
}
return res;
}
static Polylines remove_points_from_polygon(
const Polygon& polygon_ori, const std::vector<Vec2f>& skip_points, double range, float wt_width, Polygon& insert_skip_pg)
{
Polygon polygon = add_extra_point(polygon_ori, scale_(range));
if (polygon.size() < 2) return Polylines{to_polyline(polygon)};
Polylines result;
std::vector<PointWithFlag> new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point.
std::vector<IntersectionInfo> inter_info;
auto polygon_box = get_extents(polygon);
Point anchor_point = Point{polygon_box.center()[0], polygon_box.min[1]}; // for next reconnect
std::vector<Vec2f> points;
{
points.reserve(polygon.points.size());
int idx = polygon.closest_point_index(anchor_point);
Polyline tmp_poly = polygon.split_at_index(idx);
for (auto& p : tmp_poly)
points.push_back(unscale(p).cast<float>());
points.pop_back();
}
for (int i = 0; i < skip_points.size(); i++) {
bool is_left = abs(skip_points[i].x()) < wt_width / 2.f;
Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0);
for (int j = 0; j < points.size(); j++) {
Vec2f& p1 = points[j];
Vec2f& p2 = points[(j + 1) % points.size()];
auto [is_inter, inter_pos] = ray_intersetion_line(skip_points[i], ray, p1, p2);
if (is_inter) {
IntersectionInfo forward = move_point_along_polygon(points, inter_pos, j, range, true, i);
IntersectionInfo backward = move_point_along_polygon(points, inter_pos, j, range, false, i);
backward.is_forward = false;
forward.is_forward = true;
inter_info.push_back(backward);
inter_info.push_back(forward);
break;
}
}
}
// insert point to new_pl
for (const auto& p : points)
new_pl.push_back({p, -1});
std::sort(inter_info.begin(), inter_info.end(), [](const IntersectionInfo& lhs, const IntersectionInfo& rhs) {
if (rhs.idx == lhs.idx)
return lhs.dis_from_idx < rhs.dis_from_idx;
return lhs.idx < rhs.idx;
});
for (int i = inter_info.size() - 1; i >= 0; i--) {
insert_points(new_pl, inter_info[i].idx, inter_info[i].pos, inter_info[i].pair_idx, inter_info[i].is_forward);
}
{
// set insert_pg for wipe_path
for (auto& p : new_pl)
insert_skip_pg.points.push_back(scaled(p.pos));
}
int beg = 0;
bool skip = true;
int i = beg;
Polyline pl;
do {
if (skip || new_pl[i].pair_idx == -1) {
pl.points.push_back(scaled(new_pl[i].pos));
i = (i + 1) % new_pl.size();
skip = false;
} else {
if (!pl.points.empty()) {
pl.points.push_back(scaled(new_pl[i].pos));
result.push_back(pl);
pl.points.clear();
}
int left = new_pl[i].pair_idx;
int j = (i + 1) % new_pl.size();
while (j != beg && new_pl[j].pair_idx != left) {
if (new_pl[j].pair_idx != -1 && !new_pl[j].is_forward)
left = new_pl[j].pair_idx;
j = (j + 1) % new_pl.size();
}
i = j;
skip = true;
}
} while (i != beg);
if (!pl.points.empty()) {
if (new_pl[i].pair_idx == -1)
pl.points.push_back(scaled(new_pl[i].pos));
result.push_back(pl);
}
return result;
}
static Polylines contrust_gap_for_skip_points(
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon)
{
if (skip_points.empty()) {
insert_skip_polygon = polygon;
return Polylines{to_polyline(polygon)};
}
return remove_points_from_polygon(polygon, skip_points, gap_length, wt_width, insert_skip_polygon);
};
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
{
Polygon res;
@@ -1334,6 +999,12 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
bool WipeTower2::use_gap_wall(const PrintConfig& config)
{
// The cone wall has its own fully separate generator with no gap machinery.
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
}
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
m_semm(config.single_extruder_multi_material.value),
m_enable_filament_ramming(config.enable_filament_ramming.value),
@@ -1361,8 +1032,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_rib_width(config.wipe_tower_rib_width),
m_extra_rib_length(config.wipe_tower_extra_rib_length),
m_wall_type((int)config.wipe_tower_wall_type),
// The cone wall has its own fully separate generator with no gap machinery.
m_use_gap_wall(config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone),
m_use_gap_wall(use_gap_wall(config)),
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
{
@@ -1664,13 +1334,9 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
writer.speed_override_backup();
writer.speed_override(100);
Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed);
// With a boundary wipe start the wall gap sits at the first wipe row below the
// quantized ram band; enter there too so the routed entry, the gap and the wipe
// scrub all share one opening. toolchange_Unload() climbs back up to the ram band
// along the box interior.
if (!m_semm && m_use_gap_wall && ramming_depth > 0.f)
initial_position.y() += wipe_start_offset_after_ram(ramming_depth, is_first_layer());
// On a boundary wipe start this enters at the wall gap on the first wipe row;
// toolchange_Unload() then climbs back up to the ram band along the box interior.
Vec2f initial_position = toolchange_entry_pos(m_depth_traversed, ramming_depth, is_first_layer());
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
// Increase the extruder driver current to allow fast ramming.
@@ -1682,10 +1348,8 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
// Without a ram — or with the boundary wipe start, where the ram band is
// quantized to whole rows — the box is planned as whole wipe rows; the wipe
// then fills it completely so adjacent purge blocks stay contiguous. Uses the
// old tool (m_current_tool before toolchange_Change), same conditions as
// toolchange_Unload()'s do_ramming / boundary_wipe_start.
const bool do_ram_old = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming;
const bool fill_box = !do_ram_old || (!m_semm && m_use_gap_wall);
// old tool (m_current_tool before toolchange_Change).
const bool fill_box = !tool_ramming_enabled(m_current_tool) || boundary_wipe_start_enabled(m_current_tool);
auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
@@ -1763,11 +1427,10 @@ void WipeTower2::toolchange_Unload(
float remaining = xr - xl ; // keeps track of distance to the next turnaround
float e_done = 0; // measures E move done from each segment
// Orca: Do ramming when SEMM and ramming is enabled or when multi tool head when ramming is enabled on the multi tool.
const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming;
const bool do_ramming = tool_ramming_enabled(m_current_tool);
const bool cold_ramming = m_is_mk4mmu3;
// Orca: see set_toolchange() — quantized ram band + wipe restart at the boundary.
const bool boundary_wipe_start = do_ramming && !m_semm && m_use_gap_wall;
const bool boundary_wipe_start = boundary_wipe_start_enabled(m_current_tool);
float planned_ramming_depth = 0.f;
if (boundary_wipe_start && m_layer_info != m_plan.end())
for (const auto& tch : m_layer_info->tool_changes)
@@ -1994,12 +1657,9 @@ void WipeTower2::toolchange_Unload(
// first wipe row so the purge row lattice continues across the block boundary
// (previous box's last row top edge sits at its box top): with the planned depth
// of rows * dy, the last row's top edge then lands exactly on this box's top and
// no blank band is left between adjacent purge blocks. Mirrors dy/line_width in
// toolchange_Wipe().
const float wipe_dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
const float wipe_line_width = m_perimeter_width * m_extra_flow;
// no blank band is left between adjacent purge blocks.
writer.set_position(Vec2f(end_of_ramming.x(),
cleaning_box.ld.y() + m_depth_traversed + wipe_dy - (m_perimeter_width + wipe_line_width) / 2.f + m_perimeter_width));
cleaning_box.ld.y() + m_depth_traversed + wipe_start_offset_after_ram(0.f, is_first_layer()) + m_perimeter_width));
}
writer.resume_preview()
@@ -2090,7 +1750,7 @@ void WipeTower2::toolchange_Wipe(
const float& xr = cleaning_box.rd.x();
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
const float line_width = m_perimeter_width * m_extra_flow;
const float line_width = wipe_line_width();
writer.change_analyzer_line_width(line_width);
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
@@ -2098,7 +1758,7 @@ void WipeTower2::toolchange_Wipe(
// wipe until the end of the assigned area.
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
float dy = wipe_row_spacing(is_first_layer()); // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
// All the calculations in all other places take the spacing into account for all the layers.
// If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down.
@@ -2291,7 +1951,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing);
} else {
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, m_use_gap_wall);
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true);
}
// brim (first layer only)
@@ -2419,16 +2079,14 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool
float length_to_extrude = volume_to_length((m_semm ? 0.25f : m_filpar[old_tool].multitool_ramming_time) * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
layer_height);
// Orca: Reserve ramming depth only when toolchange_Unload() will actually ram
// (same condition as its do_ramming), otherwise the unprinted reservation
// leaves blank bands between the purge boxes.
const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[old_tool].multitool_ramming;
// Orca: Reserve ramming depth only when toolchange_Unload() will actually ram,
// otherwise the unprinted reservation leaves blank bands between the purge boxes.
const bool do_ramming = tool_ramming_enabled(old_tool);
// Orca: with the gap wall on a multi-tool printer the ram band is quantized up to
// the whole reserved rows and the wipe restarts at the left-edge boundary on a
// fresh row below it (BBL parity: the old-tool purge is whole rows and the wipe
// always starts at the box corner, where the entry scrub runs). SEMM keeps the
// stock continue-from-ram-end behavior. Must match toolchange_Unload()/tool_change().
const bool boundary_wipe_start = do_ramming && !m_semm && m_use_gap_wall;
// always starts at the box corner, where the entry scrub runs).
const bool boundary_wipe_start = boundary_wipe_start_enabled(old_tool);
float ramming_depth = do_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
// first_wipe_line rides for free on the last (partially used) ramming row, which
// is already covered by ramming_depth. Without ramming that row does not exist
@@ -2588,30 +2246,26 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
}
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
// Resulting ToolChangeResults are appended into vector "result"
// Precompute, for every plan layer, the wall openings ("skip points") at each toolchange's
// entry, like WipeTower::get_all_wall_skip_points(). The entry is where tool_change()
// starts: cleaning_box.ld + (0, m_depth_traversed), with m_depth_traversed advancing by
// required_depth per toolchange — reproduced here from the finalized plan so each gap
// coincides with the entry travel's target (tcr.start_pos, pre-rotation frame).
// With a boundary wipe start the entry, the wipe and its scrub sit on the first wipe row
// below the quantized ram band, so the gap moves there with them (BBL cuts its gap at the
// CP_TOOLCHANGE_WIPE start row too, never at the ram band).
// entry position, like WipeTower::get_all_wall_skip_points(). toolchange_entry_pos()
// reproduces from the finalized plan where tool_change() will start, so each gap coincides
// with the entry travel's target (tcr.start_pos, pre-rotation frame). BBL parity: the gap
// sits at the CP_TOOLCHANGE_WIPE start row, never at the ram band.
void WipeTower2::compute_wall_skip_points()
{
m_wall_skip_points.assign(m_plan.size(), std::vector<Vec2f>());
for (size_t layer_id = 0; layer_id < m_plan.size(); ++layer_id) {
float depth_traversed = 0.f;
for (const auto& toolchange : m_plan[layer_id].tool_changes) {
const float ram_offset = (!m_semm && toolchange.ramming_depth > 0.f) ?
wipe_start_offset_after_ram(toolchange.ramming_depth, layer_id == m_first_layer_idx) : 0.f;
m_wall_skip_points[layer_id].emplace_back(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed + ram_offset);
m_wall_skip_points[layer_id].emplace_back(
toolchange_entry_pos(depth_traversed, toolchange.ramming_depth, layer_id == m_first_layer_idx));
depth_traversed += toolchange.required_depth;
}
}
}
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
// Resulting ToolChangeResults are appended into vector "result"
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
{
if (m_plan.empty())
@@ -2779,8 +2433,7 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
double feedrate,
bool first_layer,
bool rib_wall,
bool extrude_perimeter,
bool skip_points)
bool extrude_perimeter)
{
float retract_length = m_filpar[m_current_tool].retract_length;
@@ -2800,7 +2453,7 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
if (!extrude_perimeter)
return wall_polygon;
if (skip_points) {
if (m_use_gap_wall) {
// Cut the wall open at each toolchange's entry (see compute_wall_skip_points()).
// The vector is empty during the save_on_last_wipe planning passes, which therefore
// measure the un-gapped wall — same approximation as the BBL tower.

View File

@@ -34,6 +34,10 @@ public:
bool is_finish,
bool is_contact = false) const;
// Whether this print cuts wall openings ("skip points") at the toolchange entries.
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
static bool use_gap_wall(const PrintConfig& config);
// x -- x coordinates of wipe tower in mm ( left bottom corner )
// y -- y coordinates of wipe tower in mm ( left bottom corner )
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
@@ -284,13 +288,35 @@ private:
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
// With a boundary wipe start (multitool ram, non-SEMM, gap wall) the wipe begins on a
// fresh row below the quantized ram band. Y offset from the box start to that first
// wipe row; must stay in sync with the alignment travel in toolchange_Unload().
// Purge row lattice of toolchange_Wipe(): row pitch and extrusion width.
float wipe_row_spacing(bool first_layer) const { return (first_layer ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; }
float wipe_line_width() const { return m_perimeter_width * m_extra_flow; }
// Whether toolchange_Unload() rams this (old) tool out.
bool tool_ramming_enabled(size_t tool) const { return (m_semm && m_enable_filament_ramming) || m_filpar[tool].multitool_ramming; }
// Whether the wipe restarts at the box boundary on a fresh row below the quantized
// ram band after ramming this (old) tool out (multi-tool gap wall; SEMM keeps the
// stock continue-from-ram-end behavior).
bool boundary_wipe_start_enabled(size_t tool) const { return tool_ramming_enabled(tool) && !m_semm && m_use_gap_wall; }
// With a boundary wipe start the wipe begins on a fresh row below the quantized ram
// band. Y offset from the box start to that first wipe row.
float wipe_start_offset_after_ram(float ramming_depth, bool first_layer) const
{
const float wipe_dy = (first_layer ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
return ramming_depth + wipe_dy - (m_perimeter_width + m_perimeter_width * m_extra_flow) / 2.f;
return ramming_depth + wipe_row_spacing(first_layer) - (m_perimeter_width + wipe_line_width()) / 2.f;
}
// Tower-local entry position of a toolchange whose box starts depth_traversed into
// the layer: the box corner, moved down to the first wipe row when the plan gives
// it a boundary wipe start (ramming_depth > 0 iff the unload rams). tool_change()
// enters here and compute_wall_skip_points() cuts the wall gap here, so the routed
// entry, the gap and the wipe scrub all share one opening.
Vec2f toolchange_entry_pos(float depth_traversed, float ramming_depth, bool first_layer) const
{
Vec2f pos(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed);
if (!m_semm && m_use_gap_wall && ramming_depth > 0.f)
pos.y() += wipe_start_offset_after_ram(ramming_depth, first_layer);
return pos;
}
// Calculates extrusion flow needed to produce required line width for given layer height
@@ -379,8 +405,7 @@ private:
double feedrate,
bool first_layer,
bool rib_wall,
bool extrude_perimeter,
bool skip_points);
bool extrude_perimeter);
Polygon generate_support_cone_wall(
WipeTowerWriter2& writer,