fix more issues

This commit is contained in:
SoftFever
2026-09-04 00:29:52 +08:00
parent 7c9b38ba04
commit df101ead91
16 changed files with 218 additions and 29 deletions

View File

@@ -9,5 +9,5 @@
"bed_texture": "creality_k1se_buildplate_texture.svg",
"hotend_model": "",
"default_bed_type": "Textured PEI Plate",
"default_materials": "Generic ABS @Creality K1-all;Generic ASA @Creality K1-all;Generic PA-CF @Creality K1-all;Generic PC @Creality K1-all;Generic PETG @Creality K1-all;Generic PLA @Creality K1-all;Generic PLA High Speed @Creality K1-all;Generic PLA Matte @Creality K1-all;Generic PLA Silk @Creality K1-all;Generic PLA-CF @Creality K1-all;Generic TPU @Creality K1-all"
"default_materials": "Generic ABS @Creality K1-all;Generic ABS @K1 SE-all;Generic ASA @Creality K1-all;Generic ASA @K1 SE-all;Generic PA-CF @Creality K1-all;Generic PC @Creality K1-all;Generic PETG @Creality K1-all;Generic PETG @K1 SE-all;Generic PLA @Creality K1-all;Generic PLA @K1 SE-all;Generic PLA High Speed @Creality K1-all;Generic PLA Matte @Creality K1-all;Generic PLA Silk @Creality K1-all;Generic PLA-CF @Creality K1-all;Generic PLA-CF @K1 SE-all;Generic TPU @Creality K1-all;Generic TPU @K1 SE-all"
}

View File

@@ -9,5 +9,5 @@
"bed_texture": "creality_k1c_buildplate_texture.svg",
"hotend_model": "",
"default_bed_type": "Textured PEI Plate",
"default_materials": "Generic ABS @Creality K1-all;Generic ASA @Creality K1-all;Generic PA-CF @Creality K1-all;Generic PC @Creality K1-all;Generic PETG @Creality K1-all;Generic PLA @Creality K1-all;Generic PLA High Speed @Creality K1-all;Generic PLA Matte @Creality K1-all;Generic PLA Silk @Creality K1-all;Generic PLA-CF @Creality K1-all;Generic TPU @Creality K1-all"
"default_materials": "Generic ABS @Creality K1-all;Generic ABS @K1C-all;Generic ASA @Creality K1-all;Generic ASA @K1C-all;Generic PA-CF @Creality K1-all;Generic PA-CF @K1C-all;Generic PC @Creality K1-all;Generic PC @K1C-all;Generic PETG @Creality K1-all;Generic PETG @K1C-all;Generic PLA @Creality K1-all;Generic PLA @K1C-all;Generic PLA High Speed @Creality K1-all;Generic PLA Matte @Creality K1-all;Generic PLA Silk @Creality K1-all;Generic PLA-CF @Creality K1-all;Generic PLA-CF @K1C-all;Generic TPU @Creality K1-all;Generic TPU @K1C-all"
}

View File

@@ -9,5 +9,5 @@
"bed_texture": "creality_k2plus_buildplate_texture.svg",
"hotend_model": "",
"default_bed_type": "Textured PEI Plate",
"default_materials": "Generic ABS @Creality K2-all;Generic ASA @Creality K2-all;Generic PETG @Creality K2-all;Generic PLA @Creality K2-all;Generic PLA High Speed @Creality K2-all;Generic PLA Matte @Creality K2-all;Generic PLA Silk @Creality K2-all"
"default_materials": "Generic ABS @Creality K2-all;Generic ABS @K2 Plus-all;Generic ASA @Creality K2-all;Generic ASA @K2 Plus-all;Generic PETG @Creality K2-all;Generic PETG @K2 Plus-all;Generic PLA @Creality K2-all;Generic PLA @K2 Plus-all;Generic PLA High Speed @Creality K2-all;Generic PLA Matte @Creality K2-all;Generic PLA Silk @Creality K2-all"
}

View File

@@ -9,5 +9,5 @@
"bed_texture": "creality_k2pro_buildplate_texture.svg",
"default_bed_type": "Textured PEI Plate",
"hotend_model": "",
"default_materials": "Generic ABS @Creality K2-all;Generic ASA @Creality K2-all;Generic PETG @Creality K2-all;Generic PLA @Creality K2-all;Generic PLA High Speed @Creality K2-all;Generic PLA Matte @Creality K2-all;Generic PLA Silk @Creality K2-all"
"default_materials": "Generic ABS @Creality K2-all;Generic ABS @K2 Pro-all;Generic ASA @Creality K2-all;Generic ASA @K2 Pro-all;Generic PETG @Creality K2-all;Generic PETG @K2 Pro-all;Generic PLA @Creality K2-all;Generic PLA @K2 Pro-all;Generic PLA High Speed @Creality K2-all;Generic PLA Matte @Creality K2-all;Generic PLA Silk @Creality K2-all"
}

View File

@@ -9,5 +9,5 @@
"default_bed_type": "Textured PEI Plate",
"bed_texture": "creality_k2_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Generic ABS @Creality K2-all;Generic ASA @Creality K2-all;Generic PETG @Creality K2-all;Generic PLA @Creality K2-all;Generic PLA High Speed @Creality K2-all;Generic PLA Matte @Creality K2-all;Generic PLA Silk @Creality K2-all"
"default_materials": "Generic ABS @Creality K2-all;Generic ABS @K2-all;Generic ASA @Creality K2-all;Generic ASA @K2-all;Generic PETG @Creality K2-all;Generic PETG @K2-all;Generic PLA @Creality K2-all;Generic PLA @K2-all;Generic PLA High Speed @Creality K2-all;Generic PLA Matte @Creality K2-all;Generic PLA Silk @Creality K2-all"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "flashforge_g3u_buildplate_model.stl",
"bed_texture": "flashforge_g3u_buildplate_texture.svg",
"hotend_model": "",
"default_materials": "Generic PVA @Flashforge;Generic HIPS @Flashforge;Generic PETG-CF @Flashforge G3U;Generic PETG @Flashforge G3U;Generic PLA-CF @Flashforge G3U;Generic PLA @Flashforge G3U;Generic ASA @Flashforge G3U;Generic ABS @Flashforge G3U;FusRock PET-CF;FusRock PAHT-CF;FusRock NexPA-CF25;FusRock S-Multi;FusRock S-PAHT;Polymaker CoPA;Polymaker S1"
"default_materials": "FusRock NexPA-CF25;FusRock PAHT-CF;FusRock PAHT-CF @G3U 0.6 Nozzle;FusRock PET-CF;FusRock PET-CF @G3U 0.6 Nozzle;FusRock S-Multi;FusRock S-PAHT;Generic ABS @Flashforge G3U;Generic ABS @Flashforge G3U 0.6 Nozzle;Generic ASA @Flashforge G3U;Generic ASA @Flashforge G3U 0.6 Nozzle;Generic HIPS @Flashforge;Generic HIPS @Flashforge G3U 0.6 Nozzle;Generic PETG @Flashforge G3U;Generic PETG @Flashforge G3U 0.6 Nozzle;Generic PETG @Flashforge G3U 0.8 Nozzle;Generic PETG-CF @Flashforge G3U;Generic PETG-CF @Flashforge G3U 0.6 Nozzle;Generic PETG-CF @Flashforge G3U 0.8 Nozzle;Generic PLA @Flashforge G3U;Generic PLA-CF @Flashforge G3U;Generic PLA-CF @Flashforge G3U 0.6 Nozzle;Generic PLA-CF @Flashforge G3U 0.8 Nozzle;Generic PVA @Flashforge;Polymaker CoPA;Polymaker S1"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "Wanhao_D12-230_buildplate_model.stl",
"bed_texture": "D12_texture.png",
"hotend_model": "Wanhao D12-300_hotend.stl",
"default_materials": "YUMI PLA Bowden;YUMI PETG;"
"default_materials": "YUMI PLA Direct Drive;YUMI PETG;"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "Wanhao_D12-230_buildplate_model.stl",
"bed_texture": "D12_texture.png",
"hotend_model": "Wanhao D12-300_hotend.stl",
"default_materials": "YUMI PLA Bowden;YUMI PETG;"
"default_materials": "YUMI PLA Direct Drive;YUMI PETG;"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "Wanhao_D12-300_buildplate_model.stl",
"bed_texture": "D12_texture.png",
"hotend_model": "Wanhao D12-300_hotend.stl",
"default_materials": "YUMI PLA Bowden;YUMI PETG;"
"default_materials": "YUMI PLA Direct Drive;YUMI PETG;"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "Wanhao_D12-300_buildplate_model.stl",
"bed_texture": "D12_texture.png",
"hotend_model": "Wanhao D12-300_hotend.stl",
"default_materials": "YUMI PLA Bowden;YUMI PETG;"
"default_materials": "YUMI PLA Direct Drive;YUMI PETG;"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "Wanhao_D12-500_buildplate_model.stl",
"bed_texture": "D12_texture.png",
"hotend_model": "Wanhao D12-300_hotend.stl",
"default_materials": "YUMI PLA Bowden;YUMI PETG;"
"default_materials": "YUMI PLA Direct Drive;YUMI PETG;"
}

View File

@@ -8,5 +8,5 @@
"bed_model": "Wanhao_D12-500_buildplate_model.stl",
"bed_texture": "D12_texture.png",
"hotend_model": "Wanhao D12-300_hotend.stl",
"default_materials": "YUMI PLA Bowden;YUMI PETG;"
"default_materials": "YUMI PLA Direct Drive;YUMI PETG;"
}

View File

@@ -392,7 +392,7 @@ int main(int argc, char* argv[])
("generate_presets,g", po::value<bool>()->default_value(false), "Generate user presets for mock test")
("slice,s", po::bool_switch()->default_value(false), "Slice a two-colour cube through every printer to expand all custom g-code (catches placeholder/flow errors that static checks miss). Off unless this flag is present.")
("outdir,o", po::value<std::string>()->default_value(""), "With -s, also save each printer's g-code to this folder (as <vendor>__<printer>.gcode) for manual inspection. Optional.")
("check_filament_subtypes,f", po::bool_switch()->default_value(false), "Also flag printers with duplicate (ambiguous) filament subtypes. Off unless this flag is present.")
("check_filament_subtypes,f", po::bool_switch()->default_value(true), "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

View File

@@ -8,7 +8,9 @@
#include <nlohmann/json.hpp>
#include <algorithm>
#include <cctype>
#include <map>
#include <set>
namespace Slic3r {
@@ -16,6 +18,12 @@ namespace {
constexpr const char* CrealityPrintAgent_VERSION = "0.1.0";
std::string to_lower(std::string s)
{
for (auto& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
return s;
}
bool has_visible_base_preset(const PresetCollection& filaments, const std::string& filament_id)
{
for (const auto& p : filaments.get_presets()) {
@@ -27,19 +35,47 @@ bool has_visible_base_preset(const PresetCollection& filaments, const std::strin
return false;
}
// Lower-case words of a preset name with the "@scope" suffix dropped:
// "Generic PLA Matte @Creality K2-all" -> {"generic", "pla", "matte"}.
std::vector<std::string> name_words(const std::string& name)
{
std::vector<std::string> words;
std::string word;
for (char c : name.substr(0, name.find('@'))) {
if (std::isalnum(static_cast<unsigned char>(c))) {
word += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
} else if (!word.empty()) {
words.push_back(word);
word.clear();
}
}
if (!word.empty())
words.push_back(word);
return words;
}
} // namespace
// Score visible compatible filament presets against the CFS spool metadata and
// return the best-matching filament_id. Scoring:
// +20 preset name contains brand_name as a substring
// (e.g. "Hyper PLA" in "Hyper PLA @Creality K2 0.4 nozzle")
// +10 preset name contains the vendor substring (e.g. "Creality")
// Tiebreak: prefer the SYSTEM (shipped) preset over user copies. Brand-
// specific system presets carry their own filament_id; user copies of
// generic presets inherit a generic filament_id from their parent, so
// preferring the user copy can collapse a brand-specific match back to
// "Generic PLA" via the inherited id. Plus: this code targets upstream
// OrcaSlicer where shipping the user's local tuning would be wrong.
// +10 the preset belongs to the spool's vendor - by its owning VendorProfile OR by
// its name. The profile test is what finds the vendor's own generics, which are
// named "Generic <material> @<scope>" and do not repeat the vendor; the name test
// still finds a third party filament shipped inside that vendor's bundle, which
// carries the bundle owner's profile but names its real brand.
// -5 per word of the preset name the spool never mentioned ("generic" excepted -
// it marks the unbranded base product rather than a qualifier), so the least
// specific preset that still explains the spool wins. Without it a spool
// reporting only "PLA" scores "Generic PLA High Speed" and "Generic PLA Matte"
// exactly as high as "Generic PLA"; those are three products with three
// filament_ids, so whichever sorted first won and the printer got the wrong
// one. Applied after the score gate, so it only reorders genuine matches.
// Tiebreak: prefer the SYSTEM (shipped) preset over user copies, then by name so the
// winner never depends on how std::sort leaves equal elements. User copies of generic
// presets inherit a generic filament_id from their parent, so preferring the user copy
// can collapse a brand-specific match back to "Generic PLA" via the inherited id.
// Requires the preset's declared filament_type to equal the spool's base type
// (PLA/PETG/ABS/...) so we never auto-pick a PETG preset for a PLA spool.
// Falls back to filaments.filament_id_by_type(base_type) when nothing scores.
@@ -48,15 +84,17 @@ std::string CrealityPrintAgent::match_filament_preset(const PresetCollection& fi
const std::string& brand_name,
const std::string& base_type)
{
auto to_lower = [](std::string s) {
for (auto& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
return s;
};
const std::string vendor_lower = to_lower(vendor);
const std::string brand_lower = to_lower(brand_name);
const std::string type_lower = to_lower(base_type);
// Everything the spool told us about itself, as words. A word in a preset's name
// that is not in here is a qualifier the spool never claimed.
std::set<std::string> spool_words{"generic"};
for (const std::string& src : {brand_lower, vendor_lower, type_lower})
for (auto& w : name_words(src))
spool_words.insert(std::move(w));
struct Match {
const Preset* preset;
int score;
@@ -83,11 +121,20 @@ std::string CrealityPrintAgent::match_filament_preset(const PresetCollection& fi
int score = 0;
if (!brand_lower.empty() && name_lower.find(brand_lower) != std::string::npos)
score += 20;
if (!vendor_lower.empty() && name_lower.find(vendor_lower) != std::string::npos)
// Profile OR name - neither alone covers both the vendor's own generics and the
// third party filaments shipped inside its bundle. See the header comment.
if (!vendor_lower.empty()
&& ((p.vendor != nullptr && to_lower(p.vendor->name) == vendor_lower)
|| name_lower.find(vendor_lower) != std::string::npos))
score += 10;
if (score > 0)
matches.push_back({&p, score, !p.is_system && !p.is_default});
if (score == 0) continue;
for (const auto& w : name_words(p.name))
if (spool_words.count(w) == 0)
score -= 5;
matches.push_back({&p, score, !p.is_system && !p.is_default});
}
if (matches.empty()) {
@@ -105,7 +152,7 @@ std::string CrealityPrintAgent::match_filament_preset(const PresetCollection& fi
[](const Match& a, const Match& b) {
if (a.score != b.score) return a.score > b.score;
if (a.is_user != b.is_user) return !a.is_user; // prefer system over user
return false;
return a.preset->name < b.preset->name; // keep the winner deterministic
});
BOOST_LOG_TRIVIAL(info)

View File

@@ -1,6 +1,7 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests_main.cpp
test_creality_cfs_match.cpp
test_dev_mapping.cpp
test_filament_bitmap_utils.cpp
test_network_versions.cpp

View File

@@ -0,0 +1,141 @@
#include <catch2/catch_test_macros.hpp>
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/CrealityPrintAgent.hpp"
using namespace Slic3r;
namespace {
// A standalone filament collection, built the same way PresetBundle builds its own, so the
// CFS matcher can be exercised without loading the shipped profiles or touching a printer.
struct FilamentTestCollection : public PresetCollection
{
FilamentTestCollection()
: PresetCollection(Preset::TYPE_FILAMENT, Preset::filament_options(),
static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()))
{}
};
struct FilamentSpec
{
const char *name;
const char *filament_id;
const char *filament_type;
bool is_library = false; // belongs to the Orca Filament Library, not the printer vendor
bool is_system = true;
};
// Load the specs into the collection, then stamp the fields the matcher reads. Done in a second
// pass because load_preset() keeps m_presets sorted, so a reference taken during the first pass
// can be left dangling by a later load.
void populate(PresetCollection &filaments, const std::vector<FilamentSpec> &specs,
const VendorProfile &vendor, const VendorProfile &library)
{
for (const FilamentSpec &spec : specs) {
DynamicPrintConfig config(filaments.default_preset().config);
config.option<ConfigOptionStrings>("filament_type", true)->values = {spec.filament_type};
filaments.load_preset(std::string(), spec.name, config, /*select=*/false);
}
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
const auto spec = std::find_if(specs.begin(), specs.end(),
[&](const FilamentSpec &s) { return it->name == s.name; });
if (spec == specs.end())
continue;
it->filament_id = spec->filament_id;
it->vendor = spec->is_library ? &library : &vendor;
it->is_system = spec->is_system;
it->is_default = false;
it->is_visible = true;
it->is_compatible = true;
}
}
// What a stock K2 with a 0.4 nozzle sees after the filament_id rework: the vendor's plain generics
// carry no "Creality" in their name (they are "Generic <material> @<scope>"), while the subtype
// variants that do keep a vendor scope are separate products with their own filament_ids.
const std::vector<FilamentSpec> k2_stock_nozzle{
{"AliZ PLA @System", "ALIZ-PLA", "PLA", /*is_library=*/true},
{"Generic PETG @K2-all", "PETG-GENERIC", "PETG"},
{"Generic PLA @K2-all", "PLA-GENERIC", "PLA"},
{"Generic PLA High Speed @Creality K2-all","PLA-HS", "PLA"},
{"Generic PLA Matte @Creality K2-all", "PLA-MATTE", "PLA"},
{"Generic PLA Silk @Creality K2-all", "PLA-SILK", "PLA"},
{"Hyper PLA @K2-all", "PLA-HYPER", "PLA"},
};
std::string match(const std::vector<FilamentSpec> &specs, const std::string &spool_vendor,
const std::string &spool_name, const std::string &base_type)
{
FilamentTestCollection filaments;
VendorProfile vendor("Creality");
VendorProfile library(PresetBundle::ORCA_FILAMENT_LIBRARY);
vendor.name = "Creality";
library.name = PresetBundle::ORCA_FILAMENT_LIBRARY;
populate(filaments, specs, vendor, library);
return CrealityPrintAgent::match_filament_preset(filaments, spool_vendor, spool_name, base_type);
}
} // namespace
// Orca: a CFS spool that names no recognised product must map to the vendor's plain generic. The
// subtype variants ("High Speed", "Matte", "Silk") score just as well on vendor alone, and since
// each is its own product with its own filament_id, letting one of them win sends the printer the
// id of a filament the user does not have loaded.
TEST_CASE("An unbranded CFS spool maps to the vendor's plain generic, not a subtype", "[CFS][Creality]")
{
CHECK(match(k2_stock_nozzle, "Creality", "", "PLA") == "PLA-GENERIC");
}
// Orca: the vendor bonus reads the preset's owning VendorProfile. "Generic PETG @K2-all" does not
// repeat "Creality" anywhere in its name, so a name based vendor test scored nothing for it and the
// spool fell through to the collection wide first-of-type - an unrelated third party PETG.
TEST_CASE("A CFS spool matches its vendor's presets even when the name omits the vendor", "[CFS][Creality]")
{
CHECK(match(k2_stock_nozzle, "Creality", "", "PETG") == "PETG-GENERIC");
}
TEST_CASE("A branded CFS spool still beats the generic", "[CFS][Creality]")
{
CHECK(match(k2_stock_nozzle, "Creality", "Hyper PLA", "PLA") == "PLA-HYPER");
}
// Orca: the specificity penalty must not stop a spool that genuinely asks for a subtype from
// getting it - only unclaimed qualifiers are penalised.
TEST_CASE("A CFS spool that names a subtype gets that subtype", "[CFS][Creality]")
{
CHECK(match(k2_stock_nozzle, "Creality", "Generic PLA Silk", "PLA") == "PLA-SILK");
CHECK(match(k2_stock_nozzle, "Creality", "Generic PLA Matte", "PLA") == "PLA-MATTE");
}
// Orca: a third party spool matches no preset by brand or vendor, so it falls back to the first
// visible preset of the same type rather than returning nothing. Which one that is depends on the
// collection's ordering, so only the type is pinned here - what matters is that a PLA spool never
// comes back empty and never comes back as another material.
TEST_CASE("A CFS spool from an unknown vendor falls back to a preset of the same type", "[CFS][Creality]")
{
const std::string matched = match(k2_stock_nozzle, "SomeOtherBrand", "", "PLA");
REQUIRE_FALSE(matched.empty());
const auto spec = std::find_if(k2_stock_nozzle.begin(), k2_stock_nozzle.end(),
[&](const FilamentSpec &s) { return matched == s.filament_id; });
REQUIRE(spec != k2_stock_nozzle.end());
CHECK(std::string(spec->filament_type) == "PLA");
}
// Orca: Creality's bundle also ships third party filaments ("eSUN PLA+ @K2 Plus-all"). Those carry
// Creality's VendorProfile but name their real brand, so the vendor bonus has to accept a name
// match as well as a profile match - otherwise an eSUN spool stops matching its own preset.
TEST_CASE("A third party spool matches its preset inside the printer vendor's bundle", "[CFS][Creality]")
{
const std::vector<FilamentSpec> with_third_party{
{"Generic PLA @K2-all", "PLA-GENERIC", "PLA"},
{"eSUN PLA+ @K2 Plus-all", "ESUN-PLA", "PLA"}, // shipped by Creality, branded eSUN
};
CHECK(match(with_third_party, "eSUN", "", "PLA") == "ESUN-PLA");
// ... and a Creality spool still prefers Creality's own generic over the eSUN preset, which
// carries Creality's profile too but adds a brand word the spool never claimed.
CHECK(match(with_third_party, "Creality", "", "PLA") == "PLA-GENERIC");
}