Compare commits

..
Author SHA1 Message Date
ExPikaPaka 391c53a8da Take the print config by reference while simplifying extrusions
simplify_path, simplify_multi_path and simplify_loop each copied the whole
PrintConfig, 10520 bytes with around 200 heap owning members, once per extrusion
entity, from inside the parallel_for over all layers. LayerRegion.cpp already
binds a reference a few hundred lines above. Three more sites in Layer.cpp do the
same thing.

The tree support changes in the same area: draw_circles() dropped layers from an
owning vector without deleting them, so every re-slice leaked the layers above
the topmost overhang. std::stable_partition rather than std::remove_if, because
remove_if leaves moved-from duplicates of kept pointers in the tail and deleting
those would be a double free. The collision and avoidance accessors returned
ExPolygons by value out of a cache that hands them out by reference, several
times per node per layer. The preview cache is released when the support step
finishes instead of at the start of the next slice; nothing reads it in between.

set_shared_object() now releases the layers the object still owns before it
starts aliasing another object's. Both clear functions are no-ops once the alias
is set, so this cannot free the sharee's layers.

PrintObjectSlice.cpp: eps was a function local static inside a parallel_for, so
slice_closing_radius was latched once per process. This one changes output for
projects that override it per object, or on a re-slice after editing it. The old
behaviour was undefined: whichever object won the race set it for the session.
2026-10-01 09:15:15 +02:00
26 changed files with 81 additions and 504 deletions
+1 -11
View File
@@ -151,12 +151,6 @@ elseif(APPLE)
# the post-install -add_rpath below.
set(_python_ldflags "${_python_arch_flags} -Wl,-headerpad_max_install_names")
# The macOS 27 SDK declares pipe2() and dup3() as available from macOS 27, so
# configure finds them and CPython 3.12 calls them without a runtime check.
# Below a macOS 27 deployment target they are weak-linked and resolve to NULL
# on older systems, where os.pipe() then segfaults -- in `make install`
# (compileall, ensurepip) and in the shipped app alike. Every configure below
# keeps the pipe()/dup2() fallbacks (python/cpython#153711).
if(IS_CROSS_COMPILE)
set(_python_build_tgt --build=${_python_build_arch}-apple-darwin --host=${_python_host_arch}-apple-darwin)
set(_python_build_arch_flags "-arch ${_python_build_arch_flag} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
@@ -180,8 +174,7 @@ elseif(APPLE)
--enable-shared \
--without-static-libpython \
--disable-test-modules \
--build=${_python_build_arch}-apple-darwin \
ac_cv_func_pipe2=no ac_cv_func_dup3=no && \
--build=${_python_build_arch}-apple-darwin && \
make -j${NPROC} python && \
cd '<SOURCE_DIR>' && \
env \
@@ -198,7 +191,6 @@ elseif(APPLE)
--without-static-libpython \
--with-openssl='${DESTDIR}' \
--disable-test-modules \
ac_cv_func_pipe2=no ac_cv_func_dup3=no \
${_python_build_tgt} \
--with-build-python='${_python_build_python}' \
py_cv_module__tkinter=n/a"
@@ -221,8 +213,6 @@ elseif(APPLE)
--with-openssl=${DESTDIR}
--disable-test-modules
${_python_build_tgt}
ac_cv_func_pipe2=no
ac_cv_func_dup3=no
# Tcl/Tk 9.0 (e.g. from Homebrew) is incompatible with CPython 3.12's
# _tkinter; OrcaSlicer's embedded Python does not need tkinter anyway.
py_cv_module__tkinter=n/a
+5 -36
View File
@@ -873,10 +873,10 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
CNumericLocalesSetter locales_setter;
std::function<bool(const json&, const char,const char,const bool,std::string&)> parse_str_arr = [&parse_str_arr](const json& arr, const char single_sep,const char array_sep,const bool escape_string_style,std::string& value_str)->bool {
std::function<bool(const json::const_iterator&, const char,const char,const bool,std::string&)> parse_str_arr = [&parse_str_arr](const json::const_iterator& it, const char single_sep,const char array_sep,const bool escape_string_style,std::string& value_str)->bool {
// must have consistent type name
std::string consistent_type;
for (auto iter = arr.begin(); iter != arr.end(); ++iter) {
for (auto iter = it.value().begin(); iter != it.value().end(); ++iter) {
if (consistent_type.empty())
consistent_type = iter.value().type_name();
else {
@@ -886,13 +886,13 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
}
bool first = true;
for (auto iter = arr.begin(); iter != arr.end(); iter++) {
for (auto iter = it.value().begin(); iter != it.value().end(); iter++) {
if (iter.value().is_array()) {
if (!first)
value_str += array_sep;
else
first = false;
bool success = parse_str_arr(iter.value(), single_sep, array_sep,escape_string_style, value_str);
bool success = parse_str_arr(iter, single_sep, array_sep,escape_string_style, value_str);
if (!success)
return false;
}
@@ -1038,39 +1038,8 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
}
}
// BambuStudio and its forks save a nozzle variant that matches the parent preset as "nil".
// An option that can't hold nil gets its default in that slot, and the slot is reported so
// the merge onto the parent keeps the parent's value. All slots nil means the key is not set.
const json *values = &it.value();
json values_with_defaults;
if (substitution_context.accept_nil && optdef && !optdef->nullable && optdef->default_value &&
(optdef->type == coFloats || optdef->type == coPercents || optdef->type == coFloatsOrPercents ||
optdef->type == coInts || optdef->type == coEnums || optdef->type == coBools)) {
auto is_nil = [](const json &v) { return v.is_string() && v.get<std::string>() == "nil"; };
std::vector<size_t> nil_slots;
for (size_t i = 0; i < values->size(); ++i)
if (is_nil((*values)[i]))
nil_slots.push_back(i);
if (!nil_slots.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": " << file << ": " << it.key() << " is nil in "
<< nil_slots.size() << " of " << values->size()
<< " slots, read as not set (the parent preset's value, or the default)";
if (nil_slots.size() == values->size())
continue;
// create_default_option() gives enums their names, which vserialize() needs.
std::unique_ptr<ConfigOption> default_option(optdef->create_default_option());
const std::vector<std::string> defaults = static_cast<const ConfigOptionVectorBase*>(default_option.get())->vserialize();
values_with_defaults = *values;
const json first_value = *std::find_if_not(values->begin(), values->end(), is_nil);
for (size_t i : nil_slots)
values_with_defaults[i] = defaults.empty() ? first_value : json(defaults[i % defaults.size()]);
values = &values_with_defaults;
substitution_context.nil_slots[opt_key] = std::move(nil_slots);
}
}
// BBS: we only support 2 depth array
valid = parse_str_arr(*values, single_sep, array_sep,escape_string_type, value_str);
valid = parse_str_arr(it, single_sep, array_sep,escape_string_type, value_str);
if (!valid) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file << " error, invalid json array for " << it.key();
break;
-5
View File
@@ -267,11 +267,6 @@ struct ConfigSubstitutionContext
ForwardCompatibilitySubstitutionRule rule;
ConfigSubstitutions substitutions;
std::vector<std::string> unrecogized_keys;
// Read "nil" in an option that can't hold it as not set instead of failing. Set by callers that hand
// nil_slots to the merge onto the parent preset, or for which the option default is the right fallback.
bool accept_nil = false;
// Slots of options that can't hold nil but were "nil" in the file; they hold the option default.
std::map<std::string, std::vector<size_t>> nil_slots;
};
// A generic value of a configuration option.
+1 -10
View File
@@ -2696,8 +2696,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
std::map<std::string, std::string> key_values;
std::string reason;
// No parent preset here: a nil slot keeps the option default.
config_substitutions.accept_nil = true;
int ret = config.load_from_json(dest_file, config_substitutions, true, key_values, reason);
if (ret) {
add_error("Error load config from json:"+reason);
@@ -2738,13 +2736,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//ConfigSubstitutions config_substitutions = config.load_from_ini(dest_file, Enable);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutionContext load_context(Enable);
load_context.accept_nil = true;
if (use_json)
config.load_from_json(dest_file, load_context, true, key_values, reason);
else
load_context.substitutions = config.load_from_ini(dest_file, Enable);
ConfigSubstitutions config_substitutions = std::move(load_context.substitutions);
ConfigSubstitutions config_substitutions = use_json? config.load_from_json(dest_file, Enable, key_values, reason) : config.load_from_ini(dest_file, Enable);
if (!reason.empty()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", load project embedded config from %1% failed\n") % dest_file;
//skip this file
@@ -2793,7 +2785,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
preset->is_project_embedded = true;
preset->is_external = true;
preset->is_dirty = false;
preset->nil_slots = std::move(load_context.nil_slots);
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> version = Semver::parse(version_str);
+3 -3
View File
@@ -360,7 +360,7 @@ void Layer::simplify_support_entity_collection(ExtrusionEntityCollection* entity
//BBS: method to simplify support path
void Layer::simplify_support_path(ExtrusionPath * path)
{
const auto print_config = this->object()->print()->config();
const PrintConfig &print_config = this->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -375,7 +375,7 @@ void Layer::simplify_support_path(ExtrusionPath * path)
//BBS: method to simplify support path
void Layer::simplify_support_multi_path(ExtrusionMultiPath* multipath)
{
const auto print_config = this->object()->print()->config();
const PrintConfig &print_config = this->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -392,7 +392,7 @@ void Layer::simplify_support_multi_path(ExtrusionMultiPath* multipath)
//BBS: method to simplify support path
void Layer::simplify_support_loop(ExtrusionLoop* loop)
{
const auto print_config = this->object()->print()->config();
const PrintConfig &print_config = this->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
+3 -3
View File
@@ -1074,7 +1074,7 @@ void LayerRegion::simplify_entity_collection(ExtrusionEntityCollection* entity_c
void LayerRegion::simplify_path(ExtrusionPath* path)
{
const auto print_config = this->layer()->object()->print()->config();
const PrintConfig &print_config = this->layer()->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -1092,7 +1092,7 @@ void LayerRegion::simplify_path(ExtrusionPath* path)
void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath)
{
const auto print_config = this->layer()->object()->print()->config();
const PrintConfig &print_config = this->layer()->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -1112,7 +1112,7 @@ void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath)
void LayerRegion::simplify_loop(ExtrusionLoop* loop)
{
const auto print_config = this->layer()->object()->print()->config();
const PrintConfig &print_config = this->layer()->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
+5 -8
View File
@@ -1726,11 +1726,9 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
//ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
std::map<std::string, std::string> key_values;
std::string reason;
ConfigSubstitutionContext load_context(substitution_rule);
load_context.accept_nil = true;
config.load_from_json(preset.file, load_context, true, key_values, reason);
if (! load_context.substitutions.empty())
out.substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(load_context.substitutions) });
ConfigSubstitutions config_substitutions = config.load_from_json(preset.file, substitution_rule, key_values, reason);
if (! config_substitutions.empty())
out.substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
out.discard_file = true;
out.errors.push_back((boost::format("parse config %1% failed") % preset.file).str());
@@ -1766,7 +1764,7 @@ PresetCollection::UserPresetLoad PresetCollection::resolve_user_preset(
preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2, &load_context.nil_slots);
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else {
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
@@ -2139,8 +2137,7 @@ void PresetCollection::load_project_embedded_presets(std::vector<Preset*>& proje
BOOST_LOG_TRIVIAL(error) << boost::format("can not find parent for config %1%!")%preset->file;
continue;
}
preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2, &preset->nil_slots);
preset->nil_slots.clear();
preset->config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
//preset->config.apply(std::move(config));
Preset::normalize(preset->config);
// Report configuration fields, which are misplaced into a wrong group.
-2
View File
@@ -269,8 +269,6 @@ public:
//BBS: add type for project-embedded
bool is_project_embedded = false;
ConfigSubstitutions *loading_substitutions{nullptr};
// Slots that were nil in the embedded preset's file, see ConfigSubstitutionContext::nil_slots.
std::map<std::string, std::vector<size_t>> nil_slots;
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded && ! this->is_from_bundle(); }
bool can_overwrite() const { return ! this->is_default && ! this->is_system && ! this->is_from_bundle(); }
//bool is_user() const { return ! this->is_default && ! this->is_system; }
+13 -11
View File
@@ -2604,6 +2604,11 @@ void Print::auto_assign_extruders(ModelObject* model_object) const
void PrintObject::set_shared_object(PrintObject *object)
{
// Orca: from now on m_layers / m_support_layers only alias the shared object's layers, so release the
// ones this object still owns (it may have sliced itself before it became shareable again).
// Both are no-ops once m_shared_object is set, so this cannot free layers owned by another object.
clear_support_layers();
clear_layers();
m_shared_object = object;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": this=%1%, found shared object from %2%")%this%m_shared_object;
}
@@ -4329,9 +4334,9 @@ bool Print::is_dynamic_group_reorder() const
return true;
}
int Print::get_filament_config_indx(int filament_id, int layer_id, bool use_cache)
int Print::get_filament_config_indx(int filament_id, int layer_id)
{
return get_config_index(filament_id, layer_id, m_config.filament_extruder_variant.values, m_filament_self_index, use_cache ? &m_filament_index_map : nullptr);
return get_config_index(filament_id, layer_id, m_config.filament_extruder_variant.values, m_filament_self_index, m_filament_index_map);
}
void Print::update_filament_self_index_cache()
@@ -4374,7 +4379,7 @@ int Print::get_nozzle_config_index(int filament_id, int layer_id)
return get_config_index(filament_id, layer_id, m_default_region_config.print_extruder_variant.values, m_default_region_config.print_extruder_id.values, m_nozzle_index_map);
}
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap *index_map)
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap &index_map)
{
auto group_result = get_layered_nozzle_group_result();
// Orca: defensive — when no grouping producer has published a result yet, fall back to the
@@ -4385,8 +4390,7 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
if (!nozzle_info.has_value()) {
// Orca: this fallback runs per-filament/per-layer in the g-code hot path — log once per filament
// (reset each slice) instead of flooding thousands of identical lines that bury the real error.
// Without the cache, the log set is left alone too; the cached caller reports the same filament.
if (index_map && m_missing_nozzle_group_logged.insert(filament_id).second)
if (m_missing_nozzle_group_logged.insert(filament_id).second)
BOOST_LOG_TRIVIAL(error) << __FUNCTION__
<< boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3% (further occurrences for this filament suppressed)") % __LINE__ % filament_id %
layer_id;
@@ -4395,17 +4399,15 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
ExtruderType extruder_type = ExtruderType(m_config.extruder_type.get_at(nozzle_info->extruder_id));
NozzleVolumeType nozzle_volume_type = nozzle_info->volume_type;
if (!index_map)
return get_config_index_base(nozzle_volume_type, extruder_type, filament_id + 1, variant_list, self_index_list);
FilamentIndexKey key{filament_id, extruder_type, nozzle_volume_type};
auto iter = index_map->find(key);
if (iter == index_map->end()) {
auto iter = index_map.find(key);
if (iter == index_map.end()) {
int index = get_config_index_base(nozzle_volume_type, extruder_type, filament_id + 1, variant_list, self_index_list);
(*index_map)[key] = index;
index_map[key] = index;
return index;
} else {
return iter->second;
return index_map[key];
}
}
+20 -83
View File
@@ -10,8 +10,6 @@
#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>
@@ -9187,18 +9185,16 @@ 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") {
// 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);
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);
}
}
value = boost::algorithm::join(entries, ";");
} else if (opt_key == "overhang_fan_threshold" && value == "5%") {
value = "10%";
} else if( opt_key == "wall_infill_order" ) {
@@ -9240,13 +9236,6 @@ 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") {
@@ -9262,11 +9251,9 @@ 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") {
// BambuStudio's "topmost" (one wall on the topmost layer only) widens to every top surface.
} else if (opt_key == "top_one_wall_type" && value != "none") {
opt_key = "only_one_wall_top";
if (!value.empty())
value = (value == "not apply" || value == "none") ? "0" : "1";
value = "1";
} else if (opt_key == "initial_layer_flow_ratio") {
opt_key = "bottom_solid_infill_flow_ratio";
} else if (opt_key == "ironing_direction") {
@@ -9284,20 +9271,6 @@ 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";
}
@@ -9326,11 +9299,12 @@ 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") {
opt_key = "wipe_tower_wall_type";
if (value == "1" || value == "true")
value = "rib";
else if (!value.empty())
value = "rectangle";
if (value == "1") {
opt_key = "wipe_tower_wall_type";
value = "rib";
} else {
opt_key = "";
}
} else if (opt_key == "prime_tower_extra_rib_length") {
opt_key = "wipe_tower_extra_rib_length";
} else if (opt_key == "prime_tower_rib_width") {
@@ -9342,18 +9316,6 @@ 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";
}
@@ -11596,8 +11558,7 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
return;
}
void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2,
const std::map<std::string, std::vector<size_t>>* nil_slots)
void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2)
{
std::vector<int> cur_extruder_ids, target_extruder_ids, variant_index;
std::vector<std::string> cur_extruder_variants, target_extruder_variants;
@@ -11658,13 +11619,6 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
if (opt_src && opt_target && (*opt_src != *opt_target)) {
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from base_value %2% to child's value %3%")
%opt %(opt_src->serialize()) %(opt_target->serialize());
const std::vector<size_t> *unset_slots = nullptr;
if (nil_slots && opt_src->is_vector())
if (auto it = nil_slots->find(opt); it != nil_slots->end())
unset_slots = &it->second;
std::unique_ptr<ConfigOption> base_value(unset_slots ? opt_src->clone() : nullptr);
int stride = 1;
bool merged_by_variant = false;
if (opt_target->is_scalar()
|| ((key_set1.find(opt) == key_set1.end()) && (key_set2.empty() || (key_set2.find(opt) == key_set2.end())))) {
//nothing to do, keep the original one
@@ -11673,6 +11627,7 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(opt_src);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(opt_target);
int stride = 1;
if (key_set2.find(opt) != key_set2.end())
stride = 2;
// set_only_diff() requires the base vector length to equal variant_index.size()*stride, where
@@ -11686,26 +11641,8 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
if (opt_vec_src->size() != variant_index.size() * size_t(stride)) {
opt_src->set(opt_target);
}
else {
else
opt_vec_src->set_only_diff(opt_vec_dest, variant_index, stride);
merged_by_variant = true;
}
}
// A slot that was nil in the child's file is not set there, so it keeps this config's value.
if (unset_slots) {
auto *merged = static_cast<ConfigOptionVectorBase*>(opt_src);
const size_t base_size = static_cast<const ConfigOptionVectorBase*>(base_value.get())->size();
for (size_t slot = 0; slot < merged->size() && slot < base_size; ++slot) {
size_t child_slot = slot;
if (merged_by_variant) {
const int child_variant = variant_index[slot / stride];
if (child_variant == -1)
continue;
child_slot = size_t(child_variant) * stride + slot % stride;
}
if (std::find(unset_slots->begin(), unset_slots->end(), child_slot) != unset_slots->end())
merged->set_at(base_value.get(), slot, slot);
}
}
}
}
+1 -4
View File
@@ -859,10 +859,7 @@ public:
void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, std::string extruder_id_name, std::string extruder_variant_name,
std::set<std::string>& key_set1, std::set<std::string>& key_set2);
// nil_slots: per option, the slots of new_config that were nil in its file (see ConfigSubstitutionContext::nil_slots);
// those variants keep this config's value.
void update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2,
const std::map<std::string, std::vector<size_t>>* nil_slots = nullptr);
void update_diff_values_to_child_config(DynamicPrintConfig& new_config, std::string extruder_id_name, std::string extruder_variant_name, std::set<std::string>& key_set1, std::set<std::string>& key_set2);
int update_values_from_single_to_multi(DynamicPrintConfig& multi_config, std::set<std::string>& key_set, std::string id_name, std::string variant_name);
int update_values_from_multi_to_multi(DynamicPrintConfig& new_config, std::set<std::string>& key_set, std::string id_name, std::string variant_name, std::vector<std::string>& extruder_variants);
+3
View File
@@ -987,6 +987,9 @@ void PrintObject::generate_support_material()
this->_generate_support_material();
m_print->throw_if_canceled();
}
// Orca: the tree support collision/avoidance caches and support nodes are only used while this step runs
// (detect_overhangs() rebuilds them from scratch), so don't keep them resident until the next slice.
this->clear_tree_support_preview_cache();
this->set_done(posSupportMaterial);
}
}
+1 -1
View File
@@ -1346,7 +1346,7 @@ void PrintObject::slice_volumes()
if (min_growth < 0.f || elfoot > 0.f) {
// Apply the negative XY compensation. (the ones that is <0)
ExPolygons trimming;
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
if (elfoot > 0.f) {
ExPolygons expolygons_to_compensate = offset_ex(layer->merged(eps), -eps);
lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate;
+12 -30
View File
@@ -1820,37 +1820,15 @@ coordf_t TreeSupport::get_radius(const SupportNode* node)
return node->radius;
}
ExPolygons TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr)
// Orca: these are hit up to several times per node per layer in drop_nodes(), so hand out a
// reference into the TreeSupportData cache instead of copying the ExPolygons out of it.
const ExPolygons& TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr)
{
#if USE_SUPPORT_3D
if (m_model_volumes) {
bool on_build_plate = m_object_config->support_on_build_plate_only.value;
const Polygons& avoid_polys = m_model_volumes->getAvoidance(radius, obj_layer_nr, TreeSupport3D::TreeModelVolumes::AvoidanceType::FastSafe, on_build_plate, true);
ExPolygons expolys;
for (auto& poly : avoid_polys)
expolys.emplace_back(std::move(poly));
return expolys;
}
return ExPolygons();
#else
return m_ts_data->get_avoidance(radius, obj_layer_nr);
#endif
}
ExPolygons TreeSupport::get_collision(coordf_t radius, size_t layer_nr)
const ExPolygons& TreeSupport::get_collision(coordf_t radius, size_t layer_nr)
{
#if USE_SUPPORT_3D
if (m_model_volumes) {
bool on_build_plate = m_object_config->support_on_build_plate_only.value;
const Polygons& collision_polys = m_model_volumes->getCollision(radius, layer_nr, true);
ExPolygons expolys;
for (auto& poly : collision_polys)
expolys.emplace_back(std::move(poly));
return expolys;
}
#else
return m_ts_data->get_collision(radius, layer_nr);
#endif
return ExPolygons();
}
Polygons TreeSupport::get_collision_polys(coordf_t radius, size_t layer_nr)
{
@@ -2639,7 +2617,11 @@ void TreeSupport::draw_circles()
#endif // SUPPORT_TREE_DEBUG_TO_SVG
SupportLayerPtrs& ts_layers = m_object->support_layers();
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
// Orca: the vector owns its layers, so the dropped ones have to be deleted, not just unlinked.
// std::stable_partition (unlike std::remove_if) leaves exactly the dropped layers in the tail.
auto iter = std::stable_partition(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height >= EPSILON; });
for (auto it = iter; it != ts_layers.end(); ++it)
delete *it;
ts_layers.erase(iter, ts_layers.end());
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
@@ -2882,7 +2864,7 @@ void TreeSupport::drop_nodes()
//Insert a completely new node and let both original nodes fade.
Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes.
coordf_t next_radius = calc_radius(node.dist_mm_to_top+height_next);
auto avoid_layer = get_avoidance(next_radius, obj_layer_nr_next);
const ExPolygons& avoid_layer = get_avoidance(next_radius, obj_layer_nr_next);
if (group_index == 0)
{
//Avoid collisions.
@@ -3069,7 +3051,7 @@ void TreeSupport::drop_nodes()
}
#endif
coordf_t next_radius = calc_radius(node.dist_mm_to_top + height_next);
auto avoidance_next = get_avoidance(next_radius, obj_layer_nr_next);
const ExPolygons& avoidance_next = get_avoidance(next_radius, obj_layer_nr_next);
Point to_outside = projection_onto(avoidance_next, node.position);
Point direction_to_outer = to_outside - node.position;
@@ -3123,7 +3105,7 @@ void TreeSupport::drop_nodes()
if (is_outside) { next_layer_vertex = candidate_vertex; }
}
}
auto next_collision = get_collision(0, obj_layer_nr_next);
const ExPolygons& next_collision = get_collision(0, obj_layer_nr_next);
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
to_outside = projection_onto(next_collision, next_layer_vertex);
+2 -2
View File
@@ -511,9 +511,9 @@ private:
coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance=true);
coordf_t calc_radius(coordf_t mm_to_top);
coordf_t get_radius(const SupportNode* node);
ExPolygons get_avoidance(coordf_t radius, size_t obj_layer_nr);
const ExPolygons& get_avoidance(coordf_t radius, size_t obj_layer_nr);
// layer's expolygon expanded by radius+m_xy_distance
ExPolygons get_collision(coordf_t radius, size_t layer_nr);
const ExPolygons& get_collision(coordf_t radius, size_t layer_nr);
// get Polygons instead of ExPolygons
Polygons get_collision_polys(coordf_t radius, size_t layer_nr);
+8 -23
View File
@@ -819,9 +819,7 @@ int OrcaCloudServiceAgent::user_logout(bool request)
}
}
// An explicit logout also wipes the backend the token storage option is not using, so a token
// stranded by switching that option cannot sign the account back in later.
clear_session(/*all_backends=*/request);
clear_session();
return BAMBU_NETWORK_SUCCESS;
}
@@ -1606,9 +1604,7 @@ void OrcaCloudServiceAgent::persist_user_secret(const std::string& secret)
}
}
if (stored) {
secret_stored = true;
}
(void) stored;
}
bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
@@ -1648,7 +1644,6 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
}
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
secret_stored = true;
out_secret = plain;
// Upgrade legacy payloads to signed format
if (payload.rfind("v2:", 0) != 0) {
@@ -1666,7 +1661,6 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
if (!out_secret.empty()) {
secret_stored = true;
return true;
}
}
@@ -1676,20 +1670,11 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
return false;
}
void OrcaCloudServiceAgent::clear_user_secret(bool all_backends)
void OrcaCloudServiceAgent::clear_user_secret()
{
// Nothing this process loaded or saved: leave the store alone. Deleting would only cost a
// keychain round trip (or a hang while the keychain is unresponsive) and could remove a
// login another instance just saved.
if (!secret_stored.exchange(false) && !all_backends) {
return;
}
if (all_backends || !m_use_encrypted_token_file) {
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
store.Delete(SECRET_STORE_SERVICE);
}
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
store.Delete(SECRET_STORE_SERVICE);
}
compute_fallback_path();
@@ -2038,13 +2023,13 @@ bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool noti
return success;
}
void OrcaCloudServiceAgent::clear_session(bool all_backends)
void OrcaCloudServiceAgent::clear_session()
{
{
std::lock_guard<std::mutex> lock(session_mutex);
session = SessionInfo{};
}
clear_user_secret(all_backends);
clear_user_secret();
}
// ============================================================================
+2 -7
View File
@@ -326,7 +326,7 @@ public:
void persist_user_secret(const std::string& secret);
bool load_user_secret(std::string& out_secret);
void clear_user_secret(bool all_backends = false);
void clear_user_secret();
// Token refresh helpers
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
@@ -344,7 +344,7 @@ public:
bool persist = true);
// Accepts either nested Orca cloud / GoTrue session JSON or flat WebView token JSON.
bool set_user_session(const nlohmann::json& session_json, bool notify_login = true);
void clear_session(bool all_backends = false);
void clear_session();
static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "");
@@ -413,11 +413,6 @@ private:
// Member variables - auth state
PkceBundle pkce_bundle;
std::string secret_fallback_path;
// Set once this process has read a secret from the store or written one. Unless the user logs
// out explicitly, clear_user_secret() only touches the store while it is set, so a logged-out
// instance (the GUI polls the login status every 2 s) makes no keychain calls and cannot wipe
// a login another instance saved.
std::atomic_bool secret_stored{false};
SessionHandler session_handler;
OnLoginCompleteHandler on_login_complete_handler;
SessionInfo session;
-152
View File
@@ -508,158 +508,6 @@ 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("load_from_json reads a BambuStudio nil slot as not set", "[Config]") {
ScopedTemporaryFile tmp(".json");
{
boost::nowide::ofstream ofs(tmp.string());
// Keys after retraction_length in file order must still load.
ofs << R"({"layer_height":"0.2","retraction_length":["0.8","nil"],"wall_loops":"3","z_hop":["nil","nil"],)"
R"("z_hop_types":["Spiral Lift","nil"]})";
}
std::map<std::string, std::string> key_values;
std::string reason;
SECTION("a loader that doesn't merge onto a parent still rejects nil") {
DynamicPrintConfig config;
ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Enable);
config.load_from_json(tmp.string(), context, true, key_values, reason);
CHECK_FALSE(reason.empty());
}
SECTION("a loader that opts in reads it as not set") {
DynamicPrintConfig config;
ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Enable);
context.accept_nil = true;
REQUIRE(config.load_from_json(tmp.string(), context, true, key_values, reason) == 0);
CHECK(reason.empty());
const auto *default_length = static_cast<const ConfigOptionFloats*>(print_config_def.get("retraction_length")->default_value.get());
const auto &retraction_length = config.option<ConfigOptionFloats>("retraction_length")->values;
REQUIRE(retraction_length.size() == 2);
CHECK_THAT(retraction_length[0], Catch::Matchers::WithinAbs(0.8, 1e-9));
CHECK_THAT(retraction_length[1], Catch::Matchers::WithinAbs(default_length->get_at(1), 1e-9));
std::unique_ptr<ConfigOption> default_types(print_config_def.get("z_hop_types")->create_default_option());
const std::vector<std::string> type_defaults = static_cast<const ConfigOptionVectorBase*>(default_types.get())->vserialize();
CHECK(static_cast<const ConfigOptionVectorBase*>(config.option("z_hop_types"))->vserialize() ==
std::vector<std::string>{"Spiral Lift", type_defaults[1 % type_defaults.size()]});
CHECK(context.nil_slots == std::map<std::string, std::vector<size_t>>{{"retraction_length", {1}}, {"z_hop_types", {1}}});
CHECK_FALSE(config.has("z_hop"));
CHECK(config.opt_int("wall_loops") == 3);
}
}
TEST_CASE("A nozzle variant that was nil keeps the parent preset's value", "[Config]") {
auto printer = [](std::vector<double> nozzle_diameter, std::vector<double> retraction_length, std::vector<double> max_speed_x) {
DynamicPrintConfig config;
config.set_key_value("printer_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard", "Direct Drive High Flow"}));
config.set_key_value("printer_extruder_id", new ConfigOptionInts({1, 1}));
// Not a per-variant key, so the merge copies it whole.
config.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameter));
config.set_key_value("retraction_length", new ConfigOptionFloats(retraction_length));
// Two values per variant: normal and silent mode.
config.set_key_value("machine_max_speed_x", new ConfigOptionFloats(max_speed_x));
return config;
};
auto check_values = [](const DynamicPrintConfig &config, const char *key, const std::vector<double> &expected) {
const std::vector<double> &values = config.option<ConfigOptionFloats>(key)->values;
INFO(key);
REQUIRE(values.size() == expected.size());
for (size_t i = 0; i < expected.size(); ++i)
CHECK_THAT(values[i], Catch::Matchers::WithinAbs(expected[i], 1e-9));
};
DynamicPrintConfig parent = printer({0.4, 0.6}, {0.6, 0.5}, {500, 200, 400, 100});
// The nil slots of the child hold the option default after loading.
DynamicPrintConfig child = printer({0.2, 0.4}, {0.8, 0.4}, {500, 200, 300, 90});
const std::map<std::string, std::vector<size_t>> nil_slots{{"nozzle_diameter", {1}}, {"retraction_length", {1}}, {"machine_max_speed_x", {3}}};
parent.update_diff_values_to_child_config(child, "printer_extruder_id", "printer_extruder_variant",
printer_options_with_variant_1, printer_options_with_variant_2, &nil_slots);
check_values(parent, "nozzle_diameter", {0.2, 0.6});
check_values(parent, "retraction_length", {0.8, 0.5});
// Only the silent-mode value of the second variant was nil.
check_values(parent, "machine_max_speed_x", {500, 200, 300, 100});
}
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));
-1
View File
@@ -13,7 +13,6 @@ add_executable(${_TEST_NAME}_tests
test_prebuild_queue.cpp
test_staged_build.cpp
test_network_versions.cpp
test_orca_cloud_agent.cpp
test_action_source.cpp
test_plugin_host_api.cpp
# Exercise seam enums and predicates through the embedded Python host API.
+1 -9
View File
@@ -6,7 +6,6 @@
#include <boost/filesystem.hpp>
#include <memory.h>
#include <stdexcept>
#include <string>
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
@@ -26,15 +25,8 @@ void ensure_python_initialized()
config.parse_argv = 0;
const auto python_home = boost::dll::program_location().parent_path() / "python";
#ifdef _WIN32
const auto stdlib = python_home / "Lib";
#else
const auto stdlib = python_home / "lib" /
("python" + std::to_string(PY_MAJOR_VERSION) + "." + std::to_string(PY_MINOR_VERSION));
#endif
// Only a real runtime: a stray python/ folder (packages a test left behind) is not a home.
if (boost::filesystem::exists(stdlib / "encodings")) {
if (boost::filesystem::exists(python_home)) {
const std::string home = python_home.string();
const PyStatus status = PyConfig_SetBytesString(&config, &config.home, home.c_str());
@@ -1,86 +0,0 @@
#include <catch2/catch_all.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <memory>
#include <string>
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
#include "test_utils.hpp"
using namespace Slic3r;
namespace fs = boost::filesystem;
namespace {
// The encrypted token file is the one secret backend a test can observe without a system
// keychain. Every agent pointed at the same directory shares it, like separate app instances
// share the keychain entry.
std::unique_ptr<OrcaCloudServiceAgent> make_file_backed_agent(const fs::path& dir)
{
auto agent = std::make_unique<OrcaCloudServiceAgent>(dir.string());
agent->set_use_encrypted_token_file(true);
agent->set_config_dir(dir.string());
return agent;
}
fs::path secret_file(const fs::path& dir) { return dir / secret_constants::USER_SECRET_FILENAME; }
} // namespace
TEST_CASE("Logging out removes the secret this instance saved", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
auto agent = make_file_backed_agent(dir.path());
agent->persist_user_secret("refresh-token");
REQUIRE(fs::exists(secret_file(dir.path())));
agent->user_logout(false);
CHECK_FALSE(fs::exists(secret_file(dir.path())));
}
TEST_CASE("Logging out removes a secret this instance loaded from the store", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
auto agent = make_file_backed_agent(dir.path());
std::string secret;
REQUIRE(agent->load_user_secret(secret));
CHECK(secret == "refresh-token");
agent->user_logout(false);
CHECK_FALSE(fs::exists(secret_file(dir.path())));
}
TEST_CASE("Logging out leaves a secret this instance never loaded or saved alone", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
// A logged-out instance is asked to log out on every login-status poll.
auto other = make_file_backed_agent(dir.path());
other->user_logout(false);
other->user_logout(false);
CHECK(fs::exists(secret_file(dir.path())));
std::string secret;
REQUIRE(make_file_backed_agent(dir.path())->load_user_secret(secret));
CHECK(secret == "refresh-token");
}
TEST_CASE("Logging out leaves a secret this instance could not read alone", "[OrcaCloudServiceAgent]")
{
ScopedTemporaryDir dir("orca-secret");
// Written under another encryption key, e.g. by another OS user sharing the data directory.
fs::ofstream(secret_file(dir.path())) << "v2:0000:not-a-payload-this-user-can-decrypt";
auto agent = make_file_backed_agent(dir.path());
std::string secret;
REQUIRE_FALSE(agent->load_user_secret(secret));
agent->user_logout(false);
CHECK(fs::exists(secret_file(dir.path())));
}
@@ -38,9 +38,6 @@ namespace {
// before this destructor's shutdown() runs.
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = PluginManager::instance().initialize();
~ScopedPluginManager()
@@ -31,9 +31,6 @@ namespace {
// same as any other plugin.
struct ScopedManagerShutdown
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = PluginManager::instance().initialize();
~ScopedManagerShutdown()
@@ -42,9 +42,6 @@ namespace {
// Declare this FIRST in a test so it is destroyed last.
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = false;
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }
@@ -12,8 +12,6 @@
#include <memory>
#include <string>
#include "plugin_test_utils.hpp"
namespace py = pybind11;
using namespace Slic3r;
@@ -23,9 +21,6 @@ namespace {
// into Python unless PythonInterpreter::instance() reports initialized.
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = PluginManager::instance().initialize();
~ScopedPluginManager()
@@ -35,9 +35,6 @@ namespace {
struct ScopedPluginManager
{
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
// which would otherwise land in the working directory.
ScopedDataDir python_data_dir{"plugin-python"};
bool initialized = false;
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }