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>
This commit is contained in:
21
.github/workflows/check_profiles.yml
vendored
21
.github/workflows/check_profiles.yml
vendored
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"1.8"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab P1P 0.2 nozzle"
|
||||
"Bambu Lab A1 mini 0.2 nozzle"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -95,6 +95,7 @@ int main(int argc, char* argv[])
|
||||
#endif
|
||||
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional, all profiles present in the folder will be validated if not specified")
|
||||
("generate_presets,g", po::value<bool>()->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<int>()->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<std::string>();
|
||||
int log_level = vm["log_level"].as<int>();
|
||||
bool generate_user_preset = vm["generate_presets"].as<bool>();
|
||||
bool check_filament_subtypes = vm["check_filament_subtypes"].as<bool>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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<std::string, std::vector<const Preset *>> 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<std::string, std::vector<const Preset *>> 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)
|
||||
{
|
||||
|
||||
@@ -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<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
//BBS: add json related logic
|
||||
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
|
||||
Reference in New Issue
Block a user