Load profile includes so synced Bambu Lab presets get their G-code back (#15869)

# Description

This brings back the Bambu Lab profile sync with BambuStudio, reverted
from main, along with its bed-model offset fix. The synced printers and
filaments share their start and end G-code and dual-nozzle settings
through template files that Orca did not read, so those printers had no
machine G-code. Orca now loads these templates the same way BambuStudio
does, so every synced preset comes in complete. `profile_include_dump`,
a new developer tool, compares the result with BambuStudio's.

Other vendors' profiles are unaffected. Bambu Lab profiles move to
version 02.08.00.10, so installs that took the earlier sync update too.

<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
  > * What issue does this PR address or fix?
  > * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

Unit tests cover how template settings combine with inherited and preset
settings, loaded from JSON and from the preset cache. The profile
validator passes on every shipped vendor, and every Bambu Lab preset
gets the same template values in Orca as in BambuStudio.

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This commit is contained in:
SoftFever
2026-09-25 13:17:43 +08:00
committed by GitHub
2618 changed files with 199595 additions and 264906 deletions
+34 -9
View File
@@ -1,11 +1,15 @@
#include <catch2/catch_all.hpp>
#include <algorithm>
#include <cstdlib>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "nlohmann/json.hpp"
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/GCode.hpp"
#include "libslic3r/Model.hpp"
@@ -726,20 +730,41 @@ static std::string slice_two_object_bbl(DynamicPrintConfig &config)
}
// The real change_filament_gcode of a shipped "<printer> 0.4 nozzle" machine profile.
// The profile does not state it inline any more: it names the template carrying it in
// `include`, and the loader layers that template under the preset. Follow the same list
// - so a profile that stops naming one fails here instead of silently slicing G-code it
// no longer ships.
static std::string shipped_change_filament_gcode(const std::string &printer)
{
const std::string path = std::string(PROFILES_DIR) + "/BBL/machine/Bambu Lab " + printer + " 0.4 nozzle.json";
const std::string machine_dir = std::string(PROFILES_DIR) + "/BBL/machine/";
const std::string machine_path = machine_dir + "Bambu Lab " + printer + " 0.4 nozzle.json";
const std::string template_name = "Bambu Lab " + printer + " 0.4 nozzle template change_filament_gcode";
// PROFILES_DIR is an absolute path baked in at build time; a sparse test checkout
// without resources/ leaves it missing. Skip rather than dereference a config that
// never loaded - this is the only fff_print test that reads a shipped profile.
if (!boost::filesystem::exists(path))
SKIP("shipped profile not present in this checkout: " << path);
DynamicPrintConfig config;
std::map<std::string, std::string> key_values;
std::string reason;
config.load_from_json(path, ForwardCompatibilitySubstitutionRule::Enable, key_values, reason);
// Fail loudly on a malformed/renamed profile instead of null-dereferencing in opt_string.
INFO("profile: " << path << (reason.empty() ? "" : (" load reason: " + reason)));
if (!boost::filesystem::exists(machine_path))
SKIP("shipped profile not present in this checkout: " << machine_path);
auto load = [](const std::string &file, std::map<std::string, std::string> &key_values) {
DynamicPrintConfig config;
ConfigSubstitutionContext substitutions{ForwardCompatibilitySubstitutionRule::Enable};
std::string reason;
// false: `inherits` and `include` stay out of the config and land in key_values,
// for the loader to resolve - neither is a slicing setting.
config.load_from_json(file, substitutions, false, key_values, reason);
// Fail loudly on a malformed/renamed profile instead of null-dereferencing in opt_string.
INFO("profile: " << file << (reason.empty() ? "" : (" load reason: " + reason)));
return config;
};
std::map<std::string, std::string> machine_values;
load(machine_path, machine_values);
REQUIRE(machine_values.count("include") == 1);
const nlohmann::json includes = nlohmann::json::parse(machine_values["include"]);
REQUIRE(std::find(includes.begin(), includes.end(), nlohmann::json(template_name)) != includes.end());
std::map<std::string, std::string> template_values;
const DynamicPrintConfig config = load(machine_dir + template_name + ".json", template_values);
REQUIRE(config.has("change_filament_gcode"));
return config.opt_string("change_filament_gcode");
}