mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 03:11:47 +00:00
Bug fixes and test cases for import filament of published 3MF. Update translations
This commit is contained in:
@@ -11,18 +11,16 @@ 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.
|
||||
// Human-readable value of opt_key (may carry a "#<index>" suffix) in config.
|
||||
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.
|
||||
// Full label of opt_key; "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.
|
||||
// Strip the "#<index>" suffix (if any) from the 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.
|
||||
// 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
|
||||
|
||||
@@ -2837,11 +2837,11 @@ void MainFrame::init_menubar_as_editor()
|
||||
auto publish_handler = [this](wxCommandEvent&) { publish_project(); };
|
||||
|
||||
#ifndef __APPLE__
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _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") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Publish 3MF") + dots + "\t" + ctrl + shift + "E", _L("Export a 3MF file with the selected settings embedded"),
|
||||
publish_handler, "", nullptr,
|
||||
[this](){return can_export_model(); }, this);
|
||||
#endif
|
||||
|
||||
+23
-25
@@ -7208,9 +7208,8 @@ 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.
|
||||
// BBS: a "published" 3MF carries a flag plus the author-selected setting keys;
|
||||
// on load keep the user's current presets and overlay only those keys.
|
||||
PublishedConfig published_config;
|
||||
if (model.model_info != nullptr) {
|
||||
auto published_it = model.model_info->metadata_items.find("published");
|
||||
@@ -7249,6 +7248,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
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 (mat.contains("setting_id") && mat["setting_id"].is_string())
|
||||
entry.setting_id = mat["setting_id"].get<std::string>();
|
||||
}
|
||||
if (m.contains("slot") && m["slot"].is_number_integer())
|
||||
entry.slot = m["slot"].get<int>();
|
||||
@@ -7257,7 +7258,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
for (const auto &k : *entry_keys_it)
|
||||
if (k.is_string())
|
||||
entry.keys.emplace_back(k.get<std::string>());
|
||||
// Filament-publishing-v2 fields; absent in legacy files.
|
||||
// Fields always written by the current exporter.
|
||||
if (m.contains("full") && m["full"].is_boolean())
|
||||
entry.full = m["full"].get<bool>();
|
||||
const auto entry_full_keys_it = m.find("full_keys");
|
||||
@@ -7282,10 +7283,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: a "published" 3MF behaves like a new project once loaded: the file's path
|
||||
// must not become the project filename (Save/Ctrl-S would otherwise overwrite the
|
||||
// shared file), and the published metadata is consumed by the overlay above and
|
||||
// stripped so a later save produces a normal, unpublished 3MF.
|
||||
// BBS: a "published" 3MF loads as a new project: its path must not become the
|
||||
// project filename (Save/Ctrl-S would overwrite the shared file), and the
|
||||
// published metadata is consumed above and stripped so a later save is a normal
|
||||
// unpublished 3MF.
|
||||
if (published_out != nullptr && published_config.published)
|
||||
*published_out = true;
|
||||
if (published_config.published && load_config && this->model.model_info != nullptr) {
|
||||
@@ -13309,10 +13310,10 @@ void Plater::load_project(wxString const& filename2,
|
||||
p->set_project_filename(filename);
|
||||
}
|
||||
else if (loaded_published) {
|
||||
// A "published" 3MF loads as a new project: the shared file's path must not become
|
||||
// the project filename, so Save/Ctrl-S prompts for a destination instead of
|
||||
// overwriting the published file. reset() above already cleared the project name
|
||||
// and folder; restore the default new-project title and keep the file in recents.
|
||||
// A "published" 3MF loads as a new project: its path must not become the project
|
||||
// filename (Save/Ctrl-S prompts for a destination instead of overwriting it);
|
||||
// reset() already cleared the project name, so restore the default title and keep
|
||||
// the file in recents.
|
||||
p->set_project_name(_L("Untitled"));
|
||||
if (!filename.IsEmpty())
|
||||
wxGetApp().mainframe->add_to_recent_projects(filename);
|
||||
@@ -16225,10 +16226,9 @@ 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).
|
||||
// Export the current project as a "published" 3MF: a pure export that never touches the
|
||||
// project's file name, dirty state, backup path or title, and attaches the published metadata
|
||||
// to the model only for the duration of the export (a later Save Project is 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:"));
|
||||
@@ -16240,14 +16240,14 @@ int Plater::export_published_3mf(const std::vector<std::string>& 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},
|
||||
jm.push_back({ {"material", {{"filament_type", e.filament_type}, {"filament_vendor", e.filament_vendor}, {"filament_id", e.filament_id}, {"setting_id", e.setting_id}}}, {"slot", e.slot}, {"keys", e.keys},
|
||||
{"full", e.full}, {"full_keys", e.full_keys},
|
||||
{"publish_type", e.publish_type}, {"type", e.publish_type_value},
|
||||
{"publish_color", e.publish_color}, {"color", e.color} });
|
||||
|
||||
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).
|
||||
// Save the previous metadata 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());
|
||||
@@ -16261,15 +16261,13 @@ int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
|
||||
model.model_info->metadata_items["published_keys"] = j.dump();
|
||||
model.model_info->metadata_items["published_material_keys"] = jm.dump();
|
||||
|
||||
// Minimal published export: filter full_config to only the published keys, material keys,
|
||||
// identity fields, and plate geometry keys, and omit project-embedded preset dumps.
|
||||
// Minimal published export: filter full_config to the published keys, material keys,
|
||||
// identity fields and plate geometry keys, and omit project-embedded preset dumps.
|
||||
DynamicPrintConfig full_cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
||||
|
||||
// Same file layout save_project() uses for its project files, plus SaveStrategy::Silence and SaveStrategy::MinimalPublished:
|
||||
// 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().
|
||||
// Same file layout as save_project(), plus Silence (so export_3mf does not set the project
|
||||
// filename on success, keeping this a pure export like export_core_3mf()) and MinimalPublished.
|
||||
auto save_strategy = SaveStrategy::SplitModel | SaveStrategy::ShareMesh | SaveStrategy::Silence | SaveStrategy::MinimalPublished;
|
||||
bool full_pathnames = wxGetApp().app_config->get_bool("export_sources_full_pathnames");
|
||||
if (full_pathnames)
|
||||
|
||||
@@ -495,9 +495,8 @@ 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.
|
||||
// Export a "published" 3MF embedding the author-selected settings in the file metadata; a
|
||||
// pure export that leaves the in-memory project 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);
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
namespace Slic3r { namespace GUI {
|
||||
namespace {
|
||||
|
||||
// Menu ids for show_menu(). Dedicated range above the standard ids so the popup cannot
|
||||
// collide with application-level bindings (e.g. MainFrame's recent-files wxID_FILE1.. range).
|
||||
// Menu ids for show_menu(): dedicated range so the popup cannot collide with application-level
|
||||
// bindings (e.g. MainFrame's recent-files wxID_FILE1.. range).
|
||||
enum {
|
||||
kPublishSelectAll = wxID_HIGHEST + 1,
|
||||
kPublishDeselectAll,
|
||||
@@ -49,9 +49,7 @@ PublishMaterialIdentity material_identity(size_t slot, const DynamicPrintConfig&
|
||||
return identity;
|
||||
}
|
||||
|
||||
// "Generic PLA @System" -> "Generic PLA"; mirrors the alias derivation in
|
||||
// PresetBundle::load_vendor_configs_from_json (PresetBundle.cpp) and
|
||||
// PresetCollection::set_custom_preset_alias (Preset.cpp).
|
||||
// "Generic PLA @System" -> "Generic PLA"; mirrors the alias derivation in PresetBundle.cpp.
|
||||
std::string material_display_name(const std::string& preset_name)
|
||||
{
|
||||
const size_t at = preset_name.find_first_of('@');
|
||||
@@ -62,8 +60,8 @@ std::string material_display_name(const std::string& preset_name)
|
||||
return bare.empty() ? preset_name : bare;
|
||||
}
|
||||
|
||||
// Human-readable section title for a filament slot: the resolved preset name,
|
||||
// falling back to the filament type, then to the generic "Material".
|
||||
// Section title for a filament slot: the resolved preset name, then the filament type, then
|
||||
// the generic "Material".
|
||||
wxString material_title(size_t slot, const PresetBundle* bundle, const DynamicPrintConfig& full)
|
||||
{
|
||||
if (slot < bundle->filament_presets.size()) {
|
||||
@@ -165,8 +163,7 @@ PublishSettingsDialog::PublishSettingsDialog(wxWindow* parent)
|
||||
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
|
||||
|
||||
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
// Publish is always allowed: no settings selected means a publish with
|
||||
// no settings override.
|
||||
// Publish is always allowed: no settings selected means no settings override.
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); });
|
||||
@@ -183,12 +180,11 @@ PublishSettingsDialog::~PublishSettingsDialog() {}
|
||||
|
||||
void PublishSettingsDialog::build_option_model()
|
||||
{
|
||||
// Structural / non-publishable keys, shared with the published-3MF overlay
|
||||
// path (see libslic3r/PublishSettings.hpp).
|
||||
// Structural / non-publishable keys, shared with the published-3MF overlay path.
|
||||
const std::set<std::string>& denylist = publish_structural_keys();
|
||||
// Base keys already added in the print/printer sections. Printer rows share
|
||||
// this set: a base key appears once (per-extruder "#N" variants collapse to
|
||||
// the first occurrence - acceptable MVP; the per-extruder context is lost).
|
||||
// Base keys already added in the print/printer sections. Printer rows share this set:
|
||||
// per-extruder "#N" variants collapse to the first occurrence (acceptable MVP; the
|
||||
// per-extruder context is lost in the UI).
|
||||
std::set<std::string> added;
|
||||
|
||||
PresetBundle* bundle = wxGetApp().preset_bundle;
|
||||
@@ -198,17 +194,17 @@ void PublishSettingsDialog::build_option_model()
|
||||
m_info_allsel = _L("All items selected...");
|
||||
m_info_empty = _L("No matching items...");
|
||||
|
||||
// Keep the tab order explicit: Section's enum order is Print, Printer,
|
||||
// Material, while the dialog presents Printer, Filament, Process.
|
||||
// Tab order differs from Section's enum order (Print, Printer, Material): the dialog
|
||||
// presents Printer, Filament, Process.
|
||||
m_sections.reserve(3);
|
||||
const Section tab_order[] = {Section::Printer, Section::Material, Section::Print};
|
||||
for (Section kind : tab_order)
|
||||
section_group_for(kind);
|
||||
bind_tab_events();
|
||||
|
||||
// Shared per-option label/value computation; returns false when the option
|
||||
// must be skipped (denylisted / unknown / empty label). value is the pure
|
||||
// stringified value; unit is the translated sidetext (may be empty).
|
||||
// Shared per-option label/value computation; returns false when the option must be skipped
|
||||
// (denylisted / unknown / empty label). value is the stringified value; unit the translated
|
||||
// sidetext (may be empty).
|
||||
auto option_text = [&denylist, &full](const std::string& opt_id, const std::string& pure_key, wxString& label, wxString& value,
|
||||
wxString& unit) -> bool {
|
||||
if (denylist.count(pure_key) > 0)
|
||||
@@ -224,9 +220,8 @@ void PublishSettingsDialog::build_option_model()
|
||||
return true;
|
||||
};
|
||||
|
||||
// --- Phase 1: printer per-extruder retraction settings (displayed first,
|
||||
// mirroring the sidebar's Printer group). The printer tab's
|
||||
// "Extruder"/"Extruder N" pages carry the per-extruder retraction options.
|
||||
// --- Phase 1: printer per-extruder retraction settings (first, mirroring the sidebar's
|
||||
// Printer group), from the printer tab's "Extruder"/"Extruder N" pages.
|
||||
{
|
||||
size_t g = section_group_for(Section::Printer);
|
||||
category_index_for(_L("Extruder"), Section::Printer, "custom-gcode_extruder", g, 0);
|
||||
@@ -238,18 +233,17 @@ void PublishSettingsDialog::build_option_model()
|
||||
continue;
|
||||
const wxString page_title = Tab::translate_category(page->title(), tab->m_type);
|
||||
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
|
||||
// Allowlist on the untranslated optgroup title; the "Retraction
|
||||
// when switching material" group is intentionally skipped.
|
||||
// Allowlist on the untranslated optgroup title; the "Retraction when
|
||||
// switching material" group is intentionally skipped.
|
||||
if (optgroup->title != "Retraction" && optgroup->title != "Z-Hop")
|
||||
continue;
|
||||
const wxString subcategory = _(optgroup->title);
|
||||
for (const auto& opt : optgroup->opt_map()) {
|
||||
const std::string& opt_id = opt.first;
|
||||
const std::string& pure_key = opt.second.first;
|
||||
// Per-extruder "#N" variants collapse to the first base key. The row stores
|
||||
// the BASE key (whole-vector semantics on load: the size-guarded apply
|
||||
// copies the author's full vector), while the "#0" opt_id is only used to
|
||||
// display the first extruder's value.
|
||||
// Per-extruder "#N" variants collapse to the first base key. The row
|
||||
// stores the base key; GetPublishedKeys() later expands it back to one
|
||||
// "#N" entry per extruder so the load side can apply per-extruder values.
|
||||
if (!added.insert(pure_key).second)
|
||||
continue;
|
||||
wxString label, value, unit;
|
||||
@@ -264,8 +258,8 @@ void PublishSettingsDialog::build_option_model()
|
||||
}
|
||||
}
|
||||
|
||||
// --- Phase 2: per-material sections synthesized from the filament tab's
|
||||
// "Setting Overrides" page, under the Filament group.
|
||||
// --- Phase 2: per-material sections synthesized from the filament tab's "Setting
|
||||
// Overrides" page, under the Filament group.
|
||||
{
|
||||
size_t g = section_group_for(Section::Material);
|
||||
Tab* filament_tab = nullptr;
|
||||
@@ -283,17 +277,16 @@ void PublishSettingsDialog::build_option_model()
|
||||
}
|
||||
|
||||
if (overrides_page != nullptr) {
|
||||
// One section per filament slot: a 4-slot printer (e.g. 1 PLA +
|
||||
// 3 PETG) shows 4 separate pages, each disambiguated internally by
|
||||
// its colour chip and slot identity while displaying the bare name.
|
||||
// One section per filament slot (a 4-slot printer shows 4 pages), each
|
||||
// disambiguated by its colour chip and slot identity while showing the bare name.
|
||||
for (size_t slot = 0; slot < bundle->filament_presets.size(); ++slot) {
|
||||
const PublishMaterialIdentity identity = material_identity(slot, full);
|
||||
const wxString title = material_title(slot, bundle, full);
|
||||
const size_t category_index = category_index_for(title, Section::Material, "custom-gcode_filament", g, slot, identity);
|
||||
|
||||
// Filament-publishing-v2 rows: the author may require a filament colour and/or
|
||||
// a vendor-agnostic material type for this slot. They live in their own
|
||||
// optgroup so they stay visually separated from the setting rows.
|
||||
// Material requirement rows: an optional filament colour and/or a
|
||||
// vendor-agnostic material type for this slot, in their own optgroup so they
|
||||
// stay visually separated from the setting rows.
|
||||
{
|
||||
const size_t req_sub = subcategory_index_for(category_index, _L("Material"), "custom-gcode_filament");
|
||||
std::string hex;
|
||||
@@ -309,18 +302,16 @@ void PublishSettingsDialog::build_option_model()
|
||||
RowKind::Type);
|
||||
}
|
||||
|
||||
// A material section must not repeat a key; the same key may
|
||||
// appear in other material sections - that is intended.
|
||||
// A material section must not repeat a key; the same key may appear in other
|
||||
// material sections - that is intended.
|
||||
std::set<std::string> material_added;
|
||||
|
||||
for (const ConfigOptionsGroupShp& optgroup : overrides_page->m_optgroups) {
|
||||
// Allowlist on the untranslated optgroup title; the
|
||||
// "Ironing" group is intentionally skipped.
|
||||
// Allowlist on the untranslated optgroup title; "Ironing" is skipped.
|
||||
if (optgroup->title != "Retraction" && optgroup->title != "Retraction when switching material")
|
||||
continue;
|
||||
for (const auto& opt : optgroup->opt_map()) {
|
||||
// Row keys are base keys (no "#N"): the load side
|
||||
// matches the material and uses the author's slot.
|
||||
// Row keys are base keys; the load side applies them positionally.
|
||||
const std::string& opt_id = opt.first;
|
||||
std::string base = opt_id.substr(0, opt_id.find('#'));
|
||||
if (!material_added.insert(base).second)
|
||||
@@ -381,17 +372,15 @@ void PublishSettingsDialog::build_option_model()
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-check the dirty (modified) settings and mark them bold. The base-key
|
||||
// match covers all sections; collect_dirty_settings_keys already unions the
|
||||
// prints, printers and filaments of the bundle.
|
||||
// Pre-check the dirty (modified) settings and mark them bold (base-key match, across all
|
||||
// sections; collect_dirty_settings_keys unions the prints, printers and filaments).
|
||||
std::set<std::string> dirty_base;
|
||||
for (const std::string& key : collect_dirty_settings_keys(*wxGetApp().preset_bundle)) {
|
||||
auto n = key.find('#');
|
||||
dirty_base.insert(n == std::string::npos ? key : key.substr(0, n));
|
||||
}
|
||||
for (Row& row : m_rows) {
|
||||
// The Color/Type requirement rows are not "dirty overrides": they are never
|
||||
// auto-checked by the dirty pre-check.
|
||||
// The Color/Type requirement rows are not "dirty overrides": never auto-checked.
|
||||
if (row.kind != RowKind::Setting)
|
||||
continue;
|
||||
std::string base = row.key.substr(0, row.key.find('#'));
|
||||
@@ -402,8 +391,8 @@ void PublishSettingsDialog::build_option_model()
|
||||
}
|
||||
}
|
||||
|
||||
// Wire the "Full Publish" checkboxes: toggling one disables/enables the material's
|
||||
// rows. Bind by index so the lambda stays valid even if the vector is reallocated later.
|
||||
// Wire the "Full Publish" checkboxes: toggling one disables/enables the material's rows.
|
||||
// Bind by index so the lambda stays valid even if the vector is reallocated later.
|
||||
for (size_t c = 0; c < m_categories.size(); ++c)
|
||||
if (m_categories[c].full_check != nullptr)
|
||||
m_categories[c].full_check->Bind(wxEVT_CHECKBOX, [this, c](wxCommandEvent&) { on_full_toggle(c); });
|
||||
@@ -449,6 +438,7 @@ size_t PublishSettingsDialog::section_group_for(Section kind)
|
||||
section.icon_name = "process";
|
||||
break;
|
||||
}
|
||||
section.icon_bmp = ScalableBitmap(this, section.icon_name, 16);
|
||||
|
||||
constexpr long tab_style = wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_NONE | wxWANTS_CHARS |
|
||||
wxTR_FULL_ROW_HIGHLIGHT;
|
||||
@@ -466,7 +456,10 @@ size_t PublishSettingsDialog::section_group_for(Section kind)
|
||||
page_sizer->Add(section.page_host, 1, wxEXPAND | wxTOP, FromDIP(4));
|
||||
section.page->SetSizer(page_sizer);
|
||||
|
||||
m_outer_tabs->AppendItem(section.title);
|
||||
if (section.icon_bmp.bmp().IsOk())
|
||||
m_outer_tabs->AppendItem(section.title, section.icon_bmp.bmp());
|
||||
else
|
||||
m_outer_tabs->AppendItem(section.title);
|
||||
m_outer_host_sizer->Add(section.page, 1, wxEXPAND);
|
||||
section.page->Hide();
|
||||
m_sections.push_back(std::move(section));
|
||||
@@ -610,7 +603,7 @@ void PublishSettingsDialog::add_row_ui(const std::string& key,
|
||||
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
row_sizer->Add(current.check, 0, wxALIGN_CENTER_VERTICAL);
|
||||
// The value is read-only text (incl. the Type row: the published type is the slot's
|
||||
// normalized type, the author cannot pick a different one here).
|
||||
// normalized type, not author-editable).
|
||||
current.value_label = new wxStaticText(category.scroll, wxID_ANY, value, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
|
||||
current.value_label->SetFont(Label::Body_13);
|
||||
current.value_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30")));
|
||||
@@ -643,8 +636,7 @@ void PublishSettingsDialog::on_full_toggle(size_t category_index)
|
||||
|
||||
void PublishSettingsDialog::set_row_bold(Row& row, bool bold)
|
||||
{
|
||||
// Real set/clear: rebase on the dialog's body font so that clearing bold
|
||||
// restores the exact original font (the old CheckList::SetBold was one-way).
|
||||
// Rebase on the dialog's body font so clearing bold restores the exact original font.
|
||||
row.check->SetFont(bold ? Label::Body_13.Bold() : Label::Body_13);
|
||||
}
|
||||
|
||||
@@ -718,12 +710,11 @@ void PublishSettingsDialog::apply_filter(const wxString& filter_text)
|
||||
Freeze();
|
||||
wxString filter = filter_text.Lower();
|
||||
|
||||
// Pseudo filters (menu only): show only checked ("::sel") or only
|
||||
// unchecked ("::nonsel") rows.
|
||||
// Pseudo filters (menu only): show only checked ("::sel") or only unchecked ("::nonsel").
|
||||
const bool pseudo = (filter == "::sel" || filter == "::nonsel");
|
||||
m_fb_sizer->Show(!pseudo);
|
||||
|
||||
// Update row matches first; page and optgroup visibility is applied below.
|
||||
// Row matches are computed first; page and optgroup visibility is applied below.
|
||||
if (pseudo) {
|
||||
if (m_filter_ctrl->GetValue().Lower() != filter) {
|
||||
m_filter_ctrl->ChangeValue(filter);
|
||||
@@ -808,8 +799,7 @@ void PublishSettingsDialog::apply_visibility()
|
||||
|
||||
void PublishSettingsDialog::select_all(bool value)
|
||||
{
|
||||
// "All" does not auto-enable gated material sections; "None" leaves a gated
|
||||
// row's preserved value untouched.
|
||||
// "All" skips disabled (gated) rows; "None" leaves a gated row's preserved value.
|
||||
for (Row& row : m_rows)
|
||||
if (row.check->IsEnabled())
|
||||
row.check->SetValue(value);
|
||||
@@ -829,19 +819,18 @@ bool PublishSettingsDialog::row_is_visible(const Row& row) const
|
||||
void PublishSettingsDialog::select_visible(bool value)
|
||||
{
|
||||
wxString filter = m_filter_ctrl->GetValue().Lower();
|
||||
// In a pseudo-filter view the rows being toggled would all disappear;
|
||||
// drop the filter afterwards so the result stays visible.
|
||||
// In a pseudo-filter view the rows being toggled would all disappear; drop the filter
|
||||
// afterwards so the result stays visible.
|
||||
bool clear_pseudo = (!value && filter == "::nonsel") || (value && filter == "::sel");
|
||||
|
||||
// Toggle the rows that are visible under the *current* filter.
|
||||
// Toggle the rows visible under the *current* filter.
|
||||
for (Row& row : m_rows)
|
||||
if (row_is_visible(row))
|
||||
row.check->SetValue(value);
|
||||
|
||||
if (clear_pseudo) {
|
||||
// Note: SetValue() may fire wxEVT_TEXT on some platforms, which
|
||||
// re-enters apply_filter() - that is fine, the rows above were already
|
||||
// toggled and the trailing call below is idempotent.
|
||||
// Note: SetValue() may fire wxEVT_TEXT on some platforms, re-entering apply_filter() -
|
||||
// that is fine; the rows above were already toggled and the trailing call is idempotent.
|
||||
m_filter_ctrl->ChangeValue("");
|
||||
apply_filter(""); // resync visibility and the All/None bar
|
||||
}
|
||||
@@ -895,12 +884,31 @@ void PublishSettingsDialog::show_menu(wxMouseEvent& evt)
|
||||
std::vector<std::string> PublishSettingsDialog::GetPublishedKeys() const
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
// Process and printer sections both travel through published_keys (the load-side
|
||||
// overlay applies process keys to the prints edited preset and the allowlisted
|
||||
// printer keys to the printers edited preset). Material keys use a separate API.
|
||||
for (const Row& row : m_rows)
|
||||
if ((row.section == Section::Print || row.section == Section::Printer) && row.check->GetValue())
|
||||
// Process and printer sections both travel through published_keys (the load-side overlay
|
||||
// applies process keys to the prints edited preset and the allowlisted printer keys to the
|
||||
// printers edited preset); material keys use a separate API.
|
||||
const DynamicPrintConfig full = wxGetApp().preset_bundle->full_config();
|
||||
for (const Row& row : m_rows) {
|
||||
if ((row.section != Section::Print && row.section != Section::Printer) || !row.check->GetValue())
|
||||
continue;
|
||||
if (row.section == Section::Printer) {
|
||||
// Printer rows store the base key (per-extruder "#N" variants collapsed during
|
||||
// build). Publish every extruder element so the load side can apply per-extruder
|
||||
// values even when the receiver has a different extruder count; a scalar printer
|
||||
// key is published as-is.
|
||||
const std::string base_key = row.key.substr(0, row.key.find('#'));
|
||||
if (const ConfigOption* opt = full.option(base_key)) {
|
||||
if (const auto* vec = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
|
||||
for (size_t i = 0; i < vec->size(); ++i)
|
||||
out.push_back(base_key + "#" + std::to_string(i));
|
||||
} else {
|
||||
out.push_back(base_key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push_back(row.key);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -915,8 +923,15 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
||||
entry.filament_vendor = cat.filament_vendor;
|
||||
entry.filament_id = cat.filament_id;
|
||||
entry.slot = static_cast<int>(cat.filament_slot);
|
||||
// "Full Publish": the entire filament preset of the slot is embedded; type and color
|
||||
// are implicitly published, and the per-key rows are disabled and their state is ignored.
|
||||
// The author's preset id distinguishes exact variants that share filament_id
|
||||
// ("Generic PLA" vs "Generic PLA Matte"), so the receiver can match precisely.
|
||||
PresetBundle *bundle = wxGetApp().preset_bundle;
|
||||
if (bundle != nullptr && cat.filament_slot < bundle->filament_presets.size()) {
|
||||
if (const Preset *preset = bundle->filaments.find_preset(bundle->filament_presets[cat.filament_slot], false, true))
|
||||
entry.setting_id = preset->setting_id;
|
||||
}
|
||||
// "Full Publish": the whole filament preset is embedded; type and colour are implicitly
|
||||
// published, and the per-key rows are disabled / their state ignored.
|
||||
if (cat.full_check != nullptr && cat.full_check->GetValue()) {
|
||||
entry.full = true;
|
||||
entry.full_keys = full_keys_for_slot();
|
||||
@@ -946,8 +961,7 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
||||
entry.keys.push_back(row.key);
|
||||
}
|
||||
}
|
||||
// A material with only setting keys but none checked, or with nothing selected at all,
|
||||
// carries no information for the writer.
|
||||
// Nothing checked at all -> nothing to write.
|
||||
if (!entry.keys.empty() || entry.publish_type || entry.publish_color)
|
||||
out.push_back(std::move(entry));
|
||||
}
|
||||
@@ -956,9 +970,9 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
||||
|
||||
std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const
|
||||
{
|
||||
// The canonical filament preset keys, minus the structural keys the published overlay must
|
||||
// The canonical filament preset keys minus the structural keys the published overlay must
|
||||
// never touch (inherits, compatibility, *_settings_id, ...), plus filament_colour (not a
|
||||
// member of Preset::filament_options). The values travel in the exported config, masked to
|
||||
// member of Preset::filament_options). Values travel in the exported config, masked to
|
||||
// this slot, and are applied on load onto the receiver's slot.
|
||||
const std::set<std::string>& denylist = publish_structural_keys();
|
||||
std::vector<std::string> keys;
|
||||
@@ -971,7 +985,7 @@ std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const
|
||||
|
||||
void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
// Rescale toolbar bitmaps and icons; collapse chevrons are vector-drawn and repaint themselves.
|
||||
// Rescale toolbar bitmaps and icons; collapse chevrons are vector-drawn and repaint.
|
||||
m_search.msw_rescale();
|
||||
m_menu.msw_rescale();
|
||||
m_filter_box->SetIcon(m_search.bmp());
|
||||
@@ -1000,8 +1014,13 @@ void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
cat.list_sizer->Layout();
|
||||
}
|
||||
|
||||
for (SectionGroup& section : m_sections)
|
||||
for (size_t s = 0; s < m_sections.size(); ++s) {
|
||||
SectionGroup& section = m_sections[s];
|
||||
section.icon_bmp.msw_rescale();
|
||||
if (section.icon_bmp.bmp().IsOk())
|
||||
m_outer_tabs->SetItemBitmap(s, section.icon_bmp.bmp());
|
||||
section.tabs->Rescale();
|
||||
}
|
||||
|
||||
// Refresh the per-row Color chips at the new DPI.
|
||||
for (Row& row : m_rows) {
|
||||
|
||||
@@ -28,25 +28,21 @@ struct PublishMaterialIdentity
|
||||
std::string id;
|
||||
};
|
||||
|
||||
// Dialog that lets a model author select which settings get embedded in a 3MF.
|
||||
// Settings are grouped into the same nested custom tab layout used by the
|
||||
// Process settings: Printer, Filament, and Process outer tabs, with category or
|
||||
// material tabs inside each section. Optgroups are ordinary grouped headers.
|
||||
// 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".
|
||||
// Dialog letting a model author select which settings get embedded in a 3MF. Nested tab layout
|
||||
// mirroring the Process settings (Printer / Filament / Process outer tabs, category or material
|
||||
// tabs inside each). Dirty settings are pre-checked and shown bold; on OK the print rows become
|
||||
// "published_keys" and the material rows become "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 '#'.
|
||||
// The selected print/printer setting keys (in display order); printer keys carry a '#N'
|
||||
// per-extruder suffix.
|
||||
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).
|
||||
// The selected keys grouped per material section (base keys, no '#N' suffix).
|
||||
std::vector<Slic3r::PublishedMaterialEntry> GetPublishedMaterialKeys() const;
|
||||
|
||||
protected:
|
||||
@@ -56,9 +52,9 @@ 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.
|
||||
// One selectable setting row: a checkbox (setting name) plus a value label and an optional
|
||||
// grey unit label. key is the full config key, possibly with a "#N" variant suffix
|
||||
// (print/printer rows); material rows carry the base key.
|
||||
enum class RowKind {
|
||||
Setting, // a regular setting key
|
||||
Color, // material colour requirement (filament_colour)
|
||||
@@ -113,9 +109,9 @@ private:
|
||||
ScalableBitmap icon_bmp; // scalable bitmap for DPI changes
|
||||
wxStaticBitmap* icon{nullptr};
|
||||
wxStaticBitmap* filament_color_chip{nullptr};
|
||||
wxStaticText* title_label{nullptr}; // material title (static text, Full Publish carries the label elsewhere)
|
||||
// "Full Publish": serializing the entire filament preset of this slot. While checked,
|
||||
// the slot's rows (incl. Color/Type) are disabled.
|
||||
wxStaticText* title_label{nullptr}; // material title (static text; Full Publish carries the label elsewhere)
|
||||
// "Full Publish": while checked, the whole slot preset is serialized and its rows
|
||||
// (incl. Color/Type) are disabled.
|
||||
bool full{false};
|
||||
wxCheckBox* full_check{nullptr};
|
||||
// Material identity, only for Section::Material categories.
|
||||
@@ -134,6 +130,7 @@ private:
|
||||
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"
|
||||
ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change
|
||||
wxPanel* page{nullptr};
|
||||
TabCtrl* tabs{nullptr};
|
||||
wxPanel* page_host{nullptr};
|
||||
|
||||
Reference in New Issue
Block a user