diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f65c0e3532..a21deaf209 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -20,6 +20,9 @@ #include "libslic3r/AppConfig.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/GCode/WipeTower2.hpp" +#include "libslic3r/GCode/WipeTowerEstimate.hpp" #include "libslic3r/Tesselate.hpp" #include "libslic3r/PrintConfig.hpp" @@ -919,6 +922,33 @@ int GLVolumeCollection::load_wipe_tower_preview( GUI::PartPlateList& ppl = GUI::wxGetApp().plater()->get_partplate_list(); std::vector plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true); TriangleMesh wipe_tower_shell = make_cube(width, depth, height); + // The brim is part of the printed footprint: draw it and fold it into the shell so the + // outside-bed shader and the drag clamp react to the true first-layer extent. + const bool show_brim = brim_width > 0.f; + const float brim_height = 0.2f; // one first layer, visual only + TriangleMesh brim_slab; + if (show_brim) { + // A Type2 cone-wall tower's base bulges past the body box — follow the real base + // outline instead of the rectangle. Type1 ignores the cone option. + Polygon cone_base; + { + // Preset enums are ConfigOptionEnumGeneric, so read them by value; the planner is + // resolved as the estimate resolves it, off the printer preset. + const DynamicPrintConfig &print_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config; + const DynamicPrintConfig &printer_cfg = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; + const ConfigOption *wall_opt = print_cfg.option("wipe_tower_wall_type"); + if (wall_opt != nullptr && wall_opt->getInt() == int(WipeTowerWallType::wtwCone) && resolve_wipe_tower_type(printer_cfg) == WipeTowerType::Type2) + cone_base = WipeTower2::cone_base_polygon(width, depth, height, print_cfg.opt_float("wipe_tower_cone_angle")); + } + if (!cone_base.empty()) { + Polygons brim_outline = offset(cone_base, scaled(brim_width)); + brim_slab = WipeTower::its_make_rib_brim(brim_outline.empty() ? cone_base : brim_outline.front(), brim_height); + } else { + brim_slab = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, brim_height); + brim_slab.translate({-brim_width, -brim_width, 0.f}); + } + wipe_tower_shell.merge(brim_slab); + } for (int extruder_id : plate_extruders) { if (extruder_id <= extruder_colors.size()) colors.push_back(extruder_colors[extruder_id - 1]); @@ -929,14 +959,19 @@ int GLVolumeCollection::load_wipe_tower_preview( // Orca: make it transparent for(auto& color : colors) color.a(0.66f); + const size_t slab_count = colors.size(); // per-filament body slabs; the brim part comes after + if (show_brim && !colors.empty()) + colors.push_back(colors.front()); volumes.emplace_back(new GLWipeTowerVolume(colors)); GLWipeTowerVolume& v = *dynamic_cast(volumes.back()); v.model_per_colors.resize(colors.size()); - for (int i = 0; i < colors.size(); i++) { - TriangleMesh color_part = make_cube(width, depth / colors.size(), height); - color_part.translate({ 0.f, depth * i / colors.size(), 0. }); + for (size_t i = 0; i < slab_count; i++) { + TriangleMesh color_part = make_cube(width, depth / slab_count, height); + color_part.translate({ 0.f, depth * i / slab_count, 0. }); v.model_per_colors[i].init_from(color_part); } + if (show_brim && !colors.empty()) + v.model_per_colors[slab_count].init_from(brim_slab); v.model.init_from(wipe_tower_shell); v.mesh_raycaster = std::make_unique(std::make_shared(wipe_tower_shell)); v.set_convex_hull(wipe_tower_shell); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5ad6151c68..8d72405cdd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2907,7 +2907,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height); // set_default_wipe_tower_pos_for_plate doesn't rerun when painting changes the - // filament count, so redo its clamp here on every reload. + // filament count, so redo its clamp here on every reload — unconditionally: a + // paint-triggered reload can arrive before the background process invalidates + // psWipeTower, so gating on it would skip the clamp exactly when it is needed. { Vec3d clamped_pos, clamped_size; part_plate->estimate_wipe_tower_polygon(full_config, plate_id, clamped_pos, clamped_size); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b6d7abde21..e74cc832a8 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1274,9 +1274,10 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position(); Vec3d actual_displacement = displacement; bool show_read_wipe_tower = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->fff_print()->is_step_done(psWipeTower); - float brim_width = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("prime_tower_brim_width"); - - const double margin = show_read_wipe_tower ? WIPE_TOWER_MARGIN : brim_width + 0.5; // 0.5 is the line width of wipe tower + // Both preview volumes carry the brim in their bounding box (the estimate + // preview merges a brim slab, the sliced preview the real brim mesh), so the + // drag clamp only pads by the wipe tower line width. + const double margin = show_read_wipe_tower ? WIPE_TOWER_MARGIN : 0.5; // 0.5 is the line width of wipe tower actual_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix())