mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Initial Commit for Publish Settings workflow
This commit is contained in:
@@ -350,6 +350,8 @@ set(lisbslic3r_sources
|
||||
Preset.hpp
|
||||
PrincipalComponents2D.cpp
|
||||
PrincipalComponents2D.hpp
|
||||
PublishSettings.cpp
|
||||
PublishSettings.hpp
|
||||
PrintApply.cpp
|
||||
PrintBase.cpp
|
||||
PrintBase.hpp
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);*/
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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_
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user