ENH: Use the real prime tower model after slicing.

1. set wipe tower real height
2. fix wipe tower small arc error
3. add rib_width constraint to ensure that the rib wall of the wipetower are attached to the infill.
jira: STUDIO-10540

Change-Id: Idfdc809f0236121d98587ac2a09a2ebbf5caf945
(cherry picked from commit 3a7dcfd23249571224a4d538c55907a0a5c6154d)
This commit is contained in:
jiangkai.zhao
2025-03-04 15:27:12 +08:00
committed by Noisyfox
parent f850d397c9
commit 5573293582
9 changed files with 323 additions and 91 deletions

View File

@@ -642,9 +642,9 @@ 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);
auto transform_wt_pt = [&alpha, this](const Vec2f& pt,bool rib_offset=true) -> Vec2f {
auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
out += m_wipe_tower_pos + (rib_offset? m_rib_offset:Vec2f(0.f,0.f));
out += m_wipe_tower_pos + m_rib_offset;
return out;
};
@@ -877,7 +877,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
avoid_bbx = scaled(m_wipe_tower_bbx);
Polygon avoid_points = avoid_bbx.polygon();
for (auto& p : avoid_points.points) {
Vec2f pp = transform_wt_pt(unscale(p).cast<float>(),false);
Vec2f pp = transform_wt_pt(unscale(p).cast<float>());
p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d);
}
avoid_bbx = BoundingBox(avoid_points.points);

View File

@@ -10,6 +10,7 @@
#include "GCodeProcessor.hpp"
#include "BoundingBox.hpp"
#include "LocalesUtils.hpp"
#include "Triangulation.hpp"
namespace Slic3r
@@ -18,7 +19,9 @@ bool flat_ironing = true; // Whether to ena
static constexpr 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;
static constexpr double WIPE_TOWER_RESOLUTION = 0.0375;
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
#define 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)
inline float align_round(float value, float base)
{
@@ -98,7 +101,7 @@ static Polygon chamfer_polygon(Polygon &polygon, double chamfer_dis = 2., double
return res;
}
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI)
Polygon WipeTower::rounding_polygon(Polygon &polygon, double rounding /*= 2.*/, double angle_tol/* = 30. / 180. * PI*/)
{
if (polygon.points.size() < 3) return polygon;
Polygon res;
@@ -140,8 +143,7 @@ static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double a
Vec2d center = b + dir * dis;
double radius = (left - center).norm();
ArcSegment arc(scaled(center), scaled(radius), scaled(left), scaled(right), is_ccw ? ArcDirection::Arc_Dir_CCW : ArcDirection::Arc_Dir_CW);
float sample_angle = 5.f / 180.f * PI;
int n = std::ceil(abs(arc.angle_radians) / sample_angle);
int n = arc_fit_size;
//std::cout << "start " << arc.start_point[0] << " " << arc.start_point[1] << std::endl;
//std::cout << "end " << arc.end_point[0] << " " << arc.end_point[1] << std::endl;
//std::cout << "start angle " << arc.polar_start_theta << " end angle " << arc.polar_end_theta << std::endl;
@@ -162,6 +164,7 @@ static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double a
} else
res.points.push_back(polygon.points[i]);
}
res.remove_duplicate_points();
res.points.shrink_to_fit();
return res;
}
@@ -198,8 +201,7 @@ static Polygon rounding_rectangle(Polygon &polygon, double rounding = 2., double
Vec2d center = b;
double radius = real_rounding_dis;
ArcSegment arc(scaled(center), scaled(radius), scaled(left), scaled(right), is_ccw ? ArcDirection::Arc_Dir_CCW : ArcDirection::Arc_Dir_CW);
float sample_angle = 5.f / 180.f * PI;
int n = std::ceil(abs(arc.angle_radians) / sample_angle);
int n = arc_fit_size;
// std::cout << "start " << arc.start_point[0] << " " << arc.start_point[1] << std::endl;
// std::cout << "end " << arc.end_point[0] << " " << arc.end_point[1] << std::endl;
// std::cout << "start angle " << arc.polar_start_theta << " end angle " << arc.polar_end_theta << std::endl;
@@ -724,9 +726,7 @@ public:
width += m_layer_height * float(1. - M_PI / 4.);
if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool));
{
float sample_angle = 5.f / 180.f * PI;
int n = std::ceil(abs(arc.angle_radians) / sample_angle);
int n = arc_fit_size;
for (int j = 0; j < n; j++) {
float cur_angle = arc.polar_start_theta + (float) j / n * arc.angle_radians;
if (cur_angle > 2 * PI)
@@ -899,6 +899,7 @@ public:
WipeTowerWriter &polygon(const Polygon &wall_polygon, const float f = 0.f)
{
Polyline pl = to_polyline(wall_polygon);
pl.simplify(WT_SIMPLIFY_TOLERANCE_SCALED);
pl.simplify_by_fitting_arc(SCALED_WIPE_TOWER_RESOLUTION);
auto get_closet_idx = [this](std::vector<Segment> &corners) -> int {
@@ -1371,6 +1372,122 @@ float WipeTower::get_auto_brim_by_height(float max_height) {
return 8.f;
}
Vec2f WipeTower::move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2,int scaled_offset)
{
Vec2f res{0, 0};
if (box1.size()[0] >= box2.size()[0]- 2*scaled_offset || box1.size()[1] >= box2.size()[1]-2*scaled_offset) return res;
if (box1.max[0] > box2.max[0] - scaled_offset) {
res[0] = unscaled<float>((box2.max[0] - scaled_offset) - box1.max[0]);
}
else if (box1.min[0] < box2.min[0] + scaled_offset) {
res[0] = unscaled<float>((box2.min[0] + scaled_offset) - box1.min[0]);
}
if (box1.max[1] > box2.max[1] - scaled_offset) {
res[1] = unscaled<float>((box2.max[1] - scaled_offset) - box1.max[1]);
}
else if (box1.min[1] < box2.min[1] + scaled_offset) {
res[1] = unscaled<float>((box2.min[1] + scaled_offset) - box1.min[1]);
}
return res;
}
Polygon WipeTower::rib_section(float width, float depth, float rib_length, float rib_width,bool fillet_wall)
{
Polygon res;
res.points.resize(16);
float theta = std::atan(width / depth);
float costheta = std::cos(theta);
float sintheta = std::sin(theta);
float w = rib_width / 2.f;
float diag = std::sqrt(width * width + depth * depth);
float l = (rib_length - diag) / 2;
Vec2f diag_dir1 = Vec2f{width, depth}.normalized();
Vec2f diag_dir1_perp{-diag_dir1[1], diag_dir1[0]};
Vec2f diag_dir2 = Vec2f{-width, depth}.normalized();
Vec2f diag_dir2_perp{-diag_dir2[1], diag_dir2[0]};
std::vector<Vec2f> p{{0, 0}, {width, 0}, {width, depth}, {0, depth}};
Polyline p_render;
for (auto &x : p) p_render.points.push_back(scaled(x));
res.points[0] = scaled(Vec2f{p[0].x(), p[0].y() + w / sintheta});
res.points[1] = scaled(Vec2f{p[0] - diag_dir1 * l + diag_dir1_perp * w});
res.points[2] = scaled(Vec2f{p[0] - diag_dir1 * l - diag_dir1_perp * w});
res.points[3] = scaled(Vec2f{p[0].x() + w / costheta, p[0].y()});
res.points[4] = scaled(Vec2f{p[1].x() - w / costheta, p[1].y()});
res.points[5] = scaled(Vec2f{p[1] - diag_dir2 * l + diag_dir2_perp * w});
res.points[6] = scaled(Vec2f{p[1] - diag_dir2 * l - diag_dir2_perp * w});
res.points[7] = scaled(Vec2f{p[1].x(), p[1].y() + w / sintheta});
res.points[8] = scaled(Vec2f{p[2].x(), p[2].y() - w / sintheta});
res.points[9] = scaled(Vec2f{p[2] + diag_dir1 * l - diag_dir1_perp * w});
res.points[10] = scaled(Vec2f{p[2] + diag_dir1 * l + diag_dir1_perp * w});
res.points[11] = scaled(Vec2f{p[2].x() - w / costheta, p[2].y()});
res.points[12] = scaled(Vec2f{p[3].x() + w / costheta, p[3].y()});
res.points[13] = scaled(Vec2f{p[3] + diag_dir2 * l - diag_dir2_perp * w});
res.points[14] = scaled(Vec2f{p[3] + diag_dir2 * l + diag_dir2_perp * w});
res.points[15] = scaled(Vec2f{p[3].x(), p[3].y() - w / sintheta});
res.remove_duplicate_points();
if (fillet_wall) { res = rounding_polygon(res); }
res.points.shrink_to_fit();
return res;
}
TriangleMesh WipeTower::its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall)
{
TriangleMesh res;
Polygon bottom = rib_section(width, depth, rib_length, rib_width, fillet_wall);
Polygon top = rib_section(width, depth, std::sqrt(width * width + depth * depth), rib_width, fillet_wall);
if (fillet_wall)
assert(bottom.points.size() == top.points.size());
int offset = bottom.points.size();
res.its.vertices.reserve(offset * 2);
auto faces_bottom = Triangulation::triangulate(bottom);
auto faces_top = Triangulation::triangulate(top);
res.its.indices.reserve(offset * 2 + faces_bottom.size() + faces_top.size());
for (auto &t : faces_bottom) res.its.indices.push_back({t[1], t[0], t[2]});
for (auto &t : faces_top) res.its.indices.push_back({t[0] + offset, t[1] + offset, t[2] + offset});
for (int i = 0; i < bottom.size(); i++) res.its.vertices.push_back({unscaled<float>(bottom[i][0]), unscaled<float>(bottom[i][1]), 0});
for (int i = 0; i < top.size(); i++) res.its.vertices.push_back({unscaled<float>(top[i][0]), unscaled<float>(top[i][1]), height});
for (int i = 0; i < offset; i++) {
int a = i;
int b = (i + 1) % offset;
int c = i + offset;
int d = b + offset;
res.its.indices.push_back({a, b, c});
res.its.indices.push_back({d, c, b});
}
return res;
}
TriangleMesh WipeTower::its_make_rib_brim(const Polygon& brim, float layer_height) {
TriangleMesh res;
int offset = brim.size();
res.its.vertices.reserve(brim.size() * 2);
auto faces= Triangulation::triangulate(brim);
res.its.indices.reserve(brim.size() * 2 + 2 * faces.size());
for (auto &t : faces) res.its.indices.push_back({t[1], t[0], t[2]});
for (auto &t : faces) res.its.indices.push_back({t[0] + offset, t[1] + offset, t[2] + offset});
for (int i = 0; i < brim.size(); i++) res.its.vertices.push_back({unscaled<float>(brim[i][0]), unscaled<float>(brim[i][1]), 0});
for (int i = 0; i < brim.size(); i++) res.its.vertices.push_back({unscaled<float>(brim[i][0]), unscaled<float>(brim[i][1]), layer_height});
for (int i = 0; i < offset; i++) {
int a = i;
int b = (i + 1) % offset;
int c = i + offset;
int d = b + offset;
res.its.indices.push_back({a, b, c});
res.its.indices.push_back({d, c, b});
}
return res;
}
WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, size_t initial_tool, const float wipe_tower_height) :
m_semm(config.single_extruder_multi_material.value),
m_wipe_tower_pos(config.wipe_tower_x.get_at(plate_idx), config.wipe_tower_y.get_at(plate_idx)),
@@ -3634,6 +3751,8 @@ void WipeTower::plan_tower_new()
update_all_layer_depth(max_depth);
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)});
m_rib_length += m_extra_rib_length;
m_rib_length = std::max(0.f, m_rib_length);
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.
}
@@ -3698,7 +3817,7 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
if (m_plan.empty())
return;
//m_extra_spacing = 1.f;
m_wipe_tower_height = m_plan.back().z;//real wipe_tower_height
plan_tower_new();
m_layer_info = m_plan.begin();

View File

@@ -28,7 +28,11 @@ public:
static const std::map<float, float> min_depth_per_height;
static float get_limit_depth_by_height(float max_height);
static float get_auto_brim_by_height(float max_height);
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0);
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
struct Extrusion
{
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
@@ -192,20 +196,16 @@ public:
if (m_outer_wall.empty()) return BoundingBoxf({Vec2d(0,0)});
BoundingBox box = get_extents(m_outer_wall.begin()->second);
BoundingBoxf res = BoundingBoxf(unscale(box.min), unscale(box.max));
res.translate(m_rib_offset.cast<double>());
return res;
}
std::map<float, Polylines> get_outer_wall() const
{
std::map<float, Polylines> res = m_outer_wall;
Point trans = scaled(m_rib_offset);
for (auto &[h,polylines] : res)
for (auto &polyline : polylines)
polyline.translate(trans.x(), trans.y());
return res;
return m_outer_wall;
}
float get_height() const { return m_wipe_tower_height; }
float get_layer_height() const { return m_layer_height; }
float get_rib_length() const { return m_rib_length; }
float get_rib_width() const { return m_rib_width; }
void set_last_layer_extruder_fill(bool extruder_fill) {
if (!m_plan.empty()) {

View File

@@ -977,13 +977,20 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
Polygons convex_hulls_temp;
if (print.has_wipe_tower()) {
Polygon wipe_tower_convex_hull;
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y));
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y));
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth));
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth));
wipe_tower_convex_hull.rotate(a);
convex_hulls_temp.push_back(wipe_tower_convex_hull);
if (!print.is_step_done(psWipeTower)) {
Polygon wipe_tower_convex_hull;
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y));
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y));
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth));
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth));
wipe_tower_convex_hull.rotate(a);
convex_hulls_temp.push_back(wipe_tower_convex_hull);
} else {
//here, wipe_tower_polygon is not always convex.
Polygon wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom;
wipe_tower_polygon.translate(Point(scale_(x), scale_(y)));
convex_hulls_temp.push_back(wipe_tower_polygon);
}
}
if (!intersection(convex_hulls_other, convex_hulls_temp).empty()) {
if (warning) {
@@ -2569,7 +2576,7 @@ Points Print::first_layer_wipe_tower_corners(bool check_wipe_tower_existance) co
{
double width = m_wipe_tower_data.bbx.max.x() - m_wipe_tower_data.bbx.min.x();
double depth = m_wipe_tower_data.bbx.max.y() -m_wipe_tower_data.bbx.min.y();
Vec2d pt0 = m_wipe_tower_data.bbx.min;
Vec2d pt0 = m_wipe_tower_data.bbx.min + m_wipe_tower_data.rib_offset.cast<double>();
// First the corners.
std::vector<Vec2d> pts = { pt0,
@@ -2874,8 +2881,8 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
}
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
}
if (m_config.prime_tower_brim_width < 0) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
}
if (m_config.prime_tower_brim_width < 0 ) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
return m_wipe_tower_data;
}
@@ -3058,10 +3065,13 @@ void Print::_make_wipe_tower()
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
m_wipe_tower_data.construct_mesh(wipe_tower.width(), wipe_tower.get_depth(), wipe_tower.get_height(), wipe_tower.get_brim_width(), config().prime_tower_rib_wall.value,
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(), config().prime_tower_fillet_wall.value);
const Vec3d origin = this->get_plate_origin();
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_height(),
wipe_tower.get_layer_height(), m_wipe_tower_data.depth, m_wipe_tower_data.brim_width,
{scale_(origin.x()), scale_(origin.y())});
m_fake_wipe_tower.rib_offset = 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_height(), wipe_tower.get_layer_height(),
m_wipe_tower_data.depth,
m_wipe_tower_data.brim_width, {scale_(origin.x()), scale_(origin.y())});
m_fake_wipe_tower.outer_wall = wipe_tower.get_outer_wall();
} else {
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
@@ -4667,6 +4677,25 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
}
return wtels;
}
void WipeTowerData::construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length,bool fillet_wall)
{
wipe_tower_mesh_data = WipeTowerMeshData{};
float first_layer_height=0.08; //brim height
if (!is_rib_wipe_tower) {
wipe_tower_mesh_data->real_wipe_tower_mesh = make_cube(width, depth, height);
wipe_tower_mesh_data->real_brim_mesh = make_cube(width + 2 * brim_width, depth + 2 * brim_width, first_layer_height);
wipe_tower_mesh_data->real_brim_mesh.translate({-brim_width, -brim_width, 0});
wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, 0}), scaled(Vec2f{width + brim_width, depth + brim_width}),
scaled(Vec2f{0, depth})};
} else {
wipe_tower_mesh_data->real_wipe_tower_mesh = WipeTower::its_make_rib_tower(width, depth, height, rib_length, rib_width, fillet_wall);
wipe_tower_mesh_data->bottom = WipeTower::rib_section(width, depth, rib_length, rib_width, fillet_wall);
wipe_tower_mesh_data->bottom = offset(wipe_tower_mesh_data->bottom, scaled(brim_width)).front();
wipe_tower_mesh_data->real_brim_mesh = WipeTower::its_make_rib_brim(wipe_tower_mesh_data->bottom, first_layer_height);
wipe_tower_mesh_data->real_wipe_tower_mesh.translate(Vec3f(rib_offset[0], rib_offset[1],0));
wipe_tower_mesh_data->real_brim_mesh.translate(Vec3f(rib_offset[0], rib_offset[1], 0));
wipe_tower_mesh_data->bottom.translate(scaled(Vec2f(rib_offset[0], rib_offset[1])));
}
}
} // namespace Slic3r

View File

@@ -594,13 +594,14 @@ struct FakeWipeTower
Vec2f pos;
float width;
float height;
float layer_height;
float layer_height;// Due to variable layer height, this parameter may be not right.
float depth;
std::vector<std::pair<float, float>> z_and_depth_pairs;
float brim_width;
float rotation_angle;
float cone_angle;
Vec2d plate_origin;
Vec2f rib_offset{0.f,0.f};
std::map<float , Polylines> outer_wall; //wipe tower's true outer wall and brim
void set_fake_extrusion_data(Vec2f p, float w, float h, float lh, float d, float bd, Vec2d o)
@@ -627,7 +628,7 @@ struct FakeWipeTower
plate_origin = o;
}
void set_pos(Vec2f p) { pos = p; }
void set_pos(Vec2f p) { pos = p+rib_offset; }
void set_pos_and_rotation(const Vec2f& p, float rotation) { pos = p; rotation_angle = rotation; }
std::vector<ExtrusionPaths> getFakeExtrusionPathsFromWipeTower() const
@@ -736,6 +737,12 @@ struct FakeWipeTower
struct WipeTowerData
{
struct WipeTowerMeshData
{
Polygon bottom;
TriangleMesh real_wipe_tower_mesh;
TriangleMesh real_brim_mesh;
};
// Following section will be consumed by the GCodeGenerator.
// Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
// Cache it here, so it does not need to be recalculated during the G-code generation.
@@ -752,9 +759,9 @@ struct WipeTowerData
std::vector<std::pair<float, float>> z_and_depth_pairs;
float brim_width;
float height;
BoundingBoxf bbx;
BoundingBoxf bbx;//including brim
Vec2f rib_offset;
std::optional<WipeTowerMeshData> wipe_tower_mesh_data;//added rib_offset
void clear() {
priming.reset(nullptr);
tool_changes.clear();
@@ -763,7 +770,9 @@ struct WipeTowerData
number_of_toolchanges = -1;
depth = 0.f;
brim_width = 0.f;
wipe_tower_mesh_data = std::nullopt;
}
void construct_mesh(float width, float depth, float height, float brim_width, bool is_rib_wipe_tower, float rib_width, float rib_length, bool fillet_wall);
private:
// Only allow the WipeTowerData to be instantiated internally by Print,