Complete the rib wipe tower port in WipeTower2

The rib tower is now always square (prime_tower_width is ignored, as the
GUI already implies), carries the rib origin offset like the BBL tower so
the rib tips sit inside the configured position, clamps the rib length to
the tower diagonal, and extends the ribs for short towers.
This commit is contained in:
SoftFever
2026-07-27 03:07:07 +08:00
parent 5792fef805
commit 466c36eaa3
5 changed files with 66 additions and 20 deletions

View File

@@ -1422,8 +1422,10 @@ 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;
Vec2f out = Eigen::Rotation2Df(alpha) * (pt + m_rib_offset);
out += m_wipe_tower_pos;
return out;
};
@@ -1435,7 +1437,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
end_pos = transform_wt_pt(end_pos);
}
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));

View File

@@ -143,7 +143,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
Transform2d trafo =
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)) *
Eigen::Translation2d(print.wipe_tower_data().rib_offset.cast<double>()); // tower-local rib-wall shift, zero unless rib
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {

View File

@@ -2228,15 +2228,21 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
return;
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
float width = m_wipe_tower_width - 3*m_perimeter_width;
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
m_plan.back().tool_changes.push_back(set_toolchange(old_tool, new_tool, layer_height_par, wipe_volume, first_layer_plan));
}
WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan)
{
float width = m_wipe_tower_width - 3*m_perimeter_width;
float length_to_extrude = volume_to_length(0.25f * 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_par);
layer_height);
// Orca: Set ramming depth to 0 if ramming is disabled.
float ramming_depth = m_enable_filament_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;
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height);
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
@@ -2245,12 +2251,11 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
// ORCA: Use the same spacing here so reserved depth matches consumed depth
// ORCA: and first-layer purge segments do not leave visible gaps.
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, planning_spacing, width);
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, m_perimeter_width, m_extra_flow, planning_spacing, width);
return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume);
}
@@ -2378,9 +2383,35 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
}
#endif
m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)});
if (m_wall_type == (int)wtwRib) {
// Rib wall: force a square tower like WipeTower::plan_tower_new(), ignoring the
// configured prime_tower_width (the GUI greys it out in rib mode). The planned depths
// already include the extra-spacing factors, so sqrt(depth * width) preserves the
// purge area. Replan every toolchange for the new width, then re-derive the depths.
float max_depth = 0.f;
for (const auto& current_plan : m_plan)
max_depth = std::max(max_depth, current_plan.depth);
if (max_depth > EPSILON) {
m_wipe_tower_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width), m_perimeter_width);
for (size_t 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,
idx == m_first_layer_idx);
plan_tower();
}
// Like WipeTower::plan_tower_new(): extend the ribs instead of the tower when the
// tower is smaller than the height-based stability minimum.
const float min_depth = WipeTower::get_limit_depth_by_height(m_wipe_tower_height);
if (m_wipe_tower_depth + EPSILON < min_depth)
m_rib_length = std::max(m_rib_length, min_depth * (float)std::sqrt(2.f));
}
const float diagonal = std::sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
m_rib_length = std::max(m_rib_length, diagonal);
m_rib_length += m_extra_rib_length;
m_rib_length = std::max(0.f, m_rib_length);
m_rib_length = std::max(diagonal, m_rib_length); // a negative extra length must not shrink the ribs below the diagonal
m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) /
2.f); // Ensure that the rib wall of the wipetower are attached to the infill.
@@ -2532,10 +2563,12 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
insert_skip_polygon = wall_polygon;
}
writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet);
//if (m_cur_layer_id == 0) {
// BoundingBox bbox = get_extents(result_wall);
// m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
//}
// Tower-local shift that puts the rib wall's protruding first-layer min corner at the
// configured tower position, like WipeTower::generate_support_wall_new().
if (rib_wall && is_first_layer()) {
BoundingBox bbox = get_extents(result_wall);
m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
}
return insert_skip_polygon;
}

View File

@@ -69,9 +69,9 @@ public:
const float brim = m_wipe_tower_brim_width_real;
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
}
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
// so expose a zero offset for consistency purposes (to maintain API parity).
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
// Tower-local shift that puts the rib wall's first-layer min corner at the configured
// tower position, like WipeTower::get_rib_offset(). Zero unless the rib wall is used.
Vec2f get_rib_offset() const { return m_rib_offset; }
float get_rib_width() const { return m_rib_width; }
float get_rib_length() const { return m_rib_length; }
@@ -231,6 +231,7 @@ private:
float m_rib_width = 10;
float m_extra_rib_length = 0;
float m_rib_length = 0;
Vec2f m_rib_offset = Vec2f::Zero();
bool m_enable_arc_fitting = false;
@@ -372,6 +373,10 @@ private:
float spacing);
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
// Computes the depth reserved for a toolchange (shared by plan_toolchange() and the
// rib-wall square-tower replanning in generate()).
WipeTowerInfo::ToolChange set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan);
};

View File

@@ -4333,7 +4333,12 @@ void Print::_make_wipe_tower()
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
config().wipe_tower_fillet_wall.value);
const Vec3d origin = Vec3d::Zero();
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
// FakeWipeTower::pos is a bed-frame translation applied after rotation
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
// tower-local rib offset must be rotated into the bed frame first.
m_fake_wipe_tower.rib_offset = Eigen::Rotation2Df(Geometry::deg2rad((float)config().wipe_tower_rotation_angle.value)) *
wipe_tower.get_rib_offset();
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
config().initial_layer_print_height, m_wipe_tower_data.depth,
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width,
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,