SPE-2660: Add information message about possibly incompatible config,

amended by @lukasmatena
This commit is contained in:
Filip Sykala - NTB T15p 2025-01-27 16:24:22 +01:00 committed by Lukas Matena
parent a3f75133c8
commit 606d3f1ef4
3 changed files with 53 additions and 16 deletions

View File

@ -3940,34 +3940,52 @@ static void handle_legacy_project_loaded(
}
}
bool is_project_3mf(const std::string& filename)
// Project = either it contains our config OR it is stamped as being produced
// by PrusaSlicer (in which case the version is passed out).
std::pair<bool, std::optional<Semver>> is_project_3mf(const std::string& filename)
{
std::pair<bool, std::optional<Semver>> out = std::make_pair(false, std::nullopt);
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, filename))
return false;
return out;
ScopeGuard guard([&archive]() {close_zip_reader(&archive); });
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
// loop the entries to search for config
mz_zip_archive_file_stat stat;
bool config_found = false;
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
if (mz_zip_reader_is_file_a_directory(&archive, i))
continue;
std::string name(stat.m_filename);
std::replace(name.begin(), name.end(), '\\', '/');
if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) {
config_found = true;
break;
if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
out.first = true;
if (boost::algorithm::iequals(name, MODEL_FILE)) {
if (auto* iter = mz_zip_reader_extract_iter_new(&archive, i, 0)) {
ScopeGuard g([&iter]() { mz_zip_reader_extract_iter_free(iter); });
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
if (size_t bytes_read = mz_zip_reader_extract_iter_read(iter, buffer, sizeof(buffer)); bytes_read > 0) {
std::string header(buffer);
boost::regex pattern(R"(^\s*<metadata name="Application".*?PrusaSlicer-(.*?)</metadata>$)");
boost::sregex_iterator it(header.begin(), header.end(), pattern);
boost::sregex_iterator end;
if (it != end) {
Semver semver;
semver.parse((*it)[1].str());
out.second = semver;
}
}
}
}
}
}
close_zip_reader(&archive);
return config_found;
return out;
}
bool load_3mf(

View File

@ -47,7 +47,7 @@ namespace Slic3r {
struct ThumbnailData;
// Returns true if the 3mf file with the given filename is a PrusaSlicer project file (i.e. if it contains a config).
extern bool is_project_3mf(const std::string& filename);
extern std::pair<bool, std::optional<Semver>> is_project_3mf(const std::string&);
// Load the content of a 3mf file into the given model and preset bundle.
extern bool load_3mf(

View File

@ -1376,6 +1376,21 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
config.null_nullables();
// and place the loaded config over the base.
config += std::move(config_loaded);
} else { // config_loaded.empty()
// Detection of possible breaking change in 3MF configuration loading sometimes in future.
if (prusaslicer_generator_version && // set only when loaded with configuration
*prusaslicer_generator_version > Semver(SLIC3R_VERSION)) {
wxString title = _L("Configuration was not loaded");
const wxString url = "https://prusa.io/3mf-transfer";
// TRN: %1% is filename of the project, %2% is url link.
wxString message = format_wxstr(_L("<b>Unable to load configuration from project\n\nFile: </b>%1%\n\n"
"This project was created in a newer version of PrusaSlicer. Only the geometry was loaded.\n"
"Update to the latest version for full compatibility.\nFor more info: <a href=%2%>%2%</a>"),
from_path(filename), url);
HtmlCapableRichMessageDialog dialog(q, message ,title, wxOK,
[&url](const std::string&) { wxGetApp().open_browser_with_warning_dialog(url); });
dialog.ShowModal();
}
}
if (!config_substitutions.empty())
show_substitutions_info(config_substitutions.substitutions, filename.string());
@ -5016,7 +5031,8 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
break;
}
// if 3mf - read archive headers to find project file
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(final_path.string())) ||
auto [is_project, ps_version] = is_project_3mf(final_path.string());
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project) ||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) {
non_project_paths.emplace_back(final_path);
break;
@ -5236,16 +5252,19 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
std::string filename = (*it).filename().string();
bool handle_as_project = boost::algorithm::iends_with(filename, ".3mf");
if (boost::algorithm::iends_with(filename, ".zip") && is_project_3mf(it->string())) {
auto [has_config, ps_version] = is_project_3mf(it->string());
if (boost::algorithm::iends_with(filename, ".zip") && has_config) {
BOOST_LOG_TRIVIAL(warning) << "File with .zip extension is 3mf project, opening as it would have .3mf extension: " << *it;
handle_as_project = true;
}
if (handle_as_project && load_just_one_file) {
ProjectDropDialog::LoadType load_type = ProjectDropDialog::LoadType::Unknown;
{
if (boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string()))
if (boost::algorithm::iends_with(filename, ".3mf") && (!has_config && !ps_version.has_value())) {
// Projects generated by PrusaSlicer is expected to have config. If absent, it will be reported later.
load_type = ProjectDropDialog::LoadType::LoadGeometry;
else {
} else {
if (wxGetApp().app_config->get_bool("show_drop_project_dialog")) {
ProjectDropDialog dlg(filename);
if (dlg.ShowModal() == wxID_OK) {