diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 32976fad76..fff437731f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,7 @@ #include "libslic3r.h" #include "I18N.hpp" #include "GCode.hpp" +#include #include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" @@ -5234,6 +5235,10 @@ std::string GCode::generate_object_brim(const Print &print, const PrintObject &o // geometry is already in plate coordinates. m_config.apply(print.default_region_config()); m_config.apply(object.config(), true); + // m_layer is not switched to this object until after brim emission, so name + // the belt-floor owner explicitly or the classification borrows whichever + // object was visited last. + BeltFloorObjectGuard floor_owner{ m_belt_floor_object, &object }; const Point &offset = object.instances()[instance_id].shift; this->set_origin(unscale(offset)); this->on_set_origin(&object, offset); @@ -6642,6 +6647,9 @@ LayerResult GCode::process_layer( // Speeds, flow and retraction all read m_config. m_config.apply(print.default_region_config()); m_config.apply(object.config(), true); + // Apron bands have no Layer at all (m_layer is null here), so the belt + // floor owner has to be named the same way the object brim names it. + BeltFloorObjectGuard floor_owner{ m_belt_floor_object, &object }; const size_t i_begin = single_object_instance_idx == size_t(-1) ? 0 : single_object_instance_idx; const size_t i_end = single_object_instance_idx == size_t(-1) ? object.instances().size() : single_object_instance_idx + 1; @@ -10307,6 +10315,48 @@ std::string GCode::set_object_info(Print *print) { return gcode.str(); } +bool GCode::belt_height_above_floor(const Vec3d &point_slicing_mm, double &height_mm) const +{ + // The owning object, which is what carries the belt description. During + // object-brim and coincident-apron emission m_layer still points at whichever + // object was visited last (or at nothing at all), so those paths publish the + // owner explicitly -- otherwise a brim's speed would depend on plate order. + const PrintObject *object = m_belt_floor_object != nullptr ? m_belt_floor_object + : (m_layer != nullptr ? m_layer->object() : nullptr); + if (object == nullptr) + return false; + // Respect an explicit first-layer-plane choice: only Auto and BeltAffine mean + // "use the belt". A user who selected XY, YZ or XZ has asked for the + // FirstLayerPlane evaluator and must keep it. + const FirstLayerPlaneMode mode = m_config.first_layer_plane.value; + if (mode != FirstLayerPlaneMode::Auto && mode != FirstLayerPlaneMode::BeltAffine) + return false; + // Likewise for a dialled-in plane offset. It is expressed as a machine-Z + // shift that FirstLayerPlane converts into a perpendicular distance in the + // slicing frame; this evaluator measures along slicing Z instead, so there is + // no faithful translation of it here. Honour the user's setting by deferring + // to the evaluator that implements it rather than silently dropping it. + if (std::abs(m_config.first_layer_plane_offset.value) > EPSILON) + return false; + + const SlicingParameters &sp = object->slicing_parameters(); + // Deliberately NOT BeltFloorContext: its init() folds in + // belt_support_floor_offset, a support-generator diagnostic. Letting that + // option move the model's first-layer speed band would be a surprising + // coupling -- a negative value would switch the slowdown off entirely. + // The belt surface itself is just shear * u + z_shift. + if (std::abs(sp.belt_floor_shear_factor) < EPSILON) + return false; + const double u = sp.belt_floor_from_axis == 0 ? point_slicing_mm.x() : point_slicing_mm.y(); + const double floor_z = sp.belt_floor_shear_factor * u + sp.belt_floor_z_shift; + // Measured along the slicing Z, not perpendicular to the belt: layers are + // horizontal slabs in the sliced frame, so the slab holding the material that + // rests on the belt at this point is the one within one layer height of it. + // A perpendicular measure would shrink the band by 1/cos(tilt). + height_mm = point_slicing_mm.z() - floor_z; + return true; +} + // convert a model-space scaled point into G-code coordinates Vec2d GCode::point_to_gcode(const Point &point) const { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 26b8bc78a5..c78a9675e2 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -831,6 +831,18 @@ protected: // Counter standing in for Layer::id() on apron layers, which precede layer 0. size_t m_belt_brim_layer_idx{0}; + // Belt brim only. Brim and coincident apron bands are emitted before m_layer + // is switched to their object, so belt_height_above_floor() would otherwise + // read the previously visited object's belt description -- making a brim's + // classification depend on plate visiting order. Those paths publish the + // owner here for the duration of the emission. Never left set. + const PrintObject *m_belt_floor_object{nullptr}; + struct BeltFloorObjectGuard { + const PrintObject *&slot; + BeltFloorObjectGuard(const PrintObject *&s, const PrintObject *o) : slot(s) { slot = o; } + ~BeltFloorObjectGuard() { slot = nullptr; } + }; + std::set m_initial_layer_extruders; std::vector> m_sorted_layer_filaments; // BBS @@ -853,6 +865,12 @@ protected: // otherwise we delegate to the legacy per-layer test. This is the // entry point used by per-path call sites in _extrude. bool on_first_layer(const Vec3d &point_slicing_mm) const { + // Belt printers: measure height above the belt surface itself, in the + // slicing frame. See belt_height_above_floor() for why this does not go + // through FirstLayerPlane. + double h; + if (this->belt_height_above_floor(point_slicing_mm, h)) + return h <= m_config.initial_layer_print_height.value + EPSILON; if (m_first_layer_plane && m_first_layer_plane->is_active()) return m_first_layer_plane->is_first_layer( point_slicing_mm, m_config.initial_layer_print_height.value); @@ -863,10 +881,40 @@ protected: // perpendicular distance to the plane in band_thickness_mm units; // otherwise it returns the legacy slicing layer index. int effective_layer_index_for_point(const Vec3d &point_slicing_mm) const { + double h; + if (this->belt_height_above_floor(point_slicing_mm, h)) { + const double lh = this->first_layer_band_mm(); + return h <= 0. ? 0 : int(std::floor(h / lh)); + } if (m_first_layer_plane && m_first_layer_plane->is_active()) return m_first_layer_plane->effective_layer_index(point_slicing_mm); return on_first_layer() ? 0 : layer_id(); } + + // Band thickness for the *effective layer index* only. FirstLayerPlane keeps + // two separate thresholds and so must this path: is_first_layer() tests + // against initial_layer_print_height, while effective_layer_index() counts + // bands of first_layer_plane_thickness. Conflating them would apply + // first-layer treatment through a whole 1mm band on a 0.2mm first layer. + double first_layer_band_mm() const { + double band = m_config.first_layer_plane_thickness.value; + if (band <= 0.) band = m_config.initial_layer_print_height.value; + return band > 0. ? band : 0.2; + } + + // Height of a slicing-frame point above the belt surface, or false when this + // is not a belt print. + // + // The belt surface is known exactly in the slicing frame from the slicing + // parameters (belt_floor_shear_factor / _from_axis / _z_shift) -- the same + // description the support generator uses. FirstLayerPlane instead derives its + // plane by composing gcode_remap_* with the g-code back-transform, so its + // answer changes with the machine's *output* axis convention: on a printer + // with a non-identity remap it reported ~86mm of clearance for geometry + // sitting directly on the belt, and no extrusion was ever classified as + // first-layer. Measuring against the belt itself is independent of every + // remap and back-transform. + bool belt_height_above_floor(const Vec3d &point_slicing_mm, double &height_mm) const; int layer_id() const { if (m_layer == nullptr) return -1;