Initial Commit for Publish Settings workflow

This commit is contained in:
Lam Wei Lun
2026-08-14 10:49:49 +08:00
parent 6dbdb1d07e
commit b280ab555c
20 changed files with 2897 additions and 243 deletions
+2
View File
@@ -350,6 +350,8 @@ set(lisbslic3r_sources
Preset.hpp
PrincipalComponents2D.cpp
PrincipalComponents2D.hpp
PublishSettings.cpp
PublishSettings.hpp
PrintApply.cpp
PrintBase.cpp
PrintBase.hpp
+285 -3
View File
@@ -3,6 +3,7 @@
#include "PresetBundle.hpp"
#include "PrintConfig.hpp"
#include "PublishSettings.hpp"
#include "libslic3r.h"
#include "I18N.hpp"
#include "Utils.hpp"
@@ -11,7 +12,7 @@
#include "libslic3r_version.h"
#include <algorithm>
#include <mutex>
#include <cstdlib>
#include <set>
#include <fstream>
#include <unordered_set>
@@ -40,6 +41,8 @@
namespace Slic3r {
// Project-level options imported from a loaded 3MF into project_config. s_project_options_published
// below is the reduced subset that crosses over in "published" 3MF mode; keep both in sync.
static std::vector<std::string> s_project_options {
"flush_volumes_vector",
"flush_volumes_matrix",
@@ -71,6 +74,24 @@ static std::vector<std::string> s_project_options {
"enable_filament_dynamic_map"
};
// Project options applied when loading a "published" 3MF project: the full s_project_options
// minus the filament/purge keys. A published file must not port the author's filament data
// (colors, colour types, filament/map/AMS slot state, purge/prime/flush volumes, nozzle
// volume types, filament switcher state) to the receiver's project_config, which feeds the
// scene colors, AMS slot colors and purge data. Only the plate/bed geometry keys cross over;
// normal (non-published) 3MF loads keep importing the author's flush data via s_project_options.
//
// KEEP IN SYNC with s_project_options above: when a new project option is added there, decide
// here whether it is plate/bed geometry (add it to this list) or filament/purge/mapping/device
// state (it must NOT be added). curr_bed_type is deliberately NOT in this list: the receiver
// keeps its own bed type when loading a published project. The published-mode project_config
// assertions in tests/libslic3r/test_preset_bundle_loading.cpp guard both directions.
static std::vector<std::string> s_project_options_published {
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle"
};
//Orca: add custom as default
const char *PresetBundle::ORCA_DEFAULT_BUNDLE = "Custom";
const char *PresetBundle::ORCA_DEFAULT_PRINTER_MODEL = "MyKlipper 0.4 nozzle";
@@ -4426,10 +4447,14 @@ static void convert_filament_preset_name(std::string& machine_name, std::string&
}
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
// is_external == false on if called from ConfigWizard
void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version, bool selected)
void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version, bool selected, PublishedConfig *published_config)
{
PrinterTechnology printer_technology = Preset::printer_technology(config);
// A "published" 3MF project keeps the user's currently-selected presets and overlays only
// the author-selected published keys onto the edited presets.
const bool is_published = published_config != nullptr && published_config->published;
auto clear_compatible_printers = [](DynamicPrintConfig& config){
ConfigOption *opt_compatible = config.optptr("compatible_printers");
if (opt_compatible != nullptr) {
@@ -4570,6 +4595,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
switch (Preset::printer_technology(config)) {
case ptFFF:
{
// A "published" 3MF project keeps the user's currently-selected presets, so the
// print / printer / filament presets are NOT loaded from the file. Only the
// project config values and the published keys are applied below.
if (!is_published) {
//BBS: add different settings logic
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load print preset from print_settings_id");
std::vector<std::string> print_different_keys_vector;
@@ -4722,9 +4751,12 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
this->filament_presets[i] = loaded->name;
}
}
} // !is_published
// 4) Load the project config values (the per extruder wipe matrix etc).
this->project_config.apply_only(config, s_project_options);
// In published mode the receiver must not inherit the author's filament/purge data,
// so only the plate/bed geometry project keys are applied.
this->project_config.apply_only(config, is_published ? s_project_options_published : s_project_options);
break;
}
@@ -4743,9 +4775,258 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
this->update_compatible(PresetSelectCompatibleType::Never);
this->update_multi_material_filament_presets();
// A "published" 3MF project overlays only the author-selected published keys onto the
// user's currently-selected (edited) process preset. Scalar keys are applied directly;
// vector (multi-extruder) keys are applied only when the edited preset has a matching
// vector size. Keys that cannot be applied are collected for notification; legacy
// filament/printer keys in a published file fall through into skipped_keys.
if (is_published) {
std::vector<std::string> skipped_keys;
std::set<std::string> applied_keys;
// Structural keys must never be applied to the user's presets: doing so would
// rewrite their preset inheritance/structure. This is the single source of truth
// shared with PublishSettingsDialog.cpp (publish_structural_keys in
// PublishSettings.hpp). Defense-in-depth: a hand-crafted 3MF could set
// published_keys to these regardless of the dialog, so skip them here too.
const std::set<std::string> &structural_keys = publish_structural_keys();
// The printer overlay is restricted to the publishable retraction/z-hop allowlist.
// Printer-class keys outside it are contract-excluded: never applied and never
// reported as skipped (a hand-crafted 3MF listing machine_start_gcode or
// nozzle_diameter must not apply them and must not spam the warning).
const std::set<std::string> &printer_allowlist = publishable_printer_keys();
const std::vector<std::string> &printer_options = Preset::printer_options();
const std::set<std::string> printer_option_set(printer_options.begin(), printer_options.end());
std::set<std::string> contract_excluded_keys;
auto apply_published = [&](DynamicPrintConfig &target, const std::set<std::string> *allowlist) {
for (const std::string &key : published_config->published_keys) {
if (applied_keys.count(key) != 0)
continue; // already applied
// A '#' suffix denotes a variant (per-extruder/per-filament) key; resolve the base key.
const std::string base_key = key.substr(0, key.find('#'));
// Structural keys are intentionally never applied (not "skipped due to
// mismatch"), so bail out before the applied/skipped bookkeeping.
if (structural_keys.count(base_key) != 0)
continue;
if (allowlist != nullptr &&
printer_option_set.count(base_key) != 0 &&
allowlist->count(base_key) == 0) {
// Printer-class key outside the publishable allowlist: contract-excluded.
contract_excluded_keys.insert(base_key);
continue;
}
const ConfigOption *src_opt = config.option(base_key);
if (src_opt == nullptr)
continue; // key not present in the loaded config; record later
if (src_opt->is_vector()) {
// Vector key: apply only when the edited preset has a matching vector size.
const ConfigOption *dst_opt = target.option(base_key);
if (dst_opt == nullptr || !dst_opt->is_vector() ||
static_cast<const ConfigOptionVectorBase*>(src_opt)->size() != static_cast<const ConfigOptionVectorBase*>(dst_opt)->size())
continue; // cannot apply; will be reported as skipped
// A '#' variant index must be in range: ConfigOptionVector::set_at would
// otherwise resize the destination vector, corrupting the receiver's preset.
if (key.size() > base_key.size()) {
const size_t idx = static_cast<size_t>(std::atoi(key.c_str() + base_key.size() + 1));
if (idx >= static_cast<const ConfigOptionVectorBase*>(src_opt)->size())
continue; // out-of-range variant: cannot apply; reported as skipped
}
target.apply_only(config, {key}, true);
applied_keys.insert(key);
} else {
// A scalar key cannot carry a '#N' variant suffix; a hand-crafted file
// listing one is reported as skipped instead of being silently marked applied.
if (key.find('#') != std::string::npos)
continue;
// Scalar key: apply only if present on the user's machine.
if (target.option(base_key) == nullptr)
continue; // not present on the edited preset; will be reported as skipped
target.apply_only(config, {key}, true);
applied_keys.insert(key);
}
}
};
apply_published(this->prints.get_edited_preset().config, nullptr);
apply_published(this->printers.get_edited_preset().config, &printer_allowlist);
// Material pass: apply the author's material-qualified keys onto the receiver's
// matching filament presets. The file config carries the author's per-slot identity
// (filament_type / filament_vendor remain in config; filament_ids was moved into a
// local earlier) and the per-slot material retraction values.
if (!published_config->material_keys.empty()) {
const ConfigOptionStrings *file_types = config.option<ConfigOptionStrings>("filament_type");
const ConfigOptionStrings *file_vendors = config.option<ConfigOptionStrings>("filament_vendor");
auto identity_matches = [](const std::string &id, const std::string &type, const std::string &vendor,
const std::string &slot_id, const std::string &slot_type, const std::string &slot_vendor) {
// When both sides carry a filament_id, equality is required; otherwise fall
// back to filament_type, with filament_vendor as an additional qualifier only
// when both sides have a non-empty vendor.
if (!id.empty() && !slot_id.empty())
return id == slot_id;
if (type.empty() || type != slot_type)
return false;
if (!vendor.empty() && !slot_vendor.empty())
return vendor == slot_vendor;
return true;
};
for (const PublishedMaterialEntry &entry : published_config->material_keys) {
// Resolve the author's source slot and its ordinal among the author slots
// carrying this entry's identity. A slotted entry (slot >= 0) names the exact
// author slot and targets the receiver's Nth matching preset (N = ordinal);
// a legacy entry (slot -1) uses the first matching author slot and applies to
// every matching receiver preset.
auto slot_identity = [&filament_ids, file_types, file_vendors](size_t slot, std::string &id, std::string &type, std::string &vendor) {
id = (slot < filament_ids.size()) ? filament_ids[slot] : std::string();
type = (file_types && slot < file_types->size()) ? file_types->get_at(slot) : std::string();
vendor = (file_vendors && slot < file_vendors->size()) ? file_vendors->get_at(slot) : std::string();
};
bool author_found = false;
size_t author_slot = 0;
size_t author_ordinal = 0;
if (entry.slot >= 0) {
// Collect every author slot carrying this identity, in slot order; the
// entry's slot must be among them, and its position is the ordinal used
// to pick the receiver's matching preset.
std::vector<size_t> matching_author_slots;
for (size_t slot = 0; slot < filament_ids.size(); ++slot) {
std::string slot_id, slot_type, slot_vendor;
slot_identity(slot, slot_id, slot_type, slot_vendor);
if (identity_matches(entry.filament_id, entry.filament_type, entry.filament_vendor,
slot_id, slot_type, slot_vendor))
matching_author_slots.emplace_back(slot);
}
const auto ordinal_it = std::find(matching_author_slots.begin(), matching_author_slots.end(), size_t(entry.slot));
if (ordinal_it != matching_author_slots.end()) {
author_slot = size_t(entry.slot);
author_ordinal = size_t(ordinal_it - matching_author_slots.begin());
author_found = true;
}
// Out of range, or the slot does not carry this identity: silent skip below.
} else {
// Legacy: the first author slot whose identity matches.
for (size_t slot = 0; slot < filament_ids.size(); ++slot) {
std::string slot_id, slot_type, slot_vendor;
slot_identity(slot, slot_id, slot_type, slot_vendor);
if (identity_matches(entry.filament_id, entry.filament_type, entry.filament_vendor,
slot_id, slot_type, slot_vendor)) {
author_slot = slot;
author_found = true;
break;
}
}
}
if (!author_found)
// No author slot carries this material: nothing to apply, nothing to report.
continue;
const std::string material_label = entry.filament_id.empty() ? entry.filament_type : entry.filament_id;
auto report_skipped = [&skipped_keys, &material_label](const std::string &key, const std::string &slot_qualifier = std::string()) {
skipped_keys.emplace_back("material:" + material_label +
(slot_qualifier.empty() ? std::string() : " " + slot_qualifier) +
" (" + key + ")");
};
// Collect the receiver's matching filament presets (distinct by preset name).
std::vector<std::string> matched_preset_names;
std::set<std::string> fallback_matched_names;
for (const std::string &preset_name : this->filament_presets) {
Preset *preset = this->filaments.find_preset(preset_name);
if (preset == nullptr)
continue;
const std::string slot_id = preset->filament_id;
// Null-guard the identity reads: a malformed user preset may lack
// filament_type / filament_vendor entirely (hand-edited preset file).
const ConfigOptionStrings *slot_types = preset->config.option<ConfigOptionStrings>("filament_type");
const ConfigOptionStrings *slot_vendors = preset->config.option<ConfigOptionStrings>("filament_vendor");
const std::string slot_type = (slot_types && !slot_types->values.empty()) ? slot_types->get_at(0) : std::string();
const std::string slot_vendor = (slot_vendors && !slot_vendors->values.empty()) ? slot_vendors->get_at(0) : std::string();
if (!identity_matches(entry.filament_id, entry.filament_type, entry.filament_vendor,
slot_id, slot_type, slot_vendor))
continue;
if (std::find(matched_preset_names.begin(), matched_preset_names.end(), preset_name) == matched_preset_names.end())
matched_preset_names.emplace_back(preset_name);
if (entry.filament_id.empty() || slot_id.empty())
fallback_matched_names.insert(preset_name);
}
if (matched_preset_names.empty()) {
// No receiver material matches this entry: report each key as skipped.
for (const std::string &key : entry.keys)
report_skipped(key);
continue;
}
if (fallback_matched_names.size() > 1) {
// The type fallback matched more than one distinct receiver preset: never
// guess which one the author meant.
for (const std::string &key : entry.keys)
report_skipped(key);
continue;
}
// Slotted entries target the receiver's matching preset at the author's
// ordinal; legacy entries apply to every matching receiver preset.
std::vector<std::string> apply_to_preset_names;
if (entry.slot >= 0) {
if (author_ordinal >= matched_preset_names.size()) {
// The receiver has fewer matching presets than the author's ordinal:
// this slot's values cannot be placed, report each key.
const std::string slot_qualifier = "slot " + std::to_string(entry.slot);
for (const std::string &key : entry.keys)
report_skipped(key, slot_qualifier);
continue;
}
apply_to_preset_names.emplace_back(matched_preset_names[author_ordinal]);
} else {
apply_to_preset_names = matched_preset_names;
}
for (const std::string &key : entry.keys) {
const std::string base_key = key.substr(0, key.find('#'));
if (structural_keys.count(base_key) != 0)
continue; // structural: silent
const ConfigOption *src_opt = config.option(base_key);
if (src_opt == nullptr || !src_opt->is_vector() ||
author_slot >= static_cast<const ConfigOptionVectorBase*>(src_opt)->size()) {
report_skipped(key);
continue;
}
for (const std::string &preset_name : apply_to_preset_names) {
Preset *preset = this->filaments.find_preset(preset_name);
if (preset == nullptr)
continue;
ConfigOption *dst_opt = preset->config.option(base_key);
// Per-slot scalar copy: the receiver's filament preset holds a single
// value per key (vector of size 1), the file holds the per-slot vector.
if (dst_opt == nullptr || !dst_opt->is_vector() ||
static_cast<const ConfigOptionVectorBase*>(dst_opt)->empty() ||
dst_opt->type() != src_opt->type()) {
report_skipped(key);
continue;
}
static_cast<ConfigOptionVectorBase*>(dst_opt)->set_at(src_opt, 0, author_slot);
}
}
}
}
for (const std::string &key : published_config->published_keys) {
if (applied_keys.count(key) != 0)
continue;
const std::string base_key = key.substr(0, key.find('#'));
// Structural keys are silently ignored, never reported as skipped: a hand-crafted
// 3MF must not trigger the "could not be applied" warning for them.
if (structural_keys.count(base_key) != 0)
continue;
// Printer-class keys outside the publishable allowlist are contract-excluded too.
if (contract_excluded_keys.count(base_key) != 0)
continue;
skipped_keys.emplace_back(key);
}
published_config->skipped_keys = std::move(skipped_keys);
}
//BBS
//const std::string &physical_printer = config.option<ConfigOptionString>("physical_printer_settings_id", true)->value;
const std::string physical_printer;
if (!is_published) {
if (this->printers.get_edited_preset().is_external || physical_printer.empty()) {
this->physical_printers.unselect_printer();
} else {
@@ -4756,6 +5037,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
else
this->physical_printers.unselect_printer();
}
}
//BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": finished");
}
+20 -3
View File
@@ -3,6 +3,7 @@
#include "Preset.hpp"
#include "AppConfig.hpp"
#include "PublishSettings.hpp"
#include "enum_bitmask.hpp"
#include <memory>
@@ -166,6 +167,22 @@ struct PresetBundleMetadata
}
};
// Configuration describing a "published" 3MF project: the file carries a flag plus a list of
// author-selected setting keys. When loading such a project the user's currently-selected
// presets are kept and only the published keys are overlaid onto the edited presets.
struct PublishedConfig
{
bool published = false;
std::vector<std::string> published_keys;
// Material-qualified published keys chosen by the author for the materials used in the
// project; applied on load only to the receiver's filament presets whose material
// identity matches (see PublishedMaterialEntry in PublishSettings.hpp).
std::vector<PublishedMaterialEntry> material_keys;
// Keys that could not be applied (missing on the user's machine or vector size mismatch),
// filled in by load_config_file_config for notification purposes.
std::vector<std::string> skipped_keys;
};
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
@@ -414,8 +431,8 @@ public:
// Load configuration that comes from a model file containing configuration, such as 3MF et al.
// This method is called by the Plater.
void load_config_model(const std::string &name, DynamicPrintConfig config, Semver file_version = Semver())
{ this->load_config_file_config(name, true, std::move(config), file_version); }
void load_config_model(const std::string &name, DynamicPrintConfig config, Semver file_version = Semver(), PublishedConfig *published_config = nullptr)
{ this->load_config_file_config(name, true, std::move(config), file_version, false, published_config); }
// Load an external config file containing the print, filament and printer presets.
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
@@ -544,7 +561,7 @@ private:
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
// and the external config is just referenced, not stored into user profile directory.
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version = Semver(), bool selected = false);
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config, Semver file_version = Semver(), bool selected = false, PublishedConfig *published_config = nullptr);
/*ConfigSubstitutions load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/
+105
View File
@@ -0,0 +1,105 @@
#include "PublishSettings.hpp"
#include "PresetBundle.hpp"
#include "Preset.hpp"
#include <algorithm>
namespace Slic3r {
const std::set<std::string>& publish_structural_keys()
{
// Structural / non-publishable keys. The *_settings_id keys are also part of
// PresetCollection::skipped_in_dirty (Preset.cpp) and are excluded there too.
// This mirrors the structural keys stripped from configs in Preset.cpp
// (profile_print_params_same) plus other keys that must never be published
// because they 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()
{
// The union of the printer tab's "Retraction" and "Z-Hop" optgroups. The "Retraction when
// switching material" keys are intentionally 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::vector<std::string> keys;
auto append_dirty = [&keys](const std::vector<std::string>& dirty) {
for (const std::string& key : dirty) {
if (std::find(keys.begin(), keys.end(), key) == keys.end())
keys.push_back(key);
}
};
// Print and printer presets each track a single edited preset; filaments may span
// multiple slots (multi-material). Union the dirty keys of each collection's edited
// preset; this feeds only the Publish dialog's pre-check.
append_dirty(bundle.prints.current_dirty_options(true));
append_dirty(bundle.printers.current_dirty_options(true));
append_dirty(bundle.filaments.current_dirty_options(true));
return keys;
}
} // namespace Slic3r
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <set>
#include <string>
#include <vector>
namespace Slic3r {
class PresetBundle;
// Structural / non-publishable setting keys, shared by the Publish dialog and the published-3MF
// overlay path in PresetBundle::load_config_file_config. These keys must never be published
// because they would rewrite the user's preset inheritance/structure. This is the single
// source of truth for the denylist.
const std::set<std::string>& publish_structural_keys();
// One option row of the printer tab's "Retraction" / "Z-Hop" optgroups (TabPrinter::build_fff,
// Tab.cpp). Key and icon id are kept together so the tab can later be migrated onto these
// lists; publishable_printer_keys() is their union, and the published-3MF loader/dialog must
// never accept printer keys outside it.
struct PublishablePrinterOption {
const char *key; // config key, e.g. "retraction_length"
const char *icon; // tab icon id, e.g. "printer_extruder_retraction#length"
};
// The printer tab's "Retraction" optgroup options, in tab order.
const std::vector<PublishablePrinterOption>& publishable_printer_retraction_options();
// The printer tab's "Z-Hop" optgroup options, in tab order.
const std::vector<PublishablePrinterOption>& publishable_printer_z_hop_options();
// Printer-class retraction / z-hop keys that are publishable: the union of
// publishable_printer_retraction_options() and publishable_printer_z_hop_options(). The
// published-3MF overlay applies printer keys only when their base key is in this allowlist;
// any other printer-class key in a published file is contract-excluded (never applied, never
// reported as skipped).
const std::set<std::string>& publishable_printer_keys();
// Returns the union of setting keys that differ from the base/system preset across the current
// print, printer and filament presets (feeds the Publish dialog's pre-check).
std::vector<std::string> collect_dirty_settings_keys(const PresetBundle& bundle);
// A material-qualified set of published setting keys, chosen by the author for one of the
// materials used in the project. The identity fields let the receiver apply the keys only
// when a matching material is selected: filament_id is the most precise (stable across
// machines/vendors when present, empty for user presets); filament_type + filament_vendor
// are the fallback. Keys are base keys (no "#N" variant suffix).
struct PublishedMaterialEntry {
std::string filament_type; // material family, e.g. "PLA" (may be empty)
std::string filament_vendor; // e.g. "Generic", "Bambu" (may be empty)
std::string filament_id; // stable material id, e.g. "GFL99" (may be empty)
// 0-based author filament slot this entry's values came from; -1 = legacy/unspecified
// (files written before the slot field). Slotted entries apply to the receiver's Nth
// matching preset (N = the slot's ordinal among the author's matching slots); legacy
// entries apply to every matching receiver preset.
int slot{-1};
std::vector<std::string> keys;
};
}
+4
View File
@@ -93,6 +93,8 @@ set(SLIC3R_GUI_SOURCES
GUI/CloneDialog.hpp
GUI/ConfigManipulation.cpp
GUI/ConfigManipulation.hpp
GUI/ConfigValueFormatter.cpp
GUI/ConfigValueFormatter.hpp
GUI/ConfigWizard.cpp
GUI/ConfigWizard.hpp
GUI/ConfigWizard_private.hpp
@@ -437,6 +439,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Project.hpp
GUI/PublishDialog.cpp
GUI/PublishDialog.hpp
GUI/PublishSettingsDialog.cpp
GUI/PublishSettingsDialog.hpp
GUI/PurgeModeDialog.cpp
GUI/PurgeModeDialog.hpp
GUI/RammingChart.cpp
+245
View File
@@ -0,0 +1,245 @@
#include "ConfigValueFormatter.hpp"
#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "I18N.hpp"
#include "GUI.hpp"
#include "Field.hpp"
namespace Slic3r {
namespace GUI {
std::string get_pure_opt_key(const std::string& opt_key)
{
std::string pure_key = opt_key;
const int pos = pure_key.find("#");
if (pos > 0)
boost::erase_tail(pure_key, pure_key.size() - pos);
return pure_key;
}
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill, int idx)
{
const ConfigOptionDef& def = config.def()->options.at(opt_key);
const std::vector<std::string>& names = def.enum_labels;//ConfigOptionEnum<T>::get_enum_names();
int val = 0;
if (idx >= 0)
val = dynamic_cast<const ConfigOptionInts*>(config.option(opt_key))->get_at(idx);
else
val = config.option(opt_key)->getInt();
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
// So we should "convert" val to the correct one
if (is_infill) {
for (auto key_val : *def.enum_keys_map)
if (int(key_val.second) == val) {
auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first);
if (it == def.enum_values.end())
return "";
return from_u8(_utf8(names[it - def.enum_values.begin()]));
}
return _L("Undefined");
}
return from_u8(_utf8(names[val]));
}
wxString get_full_label(const std::string& opt_key, const DynamicPrintConfig& config)
{
const std::string pure_key = get_pure_opt_key(opt_key);
auto option = config.option(pure_key);
if (!option || option->is_nil())
return _L("N/A");
const ConfigOptionDef* opt = config.def()->get(pure_key);
return opt->full_label.empty() ? opt->label : opt->full_label;
}
wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config)
{
int orig_opt_idx = -1;
int opt_idx = -1;
int pos = opt_key.find("#");
std::string temp_str = opt_key;
if (pos > 0) {
boost::erase_head(temp_str, pos + 1);
orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
}
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
const std::string pure_key = get_pure_opt_key(opt_key);
auto option = config.option(pure_key);
if (!option) {
return _L("N/A");
}
auto opt_vector = dynamic_cast<const ConfigOptionVectorBase *>(option);
if (option->is_scalar() && config.option(pure_key)->is_nil() ||
option->is_vector() && opt_vector && opt_idx >= 0 && opt_idx < opt_vector->size() && opt_vector->is_nil(opt_idx))
return _L("N/A");
wxString out;
const ConfigOptionDef* opt = config.def()->get(pure_key);
bool is_nullable = opt->nullable;
switch (opt->type) {
case coInt:
return from_u8((boost::format("%1%") % config.opt_int(pure_key)).str());
case coInts: {
if (is_nullable) {
auto values = config.opt<ConfigOptionIntsNullable>(pure_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
}
else {
auto values = config.opt<ConfigOptionInts>(pure_key);
if (orig_opt_idx >= 0 && orig_opt_idx < values->size()) {
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
}
else {
std::string value_str;
for (int i = 0; i < values->size(); i++) {
value_str += std::to_string(values->get_at(i));
if (i != values->size() - 1) {
value_str += ",";
}
}
return from_u8(value_str);
}
}
return _L("Undefined");
}
case coBool:
return config.opt_bool(pure_key) ? "true" : "false";
case coBools: {
if (is_nullable) {
auto values = config.opt<ConfigOptionBoolsNullable>(pure_key);
if (opt_idx < values->size())
return values->get_at(opt_idx) ? "true" : "false";
}
else {
auto values = config.opt<ConfigOptionBools>(pure_key);
if (opt_idx < values->size())
return values->get_at(opt_idx) ? "true" : "false";
}
return _L("Undefined");
}
case coPercent:
return from_u8((boost::format("%1%%%") % int(config.optptr(pure_key)->getFloat())).str());
case coPercents: {
if (is_nullable) {
auto values = config.opt<ConfigOptionPercentsNullable>(pure_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
}
else {
auto values = config.opt<ConfigOptionPercents>(pure_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
}
return _L("Undefined");
}
case coFloat:
return double_to_string(config.opt_float(pure_key));
case coFloats: {
if (is_nullable) {
auto values = config.opt<ConfigOptionFloatsNullable>(pure_key);
if (opt_idx < values->size())
return double_to_string(values->get_at(opt_idx));
}
else {
auto values = config.opt<ConfigOptionFloats>(pure_key);
if (values && opt_idx < values->size())
return double_to_string(values->get_at(opt_idx));
}
return _L("Undefined");
}
case coString:
return from_u8(config.opt_string(pure_key));
case coStrings: {
const ConfigOptionStrings* strings = config.opt<ConfigOptionStrings>(pure_key);
if (strings) {
if (pure_key == "compatible_printers" || pure_key == "compatible_prints") {
if (strings->empty())
return _L("All");
for (size_t id = 0; id < strings->size(); id++)
out += from_u8(strings->get_at(id)) + "\n";
out.RemoveLast(1);
return out;
}
if (!strings->empty() && opt_idx < strings->values.size())
return from_u8(strings->get_at(opt_idx));
}
break;
}
case coFloatOrPercent: {
const ConfigOptionFloatOrPercent* opt = config.opt<ConfigOptionFloatOrPercent>(pure_key);
if (opt)
out = double_to_string(opt->value) + (opt->percent ? "%" : "");
return out;
}
case coEnum: {
return get_string_from_enum(pure_key, config,
pure_key == "top_surface_pattern" ||
pure_key == "bottom_surface_pattern" ||
pure_key == "internal_solid_infill_pattern" ||
pure_key == "sparse_infill_pattern" ||
pure_key == "ironing_pattern" ||
pure_key == "support_ironing_pattern" ||
pure_key == "support_pattern" ||
pure_key == "support_interface_pattern")
;
}
case coEnums: {
return get_string_from_enum(pure_key, config,
pure_key == "top_surface_pattern" ||
pure_key == "bottom_surface_pattern" ||
pure_key == "internal_solid_infill_pattern" ||
pure_key == "sparse_infill_pattern" ||
pure_key == "ironing_pattern" ||
pure_key == "support_ironing_pattern" ||
pure_key == "support_pattern" ||
pure_key == "support_interface_pattern"
, opt_idx);
}
case coPoint: {
Vec2d val = config.opt<ConfigOptionPoint>(pure_key)->value;
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}
case coPoints: {
//BBS: add bed_exclude_area
if (pure_key == "printable_area" || pure_key == "thumbnails") {
ConfigOptionPoints points = *config.option<ConfigOptionPoints>(pure_key);
//BuildVolume build_volume = {points.values, 0.};
return get_thumbnails_string(points.values);
}
else if (pure_key == "bed_exclude_area") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(pure_key)->values);
}
else if (pure_key == "head_wrap_detect_zone") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(pure_key)->values);
}
else if (pure_key == "wrapping_exclude_area") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(pure_key)->values);
}
Vec2d val = config.opt<ConfigOptionPoints>(pure_key)->get_at(opt_idx);
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}
default:
break;
}
return out;
}
} // namespace GUI
} // namespace Slic3r
+31
View File
@@ -0,0 +1,31 @@
#ifndef slic3r_ConfigValueFormatter_hpp_
#define slic3r_ConfigValueFormatter_hpp_
#include <string>
#include <wx/string.h>
namespace Slic3r {
class DynamicPrintConfig;
namespace GUI {
// Return the value of the given option (identified by opt_key, which may contain
// a "#<index>" suffix) formatted as a human readable string.
wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config);
// Return the full label of the given option (identified by opt_key, which may contain
// a "#<index>" suffix). Returns "N/A" when the option is not set.
wxString get_full_label(const std::string& opt_key, const DynamicPrintConfig& config);
// Strip the "#<index>" suffix (if any) from the given option key.
std::string get_pure_opt_key(const std::string& opt_key);
// Return the localized label of the currently selected value of an enum option.
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1);
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_ConfigValueFormatter_hpp_
+20
View File
@@ -54,6 +54,7 @@
#include "GUI_App.hpp"
#include "UnsavedChangesDialog.hpp"
#include "PublishSettingsDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
#include "GUI_Factories.hpp"
@@ -2821,6 +2822,25 @@ void MainFrame::init_menubar_as_editor()
[this](){return m_plater != nullptr && can_save_as(); }, this);
#endif
// BBS: publish settings
fileMenu->AppendSeparator();
auto publish_handler = [this](wxCommandEvent&) {
if (!m_plater) return;
PublishSettingsDialog dlg(this);
if (dlg.ShowModal() != wxID_OK) return;
m_plater->export_published_3mf(dlg.GetPublishedKeys(), dlg.GetPublishedMaterialKeys());
};
#ifndef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Publish Settings") + dots, _L("Export a 3MF file with the selected settings embedded"),
publish_handler, "menu_publish", nullptr,
[this](){return can_export_model(); }, this);
#else
append_menu_item(fileMenu, wxID_ANY, _L("Publish Settings") + dots, _L("Export a 3MF file with the selected settings embedded"),
publish_handler, "", nullptr,
[this](){return can_export_model(); }, this);
#endif
fileMenu->AppendSeparator();
+142 -13
View File
@@ -1,12 +1,10 @@
#include "Plater.hpp"
#include "../Utils/NetworkAgent.hpp"
#include "../Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r_version.h"
#include <cstddef>
#include <algorithm>
#include <chrono>
#include <numeric>
#include <limits>
#include <optional>
@@ -16,8 +14,6 @@
#include <vector>
#include <string>
#include <regex>
#include <future>
#include <thread>
#include <atomic>
#include <mutex>
#include <boost/algorithm/string.hpp>
@@ -69,7 +65,6 @@
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/SLA/ReprojectPointsOnMesh.hpp"
#include "libslic3r/Polygon.hpp"
@@ -78,16 +73,15 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/PublishSettings.hpp"
#include "slic3r/Utils/CrealityPrint.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ObjColorUtils.hpp"
// For stl export
#include "libslic3r/CSGMesh/ModelToCSGMesh.hpp"
#include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GuiColor.hpp"
#include "GUI_ObjectList.hpp"
#ifdef __WXGTK__
#include "LinuxDisplayBackend.hpp"
@@ -123,7 +117,6 @@
#include "SendMultiMachinePage.hpp"
#include "SendToPrinter.hpp"
#include "PublishDialog.hpp"
#include "ModelMall.hpp"
#include "ConfigWizard.hpp"
#include "SyncAmsInfoDialog.hpp"
#include "../Utils/ASCIIFolding.hpp"
@@ -149,7 +142,6 @@
#include "ParamsDialog.hpp"
#include "ImageDPIFrame.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/RadioGroup.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/Button.hpp"
@@ -5459,7 +5451,7 @@ struct Plater::priv
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false, bool auto_drop = true);
fs::path get_export_file_path(GUI::FileType file_type);
wxString get_export_file(GUI::FileType file_type);
wxString get_export_file(GUI::FileType file_type, const wxString& title = {});
// BBS
void load_auxiliary_files();
@@ -7214,6 +7206,64 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
}
// BBS: a "published" 3MF project carries a flag plus a list of author-selected
// setting keys. When present, keep the user's currently-selected presets and
// overlay only the published keys onto the edited presets on load.
PublishedConfig published_config;
if (model.model_info != nullptr) {
auto published_it = model.model_info->metadata_items.find("published");
if (published_it != model.model_info->metadata_items.end() &&
(published_it->second == "true" || published_it->second == "1")) {
published_config.published = true;
auto keys_it = model.model_info->metadata_items.find("published_keys");
if (keys_it != model.model_info->metadata_items.end()) {
try {
auto j = nlohmann::json::parse(keys_it->second);
if (j.is_array())
for (const auto &k : j)
if (k.is_string())
published_config.published_keys.emplace_back(k.get<std::string>());
} catch (...) {
// Ignore malformed published_keys; the project still loads normally.
}
}
auto material_keys_it = model.model_info->metadata_items.find("published_material_keys");
if (material_keys_it != model.model_info->metadata_items.end()) {
try {
auto jm = nlohmann::json::parse(material_keys_it->second);
if (jm.is_array())
for (const auto &m : jm) {
// Malformed entries are skipped individually.
if (!m.is_object())
continue;
PublishedMaterialEntry entry;
const auto mat_it = m.find("material");
if (mat_it != m.end() && mat_it->is_object()) {
const auto &mat = *mat_it;
if (mat.contains("filament_type") && mat["filament_type"].is_string())
entry.filament_type = mat["filament_type"].get<std::string>();
if (mat.contains("filament_vendor") && mat["filament_vendor"].is_string())
entry.filament_vendor = mat["filament_vendor"].get<std::string>();
if (mat.contains("filament_id") && mat["filament_id"].is_string())
entry.filament_id = mat["filament_id"].get<std::string>();
}
if (m.contains("slot") && m["slot"].is_number_integer())
entry.slot = m["slot"].get<int>();
const auto entry_keys_it = m.find("keys");
if (entry_keys_it != m.end() && entry_keys_it->is_array())
for (const auto &k : *entry_keys_it)
if (k.is_string())
entry.keys.emplace_back(k.get<std::string>());
published_config.material_keys.emplace_back(std::move(entry));
}
} catch (...) {
// Ignore malformed published_material_keys; the project still loads normally.
}
}
}
}
if (load_config) {
if (!config.empty()) {
Preset::normalize(config);
@@ -7317,7 +7367,16 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (wipe_tower_y_opt)
file_wipe_tower_y = *wipe_tower_y_opt;
preset_bundle->load_config_model(filename.string(), std::move(config), file_version);
preset_bundle->load_config_model(filename.string(), std::move(config), file_version, &published_config);
// BBS: notify the user about published settings that could not be applied.
if (!published_config.skipped_keys.empty()) {
NotificationManager *notify_manager = q->get_notification_manager();
std::string message = _u8L("Some published settings could not be applied:");
for (const std::string &key : published_config.skipped_keys)
message += "\n-" + key;
notify_manager->bbl_show_3mf_warn_notification(message);
}
ConfigOption* bed_type_opt = preset_bundle->project_config.option("curr_bed_type");
if (bed_type_opt != nullptr) {
@@ -8179,7 +8238,7 @@ fs::path Plater::priv::get_export_file_path(GUI::FileType file_type)
return output_file;
}
wxString Plater::priv::get_export_file(GUI::FileType file_type)
wxString Plater::priv::get_export_file(GUI::FileType file_type, const wxString& title)
{
wxString wildcard;
switch (file_type) {
@@ -8222,7 +8281,7 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
case FT_3MF:
{
output_file.replace_extension("3mf");
dlg_title = _L("Save file as");
dlg_title = title.empty() ? _L("Save file as") : title;
break;
}
case FT_OBJ:
@@ -16105,6 +16164,76 @@ void Plater::export_core_3mf()
export_3mf(path_u8, SaveStrategy::Silence);
}
// Export the current project as a "published" 3MF. This is a pure export: unlike save_project(),
// it never touches the project's file name, dirty state, backup path or title, and the
// published metadata is attached to the model only for the duration of the export so the
// in-memory project stays exactly as it was (a later Save Project produces a normal 3MF).
int Plater::export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys)
{
wxString path = p->get_export_file(FT_3MF, _L("Publish 3MF file as:"));
if (path.empty() || path == "<cancel>")
return wxID_CANCEL;
nlohmann::json j = nlohmann::json::array();
for (const std::string& key : published_keys)
j.push_back(key);
nlohmann::json jm = nlohmann::json::array();
for (const Slic3r::PublishedMaterialEntry& e : material_keys)
jm.push_back({ {"material", {{"filament_type", e.filament_type}, {"filament_vendor", e.filament_vendor}, {"filament_id", e.filament_id}}}, {"slot", e.slot}, {"keys", e.keys} });
Model& model = this->model();
// Remember the previous metadata state so it can be restored after the export, keeping the
// in-memory project pristine (the published flag lives only in the exported file).
const bool had_model_info = (model.model_info != nullptr);
const bool had_published = had_model_info && (model.model_info->metadata_items.find("published") != model.model_info->metadata_items.end());
const bool had_published_keys = had_model_info && (model.model_info->metadata_items.find("published_keys") != model.model_info->metadata_items.end());
const bool had_material_keys = had_model_info && (model.model_info->metadata_items.find("published_material_keys") != model.model_info->metadata_items.end());
const std::string prev_published = had_published ? model.model_info->metadata_items.at("published") : std::string();
const std::string prev_published_keys = had_published_keys ? model.model_info->metadata_items.at("published_keys") : std::string();
const std::string prev_material_keys = had_material_keys ? model.model_info->metadata_items.at("published_material_keys") : std::string();
if (model.model_info == nullptr)
model.model_info = std::make_shared<ModelInfo>();
model.model_info->metadata_items["published"] = "1";
model.model_info->metadata_items["published_keys"] = j.dump();
model.model_info->metadata_items["published_material_keys"] = jm.dump();
// Same file layout save_project() uses for its project files, plus SaveStrategy::Silence:
// without it export_3mf() calls set_project_filename() on success, which would make this
// pure export the current project file. Silence keeps the project state untouched, exactly
// like export_core_3mf().
auto save_strategy = SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence;
bool full_pathnames = wxGetApp().app_config->get_bool("export_sources_full_pathnames");
if (full_pathnames)
save_strategy = save_strategy | SaveStrategy::FullPathSources;
const int ret = export_3mf(into_path(path), save_strategy);
// Restore the previous metadata state (both on success and on failure).
if (!had_model_info) {
model.model_info = nullptr;
} else {
if (had_published)
model.model_info->metadata_items["published"] = prev_published;
else
model.model_info->metadata_items.erase("published");
if (had_published_keys)
model.model_info->metadata_items["published_keys"] = prev_published_keys;
else
model.model_info->metadata_items.erase("published_keys");
if (had_material_keys)
model.model_info->metadata_items["published_material_keys"] = prev_material_keys;
else
model.model_info->metadata_items.erase("published_material_keys");
}
if (ret < 0) {
MessageDialog(this, _L("Failed to export the published 3MF file.\nPlease check whether the folder exists online or if other programs have the file open."),
_L("Publish Settings"), wxOK | wxICON_WARNING).ShowModal();
return wxID_CANCEL;
}
return wxID_YES;
}
Preset *get_printer_preset(const MachineObject *obj)
{
if (!obj)
+4
View File
@@ -495,6 +495,10 @@ public:
void export_gcode_3mf(bool export_all = false);
void send_gcode_finish(wxString name);
void export_core_3mf();
// Export the current project as a "published" 3MF: embeds the author-selected settings
// (published_keys / published_material_keys) into the file's metadata. A pure export: the
// in-memory project (filename, dirty state, model_info metadata) is left untouched.
int export_published_3mf(const std::vector<std::string>& published_keys, const std::vector<Slic3r::PublishedMaterialEntry>& material_keys);
static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {});
void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false, FileType file_type = FT_STL);
//BBS: remove amf
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
#ifndef slic3r_GUI_PublishSettingsDialog_hpp_
#define slic3r_GUI_PublishSettingsDialog_hpp_
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "libslic3r/PublishSettings.hpp"
#include <wx/wx.h>
#include <wx/scrolwin.h>
#include <wx/menu.h>
#include <vector>
#include <string>
#include <functional>
// Forward declarations (all are global classes, see Widgets/TextInput.hpp,
// Widgets/StaticLine.hpp and the CollapseChevron definition in the .cpp).
class TextInput;
class StaticLine;
class CollapseChevron;
namespace Slic3r { namespace GUI {
// Dialog that lets a model author select which settings get embedded in a 3MF.
// Settings are grouped the way the tabs show them: the process (print) pages,
// the printer's per-extruder retraction settings, and one section per material
// used in the project (filament overrides). Each main category has a select-all
// tri-state header, subcategory (optgroup) headings, one row per setting
// (checkbox + grey value label), and both header levels are collapsible (chevron
// toggle). A search filter and an All/None / select-visible menu are provided.
// Modified (dirty) settings are pre-checked and shown bold. On OK, the print
// rows become the "published_keys" list and the material rows become the
// per-material "published_material_keys".
class PublishSettingsDialog : public DPIDialog
{
public:
PublishSettingsDialog(wxWindow* parent = nullptr);
~PublishSettingsDialog();
// The selected print-section setting keys (in display order). Keys may
// contain '#'.
std::vector<std::string> GetPublishedKeys() const;
// The selected keys grouped per material: one entry per material section
// with at least one checked key. Keys are base keys (no "#N" suffix).
std::vector<Slic3r::PublishedMaterialEntry> GetPublishedMaterialKeys() const;
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
private:
// Which part of the settings the row/category came from.
enum class Section { Print, Printer, Material };
// One selectable setting row: a checkbox (setting name) plus a value label
// and a (optional) grey unit label. key is the full config key and may carry
// a "#N" variant suffix (print/printer rows); material rows carry the base key.
struct Row
{
std::string key;
wxString category;
wxString subcategory;
wxString label;
wxString value;
wxString unit;
wxString section_title; // top-level group title, for filter matching
Section section{Section::Print};
bool dirty{false}; // matches a dirty base key: pre-checked + bold
bool matches_filter{false}; // survives the active filter (computed by apply_filter)
wxCheckBox* check{nullptr};
wxStaticText* value_label{nullptr};
wxStaticText* unit_label{nullptr};
wxSizerItem* item{nullptr}; // sizer item of this row's h-sizer in m_list_sizer
};
// A subcategory (optgroup) heading. Rows store indices into m_rows.
struct Subcategory
{
wxString title;
::StaticLine* header{nullptr}; // null when the title is empty
bool collapsed{false};
CollapseChevron* chevron{nullptr};
wxSizerItem* item{nullptr}; // sizer item of the header h-sizer in m_list_sizer
std::vector<size_t> rows;
};
// A main category with its select-all tri-state header.
struct Category
{
wxString title;
Section section{Section::Print};
size_t group{0}; // index into m_sections
std::string icon_name; // bitmap name; empty = no icon
wxStaticBitmap* icon{nullptr}; // 18px category icon (null when icon_name empty)
wxCheckBox* header{nullptr}; // select-all tri-state
// Material opt-in: the master checkbox carries the material title and
// gates whether this material's keys may be exported.
bool master{false};
wxCheckBox* master_check{nullptr};
bool collapsed{false};
CollapseChevron* chevron{nullptr};
wxSizerItem* item{nullptr}; // sizer item of the header h-sizer in m_list_sizer
// Material identity, only for Section::Material categories.
std::string filament_type;
std::string filament_vendor;
std::string filament_id;
// The author's 0-based filament slot this material section represents.
size_t filament_slot{0};
std::vector<Subcategory> subs;
std::vector<size_t> rows; // flattened rows, for the tri-state math
};
// A top-level section group mirroring the editor sidebar. Categories are
// nested inside.
struct SectionGroup
{
wxString title; // _L("Printer") / _L("Filament") / _L("Process")
Section kind{Section::Print}; // maps 1:1 to the display group
std::string icon_name; // "printer" / "filament" / "process"
wxStaticBitmap* icon{nullptr}; // 18px, like Category::icon
wxCheckBox* header{nullptr}; // tri-state select-all; nullptr for the Filament group
::StaticLine* header_line{nullptr}; // Filament group's clickable title
CollapseChevron* chevron{nullptr};
wxSizerItem* item{nullptr};
bool collapsed{false};
std::vector<size_t> categories; // indices into m_categories
};
void build_option_model();
void apply_filter(const wxString& filter_text);
void select_all(bool value);
void select_visible(bool value);
void show_menu(wxMouseEvent& evt);
void update_category_header(Category& category);
void set_row_bold(Row& row, bool bold);
void on_category_toggle(size_t category_index);
// Material opt-in toggled: enables/disables the material's rows + tri-state
// and resyncs the header.
void on_master_toggle(size_t category_index);
// Collapse/expand a category or subcategory; resyncs visibility + chevrons.
void toggle_category(size_t category_index);
void toggle_subcategory(size_t category_index, size_t subcategory_index);
// Find-or-create the top-level section group for a Section kind (builds its
// header row on first use).
size_t section_group_for(Section kind);
// Top-level section group: select-all tri-state toggled / collapse/expand /
// header resync.
void on_section_toggle(size_t section_index);
void toggle_section(size_t section_index);
void update_section_header(SectionGroup& section);
// Single pass over categories/subs/rows: shows an item iff it is not hidden
// by the filter and (for subs/rows) by a collapsed ancestor. Flips the
// header chevrons. Only reads matches_filter; never re-runs filter matching.
void apply_visibility();
// Creates a collapse chevron with a hand cursor; clicking it (with the given
// mouse event, LEFT_DOWN for categories / LEFT_UP for subcategories,
// matching the header's own binding) invokes the toggle.
CollapseChevron* create_chevron(wxWindow* parent, const wxEventTypeTag<wxMouseEvent>& event_type, std::function<void()> toggle);
wxScrolledWindow* m_scroll{nullptr};
wxBoxSizer* m_list_sizer{nullptr}; // vertical sizer of the scrolled window
wxBoxSizer* m_fb_sizer{nullptr}; // "All"/"None" buttons sizer
TextInput* m_filter_box{nullptr};
wxTextCtrl* m_filter_ctrl{nullptr};
wxStaticBitmap* m_menu_button{nullptr};
wxStaticText* m_info{nullptr};
wxString m_info_nonsel;
wxString m_info_allsel;
wxString m_info_empty;
ScalableBitmap m_search;
ScalableBitmap m_menu;
std::vector<Row> m_rows;
std::vector<Category> m_categories;
// Fixed display order enforced by phase order in build_option_model():
// Printer, then Filament, then Process.
std::vector<SectionGroup> m_sections;
};
}} // namespace Slic3r::GUI
#endif // slic3r_GUI_PublishSettingsDialog_hpp_
+6
View File
@@ -5675,6 +5675,9 @@ if (is_marlin_flavor)
optgroup->append_single_option_line("extruder_offset", "printer_extruder_basic_information#extruder-offset-position", extruder_idx);
//BBS: don't show retract related config menu in machine page
// Keep this optgroup's options in sync with publishable_printer_retraction_options()
// in libslic3r/PublishSettings.hpp: the published-3MF printer allowlist is its union
// with the Z-Hop optgroup below.
optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
optgroup->append_single_option_line("retraction_length", "printer_extruder_retraction#length", extruder_idx);
optgroup->append_single_option_line("retract_restart_extra", "printer_extruder_retraction#extra-length-on-restart", extruder_idx);
@@ -5688,6 +5691,9 @@ if (is_marlin_flavor)
// Orca
optgroup->append_single_option_line("retract_after_wipe", "printer_extruder_retraction#retract-amount-after-wipe", extruder_idx);
// Keep this optgroup's options in sync with publishable_printer_z_hop_options()
// in libslic3r/PublishSettings.hpp: the published-3MF printer allowlist is its union
// with the Retraction optgroup above.
optgroup = page->new_optgroup(L("Z-Hop"), L"param_extruder_lift_enforcement");
optgroup->append_single_option_line("retract_lift_enforce", "printer_extruder_z_hop#on-surfaces", extruder_idx);
optgroup->append_single_option_line("z_hop_types", "printer_extruder_z_hop#z-hop-type", extruder_idx);
+1 -2
View File
@@ -30,7 +30,6 @@
#include <memory>
//#include "BedShapeDialog.hpp"
#include "Event.hpp"
#include "wxExtensions.hpp"
#include "ConfigManipulation.hpp"
#include "OptionsGroup.hpp"
@@ -38,7 +37,6 @@
//BBS: GUI refactor
#include "Notebook.hpp"
#include "ParamsPanel.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/CheckBox.hpp" // ORCA
@@ -472,6 +470,7 @@ protected:
std::string m_last_sparse_infill_rotate_template_value;
ConfigManipulation get_config_manipulation();
friend class EditGCodeDialog;
friend class PublishSettingsDialog;
};
class TabPrint : public Tab
+1 -222
View File
@@ -12,6 +12,7 @@
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Color.hpp"
#include "format.hpp"
#include "ConfigValueFormatter.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "Tab.hpp"
@@ -571,14 +572,6 @@ void DiffModel::Clear()
}
static std::string get_pure_opt_key(std::string opt_key)
{
const int pos = opt_key.find("#");
if (pos > 0)
boost::erase_tail(opt_key, opt_key.size() - pos);
return opt_key;
}
// ----------------------------------------------------------------------------
// DiffViewCtrl
// ----------------------------------------------------------------------------
@@ -1212,32 +1205,6 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s
return true;
}
wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false, int idx = -1)
{
const ConfigOptionDef& def = config.def()->options.at(opt_key);
const std::vector<std::string>& names = def.enum_labels;//ConfigOptionEnum<T>::get_enum_names();
int val = 0;
if (idx >= 0)
val = dynamic_cast<const ConfigOptionInts*>(config.option(opt_key))->get_at(idx);
else
val = config.option(opt_key)->getInt();
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
// So we should "convert" val to the correct one
if (is_infill) {
for (auto key_val : *def.enum_keys_map)
if (int(key_val.second) == val) {
auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first);
if (it == def.enum_values.end())
return "";
return from_u8(_utf8(names[it - def.enum_values.begin()]));
}
return _L("Undefined");
}
return from_u8(_utf8(names[val]));
}
// BBS
#if 0
static size_t get_id_from_opt_key(std::string opt_key)
@@ -1251,194 +1218,6 @@ static size_t get_id_from_opt_key(std::string opt_key)
}
#endif
static wxString get_full_label(std::string opt_key, const DynamicPrintConfig& config)
{
opt_key = get_pure_opt_key(opt_key);
auto option = config.option(opt_key);
if (!option || option->is_nil())
return _L("N/A");
const ConfigOptionDef* opt = config.def()->get(opt_key);
return opt->full_label.empty() ? opt->label : opt->full_label;
}
static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config)
{
int orig_opt_idx = -1;
int opt_idx = -1;
int pos = opt_key.find("#");
std::string temp_str = opt_key;
if (pos > 0) {
boost::erase_head(temp_str, pos + 1);
orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
}
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
opt_key = get_pure_opt_key(opt_key);
auto option = config.option(opt_key);
if (!option) {
return _L("N/A");
}
auto opt_vector = dynamic_cast<const ConfigOptionVectorBase *>(option);
if (option->is_scalar() && config.option(opt_key)->is_nil() ||
option->is_vector() && opt_vector && opt_idx >= 0 && opt_idx < opt_vector->size() && opt_vector->is_nil(opt_idx))
return _L("N/A");
wxString out;
const ConfigOptionDef* opt = config.def()->get(opt_key);
bool is_nullable = opt->nullable;
switch (opt->type) {
case coInt:
return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str());
case coInts: {
if (is_nullable) {
auto values = config.opt<ConfigOptionIntsNullable>(opt_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
}
else {
auto values = config.opt<ConfigOptionInts>(opt_key);
if (orig_opt_idx >= 0 && orig_opt_idx < values->size()) {
return from_u8((boost::format("%1%") % values->get_at(opt_idx)).str());
}
else {
std::string value_str;
for (int i = 0; i < values->size(); i++) {
value_str += std::to_string(values->get_at(i));
if (i != values->size() - 1) {
value_str += ",";
}
}
return from_u8(value_str);
}
}
return _L("Undefined");
}
case coBool:
return config.opt_bool(opt_key) ? "true" : "false";
case coBools: {
if (is_nullable) {
auto values = config.opt<ConfigOptionBoolsNullable>(opt_key);
if (opt_idx < values->size())
return values->get_at(opt_idx) ? "true" : "false";
}
else {
auto values = config.opt<ConfigOptionBools>(opt_key);
if (opt_idx < values->size())
return values->get_at(opt_idx) ? "true" : "false";
}
return _L("Undefined");
}
case coPercent:
return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str());
case coPercents: {
if (is_nullable) {
auto values = config.opt<ConfigOptionPercentsNullable>(opt_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
}
else {
auto values = config.opt<ConfigOptionPercents>(opt_key);
if (opt_idx < values->size())
return from_u8((boost::format("%1%%%") % values->get_at(opt_idx)).str());
}
return _L("Undefined");
}
case coFloat:
return double_to_string(config.opt_float(opt_key));
case coFloats: {
if (is_nullable) {
auto values = config.opt<ConfigOptionFloatsNullable>(opt_key);
if (opt_idx < values->size())
return double_to_string(values->get_at(opt_idx));
}
else {
auto values = config.opt<ConfigOptionFloats>(opt_key);
if (values && opt_idx < values->size())
return double_to_string(values->get_at(opt_idx));
}
return _L("Undefined");
}
case coString:
return from_u8(config.opt_string(opt_key));
case coStrings: {
const ConfigOptionStrings* strings = config.opt<ConfigOptionStrings>(opt_key);
if (strings) {
if (opt_key == "compatible_printers" || opt_key == "compatible_prints") {
if (strings->empty())
return _L("All");
for (size_t id = 0; id < strings->size(); id++)
out += from_u8(strings->get_at(id)) + "\n";
out.RemoveLast(1);
return out;
}
if (!strings->empty() && opt_idx < strings->values.size())
return from_u8(strings->get_at(opt_idx));
}
break;
}
case coFloatOrPercent: {
const ConfigOptionFloatOrPercent* opt = config.opt<ConfigOptionFloatOrPercent>(opt_key);
if (opt)
out = double_to_string(opt->value) + (opt->percent ? "%" : "");
return out;
}
case coEnum: {
return get_string_from_enum(opt_key, config,
opt_key == "top_surface_pattern" ||
opt_key == "bottom_surface_pattern" ||
opt_key == "internal_solid_infill_pattern" ||
opt_key == "sparse_infill_pattern" ||
opt_key == "ironing_pattern" ||
opt_key == "support_ironing_pattern" ||
opt_key == "support_pattern" ||
opt_key == "support_interface_pattern")
;
}
case coEnums: {
return get_string_from_enum(opt_key, config,
opt_key == "top_surface_pattern" ||
opt_key == "bottom_surface_pattern" ||
opt_key == "internal_solid_infill_pattern" ||
opt_key == "sparse_infill_pattern" ||
opt_key == "ironing_pattern" ||
opt_key == "support_ironing_pattern" ||
opt_key == "support_pattern" ||
opt_key == "support_interface_pattern"
, opt_idx);
}
case coPoint: {
Vec2d val = config.opt<ConfigOptionPoint>(opt_key)->value;
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}
case coPoints: {
//BBS: add bed_exclude_area
if (opt_key == "printable_area" || opt_key == "thumbnails") {
ConfigOptionPoints points = *config.option<ConfigOptionPoints>(opt_key);
//BuildVolume build_volume = {points.values, 0.};
return get_thumbnails_string(points.values);
}
else if (opt_key == "bed_exclude_area") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
}
else if (opt_key == "head_wrap_detect_zone") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
}
else if (opt_key == "wrapping_exclude_area") {
return get_thumbnails_string(config.option<ConfigOptionPoints>(opt_key)->values);
}
Vec2d val = config.opt<ConfigOptionPoints>(opt_key)->get_at(opt_idx);
return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());
}
default:
break;
}
return out;
}
void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header)
{
PresetCollection* presets = dependent_presets;