Compare commits

..
Author SHA1 Message Date
Hanif Koh 86941859fa Keep a Scalar Config Option's Value When Deserializing Fails
A failed stream extraction writes 0 into its target, so Float, Int, Percent and FloatOrPercent options were overwritten before deserialize reported the error, and Point/Point3 kept a partially scanned value. Parse into a temporary and assign only on success; FloatOrPercent sets its percent flag only then too.
2026-10-19 16:41:04 +08:00
13 changed files with 90 additions and 119 deletions

No files matched your search

-6
View File
@@ -15648,12 +15648,6 @@ msgstr ""
msgid "Enable this option to add EXCLUDE OBJECT command in G-code."
msgstr ""
msgid "Outline tolerance"
msgstr ""
msgid "Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the original outline. Larger values give fewer points, which helps firmware that stalls while parsing G-code with many objects. 0 keeps every point."
msgstr ""
msgid "Verbose G-code"
msgstr ""
+37 -13
View File
@@ -841,8 +841,12 @@ public:
{
UNUSED(append);
std::istringstream iss(str);
iss >> this->value;
return !iss.fail();
double value;
iss >> value;
if (iss.fail())
return false;
this->value = value;
return true;
}
bool operator==(const ConfigOption &rhs) const override
@@ -1033,8 +1037,12 @@ public:
{
UNUSED(append);
std::istringstream iss(str);
iss >> this->value;
return !iss.fail();
int value;
iss >> value;
if (iss.fail())
return false;
this->value = value;
return true;
}
ConfigOptionInt& operator=(const ConfigOption *opt)
@@ -1252,8 +1260,12 @@ public:
UNUSED(append);
// don't try to parse the trailing % since it's optional
std::istringstream iss(str);
iss >> this->value;
return !iss.fail();
double value;
iss >> value;
if (iss.fail())
return false;
this->value = value;
return true;
}
private:
@@ -1365,10 +1377,14 @@ public:
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
this->percent = str.find_first_of("%") != std::string::npos;
std::istringstream iss(str);
iss >> this->value;
return !iss.fail();
double value;
iss >> value;
if (iss.fail())
return false;
this->value = value;
this->percent = str.find_first_of("%") != std::string::npos;
return true;
}
private:
@@ -1538,8 +1554,12 @@ public:
{
UNUSED(append);
char dummy;
return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 ||
sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2;
Vec2d value;
if (sscanf(str.data(), " %lf , %lf %c", &value(0), &value(1), &dummy) != 2 &&
sscanf(str.data(), " %lf x %lf %c", &value(0), &value(1), &dummy) != 2)
return false;
this->value = value;
return true;
}
private:
@@ -1658,8 +1678,12 @@ public:
{
UNUSED(append);
char dummy;
return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 3 ||
sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 3;
Vec3d value;
if (sscanf(str.data(), " %lf , %lf , %lf %c", &value(0), &value(1), &value(2), &dummy) != 3 &&
sscanf(str.data(), " %lf x %lf x %lf %c", &value(0), &value(1), &value(2), &dummy) != 3)
return false;
this->value = value;
return true;
}
private:
+3 -13
View File
@@ -988,7 +988,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
MetadataList metadata;
VolumeMetadataList volumes;
std::optional<bool> auto_drop;
bool auto_drop { true };
};
struct CutObjectInfo
@@ -1208,7 +1208,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//IdToGeometryMap m_orig_geometries; // backup & restore
CurrentConfig m_curr_config;
IdToMetadataMap m_objects_metadata;
std::set<int> m_items_auto_drop_off; // object ids with a build item auto_drop="0" (2.4.x layout)
IdToCutObjectInfoMap m_cut_object_infos;
IdToLayerHeightsProfileMap m_layer_heights_profiles;
IdToLayerConfigRangesMap m_layer_config_ranges;
@@ -1464,7 +1463,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
m_objects.clear();
m_instances.clear();
m_objects_metadata.clear();
m_items_auto_drop_off.clear();
m_curr_metadata_name.clear();
m_curr_characters.clear();
@@ -1504,7 +1502,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
m_curr_config.object_id = -1;
m_curr_config.volume_id = -1;
m_objects_metadata.clear();
m_items_auto_drop_off.clear();
m_layer_heights_profiles.clear();
m_layer_config_ranges.clear();
m_brim_ear_points.clear();
@@ -2260,14 +2257,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
ObjectMetadata::VolumeMetadataList volumes;
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
// 2.4.x stored auto_drop on build items, shared by all instances of an object; any "off" wins
const bool legacy_auto_drop = !m_items_auto_drop_off.count(object.first.second);
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
if (obj_metadata != m_objects_metadata.end()) {
// config data has been found, this model was saved using slic3r pe
model_object->auto_drop = obj_metadata->second.auto_drop.value_or(legacy_auto_drop);
model_object->auto_drop = obj_metadata->second.auto_drop;
// apply object's name and config data
for (const Metadata& metadata : obj_metadata->second.metadata) {
@@ -2285,7 +2279,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
else {
// config data not found, this model was not saved using slic3r pe
model_object->auto_drop = legacy_auto_drop;
// add the entire geometry as the single volume to generate
//volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1);
@@ -4128,8 +4121,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
if (const char* auto_drop = bbs_get_attribute_value_charptr(attributes, num_attributes, AUTO_DROP_ATTR); auto_drop != nullptr && !::atoi(auto_drop))
m_items_auto_drop_off.insert(object_id);
return !m_load_model || _create_object_instance(path, object_id, transform, printable, 1);
}
@@ -4498,8 +4489,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
// int instances_count_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR);
ObjectMetadata metadata;
if (bbs_get_attribute_value_charptr(attributes, num_attributes, AUTO_DROP_ATTR) != nullptr)
metadata.auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR);
metadata.auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR);
m_objects_metadata.insert({ object_id, std::move(metadata) });
m_curr_config.object_id = object_id;
return true;
-1
View File
@@ -1421,7 +1421,6 @@ static std::vector<std::string> s_Preset_print_options{
"initial_layer_travel_acceleration",
"initial_layer_travel_jerk",
"exclude_object",
"exclude_object_tolerance",
"slow_down_layers",
"infill_anchor",
"infill_anchor_max",
+1 -2
View File
@@ -309,7 +309,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"gcode_comments",
"gcode_label_objects",
"exclude_object",
"exclude_object_tolerance",
"support_material_interface_fan_speed",
"internal_bridge_fan_speed", // ORCA: Add support for separate internal bridge fan speed control
"ironing_fan_speed",
@@ -6930,7 +6929,7 @@ BoundingBoxf3 PrintInstance::get_bounding_box() const {
Polygon PrintInstance::get_convex_hull_2d() {
Polygon poly = print_object->model_object()->convex_hull_2d(model_instance->get_matrix());
poly.douglas_peucker(scale_(print_object->print()->config().exclude_object_tolerance.value));
poly.douglas_peucker(scale_(0.1));
return poly;
}
-11
View File
@@ -4629,17 +4629,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("exclude_object_tolerance", coFloat);
def->label = L("Outline tolerance");
def->tooltip = L("Maximum deviation of the object outline written to EXCLUDE_OBJECT_DEFINE from the "
"original outline. Larger values give fewer points, which helps firmware that stalls "
"while parsing G-code with many objects. 0 keeps every point.");
def->sidetext = L("mm");
def->min = 0;
def->max = 5;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.1));
def = this->add("gcode_comments", coBool);
def->label = L("Verbose G-code");
def->tooltip = L("Enable this to get a commented G-code file, with each line explained by a descriptive text. "
-1
View File
@@ -2130,7 +2130,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionPercents, filament_shrinkage_compensation_z))
((ConfigOptionBool, gcode_label_objects))
((ConfigOptionBool, exclude_object))
((ConfigOptionFloat, exclude_object_tolerance))
((ConfigOptionFloats, grab_length))
((ConfigOptionBool, gcode_comments))
((ConfigOptionInt, slow_down_layers))
-2
View File
@@ -1245,8 +1245,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
if(gcf_is_klipper)
toggle_field("accel_to_decel_factor", config->opt_bool("accel_to_decel_enable"));
toggle_line("exclude_object_tolerance", gcf_is_klipper && config->opt_bool("exclude_object"));
bool have_make_overhang_printable = config->opt_bool("make_overhang_printable");
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
-1
View File
@@ -3269,7 +3269,6 @@ void TabPrint::build()
optgroup->append_single_option_line("gcode_comments", "others_settings_g_code_output#verbose-g-code");
optgroup->append_single_option_line("gcode_label_objects", "others_settings_g_code_output#label-objects");
optgroup->append_single_option_line("exclude_object", "others_settings_g_code_output#exclude-objects");
optgroup->append_single_option_line("exclude_object_tolerance", "others_settings_g_code_output#outline-tolerance");
option = optgroup->get_option("filename_format");
// option.opt.full_width = true;
option.opt.is_code = true;
-38
View File
@@ -51,44 +51,6 @@ TEST_CASE("Klipper object labels name each copy without the characters Klipper c
}
}
// The outline written to EXCLUDE_OBJECT_DEFINE is simplified with exclude_object_tolerance. A larger value must
// leave fewer points, which keeps the exclude-object array small on printers that stall while parsing it.
TEST_CASE("A larger exclude object tolerance writes fewer outline points", "[GCode]")
{
const auto slice_with_tolerance = [](const std::string &tolerance) {
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({{"gcode_flavor", "klipper"}, {"exclude_object", "1"},
{"exclude_object_tolerance", tolerance}});
// Default facet angle (2 degrees): 180 hull points, well inside either tolerance.
return Test::slice({make_cylinder(20., 5.)}, config);
};
// Points of the POLYGON=[[x,y],...] on the first EXCLUDE_OBJECT_DEFINE line.
const auto polygon_points = [](const std::string &gcode) {
const size_t define = gcode.find("EXCLUDE_OBJECT_DEFINE");
REQUIRE(define != std::string::npos);
const size_t begin = gcode.find("POLYGON=", define);
REQUIRE(begin != std::string::npos);
const std::string polygon = gcode.substr(begin, gcode.find('\n', begin) - begin);
size_t points = 1;
for (size_t pos = polygon.find("],["); pos != std::string::npos; pos = polygon.find("],[", pos + 1))
++points;
return points;
};
const size_t fine = polygon_points(slice_with_tolerance("0.1"));
const size_t coarse = polygon_points(slice_with_tolerance("0.4"));
INFO("points at 0.1 mm: " << fine << ", at 0.4 mm: " << coarse);
CHECK(coarse >= 4);
CHECK(coarse < fine);
}
// Existing profiles must keep writing the outlines they wrote when the tolerance was a constant 0.1 mm.
TEST_CASE("The exclude object tolerance defaults to the former fixed 0.1 mm", "[GCode]")
{
CHECK_THAT(DynamicPrintConfig::full_print_config().opt_float("exclude_object_tolerance"),
Catch::Matchers::WithinAbs(0.1, 1e-9));
}
// 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]")
-22
View File
@@ -505,28 +505,6 @@ TEST_CASE("A project whose components reference themselves fails to load", "[3mf
REQUIRE_FALSE(loaded);
}
TEST_CASE("A project in the 2.4.x layout keeps auto drop off from its build item", "[3mf][Regression]")
{
// 2.4.x wrote auto_drop on the build item and left it off the object, so the object is re-laid out here.
ScopedTemporaryFile temp(".3mf");
{
Model model;
ModelObject* object = model.add_object();
object->add_volume(make_cube(10., 10., 10.));
object->add_instance();
object->auto_drop = false;
store_project(model, temp.string(), SaveStrategy::Zip64 | SaveStrategy::Silence);
}
REQUIRE(replace_in_3mf_entry(temp.string(), "model_settings.config", " auto_drop=\"0\"", ""));
REQUIRE(replace_in_3mf_entry(temp.string(), "3dmodel.model", "printable=\"1\"/>", "printable=\"1\" auto_drop=\"0\"/>"));
ScopedTemporaryDir backup_dir("orca_auto_drop_dst");
Model model;
REQUIRE(load_project(temp.string(), model, backup_dir));
REQUIRE(model.objects.size() == 1);
CHECK_FALSE(model.objects.front()->auto_drop);
}
TEST_CASE("An object loads up to the component reference budget and fails past it", "[3mf][Regression]")
{
// The importer queues at most 100000 component references per object. Every reference besides the
+49
View File
@@ -1263,6 +1263,55 @@ TEST_CASE("read_cli rejects an invalid scalar numeric value", "[Config]") {
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
}
TEST_CASE("A scalar option keeps its value when deserializing fails", "[Config]") {
using Catch::Matchers::WithinAbs;
SECTION("float") {
ConfigOptionFloat opt(7.5);
REQUIRE_FALSE(opt.deserialize("abc"));
CHECK_THAT(opt.value, WithinAbs(7.5, 1e-9));
}
SECTION("int") {
ConfigOptionInt opt(3);
REQUIRE_FALSE(opt.deserialize("abc"));
CHECK(opt.value == 3);
}
SECTION("percent") {
ConfigOptionPercent opt(40.);
REQUIRE_FALSE(opt.deserialize("abc%"));
CHECK_THAT(opt.value, WithinAbs(40., 1e-9));
}
SECTION("float or percent") {
ConfigOptionFloatOrPercent opt(0.4, false);
REQUIRE_FALSE(opt.deserialize("abc%"));
CHECK_THAT(opt.value, WithinAbs(0.4, 1e-9));
CHECK_FALSE(opt.percent);
}
SECTION("point") {
ConfigOptionPoint opt(Vec2d(1., 2.));
REQUIRE_FALSE(opt.deserialize("5,abc"));
CHECK(opt.value == Vec2d(1., 2.));
}
SECTION("point3") {
ConfigOptionPoint3 opt(Vec3d(1., 2., 3.));
REQUIRE_FALSE(opt.deserialize("5,6,abc"));
CHECK(opt.value == Vec3d(1., 2., 3.));
}
}
TEST_CASE("A scalar option still takes a valid value", "[Config]") {
using Catch::Matchers::WithinAbs;
ConfigOptionFloatOrPercent opt(0.4, false);
REQUIRE(opt.deserialize("50%"));
CHECK_THAT(opt.value, WithinAbs(50., 1e-9));
CHECK(opt.percent);
ConfigOptionPoint point;
REQUIRE(point.deserialize("3x4"));
CHECK(point.value == Vec2d(3., 4.));
}
TEST_CASE("read_cli appends values when a vector option is repeated", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
@@ -150,13 +150,6 @@ public:
return m_close_frames;
}
// The printer answers a close frame before its own read completes, so the agent's stop can return first.
bool wait_for_close_frames(int count)
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_changed.wait_for(lock, 10s, [&] { return m_close_frames >= count; });
}
// Hangs up every connection that is waiting.
void release()
{
@@ -284,7 +277,6 @@ private:
if (ec == websocket::error::closed) {
std::lock_guard<std::mutex> lock(m_mutex);
++m_close_frames;
m_changed.notify_all();
}
if (ec)
reading = false;
@@ -457,7 +449,6 @@ TEST_CASE("Stopping the status stream sends the printer a close frame", "[Moonra
REQUIRE(f.printer.wait_for_ready_connections(1));
CHECK(f.stops_within(3s));
CHECK(f.printer.wait_for_close_frames(1));
CHECK(f.printer.close_frames() == 1);
}