mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 08:11:14 +00:00
Belt: follow-ups from a second review pass
Guard the layer count and the per-object layer collection against an object that is left without a layer to print on a belt (the counting loop stepped before begin() and front() was taken of an empty vector). Check the belt temperature tower's embossed model before the current project is replaced, not after. Only invalidate the support step of objects that own a belt brim when an object is added or removed. The empty-layers test now counts an extrusion only where material is laid down along a move. The BeltBrim.cpp SEQUENCING note says exactly which layers are read, and the machine-frame scale is 1/|sin|. Remove more code that nothing calls: the kinematics inverse (to_logical, apply_axis_remap_inverse, to_build_volume and the state kept for them), the world_coordinates(), is_active() and belt_brim_areas_by_layer() accessors, the PrintConfig overload of physical_tilt() and the DynamicPrintConfig overload of compute_belt_height_and_floor(). Comments in GCode.hpp, BeltSliceStrategy.hpp/.cpp and PrintObjectSlice.cpp that described the retired pre-slice remap and plane-evaluator still did; the purge-tower width tooltip named the wrong switch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
a564ec23fe
commit
cfad587c2d
@@ -376,12 +376,14 @@ static void belt_brim_band_paths(const BeltBrimContext &bc,
|
||||
// overhang outside the belt footprint and land in the brim ring, which the flattened
|
||||
// brim_object_gap - a belt-plane separation - does not cover.
|
||||
//
|
||||
// SEQUENCING: this reads the layers and support layers of every object on the plate, so
|
||||
// it must not overlap with another object's support step, which rebuilds them.
|
||||
// Print::process() therefore generates the belt brims one object after the other once the
|
||||
// parallel support step is over (PrintObject::generate_belt_brim()), and an object that
|
||||
// arrives on or leaves the plate invalidates every other object's support step
|
||||
// (PrintApply.cpp) so the brims are clipped against what is there now.
|
||||
// SEQUENCING: this reads every object's layers and this object's own support layers.
|
||||
// Another object's support step shifts that object's layer Z into the object frame for
|
||||
// the duration of the run (PrintObject::_generate_support_material()), so the brims must
|
||||
// not overlap with the parallel support step: Print::process() generates them one object
|
||||
// after the other once that step is over (PrintObject::generate_belt_brim()), and an
|
||||
// object that arrives on or leaves the plate invalidates the other brim owners' support
|
||||
// step (PrintApply.cpp) so their brims are clipped against what is there now. Only this
|
||||
// object's supports are dodged; another object's support at the same Z is not.
|
||||
// `region_bbox` bounds the brim; anything outside it cannot clip a brim line, so whole
|
||||
// objects are skipped without materialising their polygons. On a typical plate the
|
||||
// objects do not overlap and every foreign object drops out here, which matters because
|
||||
|
||||
@@ -25,7 +25,7 @@ void BeltGCode::write_belt_header(GCodeOutputStream &file, const Print &print)
|
||||
// for the physical tilt the G-code viewer uses to enable belt view.
|
||||
file.write_format("; belt_slice_rotation = %s\n", full_cfg.opt_serialize("belt_slice_rotation").c_str());
|
||||
file.write_format("; belt_slice_rotation_angle = %.1f\n", print.config().belt_slice_rotation_angle.value);
|
||||
// Machine-frame transform: shear (cot) + scale (1/sin) derived from the belt
|
||||
// Machine-frame transform: shear (cot) + scale (1/|sin|) derived from the belt
|
||||
// tilt angle (or belt_frame_tilt_angle when decoupled).
|
||||
file.write_format("; belt_frame_tilt_decouple = %d\n", print.config().belt_frame_tilt_decouple.value ? 1 : 0);
|
||||
file.write_format("; belt_frame_tilt_angle = %.1f\n", print.config().belt_frame_tilt_angle.value);
|
||||
|
||||
@@ -73,8 +73,7 @@ void BeltSliceStrategy::apply_preslice_transforms(Transform3d &trafo,
|
||||
z_shift.matrix()(2, 3) = z_shift_val;
|
||||
trafo = z_shift * trafo;
|
||||
}
|
||||
// out_belt_min_z is only meaningful in belt mode; the standalone-remap path
|
||||
// never reported it.
|
||||
// out_belt_min_z is only meaningful in belt mode.
|
||||
if (out_belt_min_z && config.belt_printer.value) {
|
||||
*out_belt_min_z = (min_z != std::numeric_limits<double>::max()) ? min_z : 0.;
|
||||
}
|
||||
|
||||
@@ -8,25 +8,23 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Belt printer / pre-slice transform strategy.
|
||||
// Belt printer pre-slice transform strategy.
|
||||
//
|
||||
// Composes, in order, the pre-slice mesh transforms applied before slicing:
|
||||
// 1. Pre-slice axis remap (standalone — works without belt mode)
|
||||
// 2. Belt rotation (the sole mesh-side belt transform; shear & scale are a
|
||||
// Composes, in order, the mesh transforms applied before slicing on a belt printer:
|
||||
// 1. Belt rotation (the sole mesh-side belt transform; shear & scale are a
|
||||
// g-code-side stage, see MachineFrameTransform)
|
||||
// 3. Per-object Z-shift that lifts the mesh above the build plate
|
||||
// 2. Per-object Z-shift that lifts the mesh so its slicing frame starts at the
|
||||
// belt below its footprint
|
||||
//
|
||||
// Isolates this belt/remap-specific logic from the generic slicing pipeline in
|
||||
// Isolates this belt-specific logic from the generic slicing pipeline in
|
||||
// PrintObjectSlice.cpp.
|
||||
class BeltSliceStrategy
|
||||
{
|
||||
public:
|
||||
// Apply the pre-slice remap + belt rotation + Z-shift to `trafo` in place.
|
||||
// No-op when neither a remap nor a belt rotation is configured.
|
||||
// Apply the belt rotation + Z-shift to `trafo` in place. No-op when no belt
|
||||
// rotation is configured.
|
||||
//
|
||||
// out_belt_min_z (if non-null) receives the minimum mesh Z after the
|
||||
// transforms, but only in belt-printer mode — the standalone-remap path
|
||||
// never reported it.
|
||||
// out_belt_min_z (if non-null) receives the minimum mesh Z after the transforms.
|
||||
static void apply_preslice_transforms(Transform3d &trafo,
|
||||
const PrintConfig &config,
|
||||
const ModelVolumePtrs &model_volumes,
|
||||
|
||||
@@ -46,8 +46,6 @@ Transform3d BeltTransformPipeline::build_forward_transform(const PrintConfig &co
|
||||
|
||||
// ---- Belt floor parameters ------------------------------------------------
|
||||
|
||||
// Shared implementation for both PrintConfig and DynamicPrintConfig.
|
||||
// Template avoids duplicating the math for the two config types.
|
||||
namespace {
|
||||
|
||||
// Belt floor in the rotated slicer frame: the image of z_machine = 0 under R.
|
||||
@@ -81,33 +79,15 @@ inline double belt_floor_z(const BeltTransformPipeline::BeltFloorParams &fp, con
|
||||
}
|
||||
|
||||
|
||||
template<typename Config>
|
||||
BeltTransformPipeline::BeltHeightResult compute_belt_height_and_floor_impl(
|
||||
const Config &config, const BoundingBoxf3 &bb, double original_height)
|
||||
const PrintConfig &config, const BoundingBoxf3 &bb, double original_height)
|
||||
{
|
||||
BeltTransformPipeline::BeltHeightResult result;
|
||||
result.object_height = original_height;
|
||||
|
||||
// Extract the mesh rotation from config (the sole mesh-side belt transform).
|
||||
BeltRotationAxis rot_axis;
|
||||
double rot_angle;
|
||||
|
||||
if constexpr (std::is_same_v<Config, PrintConfig>) {
|
||||
rot_axis = config.belt_slice_rotation.value;
|
||||
rot_angle = config.belt_slice_rotation_angle.value;
|
||||
} else {
|
||||
// DynamicPrintConfig path
|
||||
auto get_float = [&](const char *key) {
|
||||
auto *opt = config.template option<ConfigOptionFloat>(key);
|
||||
return opt ? opt->value : 0.0;
|
||||
};
|
||||
auto get_rot_axis = [&](const char *key) {
|
||||
auto *opt = config.template option<ConfigOptionEnum<BeltRotationAxis>>(key);
|
||||
return opt ? opt->value : BeltRotationAxis::None;
|
||||
};
|
||||
rot_axis = get_rot_axis("belt_slice_rotation");
|
||||
rot_angle = get_float("belt_slice_rotation_angle");
|
||||
}
|
||||
// The mesh rotation (the sole mesh-side belt transform).
|
||||
const BeltRotationAxis rot_axis = config.belt_slice_rotation.value;
|
||||
const double rot_angle = config.belt_slice_rotation_angle.value;
|
||||
|
||||
bool has_rotation = rot_axis != BeltRotationAxis::None && std::abs(rot_angle) > EPSILON;
|
||||
if (!has_rotation)
|
||||
@@ -159,12 +139,6 @@ BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_heig
|
||||
return compute_belt_height_and_floor_impl(config, bbox, original_height);
|
||||
}
|
||||
|
||||
BeltTransformPipeline::BeltHeightResult BeltTransformPipeline::compute_belt_height_and_floor(
|
||||
const DynamicPrintConfig &config, const BoundingBoxf3 &bbox, double original_height)
|
||||
{
|
||||
return compute_belt_height_and_floor_impl(config, bbox, original_height);
|
||||
}
|
||||
|
||||
bool BeltTransformPipeline::floor_shear(const PrintConfig &config, BeltFloorParams &out)
|
||||
{
|
||||
out = BeltFloorParams{};
|
||||
|
||||
@@ -82,12 +82,6 @@ public:
|
||||
return t;
|
||||
}
|
||||
|
||||
static PhysicalTilt physical_tilt(const PrintConfig &config)
|
||||
{
|
||||
return physical_tilt(config.belt_slice_rotation.value,
|
||||
config.belt_slice_rotation_angle.value);
|
||||
}
|
||||
|
||||
// ---- Matrix builders --------------------------------------------------
|
||||
|
||||
// Build the 3x3 rotation matrix from belt_slice_rotation* config.
|
||||
@@ -133,11 +127,6 @@ public:
|
||||
static BeltHeightResult compute_belt_height_and_floor(
|
||||
const PrintConfig &config, const BoundingBoxf3 &bbox,
|
||||
double original_height);
|
||||
|
||||
// Overload for DynamicPrintConfig (used by static slicing_parameters).
|
||||
static BeltHeightResult compute_belt_height_and_floor(
|
||||
const DynamicPrintConfig &config, const BoundingBoxf3 &bbox,
|
||||
double original_height);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -2431,6 +2431,9 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
||||
errors.push_back(e);
|
||||
continue;
|
||||
}
|
||||
// On a belt an object may be left without a layer to print at all.
|
||||
if (per_object[i].empty())
|
||||
continue;
|
||||
OrderingItem ordering_item;
|
||||
ordering_item.object_idx = i;
|
||||
ordering.reserve(ordering.size() + per_object[i].size());
|
||||
@@ -3244,6 +3247,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// Belt brim apron bands each get their own change_layer() call.
|
||||
for (const BeltBrimBand &band : object->belt_brim_prologue())
|
||||
zs.push_back(band.print_z);
|
||||
if (zs.empty())
|
||||
continue;
|
||||
std::sort(zs.begin(), zs.end());
|
||||
//BBS: merge numerically very close Z values.
|
||||
auto end_it = std::unique(zs.begin(), zs.end());
|
||||
|
||||
@@ -393,13 +393,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// Public accessor for the first-layer plane evaluator. Used by
|
||||
// CoolingBuffer (which is constructed with a GCode reference and needs
|
||||
// to read the plane for per-segment fan re-evaluation). All other
|
||||
// first-layer-plane access points (on_first_layer overload, effective
|
||||
// index helper) are in the protected section since they're called from
|
||||
// GCode internals only.
|
||||
|
||||
protected:
|
||||
class GCodeOutputStream {
|
||||
public:
|
||||
@@ -839,12 +832,6 @@ protected:
|
||||
|
||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
// First-layer plane evaluator. Constructed once per print from the
|
||||
// PrintConfig. is_active() == false on non-belt printers and on belt
|
||||
// printers without a Z-axis shear; in that case all per-path plane
|
||||
// checks short-circuit to the legacy Layer::id() == 0 path.
|
||||
// Plate origin, kept so a writer replaced during export can be given it again.
|
||||
|
||||
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
|
||||
|
||||
std::unique_ptr<AdaptivePAProcessor> m_pa_processor;
|
||||
|
||||
@@ -28,9 +28,6 @@ public:
|
||||
// no back-transform is active.
|
||||
Vec3d apply(const Vec3d &pos) const;
|
||||
|
||||
// True if a non-identity back-transform is active.
|
||||
bool is_active() const { return m_active; }
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
Transform3d m_inverse = Transform3d::Identity();
|
||||
|
||||
@@ -10,12 +10,8 @@ namespace Slic3r {
|
||||
BeltKinematics::BeltKinematics(const PrintConfig &config, bool world_coordinates)
|
||||
: m_world_coordinates(world_coordinates)
|
||||
{
|
||||
m_back_active = m_back_transform.init_from_config(config);
|
||||
m_back_transform.init_from_config(config);
|
||||
m_machine_frame.init_from_config(config);
|
||||
if (m_back_active)
|
||||
// BeltBackTransform stores the inverse of this; keep the forward so
|
||||
// to_logical() can reverse the whole chain.
|
||||
m_back_forward = BeltTransformPipeline::build_forward_transform(config);
|
||||
}
|
||||
|
||||
Vec3d BeltKinematics::to_machine(const Vec3d &p) const
|
||||
@@ -25,15 +21,6 @@ Vec3d BeltKinematics::to_machine(const Vec3d &p) const
|
||||
return m_machine_frame.apply(after_remap);
|
||||
}
|
||||
|
||||
Vec3d BeltKinematics::to_logical(const Vec3d &machine) const
|
||||
{
|
||||
const Vec3d before_frame = m_machine_frame.apply_inverse(machine);
|
||||
const Vec3d before_remap = this->apply_axis_remap_inverse(before_frame);
|
||||
if (m_world_coordinates || ! m_back_active)
|
||||
return before_remap;
|
||||
return m_back_forward * before_remap;
|
||||
}
|
||||
|
||||
void install_belt_kinematics(GCodeWriter &writer, const PrintConfig &config, bool world_coordinates)
|
||||
{
|
||||
writer.set_kinematics(std::make_unique<BeltKinematics>(config, world_coordinates));
|
||||
|
||||
@@ -31,12 +31,6 @@ public:
|
||||
explicit BeltKinematics(const PrintConfig &config, bool world_coordinates = false);
|
||||
|
||||
Vec3d to_machine(const Vec3d &p) const override;
|
||||
Vec3d to_logical(const Vec3d &machine) const override;
|
||||
// Machine -> build-volume frame. Only the machine-frame shear/scale is undone,
|
||||
// matching what GCodeProcessor's bounds validation wants. This is deliberately
|
||||
// NOT to_logical().
|
||||
Vec3d to_build_volume(const Vec3d &machine) const override
|
||||
{ return m_machine_frame.apply_inverse(machine); }
|
||||
|
||||
// A belt writer has always emitted full XYZ on every move, whether or not any
|
||||
// individual stage reports itself active. Making this conditional would change
|
||||
@@ -47,14 +41,9 @@ public:
|
||||
// coordinates and G2/G3 cannot describe it.
|
||||
bool supports_arc_moves() const override { return false; }
|
||||
|
||||
bool world_coordinates() const { return m_world_coordinates; }
|
||||
|
||||
private:
|
||||
BeltBackTransform m_back_transform;
|
||||
MachineFrameTransform m_machine_frame;
|
||||
// Forward of what m_back_transform inverts, kept so to_logical() can undo it.
|
||||
Transform3d m_back_forward { Transform3d::Identity() };
|
||||
bool m_back_active { false };
|
||||
bool m_world_coordinates { false };
|
||||
};
|
||||
|
||||
|
||||
@@ -17,32 +17,9 @@ Vec3d CartesianKinematics::apply_axis_remap(const Vec3d &pos) const
|
||||
return { remap(m_remap_x), remap(m_remap_y), remap(m_remap_z) };
|
||||
}
|
||||
|
||||
// Inverse of the above. Output axis i is fed by source axis (r_i % 3); walking
|
||||
// the three outputs therefore fills every source component exactly once, so long
|
||||
// as the remap is a permutation (which set_axis_remap callers guarantee).
|
||||
Vec3d CartesianKinematics::apply_axis_remap_inverse(const Vec3d &machine) const
|
||||
{
|
||||
if (!has_axis_remap())
|
||||
return machine;
|
||||
Vec3d out = Vec3d::Zero();
|
||||
const int r[3] = { m_remap_x, m_remap_y, m_remap_z };
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int axis = r[i] % 3;
|
||||
if (r[i] < 3) out[axis] = machine[i];
|
||||
else if (r[i] < 6) out[axis] = -machine[i];
|
||||
else out[axis] = m_build_vol_max[axis] - machine[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Vec3d CartesianKinematics::to_machine(const Vec3d &p) const
|
||||
{
|
||||
return this->apply_axis_remap(p);
|
||||
}
|
||||
|
||||
Vec3d CartesianKinematics::to_logical(const Vec3d &machine) const
|
||||
{
|
||||
return this->apply_axis_remap_inverse(machine);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -24,17 +24,6 @@ public:
|
||||
// Logical placed point -> emitted machine point.
|
||||
virtual Vec3d to_machine(const Vec3d &p) const = 0;
|
||||
|
||||
// Inverse of to_machine(), back to the logical placed frame. Intended for
|
||||
// consumers that must reconstruct model coordinates from emitted G-code
|
||||
// (the G-code viewer's upright preview).
|
||||
virtual Vec3d to_logical(const Vec3d &machine) const = 0;
|
||||
|
||||
// Machine point -> build-volume frame, for bounds validation only. This is
|
||||
// deliberately NOT to_logical(): the build-volume check wants the physical
|
||||
// frame the printable area is expressed in, not the model frame. Keeping
|
||||
// them separate stops the two contracts from being confused.
|
||||
virtual Vec3d to_build_volume(const Vec3d &machine) const = 0;
|
||||
|
||||
// True when a move must emit X, Y and Z because omitting a word would be
|
||||
// wrong under this mapping. Deliberately not called "couples_axes": a pure
|
||||
// axis permutation forces full emission without physically coupling axes.
|
||||
@@ -72,8 +61,6 @@ class CartesianKinematics : public MachineKinematics
|
||||
{
|
||||
public:
|
||||
Vec3d to_machine(const Vec3d &p) const override;
|
||||
Vec3d to_logical(const Vec3d &machine) const override;
|
||||
Vec3d to_build_volume(const Vec3d &machine) const override { return machine; }
|
||||
|
||||
bool must_emit_all_axes() const override { return this->has_axis_remap(); }
|
||||
bool suppress_lift_at_unknown_position() const override { return this->has_axis_remap(); }
|
||||
@@ -93,7 +80,6 @@ public:
|
||||
|
||||
protected:
|
||||
Vec3d apply_axis_remap(const Vec3d &pos) const;
|
||||
Vec3d apply_axis_remap_inverse(const Vec3d &pos) const;
|
||||
|
||||
int m_remap_x { 0 };
|
||||
int m_remap_y { 1 };
|
||||
|
||||
@@ -431,10 +431,7 @@ public:
|
||||
// the whole list to 0-based afterwards). Lowest positive outer_wall_filament_id
|
||||
// over the printing regions, 1 if none is explicitly set.
|
||||
unsigned int belt_brim_filament() const;
|
||||
// False when this object's instances sit at different points ALONG the belt, which
|
||||
// would need a separate set of bands each. Public so validate() can explain it.
|
||||
const std::vector<ExtrusionEntityCollection>& belt_brim_by_layer() const { return m_belt_brim_by_layer; }
|
||||
const std::vector<ExPolygons>& belt_brim_areas_by_layer() const { return m_belt_brim_areas_by_layer; }
|
||||
const std::vector<BeltBrimBand>& belt_brim_prologue() const { return m_belt_brim_prologue; }
|
||||
void clear_belt_brim();
|
||||
void set_belt_brim(std::vector<ExtrusionEntityCollection> &&by_layer,
|
||||
|
||||
@@ -1918,10 +1918,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport }));
|
||||
// A belt brim is clipped against the other objects on the plate (BeltBrim.cpp,
|
||||
// belt_brim_obstacles), and it is rebuilt with its object's support step: an
|
||||
// object that arrived or left changes every other object's brim.
|
||||
// object that arrived or left changes every other brim owner's brim.
|
||||
if ((new_objects || deleted_objects) && m_config.belt_printer.value)
|
||||
for (PrintObject *object : m_objects)
|
||||
update_apply_status(object->invalidate_step(posSupportMaterial));
|
||||
if (object->has_belt_brim())
|
||||
update_apply_status(object->invalidate_step(posSupportMaterial));
|
||||
if (new_objects)
|
||||
update_apply_status(false);
|
||||
print_regions_reshuffled = true;
|
||||
|
||||
@@ -7415,7 +7415,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Machine-frame tilt angle");
|
||||
def->category = L("Printable space");
|
||||
def->tooltip = L("Tilt angle (degrees) used to derive the machine-frame shear (cot) and "
|
||||
"scale (1/sin) applied to G-code. Only used when 'Decouple machine-frame "
|
||||
"scale (1/|sin|) applied to G-code. Only used when 'Decouple machine-frame "
|
||||
"tilt' is enabled; otherwise the belt tilt angle is used.");
|
||||
def->sidetext = L("°");
|
||||
def->min = -89.9;
|
||||
@@ -7477,7 +7477,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Belt purge tower width");
|
||||
def->category = L("Printable space");
|
||||
def->tooltip = L("Width (machine X, across the belt) of the purge prism that is automatically "
|
||||
"generated on belt printers when the prime tower is enabled and multiple "
|
||||
"generated on belt printers when the belt purge tower is enabled and multiple "
|
||||
"filaments are used. Filament-change purging is routed into this prism's "
|
||||
"extrusions instead of a classic wipe tower. Its height is computed "
|
||||
"automatically from the worst-case purge volume per layer: a wider prism "
|
||||
|
||||
@@ -909,19 +909,10 @@ void PrintObject::slice()
|
||||
|
||||
// Belt floor Z-shift: where is the belt surface in final slicer space?
|
||||
//
|
||||
// The belt surface is at model_Y=0 (XZ belt plane). After the full
|
||||
// pipeline (trafo_centered → pre_remap → shear → z_shift), the belt
|
||||
// surface equation in slicer space is:
|
||||
// Z_belt = sf * from_axis + belt_surface_z_centered + z_shift_val
|
||||
//
|
||||
// belt_surface_z_centered = remapped_bbox.min.z() (the Z position of
|
||||
// the belt surface in centered-pre-shear slicer space, which is 0
|
||||
// without pre-remap but nonzero when e.g. Y↔Z swap shifts the belt
|
||||
// surface away from Z=0 by the centering offset).
|
||||
//
|
||||
// z_shift_val = max(0, -m_belt_min_z) (lifts mesh above Z=0).
|
||||
//
|
||||
// So: belt_floor_z_shift = remapped_bb.min.z() + z_shift_val
|
||||
// The belt surface is the model's Z=0 plane. After the belt rotation and the
|
||||
// Z-shift it is the plane Z_belt = shear_factor * from_axis + z_shift_val in
|
||||
// slicer space, with z_shift_val = max(0, -m_belt_min_z), the lift that starts
|
||||
// the slicing frame at the belt below the footprint.
|
||||
if (std::abs(m_slicing_params.belt_floor_shear_factor) > EPSILON) {
|
||||
double z_shift_val = (m_belt_min_z < 0.) ? -m_belt_min_z : 0.;
|
||||
// The belt surface is at Z=0 in centered slicer space and bb.min.z() is
|
||||
@@ -967,10 +958,10 @@ void PrintObject::slice()
|
||||
if (m_layers.empty())
|
||||
throw Slic3r::SlicingError(L("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"));
|
||||
|
||||
// Belt printer global mode: offset all layer Z values so objects at
|
||||
// different bed positions print at different heights on the tilted belt.
|
||||
// This is a post-slicing adjustment — the sliced geometry is identical
|
||||
// regardless of global mode, only the output Z coordinates change.
|
||||
// Belt printer: offset all layer Z values so objects at different positions
|
||||
// along the belt print at different heights on the tilted belt. This is a
|
||||
// post-slicing adjustment: the sliced geometry is the same, only the output Z
|
||||
// coordinates change.
|
||||
{
|
||||
const auto &pcfg = this->print()->config();
|
||||
BOOST_LOG_TRIVIAL(trace) << "Belt global check: belt_printer=" << pcfg.belt_printer.value
|
||||
|
||||
+29
-12
@@ -17017,11 +17017,38 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
|
||||
}
|
||||
|
||||
|
||||
// The belt provini tower (Calib_Params::test_model 1) is one embossed model per
|
||||
// temperature range.
|
||||
static std::string belt_temp_tower_asset(const Calib_Params ¶ms)
|
||||
{
|
||||
const int t_start = (int) lround(params.start);
|
||||
const int t_end = (int) lround(params.end);
|
||||
return Slic3r::resources_dir() + "/calib/temperature_tower/belt_temp_tower_" +
|
||||
std::to_string(t_start) + "_" + std::to_string(t_end) + ".stl";
|
||||
}
|
||||
|
||||
void Plater::calib_temp(const Calib_Params& params) {
|
||||
constexpr double base_temp_tower_nozzle_diameter = 0.4;
|
||||
constexpr double base_temp_tower_block_height = 10.0;
|
||||
constexpr int base_temp_tower_temp_step = 5;
|
||||
|
||||
// A belt provini tower exists only for the ranges it was embossed for, and another
|
||||
// range's model would print numbers that do not match its temperatures. Refuse
|
||||
// before the current project is replaced.
|
||||
if (params.mode == CalibMode::Calib_Temp_Tower && params.test_model >= 1) {
|
||||
const auto &printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
if (printer_config.has("belt_printer") && printer_config.opt_bool("belt_printer") &&
|
||||
! boost::filesystem::exists(belt_temp_tower_asset(params))) {
|
||||
MessageDialog dlg(static_cast<wxWindow *>(wxGetApp().mainframe),
|
||||
format_wxstr(_L("No belt temperature tower is available for the range %1% to %2% °C. "
|
||||
"Use a range the tower models cover, for example 230 to 190."),
|
||||
(int) lround(params.start), (int) lround(params.end)),
|
||||
_L("Temperature tower"), wxICON_ERROR | wxOK);
|
||||
dlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto calib_temp_name = _L("Nozzle temperature test");
|
||||
new_project(false, false, calib_temp_name);
|
||||
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||
@@ -17077,19 +17104,9 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
temps.push_back(t);
|
||||
if (temps.empty()) temps.push_back(t_start);
|
||||
|
||||
const std::string calib_dir = Slic3r::resources_dir() + "/calib/temperature_tower/";
|
||||
std::string asset = calib_dir + "belt_temp_tower_" + std::to_string(t_start) + "_" + std::to_string(t_end) + ".stl";
|
||||
if (!boost::filesystem::exists(asset)) {
|
||||
// The embossed numbers are part of the model, so another model's tower would
|
||||
// print numbers that do not match its temperatures.
|
||||
MessageDialog dlg(static_cast<wxWindow *>(wxGetApp().mainframe),
|
||||
format_wxstr(_L("No belt temperature tower is available for the range %1% to %2% °C. "
|
||||
"Use a range the tower models cover, for example 230 to 190."),
|
||||
t_start, t_end),
|
||||
_L("Temperature tower"), wxICON_ERROR | wxOK);
|
||||
dlg.ShowModal();
|
||||
const std::string asset = belt_temp_tower_asset(params);
|
||||
if (!boost::filesystem::exists(asset)) // refused above, before new_project()
|
||||
return;
|
||||
}
|
||||
if (!add_model(false, asset) || model().objects.empty())
|
||||
return;
|
||||
|
||||
|
||||
@@ -1381,26 +1381,27 @@ TEST_CASE("Belt G-code has no layer that prints nothing", "[Print][belt][GCode][
|
||||
|
||||
size_t layers = 0, empty = 0, total_header = 0, total_count = 0;
|
||||
bool extruded = true; // before the first layer change
|
||||
std::istringstream in(gc);
|
||||
std::string line;
|
||||
auto close_layer = [&]() { if (! extruded) ++ empty; };
|
||||
while (std::getline(in, line)) {
|
||||
if (line.rfind(";LAYER_CHANGE", 0) == 0) {
|
||||
GCodeReader reader;
|
||||
reader.apply_config(config);
|
||||
reader.parse_buffer(gc, [&](GCodeReader &self, const GCodeReader::GCodeLine &line) {
|
||||
const std::string &raw = line.raw();
|
||||
if (raw.rfind(";LAYER_CHANGE", 0) == 0) {
|
||||
close_layer();
|
||||
++ layers;
|
||||
extruded = false;
|
||||
} else if (line.rfind("; total layer number: ", 0) == 0) {
|
||||
} else if (raw.rfind("; total layer number: ", 0) == 0) {
|
||||
// Counted by the G-code processor from the layer changes it saw.
|
||||
total_header = size_t(std::atoi(line.c_str() + 22));
|
||||
} else if (line.rfind("; total layers count = ", 0) == 0) {
|
||||
total_header = size_t(std::atoi(raw.c_str() + 22));
|
||||
} else if (raw.rfind("; total layers count = ", 0) == 0) {
|
||||
// GCode::m_layer_count, counted up front from the objects' layers; it also
|
||||
// drives the M73 progress and the total_layer_count placeholder.
|
||||
total_count = size_t(std::atoi(line.c_str() + 23));
|
||||
} else if (! extruded && line.rfind("G1 ", 0) == 0 && line.find('E') != std::string::npos
|
||||
&& (line.find('X') != std::string::npos || line.find('Y') != std::string::npos)) {
|
||||
total_count = size_t(std::atoi(raw.c_str() + 23));
|
||||
} else if (! extruded && line.extruding(self) && line.dist_XY(self) > EPSILON) {
|
||||
// Material laid down along a move: a wipe or an unretraction does not count.
|
||||
extruded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
close_layer();
|
||||
INFO("layers " << layers << ", header " << total_header << ", count " << total_count
|
||||
<< ", layers without extrusion " << empty);
|
||||
|
||||
Reference in New Issue
Block a user