Correctness fixes. Remove hard-coded appends for printer settings

This commit is contained in:
Lam Wei Lun
2026-09-03 14:12:51 +08:00
parent bcff39661c
commit 904796cf24
3 changed files with 111 additions and 45 deletions

View File

@@ -6999,16 +6999,24 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
// as From_Other and import the geometry silently.
if (m_minimal_published) {
// metadata_item_map is seeded from the input file's metadata_items above, so a
// project opened from a regular Orca/BBS 3MF still carries the Application /
// OrcaSlicer tags it came with. Erase them: skipping the overwrite is not enough,
// and an empty value would still emit a "present-looking" tag to old receivers.
// project opened from a regular Orca/BBS 3MF still carries the slicer-identifying
// tags it came with. Erase every one of them - not just the two most common -
// so a published 3MF is fully tag-less: old receivers classify it as From_Other
// and import the geometry silently instead of showing a baked-in "old version"
// popup, and no version marker survives to seed a later re-save.
metadata_item_map.erase(BBL_APPLICATION_TAG);
metadata_item_map.erase(ORCASLICER_TAG);
metadata_item_map.erase(BBS_3MF_VERSION);
metadata_item_map.erase(BBS_3MF_VERSION1);
} else {
metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % "BambuStudio" % SLIC3R_VERSION).str();
}
}
metadata_item_map[BBS_3MF_VERSION] = std::to_string(VERSION_BBS_3MF);
// The Bambu 3MF version marker is part of the slicer identity: omit it for a minimal
// published file along with the tags erased above (skipping the overwrite alone would
// leave the value the source file seeded into metadata_item_map).
if (!m_minimal_published)
metadata_item_map[BBS_3MF_VERSION] = std::to_string(VERSION_BBS_3MF);
if (!model.mk_name.empty()) {
metadata_item_map[BBL_MAKERLAB_TAG] = xml_escape(model.mk_name);
@@ -7031,10 +7039,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
BOOST_LOG_TRIVIAL(info) << "bbs_3mf: save key= " << item.first << ", value = " << item.second;
stream << " <" << METADATA_TAG << " name=\"" << item.first << "\">"
<< xml_escape(item.second) << "</" << METADATA_TAG << ">\n";
if (item.first == BBL_APPLICATION_TAG) {
if (item.first == BBL_APPLICATION_TAG && !m_minimal_published) {
// The OrcaSlicer tag is only written for files that carry the Application
// tag, which a minimal published 3MF erases (see the map assignment above):
// the branch below is unreachable in minimal mode.
// the explicit !m_minimal_published guard keeps the tag-less guarantee from
// depending on that erase happening to run first.
stream << " <" << METADATA_TAG << " name=\"" << ORCASLICER_TAG << "\">"
<< xml_escape(SoftFever_VERSION) << "</" << METADATA_TAG << ">\n";
}
@@ -9213,6 +9222,68 @@ std::string bbs_3mf_get_thumbnail(const char *path)
return data;
}
namespace {
// Parses just the model-file <metadata> elements, mirroring the importer's
// _handle_start_metadata/_handle_end_metadata (attribute-order independent, entity-unescaped,
// whitespace tolerant). Stops the parser as soon as the published flag node is read so the
// geometry/resources that follow are skipped, which keeps the per-file cost small.
struct PublishedXmlProbe
{
XML_Parser parser{nullptr};
bool in_metadata{false};
bool found{false};
bool published{false};
std::string curr_name;
std::string curr_value;
static std::string attribute(const char** attrs, const char* key)
{
if (attrs == nullptr)
return std::string();
// expat hands the attrs as a NULL-terminated {name, value, ...} array.
for (unsigned int a = 0; attrs[a] != nullptr; a += 2)
if (::strcmp(attrs[a], key) == 0 && attrs[a + 1] != nullptr)
return attrs[a + 1];
return std::string();
}
static void XMLCALL start(void* user_data, const char* name, const char** attrs)
{
auto* self = static_cast<PublishedXmlProbe*>(user_data);
if (::strcmp(name, METADATA_TAG) == 0) {
self->in_metadata = true;
self->curr_name = attribute(attrs, NAME_ATTR);
self->curr_value.clear();
} else {
self->in_metadata = false;
}
}
static void XMLCALL characters(void* user_data, const XML_Char* s, int len)
{
auto* self = static_cast<PublishedXmlProbe*>(user_data);
if (self->in_metadata)
self->curr_value.append(s, len);
}
static void XMLCALL end(void* user_data, const char* name)
{
auto* self = static_cast<PublishedXmlProbe*>(user_data);
if (!self->in_metadata || ::strcmp(name, METADATA_TAG) != 0)
return;
self->in_metadata = false;
if (self->curr_name == ORCA_PUBLISHED_TAG) {
self->published = is_published_3mf_flag(xml_unescape(self->curr_value));
self->found = true;
if (self->parser != nullptr)
XML_StopParser(self->parser, false);
}
}
};
} // namespace
bool bbs_3mf_is_published(const std::string &path)
{
mz_zip_archive archive;
@@ -9234,7 +9305,8 @@ bool bbs_3mf_is_published(const std::string &path)
if (!open_zip_reader(&archive, path))
return false;
// Read just the model XML and locate the published metadata node; no geometry parsing.
// Read just the model XML (the metadata node sits before the resources, so the probe below
// stops early) rather than by a raw substring match; no geometry parsing.
int index = mz_zip_reader_locate_file(&archive, MODEL_FILE.c_str(), nullptr, 0);
if (index < 0)
return false;
@@ -9245,22 +9317,29 @@ bool bbs_3mf_is_published(const std::string &path)
if (!mz_zip_reader_extract_to_mem(&archive, index, xml.data(), xml.size(), 0))
return false;
const std::string needle = std::string("<metadata name=\"") + ORCA_PUBLISHED_TAG + "\">";
size_t pos = xml.find(needle);
if (pos == std::string::npos)
return false;
pos += needle.size();
size_t end = xml.find("</metadata>", pos);
if (end == std::string::npos)
XML_Parser parser = XML_ParserCreate(nullptr);
if (parser == nullptr)
return false;
size_t value_begin = pos, value_end = end;
while (value_begin < value_end && (xml[value_begin] == ' ' || xml[value_begin] == '\t' || xml[value_begin] == '\n' || xml[value_begin] == '\r'))
++value_begin;
while (value_end > value_begin && (xml[value_end - 1] == ' ' || xml[value_end - 1] == '\t' || xml[value_end - 1] == '\n' || xml[value_end - 1] == '\r'))
--value_end;
PublishedXmlProbe probe;
probe.parser = parser;
XML_SetUserData(parser, &probe);
XML_SetElementHandler(parser, PublishedXmlProbe::start, PublishedXmlProbe::end);
XML_SetCharacterDataHandler(parser, PublishedXmlProbe::characters);
// Never resolve external entities from a file we are only probing.
XML_SetExternalEntityRefHandler(parser, nullptr);
XML_SetEntityDeclHandler(parser, nullptr);
return is_published_3mf_flag(xml.substr(value_begin, value_end - value_begin));
const XML_Status status = XML_Parse(parser, xml.data(), static_cast<int>(xml.size()), 1);
// XML_StopParser(parser, false) from the end handler makes XML_Parse return
// XML_STATUS_ERROR with XML_ERROR_ABORTED - treat that as success (we stopped on the flag).
const bool parse_ok = (status == XML_STATUS_OK) ||
(XML_GetErrorCode(parser) == XML_ERROR_ABORTED && probe.found);
XML_ParserFree(parser);
if (!parse_ok)
return false;
return probe.published;
}
bool load_gcode_3mf_from_stream(std::istream &data, DynamicPrintConfig *config, Model *model, PlateDataPtrs *plate_data_list, Semver *file_version)

View File

@@ -8752,7 +8752,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
Semver old_version(1, 5, 9);
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && (file_version < old_version) && load_model && load_config && !config_loaded.empty()) {
// A published 3MF has no project config to migrate: skip the old-version
// translations even if a slicer tag slipped through classification.
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && (file_version < old_version) && !published_config.published && load_model && load_config && !config_loaded.empty()) {
translate_old = true;
partplate_list.get_plate_size(current_width, current_depth, current_height);
}
@@ -8854,7 +8856,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
{
// BBS: modify the prime tower params for old version file
Semver old_version3(2, 0, 0);
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && file_version < old_version3) {
if ((en_3mf_file_type == En3mfType::From_BBS || en_3mf_file_type == En3mfType::From_Orca) && !published_config.published && file_version < old_version3) {
double old_filament_prime_volume = 0.;
int filament_count = 0;
{

View File

@@ -7,6 +7,7 @@
#include "libslic3r/FilamentMixer.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PublishSettings.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "Search.hpp"
@@ -5699,32 +5700,16 @@ 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.
// These optgroups are built from publishable_printer_retraction/z_hop_options() so the
// published-3MF printer allowlist (their union in libslic3r/PublishSettings.hpp) can
// never drift from what the machine page actually shows.
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);
optgroup->append_single_option_line("retraction_speed", "printer_extruder_retraction#retraction-speed", extruder_idx);
optgroup->append_single_option_line("deretraction_speed", "printer_extruder_retraction#deretraction-speed", extruder_idx);
optgroup->append_single_option_line("retraction_minimum_travel", "printer_extruder_retraction#travel-distance-threshold", extruder_idx);
optgroup->append_single_option_line("retract_when_changing_layer", "printer_extruder_retraction#retract-on-layer-change", extruder_idx);
optgroup->append_single_option_line("wipe", "printer_extruder_retraction#wipe-while-retracting", extruder_idx);
optgroup->append_single_option_line("wipe_distance", "printer_extruder_retraction#wipe-distance", extruder_idx);
optgroup->append_single_option_line("retract_before_wipe", "printer_extruder_retraction#retract-amount-before-wipe", extruder_idx);
// Orca
optgroup->append_single_option_line("retract_after_wipe", "printer_extruder_retraction#retract-amount-after-wipe", extruder_idx);
for (const PublishablePrinterOption& opt : publishable_printer_retraction_options())
optgroup->append_single_option_line(opt.key, opt.icon, 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);
optgroup->append_single_option_line("z_hop", "printer_extruder_z_hop#z-hop-height", extruder_idx);
optgroup->append_single_option_line("travel_slope", "printer_extruder_z_hop#traveling-angle", extruder_idx);
optgroup->append_single_option_line("retract_lift_above", "printer_extruder_z_hop#only-lift-z-above", extruder_idx);
optgroup->append_single_option_line("retract_lift_below", "printer_extruder_z_hop#only-lift-z-below", extruder_idx);
for (const PublishablePrinterOption& opt : publishable_printer_z_hop_options())
optgroup->append_single_option_line(opt.key, opt.icon, extruder_idx);
optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction_material_change");
optgroup->append_single_option_line("retract_length_toolchange", "printer_extruder_retraction#retraction-when-switching-materials", extruder_idx);