From 4c2ae6d2022bdba6bc587b74a8a2cf443a206348 Mon Sep 17 00:00:00 2001 From: Lam Wei Lun Date: Mon, 19 Oct 2026 16:08:59 +0800 Subject: [PATCH] Keep Auto Drop off for projects saved by 2.4.x (#16633) 2.4.0 to 2.4.2 wrote auto_drop on each build item, and 2.4.x objects carry no auto_drop attribute in model_settings.config. The importer only read the object attribute and treated a missing one as on, so floating objects were dropped to the bed on load and the flag was rewritten as on on the next save. Record object ids with an item auto_drop="0" and use that as the fallback when the object has no attribute. The attribute's presence is now tracked so the fallback applies only when it is absent. Any item set to off turns auto drop off for the object, since the flag is shared by all instances. --- src/libslic3r/Format/bbs_3mf.cpp | 16 +++++++++++++--- tests/libslic3r/test_3mf.cpp | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index aca428f171..df17940ece 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -988,7 +988,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) MetadataList metadata; VolumeMetadataList volumes; - bool auto_drop { true }; + std::optional auto_drop; }; struct CutObjectInfo @@ -1208,6 +1208,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) //IdToGeometryMap m_orig_geometries; // backup & restore CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; + std::set 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; @@ -1463,6 +1464,7 @@ 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(); @@ -1502,6 +1504,7 @@ 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(); @@ -2257,11 +2260,14 @@ 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; + model_object->auto_drop = obj_metadata->second.auto_drop.value_or(legacy_auto_drop); // apply object's name and config data for (const Metadata& metadata : obj_metadata->second.metadata) { @@ -2279,6 +2285,7 @@ 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); @@ -4121,6 +4128,8 @@ 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); } @@ -4489,7 +4498,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // int instances_count_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR); ObjectMetadata metadata; - metadata.auto_drop = bbs_get_attribute_value_bool(attributes, num_attributes, AUTO_DROP_ATTR); + 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); m_objects_metadata.insert({ object_id, std::move(metadata) }); m_curr_config.object_id = object_id; return true; diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index f7b5049ebe..7287b9d5d3 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -505,6 +505,28 @@ 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