From 98acd687f7c4792f105083b9287d514b1c272726 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Tue, 1 Sep 2026 20:30:56 +0800 Subject: [PATCH] Fixes for Wipe Tower Position Clamping Validation grows the estimated body by the brim before the tower is generated, so a tower whose brim leaves the bed is rejected up front instead of at export. The scene reload re-clamps the stored position, since set_default_wipe_tower_pos_for_plate does not rerun when painting changes the filament count. The rectangle-wall footprint polygon gets its two missing brim corners (it was a skewed quad), so the post-generation check covers the whole brim. --- src/libslic3r/Print.cpp | 17 ++++++++--------- src/slic3r/GUI/GLCanvas3D.cpp | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 39410a8cef..b014b0f299 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1046,6 +1046,7 @@ static StringObjectException layered_print_cleareance_valid(const Print &print, const WipeTowerData &wipe_tower_estimate = print.wipe_tower_data(filaments_count); float width = wipe_tower_estimate.width; float depth = wipe_tower_estimate.depth; + float brim_width = wipe_tower_estimate.brim_width; Polygons convex_hulls_temp; if (print.has_wipe_tower()) { @@ -1084,17 +1085,15 @@ static StringObjectException layered_print_cleareance_valid(const Print &print, // No gate on "is there a tower": one that is not printed estimates to zero, so the hull // is degenerate and every check passes. Re-deriving it here missed the wrapping-detection // tower on a single-filament plate. - // Pre-generation only the body square is tested — the auto-brim estimate can overshoot - // the generated brim by several mm and must not hard-fail a print that physically fits. - // Post-generation the mesh bottom already includes the real brim, so the exact - // footprint is tested. - // The shared printable polygon is plate-local, while the tower polygons above are - // already shifted by the plate origin. + // Pre-generation, grow the body by the brim to match what the generator draws; + // post-generation the mesh already includes it. Polygons printable_polys = print.get_extruder_shared_printable_polygon(); const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y())); for (Polygon &p : printable_polys) p.translate(plate_shift); - if (!diff(convex_hulls_temp, printable_polys).empty()) + Polygons tower_polys_with_brim = print.is_step_done(psWipeTower) ? + convex_hulls_temp : offset(convex_hulls_temp, float(scale_(brim_width))); + if (!diff(tower_polys_with_brim, printable_polys).empty()) return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")}; return {}; } @@ -5961,8 +5960,8 @@ void WipeTowerData::construct_mesh(float width, float depth, float height, float 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})}; + wipe_tower_mesh_data->bottom = {scaled(Vec2f{-brim_width, -brim_width}), scaled(Vec2f{width + brim_width, -brim_width}), + scaled(Vec2f{width + brim_width, depth + brim_width}), scaled(Vec2f{-brim_width, depth + brim_width})}; } 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); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b0675360b8..5ad6151c68 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2906,8 +2906,20 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float brim_width = float(footprint.brim_width); Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height); - // The stored position is already clamped onto the bed, by - // set_default_wipe_tower_pos_for_plate and again on every drag. + // set_default_wipe_tower_pos_for_plate doesn't rerun when painting changes the + // filament count, so redo its clamp here on every reload. + { + Vec3d clamped_pos, clamped_size; + part_plate->estimate_wipe_tower_polygon(full_config, plate_id, clamped_pos, clamped_size); + if (std::abs(x - (float) clamped_pos(0)) > EPSILON || std::abs(y - (float) clamped_pos(1)) > EPSILON) { + x = (float) clamped_pos(0); + y = (float) clamped_pos(1); + ConfigOptionFloat wt_x_opt(x), wt_y_opt(y); + dynamic_cast(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_id, 0); + dynamic_cast(proj_cfg.option("wipe_tower_y"))->set_at(&wt_y_opt, plate_id, 0); + } + } + if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) { // update for wipe tower position int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),