Map BambuStudio Keys and Values to Their OrcaSlicer Equivalents

BambuStudio projects lost or misread several settings on import:

- Rename role_base_wipe_speed, no_slow_down_for_cooling_on_outwalls,
  process_notes, prime_tower_max_speed, enable_support_ironing and
  sparse_infill_lattice_angle_1/2 to their OrcaSlicer names.
- Map the values 2dlattice, 2dhoneycomb and tree_organic.
- Map BambuStudio's ensure_vertical_shell_thickness levels, and read
  top_one_wall_type = not apply as off.
- Keep prime_tower_rib_wall = 0 as a rectangle wall instead of dropping
  the key, which fell back to a rib wall.
- Read a negative raft_first_layer_expansion (BambuStudio's auto) as
  2 mm, which also stops it failing the range check.
- Rename whole entries of different_settings_to_system, so a rename no
  longer rewrites longer keys that contain the old name.
This commit is contained in:
Hanif Koh
2026-10-02 17:59:51 +08:00
parent 3384daa6bc
commit 0bc8912667
2 changed files with 132 additions and 17 deletions
+55 -17
View File
@@ -10,6 +10,8 @@
#include "GCode/Thumbnails.hpp"
#include <set>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
@@ -9185,16 +9187,18 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "tree_support_wall_count" && value == "-1") {
value = "0";
} else if (opt_key == "different_settings_to_system") {
std::string copy_value = value;
copy_value.erase(std::remove(copy_value.begin(), copy_value.end(), '\"'), copy_value.end()); // remove '"' in string
std::set<std::string> split_keys = SplitStringAndRemoveDuplicateElement(copy_value, ";");
for (std::string split_key : split_keys) {
std::string copy_key = split_key, copy_value = "";
handle_legacy(copy_key, copy_value);
if (copy_key != split_key) {
ReplaceString(value, split_key, copy_key);
}
// Rename whole entries: a substring rename would also hit longer keys that contain the old name.
std::vector<std::string> entries;
boost::split(entries, value, boost::is_any_of(";"));
for (std::string &entry : entries) {
std::string key = entry, unused_value;
key.erase(std::remove(key.begin(), key.end(), '\"'), key.end());
std::string new_key = key;
handle_legacy(new_key, unused_value);
if (new_key != key)
ReplaceString(entry, key, new_key);
}
value = boost::algorithm::join(entries, ";");
} else if (opt_key == "overhang_fan_threshold" && value == "5%") {
value = "10%";
} else if( opt_key == "wall_infill_order" ) {
@@ -9236,6 +9240,13 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
else if (value == "0"){
value = "ensure_moderate";
}
// BambuStudio's levels
else if (value == "enabled")
value = "ensure_all";
else if (value == "partial")
value = "ensure_moderate";
else if (value == "disabled")
value = "none";
} else if (opt_key == "rotate_solid_infill_direction") {
opt_key = "solid_infill_rotate_template";
if (value == "1") {
@@ -9251,9 +9262,11 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
opt_key = "chamber_temperature";
} else if (opt_key == "thumbnail_size") {
opt_key = "thumbnails";
} else if (opt_key == "top_one_wall_type" && value != "none") {
} else if (opt_key == "top_one_wall_type") {
// BambuStudio's "topmost" (one wall on the topmost layer only) widens to every top surface.
opt_key = "only_one_wall_top";
value = "1";
if (!value.empty())
value = (value == "not apply" || value == "none") ? "0" : "1";
} else if (opt_key == "initial_layer_flow_ratio") {
opt_key = "bottom_solid_infill_flow_ratio";
} else if (opt_key == "ironing_direction") {
@@ -9271,6 +9284,20 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
opt_key == "ironing_pattern" ||
opt_key == "support_ironing_pattern") && value == "zig-zag") {
value = "rectilinear";
} else if (opt_key == "sparse_infill_pattern" && value == "2dlattice") {
// BambuStudio's name, and OrcaSlicer's before the lateral patterns were renamed
value = "lateral-lattice";
} else if (opt_key == "sparse_infill_pattern" && value == "2dhoneycomb") {
value = "lateral-honeycomb";
} else if (opt_key == "sparse_infill_lattice_angle_1" || opt_key == "lattice_angle_1") {
opt_key = "lateral_lattice_angle_1";
} else if (opt_key == "sparse_infill_lattice_angle_2" || opt_key == "lattice_angle_2") {
opt_key = "lateral_lattice_angle_2";
} else if (opt_key == "support_style" && value == "tree_organic") {
value = "organic";
} else if (opt_key == "raft_first_layer_expansion" && !value.empty() && value.front() == '-') {
// BambuStudio's auto: 2 mm for normal supports and rafts. Tree branches use tree_support_auto_brim here.
value = "2";
} else if (opt_key == "filament_map_mode") {
if (value == "Auto") value = "Auto For Flush";
}
@@ -9299,12 +9326,11 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
}
// Orca: Rename wipe tower ribs related options
else if (opt_key == "prime_tower_rib_wall") {
if (value == "1") {
opt_key = "wipe_tower_wall_type";
value = "rib";
} else {
opt_key = "";
}
opt_key = "wipe_tower_wall_type";
if (value == "1" || value == "true")
value = "rib";
else if (!value.empty())
value = "rectangle";
} else if (opt_key == "prime_tower_extra_rib_length") {
opt_key = "wipe_tower_extra_rib_length";
} else if (opt_key == "prime_tower_rib_width") {
@@ -9316,6 +9342,18 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "machine_switch_extruder_time") {
opt_key = "machine_tool_change_time";
}
// BambuStudio names of OrcaSlicer options
else if (opt_key == "role_base_wipe_speed") {
opt_key = "role_based_wipe_speed";
} else if (opt_key == "no_slow_down_for_cooling_on_outwalls") {
opt_key = "dont_slow_down_outer_wall";
} else if (opt_key == "process_notes") {
opt_key = "notes";
} else if (opt_key == "prime_tower_max_speed") {
opt_key = "wipe_tower_max_purge_speed";
} else if (opt_key == "enable_support_ironing") {
opt_key = "support_ironing";
}
else if (opt_key == "wall_direction" && value == "auto") {
value = "ccw";
}
+77
View File
@@ -508,6 +508,83 @@ TEST_CASE("load_from_json hands a preset's include list to the caller instead of
CHECK(config.opt_string("machine_end_gcode") == "M84");
}
TEST_CASE("handle_legacy maps BambuStudio keys and values to their OrcaSlicer equivalents", "[Config]") {
struct Row { std::string key, value, expected_key, expected_value; };
const Row row = GENERATE(values<Row>({
{"role_base_wipe_speed", "0", "role_based_wipe_speed", "0"},
{"no_slow_down_for_cooling_on_outwalls", "1", "dont_slow_down_outer_wall", "1"},
{"process_notes", "note", "notes", "note"},
{"prime_tower_max_speed", "70", "wipe_tower_max_purge_speed", "70"},
{"enable_support_ironing", "1", "support_ironing", "1"},
{"sparse_infill_lattice_angle_1", "-30", "lateral_lattice_angle_1", "-30"},
{"sparse_infill_lattice_angle_2", "20", "lateral_lattice_angle_2", "20"},
{"lattice_angle_1", "-30", "lateral_lattice_angle_1", "-30"},
{"lattice_angle_2", "20", "lateral_lattice_angle_2", "20"},
{"sparse_infill_pattern", "2dlattice", "sparse_infill_pattern", "lateral-lattice"},
{"sparse_infill_pattern", "2dhoneycomb", "sparse_infill_pattern", "lateral-honeycomb"},
{"support_style", "tree_organic", "support_style", "organic"},
{"raft_first_layer_expansion", "-1", "raft_first_layer_expansion", "2"},
{"raft_first_layer_expansion", "3.5", "raft_first_layer_expansion", "3.5"},
{"ensure_vertical_shell_thickness", "enabled", "ensure_vertical_shell_thickness", "ensure_all"},
{"ensure_vertical_shell_thickness", "partial", "ensure_vertical_shell_thickness", "ensure_moderate"},
{"ensure_vertical_shell_thickness", "disabled", "ensure_vertical_shell_thickness", "none"},
{"top_one_wall_type", "not apply", "only_one_wall_top", "0"},
{"top_one_wall_type", "all top", "only_one_wall_top", "1"},
{"top_one_wall_type", "topmost", "only_one_wall_top", "1"},
{"prime_tower_rib_wall", "1", "wipe_tower_wall_type", "rib"},
{"prime_tower_rib_wall", "0", "wipe_tower_wall_type", "rectangle"},
{"prime_tower_rib_wall", "true", "wipe_tower_wall_type", "rib"},
// A rename touches whole entries, not keys that contain the old name.
{"different_settings_to_system", "lattice_angle_1;sparse_infill_lattice_angle_1;wall_loops", "different_settings_to_system",
"lateral_lattice_angle_1;lateral_lattice_angle_1;wall_loops"},
// different_settings_to_system and JSON arrays pass the key alone
{"prime_tower_rib_wall", "", "wipe_tower_wall_type", ""},
{"top_one_wall_type", "", "only_one_wall_top", ""},
{"enable_height_slowdown", "1", "", "1"},
{"counter_coef_2", "0.025", "", "0.025"},
}));
t_config_option_key key = row.key;
std::string value = row.value;
PrintConfigDef::handle_legacy(key, value);
INFO(row.key << " = " << row.value);
CHECK(key == row.expected_key);
CHECK(value == row.expected_value);
}
TEST_CASE("A BambuStudio project config loads its renamed settings without substitutions", "[Config]") {
ScopedTemporaryFile tmp(".json");
{
boost::nowide::ofstream ofs(tmp.string());
ofs << R"({"role_base_wipe_speed":"0","no_slow_down_for_cooling_on_outwalls":["1","0"],)"
R"("process_notes":"note","prime_tower_max_speed":"70","enable_support_ironing":"1",)"
R"("sparse_infill_pattern":"2dlattice","sparse_infill_lattice_angle_1":"-30",)"
R"("support_style":"tree_organic","ensure_vertical_shell_thickness":"partial",)"
R"("top_one_wall_type":"not apply","prime_tower_rib_wall":"0","raft_first_layer_expansion":"-1",)"
R"("enable_height_slowdown":["1"]})";
}
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
ConfigSubstitutionContext substitutions(ForwardCompatibilitySubstitutionRule::Disable);
std::map<std::string, std::string> key_values;
std::string reason;
REQUIRE(config.load_from_json(tmp.string(), substitutions, true, key_values, reason) == 0);
CHECK(substitutions.substitutions.empty());
CHECK(substitutions.unrecogized_keys == std::vector<std::string>{"enable_height_slowdown"});
CHECK_FALSE(config.opt_bool("role_based_wipe_speed"));
CHECK(config.option<ConfigOptionBools>("dont_slow_down_outer_wall")->values == std::vector<unsigned char>{1, 0});
CHECK(config.opt_string("notes") == "note");
CHECK_THAT(config.opt_float("wipe_tower_max_purge_speed"), Catch::Matchers::WithinAbs(70., 1e-9));
CHECK(config.opt_bool("support_ironing"));
CHECK(config.opt_enum<InfillPattern>("sparse_infill_pattern") == ipLateralLattice);
CHECK_THAT(config.opt_float("lateral_lattice_angle_1"), Catch::Matchers::WithinAbs(-30., 1e-9));
CHECK(config.opt_enum<SupportMaterialStyle>("support_style") == smsTreeOrganic);
CHECK(config.opt_enum<EnsureVerticalShellThickness>("ensure_vertical_shell_thickness") == evstModerate);
CHECK_FALSE(config.opt_bool("only_one_wall_top"));
CHECK(config.opt_enum<WipeTowerWallType>("wipe_tower_wall_type") == wtwRectangle);
CHECK_THAT(config.opt_float("raft_first_layer_expansion"), Catch::Matchers::WithinAbs(2., 1e-9));
CHECK(config.validate().count("raft_first_layer_expansion") == 0);
}
TEST_CASE("save_to_json writes the same document to a stream as to a file", "[Config]") {
DynamicPrintConfig config;
config.set_key_value("layer_height", new ConfigOptionFloat(0.2));