diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e83e461ea91..46401acdb5d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -38,7 +38,6 @@ #include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/MacDarkMode.hpp" -#include #include #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(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; - // BBS float v = dynamic_cast(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; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6434ab4389b..17a388c731d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -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 diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 5be497059c8..98c285f46aa 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -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("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>("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 &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 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(print_cfg.option("prime_tower_width"))->value; + float v = dynamic_cast(full_config.option("prime_volume"))->value; + bool enable_wrapping = false; + const ConfigOptionBool *wrapping_opt = dynamic_cast(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("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(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); } @@ -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); diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 70e3ec8e9ac..9585c7aa953 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -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 { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5e68e9de30e..4534b0886af 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10505,11 +10505,16 @@ void Plater::priv::init_notification_manager() void Plater::priv::update_objects_position_when_select_preset(const std::function &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(); }