diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index b1e3cdd6dd..e56682fd3c 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -95,29 +95,6 @@ TreeModelVolumes::TreeModelVolumes( #else { m_anti_overhang = print_object.slice_support_blockers(); - // Belt floor: add belt surface polygons to anti_overhang so support - // is never generated inside the belt. Only in global shear mode — - // in local mode the belt floor clipping handles everything and - // anti_overhang at the bottom layers would block all support. - { - const auto &sp = print_object.slicing_parameters(); - const auto &pcfg = print_object.print()->config(); - BeltFloorContext ctx; - ctx.init_local(sp, pcfg, print_object.belt_global_z_offset()); - if (ctx.is_active() - && std::abs(print_object.belt_global_z_offset()) > EPSILON - && pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { - size_t num_layers_needed = print_object.layer_count(); - // Ensure m_anti_overhang is large enough. - if (m_anti_overhang.size() < num_layers_needed) - m_anti_overhang.resize(num_layers_needed, Polygons{}); - for (size_t layer_idx = 0; layer_idx < num_layers_needed; ++layer_idx) { - double print_z = print_object.get_layer(layer_idx)->print_z - - print_object.belt_global_z_offset(); - append(m_anti_overhang[layer_idx], ctx.surface_polygon(print_z)); - } - } - } TreeSupportMeshGroupSettings mesh_settings(print_object); const TreeSupportSettings config{ mesh_settings, print_object.slicing_parameters() }; m_current_min_xy_dist = config.xy_min_distance; @@ -147,6 +124,38 @@ TreeModelVolumes::TreeModelVolumes( } } } + // Belt floor: add belt surface polygons to anti_overhang so support is + // never generated inside the belt. + // + // This MUST run after m_raft_layers is final. m_anti_overhang is consumed + // in the same index space as m_layer_outlines -- object layer i lives at + // index num_raft_layers + i -- but slice_support_blockers() returns it in + // object-layer space. Without the shift below, every entry lands + // num_raft_layers too low: with the belt raft that is tens of layers, so + // the belt suppression is applied to the wrong layers entirely and the + // topmost object layers get none at all. + { + const size_t num_raft = m_raft_layers.size(); + const size_t num_obj = print_object.layer_count(); + if (num_raft > 0 && ! m_anti_overhang.empty()) + // Shift the support blockers into the same space. + m_anti_overhang.insert(m_anti_overhang.begin(), num_raft, Polygons{}); + const auto &sp = print_object.slicing_parameters(); + const auto &pcfg = print_object.print()->config(); + BeltFloorContext ctx; + ctx.init_local(sp, pcfg, print_object.belt_global_z_offset()); + if (ctx.is_active() + && std::abs(print_object.belt_global_z_offset()) > EPSILON + && pcfg.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { + if (m_anti_overhang.size() < num_raft + num_obj) + m_anti_overhang.resize(num_raft + num_obj, Polygons{}); + for (size_t i = 0; i < num_obj; ++i) { + const double print_z = print_object.get_layer(i)->print_z + - print_object.belt_global_z_offset(); + append(m_anti_overhang[num_raft + i], ctx.surface_polygon(print_z)); + } + } + } m_current_outline_idx = 0; m_layer_outlines.emplace_back(mesh_settings, std::vector{}); diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 3b7743407c..d472681c41 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -13,6 +13,7 @@ #include "SVG.hpp" #include "TreeSupportCommon.hpp" #include "TreeSupport.hpp" +#include #include "TreeSupport3D.hpp" #include "BeltFloorContext.hpp" #include @@ -712,6 +713,17 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) const double tilt_x_rad = Geometry::deg2rad(print_cfg.build_plate_tilt_x.value); const double tilt_y_rad = Geometry::deg2rad(print_cfg.build_plate_tilt_y.value); const bool has_tilt = std::abs(tilt_x_rad) > EPSILON || std::abs(tilt_y_rad) > EPSILON; + + // Belt printers: the object is pre-rotated by the belt angle before slicing, so a wall + // that is vertical in the world advances by one layer height per layer in the sliced + // frame. The build-plate tilt shift above compensates for that, but its direction has to + // follow the belt shear -- the sign and axis are already known exactly from the slicing + // parameters, so take them from there rather than from tan(build_plate_tilt), which + // carries a magnitude but no direction. Non-belt tilted beds keep the existing behaviour. + BeltFloorContext ovh_belt_ctx; + const bool belt_ovh_active = ovh_belt_ctx.init(m_slicing_params, print_cfg); + const double belt_shear = ovh_belt_ctx.shear_factor(); + const int belt_axis = ovh_belt_ctx.from_axis(); // FIXME this is a fudge constant! double support_tree_tip_diameter = 0.8; auto enforcer_overhang_offset = scaled(support_tree_tip_diameter); @@ -855,15 +867,61 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) ExPolygons& lower_polys = lower_layer->lslices_extrudable; // Apply build plate tilt: shift lower layer polygons to simulate tilted gravity + // + // On a belt the object's very first slice can come out empty (the bottom + // vertex is a sub-extrudable sliver), leaving the layer above it with an + // empty predecessor even though it rests on the belt. That case needs no + // special handling here: the belt surface is unioned into effective_lower + // below and sampled at the bottom of the layer, so a contacting island is + // covered and a genuinely floating one still reports its overhang. Doing it + // that way keeps the decision per-island -- an earlier whole-layer skip, + // conditioned on the nearest point of the *union* of the cross-section, + // let one contacting island silence a separate floating one. ExPolygons shifted_lower; - if (has_tilt) { + if (belt_ovh_active || has_tilt) { shifted_lower = lower_polys; // copy const double lh = lower_layer->height; - Point tilt_shift(coord_t(scale_(lh * tan(tilt_y_rad))), - coord_t(scale_(lh * tan(tilt_x_rad)))); + Point tilt_shift(0, 0); + if (belt_ovh_active) { + // Advance the lower layer along the belt by exactly the amount a + // world-vertical wall moves per layer, so such a wall stops reading + // as an overhang. Sign comes from the shear, not from a tilt angle. + const coord_t d = coord_t(-scale_(lh * belt_shear)); + if (belt_axis == 0) tilt_shift.x() = d; else tilt_shift.y() = d; + } else { + tilt_shift = Point(coord_t(scale_(lh * tan(tilt_y_rad))), + coord_t(scale_(lh * tan(tilt_x_rad)))); + } translate(shifted_lower, tilt_shift); } - const ExPolygons &effective_lower = has_tilt ? shifted_lower : lower_polys; + ExPolygons effective_lower = (belt_ovh_active || has_tilt) ? shifted_lower : lower_polys; + + // Belt printers: material resting on the belt is held up by the belt, not by + // the layer below it, so the belt surface counts as support from underneath. + // Without this the object's belt-contact face reads as a fresh overhang on + // every layer -- the leading strip that produced the spurious support nub. + if (belt_ovh_active) { + // surface_polygon() is a +/-1000mm half-plane. Unioning that raw with + // 20mm-scale geometry and then offsetting it puts a huge dynamic range + // through Clipper, which left intermittent artefacts every few layers. + // Clip it to the layer's own bounding box first. + // Evaluate the belt surface at the BOTTOM of the layer, not its top: + // a layer meets the belt across its whole thickness, and print_z is the + // top. On the object's first layer -- which is thicker, and whose lower + // layer is empty -- using print_z left the leading 0.37mm uncovered and + // produced the one remaining spurious overhang. + Polygons belt_surface = ovh_belt_ctx.surface_polygon(layer->print_z - layer->height); + if (! belt_surface.empty()) { + BoundingBox clip_bb = get_extents(curr_polys); + clip_bb.merge(get_extents(lower_polys)); + clip_bb.offset(scale_(10.)); + belt_surface = intersection(belt_surface, Polygons{ clip_bb.polygon() }); + if (! belt_surface.empty()) { + append(effective_lower, union_ex(belt_surface)); + effective_lower = union_ex(effective_lower); + } + } + } // normal overhang ExPolygons lower_layer_offseted = offset_ex(effective_lower, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS); @@ -882,8 +940,13 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) for (const ExPolygon& expoly : curr_polys) { bool is_sharp_tail = false; // 1. nothing below - // this is a sharp tail region if it's floating and non-ignorable - if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) { + // this is a sharp tail region if it's floating and non-ignorable. + // On a belt, "below" has to include the belt itself and the + // shear-advanced lower layer, or every belt-contact island reads as + // a sharp tail -- which is what the empty-predecessor skip above was + // really masking. effective_lower is exactly that notion of below. + const ExPolygons &tail_lower = belt_ovh_active ? effective_lower : lower_polys; + if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), tail_lower)) { is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty(); } diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index 07dcb324b8..979adcd9ed 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -651,7 +651,22 @@ inline SupportGeneratorLayer& layer_initialize( const size_t layer_idx) { layer_new.print_z = layer_z(slicing_params, config, layer_idx); - layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0; + // Layer 0 has no layer below it, so its bottom is the build plate at z = 0 -- + // true for a flat bed, false for a belt, whose virtual support layers extend + // below zero. Taking 0 there made the bottom-most belt layer's height come out + // as its own (negative) print_z, which reached Flow::with_height() and threw + // FlowErrorNegativeFlow, so tree support could not slice any belt model whose + // branches reached down that far. + // + // Only the negative case is corrected. An earlier version used + // min(0, print_z - layer_height), which also fires whenever the initial layer + // is THINNER than the regular layer height -- e.g. 0.2 over 0.3, both + // independently configurable -- and silently changed flat-bed support layer + // heights. Keying on the sign leaves every non-negative print_z on exactly + // the previous value of 0. + layer_new.bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0.; + if (layer_idx == 0 && layer_new.print_z < 0.) + layer_new.bottom_z = layer_new.print_z - slicing_params.layer_height; layer_new.height = layer_new.print_z - layer_new.bottom_z; return layer_new; }