Files
OrcaSlicer/src/libslic3r/PublishSettings.cpp
T
2026-08-21 19:04:10 +08:00

234 lines
10 KiB
C++

#include "PublishSettings.hpp"
#include "PresetBundle.hpp"
#include "Preset.hpp"
#include "PrintConfig.hpp"
#include "MaterialType.hpp"
#include <boost/log/trivial.hpp>
#include <map>
#include <set>
namespace Slic3r {
std::string normalize_filament_type(const std::string& type)
{
if (type.empty())
return type;
if (MaterialType::find(type) != nullptr)
return type;
// "PLA High Speed" -> "PLA": strip a space-separated modifier, but keep dash-separated
// types like "PA-CF" / "PETG-CF" intact (they are distinct materials, not modifiers).
const size_t sep = type.find(' ');
if (sep != std::string::npos) {
const std::string base = type.substr(0, sep);
if (MaterialType::find(base) != nullptr)
return base;
}
return type;
}
const std::set<std::string>& publish_structural_keys()
{
// Non-publishable keys: the *_settings_id keys are also in PresetCollection::skipped_in_dirty
// (Preset.cpp) / stripped from configs (profile_print_params_same); publishing them would
// rewrite the user's preset inheritance/structure.
static const std::set<std::string> structural_keys = {
"printer_settings_id", "filament_settings_id", "print_settings_id",
"sla_print_settings_id", "sla_material_settings_id",
"compatible_printers", "compatible_prints",
"compatible_printers_condition", "compatible_prints_condition",
"default_filament_profile", "default_print_profile",
"default_sla_print_profile", "default_sla_material_profile",
"extruder_count", "bed_shape",
"inherits", "inherits_group",
"printer_technology", "printer_model", "printer_variant",
"physical_printer_settings_id", "filament_ids",
"different_settings_to_system"
};
return structural_keys;
}
// The printer tab's "Retraction" optgroup (TabPrinter::build_fff, Tab.cpp), in tab order.
// KEEP IN SYNC with that optgroup: the published-3MF printer allowlist is built from these
// lists, so any key shown there must be publishable here (and vice versa).
const std::vector<PublishablePrinterOption>& publishable_printer_retraction_options()
{
static const std::vector<PublishablePrinterOption> options = {
{ "retraction_length", "printer_extruder_retraction#length" },
{ "retract_restart_extra", "printer_extruder_retraction#extra-length-on-restart" },
{ "retraction_speed", "printer_extruder_retraction#retraction-speed" },
{ "deretraction_speed", "printer_extruder_retraction#deretraction-speed" },
{ "retraction_minimum_travel", "printer_extruder_retraction#travel-distance-threshold" },
{ "retract_when_changing_layer", "printer_extruder_retraction#retract-on-layer-change" },
{ "wipe", "printer_extruder_retraction#wipe-while-retracting" },
{ "wipe_distance", "printer_extruder_retraction#wipe-distance" },
{ "retract_before_wipe", "printer_extruder_retraction#retract-amount-before-wipe" },
{ "retract_after_wipe", "printer_extruder_retraction#retract-amount-after-wipe" },
};
return options;
}
// The printer tab's "Z-Hop" optgroup (TabPrinter::build_fff, Tab.cpp), in tab order. KEEP IN
// SYNC with that optgroup, same as publishable_printer_retraction_options().
const std::vector<PublishablePrinterOption>& publishable_printer_z_hop_options()
{
static const std::vector<PublishablePrinterOption> options = {
{ "retract_lift_enforce", "printer_extruder_z_hop#on-surfaces" },
{ "z_hop_types", "printer_extruder_z_hop#z-hop-type" },
{ "z_hop", "printer_extruder_z_hop#z-hop-height" },
{ "travel_slope", "printer_extruder_z_hop#traveling-angle" },
{ "retract_lift_above", "printer_extruder_z_hop#only-lift-z-above" },
{ "retract_lift_below", "printer_extruder_z_hop#only-lift-z-below" },
};
return options;
}
const std::set<std::string>& publishable_printer_keys()
{
// Union of the two optgroups; "Retraction when switching material" keys are excluded
// (toolchange retraction is device/profile territory, not a publishable behavior tweak).
static const std::set<std::string> printer_keys = [] {
std::set<std::string> keys;
for (const PublishablePrinterOption &opt : publishable_printer_retraction_options())
keys.insert(opt.key);
for (const PublishablePrinterOption &opt : publishable_printer_z_hop_options())
keys.insert(opt.key);
return keys;
}();
return printer_keys;
}
std::vector<std::string> collect_dirty_settings_keys(const PresetBundle& bundle)
{
std::set<std::string> keys;
// Union the dirty keys of each collection's edited preset (filaments may span multiple
// slots); feeds only the Publish dialog's pre-check.
for (const std::string& key : bundle.prints.current_dirty_options(true))
keys.insert(key);
for (const std::string& key : bundle.printers.current_dirty_options(true))
keys.insert(key);
for (const std::string& key : bundle.filaments.current_dirty_options(true))
keys.insert(key);
return std::vector<std::string>(keys.begin(), keys.end());
}
DynamicPrintConfig filter_published_config(
const DynamicPrintConfig &full_config,
const std::vector<std::string> &published_keys,
const std::vector<PublishedMaterialEntry> &material_keys)
{
DynamicPrintConfig filtered;
std::set<std::string> base_keys_to_include;
// Never masked (whole-vector serialization): identity, plate geometry and process/printer
// keys.
std::set<std::string> mask_exempt_keys;
// Material entries: base key -> author slots whose values must survive; other slots are
// masked to their defaults so a publish (partial or full) does not leak unrelated slot
// data.
std::map<std::string, std::set<int>> slot_mask_map;
// 1. Mandatory material identity & slot count keys for 3MF validation/normalization
// (filament_ids: exported for validation, denylisted on apply - see publish_structural_keys).
static const std::vector<std::string> s_material_identity_keys = {
"filament_colour",
"filament_type",
"filament_vendor",
"filament_ids",
"filament_diameter",
"filament_self_index",
"filament_extruder_variant"
};
for (const std::string &key : s_material_identity_keys) {
base_keys_to_include.insert(key);
mask_exempt_keys.insert(key);
}
// 2. Published plate / bed geometry keys (wipe tower positioning)
static const std::vector<std::string> s_plate_geometry_keys = {
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle"
};
for (const std::string &key : s_plate_geometry_keys) {
base_keys_to_include.insert(key);
mask_exempt_keys.insert(key);
}
// 3. Process and printer published keys
for (const std::string &key : published_keys) {
const std::string base_key = publish_base_key(key);
if (!base_key.empty()) {
base_keys_to_include.insert(base_key);
mask_exempt_keys.insert(base_key);
}
}
// 4. Material-specific published keys. Both partial (entry.keys) and full-publish
// (entry.full_keys) entries mask to the author's slot on export (see the copy loop below);
// slot-less entries (hand-crafted files) stay unmasked (whole vector).
for (const PublishedMaterialEntry &entry : material_keys) {
for (const std::string &key : entry.keys) {
const std::string base_key = publish_base_key(key);
if (base_key.empty())
continue;
base_keys_to_include.insert(base_key);
if (entry.slot >= 0)
slot_mask_map[base_key].insert(entry.slot);
}
for (const std::string &key : entry.full_keys) {
const std::string base_key = publish_base_key(key);
if (base_key.empty())
continue;
base_keys_to_include.insert(base_key);
if (entry.slot >= 0)
slot_mask_map[base_key].insert(entry.slot);
}
}
// Masking restores every non-published slot of a vector option with the option default, so
// a partial publish does not leak unrelated slot data. can_mask_slots reports whether a key
// is maskable at all (vector option plus a registered default of the same type); an
// unmaskable key is dropped from the payload entirely instead of shipping the author's
// whole vector.
auto can_mask_slots = [](const ConfigOption &opt, const ConfigOptionDef *def) -> bool {
if (def == nullptr || !def->default_value || def->default_value->type() != opt.type())
return false;
const auto *vec = dynamic_cast<const ConfigOptionVectorBase *>(&opt);
const auto *default_vec = dynamic_cast<const ConfigOptionVectorBase *>(def->default_value.get());
return vec != nullptr && vec->size() > 0 && default_vec != nullptr && !default_vec->empty();
};
auto mask_slots = [](ConfigOption &opt, const ConfigOptionDef *def, const std::set<int> &keep_slots) {
auto *vec = dynamic_cast<ConfigOptionVectorBase*>(&opt);
for (size_t idx = 0; idx < vec->size(); ++idx)
if (keep_slots.count(static_cast<int>(idx)) == 0)
vec->set_at(def->default_value.get(), idx, 0);
};
// Copy the selected options from full_config into the filtered config.
for (const std::string &key : base_keys_to_include) {
const ConfigOption *opt = full_config.option(key);
if (opt == nullptr)
continue;
const auto mask_it = slot_mask_map.find(key);
const bool needs_masking = mask_exempt_keys.count(key) == 0 && mask_it != slot_mask_map.end() && !mask_it->second.empty();
if (needs_masking && !can_mask_slots(*opt, print_config_def.get(key))) {
BOOST_LOG_TRIVIAL(warning) << "publish: dropping unmaskable key \"" << key
<< "\" from the published payload (no usable option default)";
continue;
}
ConfigOption *cloned = opt->clone();
if (needs_masking)
mask_slots(*cloned, print_config_def.get(key), mask_it->second);
filtered.set_key_value(key, cloned);
}
return filtered;
}
} // namespace Slic3r