From d1369e45c8b2e8516dc392f1e64b8770e9500b71 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 28 Jun 2026 00:42:02 +0800 Subject: [PATCH] validator: detect ambiguous (duplicate) filament subtypes per printer (#14459) * validator: detect duplicate filament subtype per printer (opt-in) A filament is matched from the AMS by (filament_id + printer compatibility); if two compatible filament presets for one printer share a filament_id, the match is ambiguous and the runtime silently picks whichever loads first. Add PresetBundle::check_duplicate_filament_subtypes(), gated behind a new has_errors(check_duplicate_filament_subtypes) parameter and the validator's -f/--check_filament_subtypes flag (off by default). For each system printer it groups its vendor's compatible filament presets by filament_id and errors on any group of 2+, reporting each preset as a clickable file:// URI with a single "how to fix" hint. CI runs it for BBL only (-v BBL -f) until the other vendors' profiles are cleaned up. * profiles: fix ambiguous BBL filament matches Resolve the duplicate-filament-subtype errors flagged by the validator: - align compatible_printers with Bambu Studio where Orca over-claimed a nozzle that already has a dedicated preset (Bambu PLA Basic/Matte/ABS @BBL H2DP; Bambu ASA/PETG HF @BBL H2DP 0.6 nozzle; Fiberon PETG-ESD @BBL X1) - fix a copy-pasted printer name in Overture Matte PLA @BBL A1M 0.2 nozzle - fix a wrong inherits in Panchroma PLA Silk @BBL X1C 0.2 nozzle (was inheriting Panchroma PLA @base, giving it filament_id GFPM001 instead of GFPM004) Bump BBL profile version. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/check_profiles.yml | 21 +++- resources/profiles/BBL.json | 2 +- .../BBL/filament/Bambu ABS @BBL H2DP.json | 3 +- .../Bambu ASA @BBL H2DP 0.6 nozzle.json | 3 +- .../Bambu PETG HF @BBL H2DP 0.6 nozzle.json | 3 +- .../filament/Bambu PLA Basic @BBL H2DP.json | 3 +- .../filament/Bambu PLA Matte @BBL H2DP.json | 3 +- ...verture Matte PLA @BBL A1M 0.2 nozzle.json | 2 +- .../Polymaker/Fiberon PETG-ESD @BBL X1.json | 2 - ...anchroma PLA Silk @BBL X1C 0.2 nozzle.json | 2 +- .../OrcaSlicer_profile_validator.cpp | 4 +- src/libslic3r/PresetBundle.cpp | 99 ++++++++++++++++++- src/libslic3r/PresetBundle.hpp | 8 +- 13 files changed, 134 insertions(+), 21 deletions(-) diff --git a/.github/workflows/check_profiles.yml b/.github/workflows/check_profiles.yml index c27c5fa5520..9a5272a314f 100644 --- a/.github/workflows/check_profiles.yml +++ b/.github/workflows/check_profiles.yml @@ -49,6 +49,14 @@ jobs: set +e ./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log exit ${PIPESTATUS[0]} + # For now run filament subtype check only for BBL profiles until we fix other vendors' profiles. + - name: validate filament subtype check for BBL profiles + id: validate_filament_subtypes + continue-on-error: true + run: | + set +e + ./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 -v BBL -f 2>&1 | tee ${{ runner.temp }}/validate_filament_subtypes.log + exit ${PIPESTATUS[0]} - name: validate custom presets id: validate_custom @@ -68,7 +76,7 @@ jobs: echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt - name: Prepare comment artifact - if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }} + if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }} run: | { # Marker matched by check_profiles_comment.yml to delete prior comments. @@ -94,6 +102,15 @@ jobs: echo "" fi + if [ "${{ steps.validate_filament_subtypes.outcome }}" = "failure" ]; then + echo "### BBL Filament Subtype Validation Failed" + echo "" + echo '```' + head -c 30000 ${{ runner.temp }}/validate_filament_subtypes.log || echo "No output captured" + echo '```' + echo "" + fi + if [ "${{ steps.validate_custom.outcome }}" = "failure" ]; then echo "### Custom Preset Validation Failed" echo "" @@ -116,7 +133,7 @@ jobs: retention-days: 1 - name: Fail if any check failed - if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }} + if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }} run: | echo "One or more profile checks failed. See above for details." exit 1 diff --git a/resources/profiles/BBL.json b/resources/profiles/BBL.json index 0c736e5d122..654ce46f60b 100644 --- a/resources/profiles/BBL.json +++ b/resources/profiles/BBL.json @@ -1,7 +1,7 @@ { "name": "Bambulab", "url": "http://www.bambulab.com/Parameters/vendor/BBL.json", - "version": "02.01.00.17", + "version": "02.01.00.18", "force_update": "0", "description": "BBL configurations", "machine_model_list": [ diff --git a/resources/profiles/BBL/filament/Bambu ABS @BBL H2DP.json b/resources/profiles/BBL/filament/Bambu ABS @BBL H2DP.json index 64d87e8fcf9..c8b70f19cc7 100644 --- a/resources/profiles/BBL/filament/Bambu ABS @BBL H2DP.json +++ b/resources/profiles/BBL/filament/Bambu ABS @BBL H2DP.json @@ -123,8 +123,7 @@ "0 0 0 0 0 0" ], "compatible_printers": [ - "Bambu Lab H2D Pro 0.4 nozzle", - "Bambu Lab H2D Pro 0.6 nozzle" + "Bambu Lab H2D Pro 0.4 nozzle" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/BBL/filament/Bambu ASA @BBL H2DP 0.6 nozzle.json b/resources/profiles/BBL/filament/Bambu ASA @BBL H2DP 0.6 nozzle.json index a632048629b..6ff818396f0 100644 --- a/resources/profiles/BBL/filament/Bambu ASA @BBL H2DP 0.6 nozzle.json +++ b/resources/profiles/BBL/filament/Bambu ASA @BBL H2DP 0.6 nozzle.json @@ -117,8 +117,7 @@ "0 0 0 0 0 0" ], "compatible_printers": [ - "Bambu Lab H2D Pro 0.6 nozzle", - "Bambu Lab H2D Pro 0.8 nozzle" + "Bambu Lab H2D Pro 0.6 nozzle" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/BBL/filament/Bambu PETG HF @BBL H2DP 0.6 nozzle.json b/resources/profiles/BBL/filament/Bambu PETG HF @BBL H2DP 0.6 nozzle.json index d1a9a461aaa..f72a06450b0 100644 --- a/resources/profiles/BBL/filament/Bambu PETG HF @BBL H2DP 0.6 nozzle.json +++ b/resources/profiles/BBL/filament/Bambu PETG HF @BBL H2DP 0.6 nozzle.json @@ -132,8 +132,7 @@ "0 0 0 0 0 0" ], "compatible_printers": [ - "Bambu Lab H2D Pro 0.6 nozzle", - "Bambu Lab H2D Pro 0.8 nozzle" + "Bambu Lab H2D Pro 0.6 nozzle" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/BBL/filament/Bambu PLA Basic @BBL H2DP.json b/resources/profiles/BBL/filament/Bambu PLA Basic @BBL H2DP.json index 58f53237ace..85660b9ad09 100644 --- a/resources/profiles/BBL/filament/Bambu PLA Basic @BBL H2DP.json +++ b/resources/profiles/BBL/filament/Bambu PLA Basic @BBL H2DP.json @@ -144,8 +144,7 @@ "0 0 0 0 0 0" ], "compatible_printers": [ - "Bambu Lab H2D Pro 0.4 nozzle", - "Bambu Lab H2D Pro 0.6 nozzle" + "Bambu Lab H2D Pro 0.4 nozzle" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/BBL/filament/Bambu PLA Matte @BBL H2DP.json b/resources/profiles/BBL/filament/Bambu PLA Matte @BBL H2DP.json index de4a8fa7b02..a12465e3811 100644 --- a/resources/profiles/BBL/filament/Bambu PLA Matte @BBL H2DP.json +++ b/resources/profiles/BBL/filament/Bambu PLA Matte @BBL H2DP.json @@ -150,8 +150,7 @@ "0 0 0 0 0 0" ], "compatible_printers": [ - "Bambu Lab H2D Pro 0.4 nozzle", - "Bambu Lab H2D Pro 0.6 nozzle" + "Bambu Lab H2D Pro 0.4 nozzle" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/BBL/filament/Overture/Overture Matte PLA @BBL A1M 0.2 nozzle.json b/resources/profiles/BBL/filament/Overture/Overture Matte PLA @BBL A1M 0.2 nozzle.json index c3762e96d32..57370c05efa 100644 --- a/resources/profiles/BBL/filament/Overture/Overture Matte PLA @BBL A1M 0.2 nozzle.json +++ b/resources/profiles/BBL/filament/Overture/Overture Matte PLA @BBL A1M 0.2 nozzle.json @@ -9,6 +9,6 @@ "1.8" ], "compatible_printers": [ - "Bambu Lab P1P 0.2 nozzle" + "Bambu Lab A1 mini 0.2 nozzle" ] } diff --git a/resources/profiles/BBL/filament/Polymaker/Fiberon PETG-ESD @BBL X1.json b/resources/profiles/BBL/filament/Polymaker/Fiberon PETG-ESD @BBL X1.json index e348b1e22f7..6d721595324 100644 --- a/resources/profiles/BBL/filament/Polymaker/Fiberon PETG-ESD @BBL X1.json +++ b/resources/profiles/BBL/filament/Polymaker/Fiberon PETG-ESD @BBL X1.json @@ -7,8 +7,6 @@ "instantiation": "true", "compatible_printers": [ "Bambu Lab X1 0.4 nozzle", - "Bambu Lab X1 Carbon 0.4 nozzle", - "Bambu Lab P1S 0.4 nozzle", "Bambu Lab X1E 0.4 nozzle" ], "overhang_fan_speed": [ diff --git a/resources/profiles/BBL/filament/Polymaker/Panchroma PLA Silk @BBL X1C 0.2 nozzle.json b/resources/profiles/BBL/filament/Polymaker/Panchroma PLA Silk @BBL X1C 0.2 nozzle.json index 23d4b2af549..592eea2e736 100644 --- a/resources/profiles/BBL/filament/Polymaker/Panchroma PLA Silk @BBL X1C 0.2 nozzle.json +++ b/resources/profiles/BBL/filament/Polymaker/Panchroma PLA Silk @BBL X1C 0.2 nozzle.json @@ -1,7 +1,7 @@ { "type": "filament", "name": "Panchroma PLA Silk @BBL X1C 0.2 nozzle", - "inherits": "Panchroma PLA @base", + "inherits": "Panchroma PLA Silk @base", "from": "system", "setting_id": "GFSPM004_09", "instantiation": "true", diff --git a/src/dev-utils/OrcaSlicer_profile_validator.cpp b/src/dev-utils/OrcaSlicer_profile_validator.cpp index a232588617e..3208f554524 100644 --- a/src/dev-utils/OrcaSlicer_profile_validator.cpp +++ b/src/dev-utils/OrcaSlicer_profile_validator.cpp @@ -95,6 +95,7 @@ int main(int argc, char* argv[]) #endif ("vendor,v", po::value()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified") ("generate_presets,g", po::value()->default_value(false), "Generate user presets for mock test") + ("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.") ("log_level,l", po::value()->default_value(2), "Log level. Optional, default is 2 (warning). Higher values produce more detailed logs."); // clang-format on @@ -118,6 +119,7 @@ int main(int argc, char* argv[]) std::string vendor = vm["vendor"].as(); int log_level = vm["log_level"].as(); bool generate_user_preset = vm["generate_presets"].as(); + bool check_filament_subtypes = vm["check_filament_subtypes"].as(); // check if path is valid, and return error if not if (!fs::exists(path) || !fs::is_directory(path)) { @@ -164,7 +166,7 @@ int main(int argc, char* argv[]) return 0; } - if (preset_bundle->has_errors()) { + if (preset_bundle->has_errors(check_filament_subtypes)) { std::cout << "Validation failed" << std::endl; return 1; } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d642a849f20..d917d73e555 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -5471,7 +5471,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } -bool PresetBundle::has_errors() const +bool PresetBundle::has_errors(bool check_duplicate_filament_subtypes) const { if (m_errors != 0 || printers.m_errors != 0 || filaments.m_errors != 0 || prints.m_errors != 0) return true; @@ -5491,9 +5491,106 @@ bool PresetBundle::has_errors() const } } + if (check_duplicate_filament_subtypes && this->check_duplicate_filament_subtypes()) + has_errors = true; + return has_errors; } +// Orca: turn a preset file path into an absolute file:// URI for log messages. +// Printed unquoted on its own line, this is the one format clickable in both the +// VS Code integrated terminal (Cmd/Ctrl+click) and macOS Terminal.app +// (Cmd+double-click); quotes or literal spaces break link detection in both, so +// the characters that would terminate the URI token are percent-encoded. +static std::string preset_file_uri(const std::string &file) +{ + std::string path; + try { + path = boost::filesystem::canonical(file).generic_string(); + } catch (...) { + path = file; + } + std::string uri = "file://"; + if (path.empty() || path.front() != '/') + uri += '/'; // Windows drive paths (e.g. C:/...) need the extra leading slash + for (char c : path) { + switch (c) { + case ' ': uri += "%20"; break; + case '#': uri += "%23"; break; + case '%': uri += "%25"; break; + default: uri += c; + } + } + return uri; +} + +// Orca: a filament is matched from the AMS by (filament_id + printer compatibility). +// For any one printer, at most one instantiated filament preset with a given +// filament_id may be compatible - otherwise the AMS match is ambiguous and the +// runtime silently picks whichever loads first. This validator-only check flags +// any printer that has two or more compatible filament presets sharing a filament_id. +bool PresetBundle::check_duplicate_filament_subtypes() const +{ + // Pre-collect system filament presets (each carries its effective filament_id, + // inherited from its @base at load time), grouped by vendor so we only test a + // printer against its own vendor's filaments. A vendor's compatible_printers + // only names that vendor's printers, so same-vendor scoping is correctness + // preserving and avoids an O(all printers x all filaments) sweep. + std::map> filaments_by_vendor; + for (const auto &preset : filaments) { + if (!preset.is_system || preset.filament_id.empty() || preset.vendor == nullptr) + continue; + filaments_by_vendor[preset.vendor->name].push_back(&preset); + } + + bool found_duplicates = false; + for (const auto &printer : printers) { + if (!printer.is_system || printer.vendor == nullptr) + continue; + auto vendor_it = filaments_by_vendor.find(printer.vendor->name); + if (vendor_it == filaments_by_vendor.end()) + continue; + + const PresetWithVendorProfile active_printer = printers.get_preset_with_vendor_profile(printer); + // std::map keeps the reported errors in a deterministic (sorted) order. + std::map> by_filament_id; + for (const Preset *fil : vendor_it->second) + if (is_compatible_with_printer(filaments.get_preset_with_vendor_profile(*fil), active_printer)) + by_filament_id[fil->filament_id].push_back(fil); + + for (const auto &entry : by_filament_id) { + if (entry.second.size() < 2) + continue; + found_duplicates = true; + // List each conflicting preset with a clickable file:// URI on its own + // line, so the profile author can jump straight to the files to fix. + std::string presets; + for (const Preset *p : entry.second) + presets += "\n - " + p->name + "\n " + preset_file_uri(p->file); + BOOST_LOG_TRIVIAL(error) + << "Ambiguous AMS filament match: " << entry.second.size() + << " filament presets share filament_id \"" << entry.first + << "\" and are all compatible with printer \"" << printer.name + << "\". When matching an AMS spool the slicer cannot tell them apart and" + " silently picks whichever loads first." << presets; + } + } + + // Print the troubleshooting guidance once, not per error, to keep the log readable. + if (found_duplicates) + BOOST_LOG_TRIVIAL(error) << "\n========================================\n" + << "How to fix \"Ambiguous AMS filament match\" errors: make sure only ONE filament" + " preset with a given filament_id is compatible with each printer. Either" + "\n (a) remove the overlapping printer from a preset's \"compatible_printers\"" + " list (e.g. a '@printer' preset over-claiming a nozzle that already has its own" + " '@printer 0.x nozzle' preset), or" + "\n (b) if these are genuinely different materials, give each its own" + " \"filament_id\" - a common cause is a wrong \"inherits\" pointing at another" + " material's @base preset."; + + return found_duplicates; +} + // Orca: BundleMetadata method implementations bool BundleMetadata::load_from_json(const std::string& path) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 220de95b781..348ef9442e4 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -480,10 +480,14 @@ public: return { Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL }; } - // Orca: for validation only - bool has_errors() const; + // Orca: for validation only. The duplicate filament subtype check is opt-in for now + bool has_errors(bool check_duplicate_filament_subtypes = false) const; private: + // Orca: validation only - flag any printer with two or more compatible + // filament presets sharing one filament_id (ambiguous AMS subtype match). + bool check_duplicate_filament_subtypes() const; + //std::pair load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule); //BBS: add json related logic std::pair load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule);