collapse_printer_variants_to_extruders for bbl profiles

This commit is contained in:
SoftFever
2026-05-12 21:52:51 +08:00
parent af2d2d4803
commit a936081f52
2 changed files with 222 additions and 0 deletions

View File

@@ -225,9 +225,101 @@ static const std::unordered_map<std::string, std::string> pre_family_model_map {
}};
// Orca: BBL printer profiles encode a per-(extruder × nozzle_volume_type) variant matrix,
// producing arrays of size n_extruders * n_variants_per_extruder for keys in
// printer_options_with_variant_1/_2. OrcaSlicer keeps one machine setting slot per
// physical extruder, so collapse these arrays in memory and intentionally keep the
// Standard flow variant for each physical extruder. Profile JSON on disk is untouched,
// so vendor profiles sync from Bambu Studio without edits.
//
// extruder_variant_list (one entry per physical extruder, each a comma-separated list of
// supported variants for that extruder) is the source of truth — a child profile may
// override printer_extruder_variant with the full matrix while inheriting an
// already-flattened printer_extruder_id from a parent this function previously processed.
static void collapse_printer_variants_to_extruders(DynamicPrintConfig& config)
{
auto* variant_opt = config.option<ConfigOptionStrings>("printer_extruder_variant");
auto* nd_opt = config.option<ConfigOptionFloats>("nozzle_diameter");
if (!variant_opt || !nd_opt)
return;
const size_t n_extruders = nd_opt->values.size();
const size_t orig_variants = variant_opt->values.size();
if (n_extruders == 0 || orig_variants <= n_extruders)
return;
auto* variant_list_opt = config.option<ConfigOptionStrings>("extruder_variant_list");
if (!variant_list_opt || variant_list_opt->values.size() != n_extruders)
return;
// Use cumulative offsets, not name search: symmetric printers (H2D) may have
// identical variant strings on multiple extruders and a name search would collapse
// them all to the first match.
const std::string standard_flow = get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard);
std::vector<int> keep;
keep.reserve(n_extruders);
int cursor = 0;
for (size_t e = 0; e < n_extruders; ++e) {
std::vector<std::string> variants;
boost::split(variants, variant_list_opt->values[e], boost::is_any_of(","), boost::token_compress_on);
int variant_count = 0;
int standard_offset = -1;
for (auto& v : variants) {
boost::trim(v);
if (v.empty())
continue;
if (standard_offset < 0 && (v == standard_flow || boost::ends_with(v, " " + standard_flow)))
standard_offset = variant_count;
++variant_count;
}
if (variant_count == 0)
return;
keep.push_back(cursor + (standard_offset >= 0 ? standard_offset : 0));
cursor += variant_count;
}
if (size_t(cursor) != orig_variants)
return;
auto pick_with_stride = [&](const std::string& key, int stride) {
ConfigOption* opt = config.option(key);
if (!opt || !opt->is_vector())
return;
auto* vec = static_cast<ConfigOptionVectorBase*>(opt);
// Skip arrays not sized to the full variant grid; replace_nil_and_resize (called
// later from extend_default_config_length) handles those.
if (vec->size() != orig_variants * size_t(stride))
return;
// In-place compaction is safe because keep[] is monotonically non-decreasing
// (cursor only advances), so read_idx >= write_idx for every assignment.
for (size_t e = 0; e < keep.size(); ++e) {
for (int s = 0; s < stride; ++s) {
vec->set_at(vec, e * stride + s, keep[e] * stride + s);
}
}
vec->resize(keep.size() * stride);
};
for (const auto& key : printer_options_with_variant_1)
pick_with_stride(key, 1);
for (const auto& key : printer_options_with_variant_2)
pick_with_stride(key, 2);
// Rebuild rather than flatten: printer_extruder_id may have arrived parent-flattened,
// stretched by an earlier replace_nil_and_resize, or fresh from JSON.
if (auto* id_opt = config.option<ConfigOptionInts>("printer_extruder_id")) {
id_opt->values.clear();
id_opt->values.reserve(n_extruders);
for (size_t e = 1; e <= n_extruders; ++e)
id_opt->values.push_back(int(e));
}
}
// 中间版本兼容性处理如果是nil值先改成default值再进行扩展
void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults)
{
// Orca: flatten BBL variant arrays to one slot per physical extruder before sizing.
collapse_printer_variants_to_extruders(config);
constexpr int default_param_length = 1;
int filament_variant_length = default_param_length;
int process_variant_length = default_param_length;

View File

@@ -1,5 +1,6 @@
#include <catch2/catch_all.hpp>
#include "libslic3r/Preset.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PrintConfigConstants.hpp"
#include "libslic3r/LocalesUtils.hpp"
@@ -315,6 +316,135 @@ SCENARIO("update_non_diff_values_to_base_config preserves child vectors when chi
}
}
SCENARIO("extend_default_config_length collapses selectable nozzle flow variants to Standard",
"[Config][Variant]") {
GIVEN("A single-extruder BBL-style profile with Standard and High Flow variants") {
Slic3r::DynamicPrintConfig config;
config.set_key_value("nozzle_diameter", new Slic3r::ConfigOptionFloats({0.4}));
config.set_key_value("extruder_type", new Slic3r::ConfigOptionEnumsGeneric({Slic3r::etDirectDrive}));
config.set_key_value("extruder_variant_list", new Slic3r::ConfigOptionStrings({
"Direct Drive Standard,Direct Drive High Flow"
}));
config.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1}));
config.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({
"Direct Drive Standard", "Direct Drive High Flow"
}));
config.set_key_value("machine_max_speed_x", new Slic3r::ConfigOptionFloats({
500, 200, 200, 200
}));
WHEN("default config lengths are normalized") {
Slic3r::extend_default_config_length(config, false, {});
THEN("only the Standard nozzle flow variant remains") {
auto* variants = config.option<Slic3r::ConfigOptionStrings>("printer_extruder_variant");
REQUIRE(variants->values == std::vector<std::string>({
"Direct Drive Standard"
}));
}
THEN("Standard machine limits are kept") {
auto* speed_x = config.option<Slic3r::ConfigOptionFloats>("machine_max_speed_x");
REQUIRE(speed_x->values == std::vector<double>({500, 200}));
}
THEN("printer extruder ids are rebuilt per physical extruder") {
auto* ids = config.option<Slic3r::ConfigOptionInts>("printer_extruder_id");
REQUIRE(ids->values == std::vector<int>({1}));
}
}
}
}
SCENARIO("extend_default_config_length collapses mixed physical extruder variants",
"[Config][Variant]") {
GIVEN("An X2D-style profile with one Direct Drive and one Bowden extruder") {
Slic3r::DynamicPrintConfig config;
config.set_key_value("nozzle_diameter", new Slic3r::ConfigOptionFloats({0.4, 0.4}));
config.set_key_value("extruder_type", new Slic3r::ConfigOptionEnumsGeneric({
Slic3r::etDirectDrive, Slic3r::etBowden
}));
config.set_key_value("extruder_variant_list", new Slic3r::ConfigOptionStrings({
"Direct Drive Standard,Direct Drive High Flow",
"Bowden Standard,Bowden High Flow"
}));
config.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1, 2, 2}));
config.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({
"Direct Drive Standard", "Direct Drive High Flow",
"Bowden Standard", "Bowden High Flow"
}));
config.set_key_value("retraction_length", new Slic3r::ConfigOptionFloats({
0.8, 0.8, 2.0, 2.0
}));
config.set_key_value("machine_max_speed_e", new Slic3r::ConfigOptionFloats({
30, 30, 30, 30, 120, 120, 120, 120
}));
WHEN("default config lengths are normalized") {
Slic3r::extend_default_config_length(config, false, {});
THEN("one Standard variant is kept for each physical extruder") {
auto* variants = config.option<Slic3r::ConfigOptionStrings>("printer_extruder_variant");
REQUIRE(variants->values == std::vector<std::string>({
"Direct Drive Standard", "Bowden Standard"
}));
}
THEN("per-extruder retraction settings are aligned") {
auto* retraction = config.option<Slic3r::ConfigOptionFloats>("retraction_length");
REQUIRE(retraction->values == std::vector<double>({0.8, 2.0}));
}
THEN("stride-2 machine limits are aligned") {
auto* speed_e = config.option<Slic3r::ConfigOptionFloats>("machine_max_speed_e");
REQUIRE(speed_e->values == std::vector<double>({30, 30, 120, 120}));
}
}
}
}
SCENARIO("extend_default_config_length collapses symmetric physical extruder variants",
"[Config][Variant]") {
GIVEN("An H2D-style profile with matching Direct Drive extruder variant lists") {
Slic3r::DynamicPrintConfig config;
config.set_key_value("nozzle_diameter", new Slic3r::ConfigOptionFloats({0.4, 0.4}));
config.set_key_value("extruder_type", new Slic3r::ConfigOptionEnumsGeneric({
Slic3r::etDirectDrive, Slic3r::etDirectDrive
}));
config.set_key_value("extruder_variant_list", new Slic3r::ConfigOptionStrings({
"Direct Drive Standard,Direct Drive High Flow",
"Direct Drive Standard,Direct Drive High Flow"
}));
config.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1, 2, 2}));
config.set_key_value("printer_extruder_variant", new Slic3r::ConfigOptionStrings({
"Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard", "Direct Drive High Flow"
}));
config.set_key_value("machine_max_speed_e", new Slic3r::ConfigOptionFloats({
50, 50, 60, 60, 70, 70, 80, 80
}));
WHEN("default config lengths are normalized") {
Slic3r::extend_default_config_length(config, false, {});
THEN("the Standard variant is kept for each physical extruder, not the first matching name globally") {
auto* variants = config.option<Slic3r::ConfigOptionStrings>("printer_extruder_variant");
REQUIRE(variants->values == std::vector<std::string>({
"Direct Drive Standard", "Direct Drive Standard"
}));
}
THEN("stride-2 machine limits are kept from each extruder's Standard slot") {
auto* speed_e = config.option<Slic3r::ConfigOptionFloats>("machine_max_speed_e");
REQUIRE(speed_e->values == std::vector<double>({50, 50, 70, 70}));
}
}
}
}
// SCENARIO("DynamicPrintConfig JSON serialization", "[Config]") {
// WHEN("DynamicPrintConfig is serialized and deserialized") {
// auto now = std::chrono::high_resolution_clock::now();