Feature/fix wipetower pos issues (#12820)

# Description

Reverted PR #12191 and #12777 and added new fixes. This also resolves an
issue where, in some cases, the sliced result was immediately
invalidated after the first slicing.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
This commit is contained in:
SoftFever
2026-03-18 14:46:19 +08:00
committed by GitHub
5 changed files with 81 additions and 67 deletions

View File

@@ -38,7 +38,6 @@
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include <libslic3r/BoundingBox.hpp>
#include <slic3r/GUI/GUI_Utils.hpp>
#if ENABLE_RETINA_GL
@@ -2835,15 +2834,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
float y = dynamic_cast<const ConfigOptionFloats*>(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<ConfigOptionFloats>("wipe_tower_x")->set_at(&cx, plate_id, 0);
proj_cfg.option<ConfigOptionFloats>("wipe_tower_y")->set_at(&cy, plate_id, 0);
};
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
float a = dynamic_cast<const ConfigOptionFloat*>(proj_cfg.option("wipe_tower_rotation_angle"))->value;
// BBS
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
@@ -2870,31 +2862,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
}
coordf_t plate_bbox_x_min_local_coord = plate_bbox_2d.min(0) - plate_origin(0);
coordf_t plate_bbox_y_min_local_coord = plate_bbox_2d.min(1) - plate_origin(1);
coordf_t plate_bbox_x_max_local_coord = plate_bbox_2d.max(0) - plate_origin(0);
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
const float tower_w = (float) wipe_tower_size(0);
const float tower_h = (float) wipe_tower_size(1);
const float min_x = (float) plate_bbox_x_min_local_coord + margin;
const float max_x = (float) plate_bbox_x_max_local_coord - margin;
const float min_y = (float) plate_bbox_y_min_local_coord + margin;
const float max_y = (float) plate_bbox_y_max_local_coord - margin;
// snap wipe tower back to nearest edge if it was initially loaded outside the plate boundary
float new_x = (x < min_x) ? min_x : ((x + tower_w > max_x) ? (max_x - tower_w) : x);
float new_y = (y < min_y) ? min_y : ((y + tower_h > max_y) ? (max_y - tower_h) : y);
if (new_x != x || new_y != y) {
// do notification
_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);
}
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
// update for wipe tower position
{
@@ -2914,13 +2884,7 @@ 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));
// 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),
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), 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);
@@ -9679,9 +9643,6 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
case EWarning::PrimeTowerOutside:
text = _u8L("The prime tower extends beyond the plate boundary.");
break;
case EWarning::PreviewPrimeTowerOutside:
text = _u8L("Prime tower position exceeded build plate boundaries and was repositioned to the nearest valid edge.");
break;
case EWarning::NozzleFilamentIncompatible: {
text = _u8L(get_nozzle_filament_incompatible_text());
break;

View File

@@ -381,14 +381,13 @@ class GLCanvas3D
ToolHeightOutside,
TPUPrintableError,
FilamentPrintableError,
LeftExtruderPrintableError, // before slice
RightExtruderPrintableError, // before slice
MultiExtruderPrintableError, // after slice
MultiExtruderHeightOutside, // after slice
LeftExtruderPrintableError, // before slice
RightExtruderPrintableError, // before slice
MultiExtruderPrintableError, // after slice
MultiExtruderHeightOutside, // after slice
FilamentUnPrintableOnFirstLayer,
MixUsePLAAndPETG,
PrimeTowerOutside, // after slice
PreviewPrimeTowerOutside, // before slice
PrimeTowerOutside,
NozzleFilamentIncompatible,
MixtureFilamentIncompatible,
FlushingVolumeZero

View File

@@ -3755,11 +3755,6 @@ void PartPlateList::init()
m_plate_cols = 1;
m_current_plate = 0;
if (m_plater) {
// In GUI mode
set_default_wipe_tower_pos_for_plate(0);
}
select_plate(0);
unprintable_plate.set_index(1);
@@ -4058,7 +4053,7 @@ void PartPlateList::release_icon_textures()
}
}
void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx)
void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool init_pos)
{
DynamicConfig & proj_cfg = wxGetApp().preset_bundle->project_config;
ConfigOptionFloats *wipe_tower_x = proj_cfg.opt<ConfigOptionFloats>("wipe_tower_x");
@@ -4068,19 +4063,69 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx)
auto printer_structure_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
// set the default position, the same with print config(left top)
ConfigOptionFloat wt_x_opt(WIPE_TOWER_DEFAULT_X_POS);
ConfigOptionFloat wt_y_opt(WIPE_TOWER_DEFAULT_Y_POS);
float x = WIPE_TOWER_DEFAULT_X_POS;
float y = WIPE_TOWER_DEFAULT_Y_POS;
if (printer_structure_opt && printer_structure_opt->value == PrinterStructure::psI3) {
wt_x_opt = ConfigOptionFloat(I3_WIPE_TOWER_DEFAULT_X_POS);
wt_y_opt = ConfigOptionFloat(I3_WIPE_TOWER_DEFAULT_Y_POS);
x = I3_WIPE_TOWER_DEFAULT_X_POS;
y = 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));
PartPlate *part_plate = get_plate(plate_idx);
Vec3d plate_origin = part_plate->get_origin();
BoundingBoxf3 plate_bbox = part_plate->get_bounding_box();
BoundingBoxf plate_bbox_2d(Vec2d(plate_bbox.min(0), plate_bbox.min(1)), Vec2d(plate_bbox.max(0), plate_bbox.max(1)));
const std::vector<Pointfs> &extruder_areas = part_plate->get_extruder_areas();
for (const Pointfs &points : extruder_areas) {
BoundingBoxf bboxf(points);
plate_bbox_2d.min = plate_bbox_2d.min(0) >= bboxf.min(0) ? plate_bbox_2d.min : bboxf.min;
plate_bbox_2d.max = plate_bbox_2d.max(0) <= bboxf.max(0) ? plate_bbox_2d.max : bboxf.max;
}
coordf_t plate_bbox_x_min_local_coord = plate_bbox_2d.min(0) - plate_origin(0);
coordf_t plate_bbox_x_max_local_coord = plate_bbox_2d.max(0) - plate_origin(0);
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
std::vector<int> filament_maps = part_plate->get_real_filament_maps(proj_cfg);
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps);
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value;
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
bool enable_wrapping = false;
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 2, false, enable_wrapping);
}
// Compute brim-aware margin: brim extends outward from tower position
float brim_width = 0.f;
const ConfigOptionFloat *brim_opt = print_cfg.option<ConfigOptionFloat>("prime_tower_brim_width");
if (brim_opt) {
brim_width = brim_opt->value;
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
}
const float margin = WIPE_TOWER_MARGIN + brim_width;
// clamp wipe tower position within plate boundaries
{
if (x + margin + wipe_tower_size(0) > plate_bbox_x_max_local_coord) {
x = plate_bbox_x_max_local_coord - wipe_tower_size(0) - margin;
} else if (x < margin + plate_bbox_x_min_local_coord) {
x = margin + plate_bbox_x_min_local_coord;
}
if (y + margin + wipe_tower_size(1) > plate_bbox_y_max_local_coord) {
y = plate_bbox_y_max_local_coord - wipe_tower_size(1) - margin;
} else if (y < margin) {
y = margin;
}
}
ConfigOptionFloat wt_x_opt(x);
ConfigOptionFloat wt_y_opt(y);
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_idx, 0);
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_y"))->set_at(&wt_y_opt, plate_idx, 0);
}
@@ -4191,6 +4236,11 @@ void PartPlateList::reinit()
//re-calc the bounding boxes
calc_bounding_boxes();
if (m_plater) {
// In GUI mode
set_default_wipe_tower_pos_for_plate(0, true);
}
return;
}
@@ -4262,7 +4312,7 @@ int PartPlateList::create_plate(bool adjust_position)
// update wipe tower config
if (m_plater) {
// In GUI mode
set_default_wipe_tower_pos_for_plate(new_index);
set_default_wipe_tower_pos_for_plate(new_index, true);
}
unprintable_plate.set_index(new_index+1);

View File

@@ -628,13 +628,12 @@ class PartPlateList : public ObjectBase
void generate_icon_textures();
void release_icon_textures();
void set_default_wipe_tower_pos_for_plate(int plate_idx);
friend class cereal::access;
friend class UndoRedo::StackImpl;
friend class PartPlate;
public:
void set_default_wipe_tower_pos_for_plate(int plate_idx, bool init_pos = false);
class BedTextureInfo {
public:
class TexturePart {

View File

@@ -10505,11 +10505,16 @@ void Plater::priv::init_notification_manager()
void Plater::priv::update_objects_position_when_select_preset(const std::function<void()> &select_prest)
{
// TODO: Orca hack
select_prest();
wxGetApp().obj_list()->update_object_list_by_printer_technology();
// Re-clamp wipe tower positions to new bed boundaries after preset change
PartPlateList &cur_plate_list = this->partplate_list;
for (size_t plate_id = 0; plate_id < cur_plate_list.get_plate_list().size(); ++plate_id) {
cur_plate_list.set_default_wipe_tower_pos_for_plate(plate_id);
}
update();
}