#include "PublishSettings.hpp" #include "PresetBundle.hpp" #include "Preset.hpp" #include "PrintConfig.hpp" #include "MaterialType.hpp" #include #include #include 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& 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 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& publishable_printer_retraction_options() { static const std::vector 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& publishable_printer_z_hop_options() { static const std::vector 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& 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 printer_keys = [] { std::set 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 collect_dirty_settings_keys(const PresetBundle& bundle) { std::set 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(keys.begin(), keys.end()); } DynamicPrintConfig filter_published_config( const DynamicPrintConfig &full_config, const std::vector &published_keys, const std::vector &material_keys) { DynamicPrintConfig filtered; std::set base_keys_to_include; // Never masked (whole-vector serialization): identity, plate geometry and process/printer // keys. std::set 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> 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 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 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(&opt); const auto *default_vec = dynamic_cast(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 &keep_slots) { auto *vec = dynamic_cast(&opt); for (size_t idx = 0; idx < vec->size(); ++idx) if (keep_slots.count(static_cast(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