Compare commits

...
Author SHA1 Message Date
Hanif Koh 9fe75c776d Refuse a Filament Flow Ratio of 0 Instead of Writing NaN Extrusion
A used filament with a flow ratio of 0 sliced in the GUI and wrote
E-92233720368547.758 on every extrusion move: GCode::_extrude multiplied
the flow by the ratio and divided by it again, giving 0 * 0 / 0 = NaN.
The CLI already rejected the value through the config check, but
Print::validate never looked at it.

Print::validate now stops the slice with an error naming the filament
when a used filament's flow ratio is 0 or nil, and _extrude converts the
flow to extrusion length with the bare filament cross-section, so the
ratio is applied once without a divide.
2026-10-21 14:19:17 +08:00
4 changed files with 68 additions and 4 deletions

No files matched your search

+3 -4
View File
@@ -9214,10 +9214,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &path_d
effective_height = static_cast<float>(m_sub_layer_height);
}
// Effective extrusion length per distance unit = (filament_flow_ratio/cross_section) * mm3_per_mm / print flow ratio
// m_writer.extruder()->e_per_mm3() below is (filament flow ratio / cross-sectional area)
double e_per_mm = m_writer.filament()->e_per_mm3() * _mm3_per_mm;
e_per_mm /= filament_flow_ratio;
// _mm3_per_mm already includes the filament flow ratio, so convert it with the bare cross-section
// rather than e_per_mm3(), which would apply the ratio a second time.
double e_per_mm = _mm3_per_mm / m_writer.filament()->filament_crossection();
// set speed
if (speed == -1) {
+17
View File
@@ -1882,6 +1882,23 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
if (extruders.empty())
return { L("No extrusions under current settings.") };
// A flow ratio of 0 would extrude nothing, and a nil one would write NaN extrusion lengths.
{
const std::vector<double> &flow_ratios = m_config.filament_flow_ratio.values;
const std::vector<int> &self_index = m_config.filament_self_index.values;
for (size_t column = 0; column < flow_ratios.size(); ++column) {
// filament_self_index maps a column to its filament once the filament arrays hold one column per variant
const unsigned int filament_id = self_index.size() == flow_ratios.size() ? self_index[column] - 1 : column;
if (flow_ratios[column] > 0. || !std::binary_search(extruders.begin(), extruders.end(), filament_id))
continue;
StringObjectException err;
// TRN %1% is the filament number as shown in the filament list, starting from 1.
err.string = Slic3r::format(L("The flow ratio of filament %1% must be greater than 0."), filament_id + 1);
err.opt_key = "filament_flow_ratio";
return err;
}
}
// IDEX/IQEX: block multi-color in non-primary parallel modes when the active
// configuration can't physically support it (no within-gantry toolchange path
// or MMU lane sharing). See imex_multicolor_block_reason() for the full rule.
+23
View File
@@ -4,6 +4,7 @@
#include <catch2/catch_message.hpp>
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/GCodeReader.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Print.hpp"
@@ -89,6 +90,28 @@ TEST_CASE("The exclude object tolerance defaults to the former fixed 0.1 mm", "[
Catch::Matchers::WithinAbs(0.1, 1e-9));
}
TEST_CASE("The filament flow ratio scales the extruded length once", "[GCode]")
{
// Retraction off, so every positive relative E move is extrusion and scales with the ratio.
auto extruded_length = [](double flow_ratio) {
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "use_relative_e_distances", "1" }, { "retraction_length", "0" }, { "wipe", "0" } });
config.set_key_value("filament_flow_ratio", new ConfigOptionFloatsNullable{ flow_ratio });
double total = 0.;
GCodeReader reader;
reader.parse_buffer(Test::slice({ Test::cube(20) }, config),
[&](GCodeReader &, const GCodeReader::GCodeLine &line) {
if ((line.cmd_is("G1") || line.cmd_is("G2") || line.cmd_is("G3")) && line.has_e() && line.e() > 0.f)
total += line.e();
});
return total;
};
const double full = extruded_length(1.);
REQUIRE(full > 0.);
CHECK_THAT(extruded_length(0.9) / full, Catch::Matchers::WithinRel(0.9, 1e-3));
}
// Firmwares read the compatibility header lines (Anycubic's GoKlipper heats the bed with
// first_layer_bed_temperature), so they must describe the filament the plate prints, not filament 1.
TEST_CASE("The G-code header names the first layer temperatures of the filament the plate prints", "[GCode]")
+25
View File
@@ -547,6 +547,31 @@ TEST_CASE("Print::validate tolerates a null warnings pointer", "[Print][validate
CHECK(err.string.empty());
}
TEST_CASE("Print::validate refuses a used filament with a flow ratio of 0", "[Print][validate][Regression]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_key_value("filament_flow_ratio", new ConfigOptionFloatsNullable{ 0. });
std::vector<StringObjectException> warnings;
StringObjectException err = validate_cubes(config, warnings);
CHECK_FALSE(err.string.empty());
CHECK_FALSE(err.is_warning);
CHECK(err.opt_key == "filament_flow_ratio");
}
TEST_CASE("Print::validate ignores a flow ratio of 0 on an unused filament", "[Print][validate]")
{
// The cube prints with filament 1, so filament 2's ratio is never used.
DynamicPrintConfig config = multifilament_config(2, { { "filament_self_index", "1,2" } });
config.set_key_value("filament_flow_ratio", new ConfigOptionFloatsNullable{ 1., 0. });
std::vector<StringObjectException> warnings;
StringObjectException err = validate_cubes(config, warnings);
CHECK(err.string.empty());
}
TEST_CASE("Purge tower selection keeps ordinary printers on the classic path", "[Print][PurgeTower][Regression]")
{
DynamicPrintConfig config = multifilament_config(2, {