From ad0cce4c3860c9321f8f304d5df45d7da7e752f4 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 15 Mar 2026 17:38:44 +0800 Subject: [PATCH] Fix wipe tower placed outside bed boundary on first slice The wipe tower config position (wipe_tower_x/y) could be outside the plate boundary (e.g. default y=250 on a 200mm printer). No constraint was applied at slice time, so the tower was generated out-of-bounds. --- src/slic3r/GUI/GLCanvas3D.cpp | 16 +++++++++++++++- src/slic3r/GUI/PartPlate.cpp | 7 +++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2eff0298c9..e83e461ea9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2835,6 +2835,12 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config; float x = dynamic_cast(proj_cfg.option("wipe_tower_x"))->get_at(plate_id); float y = dynamic_cast(proj_cfg.option("wipe_tower_y"))->get_at(plate_id); + // Helper: persist corrected wipe tower position to config so the next slice uses valid coords. + auto persist_wipe_tower_pos = [&](float nx, float ny) { + ConfigOptionFloat cx(nx), cy(ny); + proj_cfg.option("wipe_tower_x")->set_at(&cx, plate_id, 0); + proj_cfg.option("wipe_tower_y")->set_at(&cy, plate_id, 0); + }; float w = dynamic_cast(m_config->option("prime_tower_width"))->value; float a = dynamic_cast(proj_cfg.option("wipe_tower_rotation_angle"))->value; @@ -2884,6 +2890,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re _set_warning_notification(EWarning::PreviewPrimeTowerOutside, true); x = new_x; y = new_y; + // Persist the correction to config so the next slice uses the valid position + persist_wipe_tower_pos(new_x, new_y); } @@ -2906,7 +2914,13 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_id)->get_build_volume(true); BoundingBox plate_bbox2d = BoundingBox(scaled(Vec2f(plate_bbox.min[0], plate_bbox.min[1])), scaled(Vec2f(plate_bbox.max[0], plate_bbox.max[1]))); Vec2f offset = WipeTower::move_box_inside_box(tower_bottom_bbox, plate_bbox2d, scaled(margin)); - int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1), + // move_box_inside_box returns mm (already unscaled); apply directly. + // If the actual brim polygon is outside bounds, persist the correction to config. + float display_x = x + offset[0]; + float display_y = y + offset[1]; + if (offset.norm() > float(EPSILON)) + persist_wipe_tower_pos(display_x, display_y); + int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, display_x + plate_origin(0), display_y + plate_origin(1), current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh, current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh, true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 332dc4c1df..5be497059c 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -4074,6 +4074,13 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx) wt_x_opt = ConfigOptionFloat(I3_WIPE_TOWER_DEFAULT_X_POS); wt_y_opt = ConfigOptionFloat(I3_WIPE_TOWER_DEFAULT_Y_POS); } + // Clamp default position to fit within the actual plate dimensions so the wipe tower + // doesn't start outside the bed for printers smaller than the hardcoded defaults. + const double wt_default_margin = 2.; + const double wt_estimated_width = 60.; // conservative estimate matching prime_tower_width default + const double wt_estimated_depth = 20.; // conservative depth estimate + wt_x_opt.value = std::max(wt_default_margin, std::min(wt_x_opt.value, m_plate_width - wt_estimated_width - wt_default_margin)); + wt_y_opt.value = std::max(wt_default_margin, std::min(wt_y_opt.value, m_plate_depth - wt_estimated_depth - wt_default_margin)); dynamic_cast(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_idx, 0); dynamic_cast(proj_cfg.option("wipe_tower_y"))->set_at(&wt_y_opt, plate_idx, 0); }