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
+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;