diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index eaf5159d9c..ed3a8fc01f 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -1775,7 +1775,14 @@ void generate_support_toolpaths( bool sheath = support_params.with_sheath; bool no_sort = false; bool done = false; - if (base_layer.layer->bottom_z < EPSILON) { + // Belt printers have no flat bed first layer — the belt is the tilted + // build surface — so the dense raft_first_layer_density flange must not + // fire anywhere, including the layer at z=0 (the belt-surface line). + // (belt_floor_shear_factor is non-zero only when belt_printer is on.) + // For every other printer type, support z is never negative, so this + // matches the original "first layer at z=0" behaviour unchanged. + const bool is_belt_printer = std::abs(slicing_params.belt_floor_shear_factor) > EPSILON; + if (! is_belt_printer && base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). filler = filler_first_layer; filler->angle = Geometry::deg2rad(float(config.support_angle.value + 90.)); diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index ebc3d2c958..b1e3cdd6dd 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -172,11 +172,17 @@ TreeModelVolumes::TreeModelVolumes( if (ctx.is_active() && pcfg2.belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { m_belt_floor = ctx.compute_per_layer_floors(num_layers, [&](size_t layer_idx) -> double { - // Use local print_z (subtract global offset from object layer). - return (layer_idx >= num_raft_layers) - ? print_object.get_layer(layer_idx - num_raft_layers)->print_z - - print_object.belt_global_z_offset() - : 0.; + // Object layers: local print_z (subtract global offset). + if (layer_idx >= num_raft_layers) + return print_object.get_layer(layer_idx - num_raft_layers)->print_z + - print_object.belt_global_z_offset(); + // Belt raft layers (below the object): each carries its own + // local print_z in m_raft_layers. The belt floor is a tilted + // plane, so the half-plane to clip grows as print_z drops — + // using 0 here clipped every below-object layer against the + // Z=0 belt surface, under-clipping the raft region and leaving + // a dense support mass below the floor. + return (layer_idx < m_raft_layers.size()) ? m_raft_layers[layer_idx] : 0.; }); } } diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 904da75f7f..457bc5d8d2 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1506,6 +1506,16 @@ void TreeSupport::generate_toolpaths() // ORCA: base angle used for explicit interlaced interface orientation. const float base_support_angle = Geometry::deg2rad(object_config.support_angle.value); + // Belt floor: the lowest support layer rests on the moving, tilted belt, not + // on a flat bed — so it must NOT get the bed first-layer treatment (a brim on + // interface areas, a first-layer-flow sheath at raft_first_layer_density on + // base areas). That treatment draws a loop along the Z=0 belt-floor line that + // reads as a stray brim/skirt. Gate those layer_id==0 special cases off when + // the belt floor is active; false on non-belt printers so behavior is unchanged. + BeltFloorContext belt_ctx; + const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config) + && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly; + // generate tree support tool paths tbb::parallel_for( tbb::blocked_range(m_raft_layers, m_object->support_layer_count()), @@ -1539,7 +1549,7 @@ void TreeSupport::generate_toolpaths() filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle if (area_group.type != SupportLayer::BaseType) { // interface - if (layer_id == 0) { + if (layer_id == 0 && !belt_floor_active) { Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow; ExtrusionRole brim_role = (area_group.type == SupportLayer::RoofType && !area_group.interface_as_base) ? erSupportMaterialInterface : erSupportMaterial; @@ -1623,7 +1633,7 @@ void TreeSupport::generate_toolpaths() } else { // base_areas - bool support_base_on_bed = (layer_id == 0 && m_raft_layers == 0); + bool support_base_on_bed = (layer_id == 0 && m_raft_layers == 0 && !belt_floor_active); Flow flow = support_base_on_bed ? m_support_params.first_layer_flow : support_flow; bool need_infill = with_infill; if(m_object_config->support_base_pattern==smpDefault) @@ -1644,7 +1654,7 @@ void TreeSupport::generate_toolpaths() std::unique_ptr base_eec = std::make_unique(); base_eec->no_sort = true; ExtrusionEntitiesPtr &base_dst = base_eec->entities; - if (layer_id == 0) { + if (layer_id == 0 && !belt_floor_active) { float density = float(m_object_config->raft_first_layer_density.value * 0.01); fill_expolygons_with_sheath_generate_paths(base_dst, loops, filler_support.get(), density, erSupportMaterial, flow, m_support_params, true, false); @@ -1916,9 +1926,6 @@ void TreeSupport::generate() if (!belt_ext_layers.empty()) { auto &sl_vec = m_object->support_layers(); sl_vec.insert(sl_vec.begin(), belt_ext_layers.begin(), belt_ext_layers.end()); - BOOST_LOG_TRIVIAL(debug) << "[BELT-CALIB] wedge ext layers=" << belt_ext_layers.size() - << " z=" << belt_ext_layers.front()->print_z << ".." << belt_ext_layers.back()->print_z - << " seeded=" << seeded; } } } @@ -2177,6 +2184,16 @@ void TreeSupport::draw_circles() coordf_t support_extrusion_width = m_support_params.support_extrusion_width; const float tree_brim_width = config.tree_support_brim_width.value; + // Belt floor: the first object layer is not on a flat bed — it rests on the + // tilted, moving belt. So the first-object-layer adhesion features (the tree + // support brim, the hybrid first-layer base expansion) must be suppressed: + // their expanded contact rings project to a stray brim/skirt loop sitting in + // the Z=0 belt plane around the support footprint. false on non-belt printers, + // so behavior there is unchanged. + BeltFloorContext belt_ctx; + const bool belt_floor_active = belt_ctx.init(m_slicing_params, *m_print_config) + && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly; + if (m_object->support_layer_count() <= m_raft_layers) return; BOOST_LOG_TRIVIAL(info) << "draw_circles for object: " << m_object->model_object()->name; @@ -2287,7 +2304,7 @@ void TreeSupport::draw_circles() circle.points[i] = circle.points[i] * scale + node.position; } } - if (obj_layer_nr == 0 && m_raft_layers == 0) { + if (obj_layer_nr == 0 && m_raft_layers == 0 && !belt_floor_active) { double brim_width = !config.tree_support_auto_brim ? tree_brim_width : std::max(MIN_BRANCH_RADIUS_FIRST_LAYER, std::min(node.radius + node.dist_mm_to_top / (scale * branch_radius) * 0.5, MAX_BRANCH_RADIUS_FIRST_LAYER) - node.radius); auto tmp=offset(circle, scale_(brim_width)); if(!tmp.empty()) @@ -2352,12 +2369,19 @@ void TreeSupport::draw_circles() base_areas = intersection_ex(base_areas, m_machine_border); // Belt floor: clip tree support polygons by the belt surface plane. - // ts_layer->print_z is at LOCAL Z (global offset applied later in - // _generate_support_material), so use init_local to subtract - // belt_global_z_offset from z_shift. + // Non-organic tree support layers inherit their print_z from the + // (already globally-offset) object layers — see plan_layer_heights() + // and add_tree_support_layer(); only ORGANIC layers get the global + // Z offset applied later in _generate_support_material(). So here + // ts_layer->print_z is in the GLOBAL frame and we must use init() + // (global), not init_local(): mixing a local-frame clip plane with + // a global print_z displaces the cutoff line by belt_global_z_offset + // along the shear axis, leaving an un-clipped wedge of support below + // the belt floor. In per-object (non-global) mode belt_global_z_offset + // is 0 so init() and init_local() coincide — this is a no-op there. { BeltFloorContext ctx; - if (ctx.init_local(m_slicing_params, *m_print_config, m_object->belt_global_z_offset()) + if (ctx.init(m_slicing_params, *m_print_config) && m_print_config->belt_support_floor_mode.value == BeltSupportFloorMode::GeneratorOnly) { Polygons belt_surface = ctx.surface_polygon(ts_layer->print_z); base_areas = diff_ex(base_areas, belt_surface); @@ -2500,7 +2524,7 @@ void TreeSupport::draw_circles() // part. area_poly is collected from ePolygon nodes above, which are the normal // support nodes in Hybrid mode. Apply the expansion before area_groups and // lslices are built so toolpaths and brim avoidance use the same footprint. - if (layer_nr == 0 && m_raft_layers == 0 && m_support_params.support_style == smsTreeHybrid && + if (layer_nr == 0 && m_raft_layers == 0 && !belt_floor_active && m_support_params.support_style == smsTreeHybrid && m_object_config->raft_first_layer_expansion.value > 0.f) { ExPolygons expanded_base_areas; const float inflate_factor_1st_layer = float(scale_(m_object_config->raft_first_layer_expansion.value)); diff --git a/src/libslic3r/Support/TreeSupport3D.cpp b/src/libslic3r/Support/TreeSupport3D.cpp index 89e6c014f1..3386d72837 100644 --- a/src/libslic3r/Support/TreeSupport3D.cpp +++ b/src/libslic3r/Support/TreeSupport3D.cpp @@ -4016,12 +4016,20 @@ void organic_draw_branches( std::vector bottom_extra_slices; Polygons rest_support; coord_t bottom_radius = support_element_radius(config, *branch.path.front()); + // Belt printer (GeneratorOnly belt floor): the tilted belt surface is the + // build surface, so a branch should terminate ON the belt with a thin tip, + // not stamp its full footprint straight down to Z=0 and weld neighbouring + // branches into a solid floor slab. m_belt_floor is only populated in that + // mode, so it doubles as the gate (no effect on other printer types). + const bool belt_mode = !volumes.m_belt_floor.empty(); // Don't propagate further than 1.5 * bottom radius. //LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height; LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height; - LayerIndex layer_bottommost = branch.path.front()->state.verylost ? + LayerIndex layer_bottommost = (branch.path.front()->state.verylost && !belt_mode) ? // If the tree bottom is hanging in the air, bring it down to some surface. 0 : + // In belt mode never force-drop to Z=0 (the belt clip below handles + // termination); otherwise the "verylost" branch welds into the slab. //FIXME the "verylost" branches should stop when crossing another support. std::max(0, layer_begin - layers_propagate_max); double support_area_min_radius = M_PI * sqr(double(config.branch_radius)); @@ -4033,14 +4041,28 @@ void organic_draw_branches( Polygons collision = volumes.getCollision(0, collision_layer, false); rest_support = diff_clipped(rest_support.empty() ? slice_front_contact : rest_support, collision, ApplySafetyOffset::Yes); // Belt floor: clip propagated support at belt surface. - if (layer_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[layer_idx].empty()) + bool belt_cut = false; + if (layer_idx < LayerIndex(volumes.m_belt_floor.size()) && !volumes.m_belt_floor[layer_idx].empty()) { + double area_before = area(rest_support); rest_support = diff(rest_support, volumes.m_belt_floor[layer_idx]); + // The belt counts as "reached" only when it actually removes part + // of this branch's footprint. The belt half-plane is non-empty at + // every near-belt layer, so testing non-emptiness alone would + // terminate a laterally-distant branch ~1 layer above true contact, + // leaving a gap. Require a real area reduction instead. + belt_cut = belt_mode && area(rest_support) < area_before - tiny_area; + } remove_small(rest_support, tiny_area); double rest_support_area = area(rest_support); if (rest_support_area < support_area_stop) // Don't propagate a fraction of the tree contact surface. break; bottom_extra_slices.push_back({ rest_support, rest_support_area }); + // Belt mode: once the belt surface actually starts cutting this branch + // it has reached the belt — keep this last (belt-clipped) slice as the + // contact and stop, rather than stamping the footprint further down. + if (belt_cut) + break; } // Now remove those bottom slices that are not supported at all. #if 0 @@ -4058,7 +4080,10 @@ void organic_draw_branches( } } #endif - if (config.settings.support_floor_layers > 0) { + // Belt mode: no solid support-floor pad under these branches — it is what + // welds neighbouring belt-terminating branches into the dense Z=0 slab. + // They simply taper out onto the tilted belt as distributed thin contacts. + if (!belt_mode && config.settings.support_floor_layers > 0) { Polygons contacts; if (!bottom_extra_slices.empty()) { const int contact_idx = int(bottom_extra_slices.size()) - 1; // Use the lowest contact slice as the footprint. @@ -4097,7 +4122,10 @@ void organic_draw_branches( } // ORCA: retain bottom contacts even when no placeable areas intersect. - if (branch.has_root && config.support_rests_on_model && branch.path.front()->state.layer_idx > 0 && + // Skipped in belt mode (m_belt_floor populated) so we don't re-introduce a + // solid floor pad for branches that terminate on the tilted belt surface. + if (volumes.m_belt_floor.empty() && + branch.has_root && config.support_rests_on_model && branch.path.front()->state.layer_idx > 0 && config.settings.support_floor_layers > 0 && config.z_distance_bottom_layers > 0 && bottom_contacts.empty() && !slice_front_contact.empty()) bottom_contacts.emplace_back(slice_front_contact); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 9d9090480f..22c2cc85db 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1289,16 +1289,41 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const size_t n_filtered = 0, n_clip = 0; const double y_lo = model_bb.defined ? model_bb.min.y() - ANCHOR_CLIP_MARGIN_MM : -1e30; const double y_hi = model_bb.defined ? model_bb.max.y() + ANCHOR_CLIP_MARGIN_MM : 1e30; - for (const GCodeProcessorResult::MoveVertex& mv : gcode_result.moves) - if (mv.type == EMoveType::Extrude && mv.layer_id >= 1) { // skip layer-0 prime/skirt - ++n_filtered; - const Vec3d p = belt_inv * mv.position.cast(); - tp_bb_full.merge(p); - if (p.y() >= y_lo && p.y() <= y_hi) { - tp_bb_clip.merge(p); - ++n_clip; + // The anchor aligns the toolpath body onto the object-only model bbox, so it must be + // built from OBJECT-body toolpaths only. Support, skirt, brim and wipe-tower extrusions + // are not part of the model mesh and extend past it (support especially reaches well + // beyond the object on a belt), so including them drags tp_bb.min and shifts the whole + // back-transformed g-code off the mesh — but only when those features are present. That + // is the "g-code shifts vs mesh as soon as supports are enabled" bug. Filter them out. + auto is_object_body = [](ExtrusionRole r) { + return r != erSupportMaterial && r != erSupportMaterialInterface + && r != erSupportTransition + && r != erSkirt && r != erBrim && r != erWipeTower; + }; + auto accumulate = [&](bool body_only) { + tp_bb_clip = BoundingBoxf3(); + tp_bb_full = BoundingBoxf3(); + n_filtered = 0; + n_clip = 0; + for (const GCodeProcessorResult::MoveVertex& mv : gcode_result.moves) + if (mv.type == EMoveType::Extrude && mv.layer_id >= 1 // skip layer-0 prime/skirt + && (!body_only || is_object_body(mv.extrusion_role))) { + ++n_filtered; + const Vec3d p = belt_inv * mv.position.cast(); + tp_bb_full.merge(p); + if (p.y() >= y_lo && p.y() <= y_hi) { + tp_bb_clip.merge(p); + ++n_clip; + } } - } + }; + accumulate(/*body_only=*/true); + // Fallback: a plate with no object-body extrusions above layer 0 (e.g. a + // support-only object) would leave tp_bb undefined and silently skip the + // anchor. Re-accumulate over ALL extrude moves so the min-corner anchor is + // still computed rather than letting the preview float by the placement offset. + if (n_filtered == 0) + accumulate(/*body_only=*/false); // Use the clipped bbox when it still holds the bulk of the moves (outliers removed). // If the object sits far from the belt entry the toolpaths are grossly offset and the // clip drops most of them — fall back to the full bbox so the min-corner anchor still