FileReader: Use separate functions for loading of a model from any file and for loading of all information (model, config and config substitutions) from the 3mf.

+ Added localization markers for errors

Plater: load_files() : Check for type_zip_amf and prusa is removed from conditions (it doesn't used as project's extensions anymore).

+ ModelProcessing: Some functions, like delete zero object, detection if model need to be converted from other units or looks like multipart are moved inside load_model() and theirs return values are written in load statistics, which have to be used by UI later.
This commit is contained in:
YuSanka 2024-12-18 14:34:41 +01:00 committed by Lukas Matena
parent 1bc395cac2
commit 0494021cc3
7 changed files with 314 additions and 395 deletions

View File

@ -298,8 +298,9 @@ int CLI::run(int argc, char **argv)
// When loading an AMF or 3MF, config is imported as well, including the printer technology. // When loading an AMF or 3MF, config is imported as well, including the printer technology.
DynamicPrintConfig config; DynamicPrintConfig config;
ConfigSubstitutionContext config_substitutions(config_substitution_rule); ConfigSubstitutionContext config_substitutions(config_substitution_rule);
boost::optional<Semver> prusaslicer_generator_version;
//FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
model = FileReader::read_from_file(file, &config, &config_substitutions, FileReader::LoadAttribute::AddDefaultInstances); model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances);
PrinterTechnology other_printer_technology = get_printer_technology(config); PrinterTechnology other_printer_technology = get_printer_technology(config);
if (printer_technology == ptUnknown) { if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology; printer_technology = other_printer_technology;

View File

@ -13,6 +13,7 @@
///|/ ///|/
#include "FileReader.hpp" #include "FileReader.hpp"
#include "Model.hpp" #include "Model.hpp"
#include "ModelProcessing.hpp"
#include "TriangleMesh.hpp" #include "TriangleMesh.hpp"
#include "Format/AMF.hpp" #include "Format/AMF.hpp"
@ -23,19 +24,19 @@
#include "Format/SVG.hpp" #include "Format/SVG.hpp"
#include "Format/PrintRequest.hpp" #include "Format/PrintRequest.hpp"
#include <boost/filesystem.hpp>
#include "I18N.hpp"
namespace Slic3r::FileReader namespace Slic3r::FileReader
{ {
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
Model read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) static Model read_model_from_file(const std::string& input_file, LoadAttributes options)
{ {
Model model; Model model;
DynamicPrintConfig temp_config; DynamicPrintConfig temp_config;
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent); ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
if (config == nullptr)
config = &temp_config;
if (config_substitutions == nullptr)
config_substitutions = &temp_config_substitutions_context;
bool result = false; bool result = false;
if (boost::algorithm::iends_with(input_file, ".stl")) if (boost::algorithm::iends_with(input_file, ".stl"))
@ -45,23 +46,25 @@ Model read_from_file(const std::string& input_file, DynamicPrintConfig* config,
else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp")) else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp"))
result = load_step(input_file.c_str(), &model); result = load_step(input_file.c_str(), &model);
else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); //? result = load_amf(input_file.c_str(), &temp_config, &temp_config_substitutions_context, &model, options & LoadAttribute::CheckVersion);
//? LoadAttribute::CheckVersion is needed here, when we loading just a geometry
result = load_amf(input_file.c_str(), &temp_config, &temp_config_substitutions_context, &model, false);
else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) { else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) {
//FIXME options & LoadAttribute::CheckVersion ? //FIXME options & LoadAttribute::CheckVersion ?
boost::optional<Semver> prusaslicer_generator_version; boost::optional<Semver> prusaslicer_generator_version;
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false, prusaslicer_generator_version); result = load_3mf(input_file.c_str(), temp_config, temp_config_substitutions_context, &model, false, prusaslicer_generator_version);
} else if (boost::algorithm::iends_with(input_file, ".svg")) } else if (boost::algorithm::iends_with(input_file, ".svg"))
result = load_svg(input_file, model); result = load_svg(input_file, model);
else if (boost::ends_with(input_file, ".printRequest")) else if (boost::ends_with(input_file, ".printRequest"))
result = load_printRequest(input_file.c_str(), &model); result = load_printRequest(input_file.c_str(), &model);
else else
throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."); throw Slic3r::RuntimeError(L("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."));
if (!result) if (!result)
throw Slic3r::RuntimeError("Loading of a model file failed."); throw Slic3r::RuntimeError(L("Loading of a model file failed."));
if (model.objects.empty()) if (model.objects.empty())
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); throw Slic3r::RuntimeError(L("The supplied file couldn't be read because it's empty"));
if (!boost::ends_with(input_file, ".printRequest")) if (!boost::ends_with(input_file, ".printRequest"))
for (ModelObject* o : model.objects) for (ModelObject* o : model.objects)
@ -70,86 +73,180 @@ Model read_from_file(const std::string& input_file, DynamicPrintConfig* config,
if (options & LoadAttribute::AddDefaultInstances) if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances(); model.add_default_instances();
return model;
}
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
static Model read_all_from_file(const std::string& input_file,
DynamicPrintConfig* config,
ConfigSubstitutionContext* config_substitutions,
boost::optional<Semver> &prusaslicer_generator_version,
LoadAttributes options)
{
const bool is_project_file = boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip");
assert(is_project_file);
assert(config != nullptr);
assert(config_substitutions != nullptr);
Model model;
bool result = false;
if (is_project_file)
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion, prusaslicer_generator_version);
else
throw Slic3r::RuntimeError(L("Unknown file format. Input file must have .3mf extension."));
if (!result)
throw Slic3r::RuntimeError(L("Loading of a model file failed."));
if (model.objects.empty())
throw Slic3r::RuntimeError(L("The supplied file couldn't be read because it's empty"));
for (ModelObject* o : model.objects)
o->input_file = input_file;
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
for (CustomGCode::Info& info : model.get_custom_gcode_per_print_z_vector()) { for (CustomGCode::Info& info : model.get_custom_gcode_per_print_z_vector()) {
CustomGCode::update_custom_gcode_per_print_z_from_config(info, config); CustomGCode::update_custom_gcode_per_print_z_from_config(info, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(info); CustomGCode::check_mode_for_custom_gcode_per_print_z(info);
} }
sort_remove_duplicates(config_substitutions->substitutions); sort_remove_duplicates(config_substitutions->substitutions);
return model; return model;
} }
// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
Model read_from_archive(const std::string& input_file,
DynamicPrintConfig* config,
ConfigSubstitutionContext*,
config_substitutions,
boost::optional<Semver> &prusaslicer_generator_version,
LoadAttributes options)
{
assert(config != nullptr);
assert(config_substitutions != nullptr);
Model model;
bool result = false;
if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"))
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion, prusaslicer_generator_version);
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
else
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
if (!result)
throw Slic3r::RuntimeError("Loading of a model file failed.");
for (ModelObject* o : model.objects) {
//if (boost::algorithm::iends_with(input_file, ".zip.amf")) {
// // we remove the .zip part of the extension to avoid it be added to filenames when exporting
// o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
//}
//else
o->input_file = input_file;
}
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
for (CustomGCode::Info& info : model.get_custom_gcode_per_print_z_vector()) {
CustomGCode::update_custom_gcode_per_print_z_from_config(info, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(info);
}
handle_legacy_sla(*config);
return model;
}
Model load_model(const std::string& input_file, std::string& errors)
{
Model model;
try {
model = read_from_file(input_file);
}
catch (std::exception& e) {
errors = input_file + " : " + e.what();
return model;
}
return model;
}
Model load_model(const std::string& input_file)
{
std::string errors;
Model model = load_model(input_file, errors);
return model;
}
TriangleMesh load_mesh(const std::string& input_file) TriangleMesh load_mesh(const std::string& input_file)
{ {
Model model = load_model(input_file); Model model;
try {
model = read_model_from_file(input_file, LoadAttribute::AddDefaultInstances);
}
catch (std::exception&) {
throw Slic3r::RuntimeError(L("Error! Invalid model"));
}
return model.mesh(); return model.mesh();
} }
static bool looks_like_multipart_object(const Model& model)
{
if (model.objects.size() <= 1)
return false;
BoundingBoxf3 tbb;
for (const ModelObject* obj : model.objects) {
if (obj->volumes.size() > 1 || obj->config.keys().size() > 1)
return false;
BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box();
// FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that
// case is performed only after this function is called. For now (shortly before the 2.7.2 release, let's
// just do this non-invasive check. Reordering all the functions could break it much more.
BoundingBoxf3 tbb_this = (!obj->instances.empty() ? obj->instances[0]->transform_bounding_box(bb_this) : bb_this);
if (!tbb.defined)
tbb = tbb_this;
else if (tbb.intersects(tbb_this) || tbb.shares_boundary(tbb_this))
// The volumes has intersects bounding boxes or share some boundary
return true;
}
return false;
}
static bool looks_like_imperial_units(const Model& model)
{
if (model.objects.empty())
return false;
for (ModelObject* obj : model.objects)
if (ModelProcessing::get_object_mesh_stats(obj).volume < ModelProcessing::volume_threshold_inches) {
if (!obj->is_cut())
return true;
bool all_cut_parts_look_like_imperial_units = true;
for (ModelObject* obj_other : model.objects) {
if (obj_other == obj)
continue;
if (obj_other->cut_id.is_equal(obj->cut_id) && ModelProcessing::get_object_mesh_stats(obj_other).volume >= ModelProcessing::volume_threshold_inches) {
all_cut_parts_look_like_imperial_units = false;
break;
}
}
if (all_cut_parts_look_like_imperial_units)
return true;
}
return false;
}
static bool looks_like_saved_in_meters(const Model& model)
{
if (model.objects.size() == 0)
return false;
for (ModelObject* obj : model.objects)
if (ModelProcessing::get_object_mesh_stats(obj).volume < ModelProcessing::volume_threshold_meters)
return true;
return false;
}
static constexpr const double zero_volume = 0.0000000001;
static int removed_objects_with_zero_volume(Model& model)
{
if (model.objects.size() == 0)
return 0;
int removed = 0;
for (int i = int(model.objects.size()) - 1; i >= 0; i--)
if (ModelProcessing::get_object_mesh_stats(model.objects[i]).volume < zero_volume) {
model.delete_object(size_t(i));
removed++;
}
return removed;
}
Model load_model(const std::string& input_file,
LoadAttributes options/* = LoadAttribute::AddDefaultInstances*/,
LoadStats* stats/*= nullptr*/)
{
Model model = read_model_from_file(input_file, options);
for (auto obj : model.objects)
if (obj->name.empty())
obj->name = boost::filesystem::path(obj->input_file).filename().string();
if (stats) {
// 3mf contains information about units, so there is no need to detect possible convertions for these files
bool from_3mf = boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip");
stats->deleted_objects_cnt = removed_objects_with_zero_volume(model);
stats->looks_like_multipart_object = looks_like_multipart_object(model);
stats->looks_like_saved_in_meters = !from_3mf && looks_like_saved_in_meters(model);
stats->looks_like_imperial_units = !from_3mf && looks_like_imperial_units(model);
}
return model;
}
Model load_model_with_config(const std::string& input_file,
DynamicPrintConfig* config,
ConfigSubstitutionContext* config_substitutions,
boost::optional<Semver>& prusaslicer_generator_version,
LoadAttributes options,
LoadStats* stats)
{
Model model = read_all_from_file(input_file, config, config_substitutions, prusaslicer_generator_version, options);
if (stats && !model.mesh().empty()) {
stats->deleted_objects_cnt = removed_objects_with_zero_volume(model);
stats->looks_like_multipart_object = looks_like_multipart_object(model);
}
return model;
}
} }

View File

@ -12,8 +12,6 @@
///|/ ///|/
#pragma once #pragma once
//#include "TriangleMesh.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "enum_bitmask.hpp" #include "enum_bitmask.hpp"
@ -30,19 +28,32 @@ namespace FileReader
}; };
using LoadAttributes = enum_bitmask<LoadAttribute>; using LoadAttributes = enum_bitmask<LoadAttribute>;
Model read_from_file(const std::string& input_file, struct LoadStats {
DynamicPrintConfig* config = nullptr, int deleted_objects_cnt { 0 };
ConfigSubstitutionContext* config_substitutions = nullptr, bool looks_like_saved_in_meters { false };
LoadAttributes options = LoadAttribute::AddDefaultInstances); bool looks_like_imperial_units { false };
Model read_from_archive(const std::string& input_file, bool looks_like_multipart_object { false };
};
// Load model from input file and return the its mesh.
// Throw RuntimeError if some problem was detected during model loading
TriangleMesh load_mesh(const std::string& input_file);
// Load model from input file and fill statistics if it's required.
// In respect to the params will be applied needed convertions over the model.
// Exceptions don't catched inside
Model load_model(const std::string& input_file,
LoadAttributes options = LoadAttribute::AddDefaultInstances,
LoadStats* statistics = nullptr);
// Load model, config and config substitutions from input file and fill statistics if it's required.
// Exceptions don't catched inside
Model load_model_with_config(const std::string& input_file,
DynamicPrintConfig* config, DynamicPrintConfig* config,
ConfigSubstitutionContext* config_substitutions, ConfigSubstitutionContext* config_substitutions,
boost::optional<Semver> &prusaslicer_generator_version, boost::optional<Semver> &prusaslicer_generator_version,
LoadAttributes options = LoadAttribute::AddDefaultInstances); LoadAttributes options,
LoadStats* statistics = nullptr);
Model load_model(const std::string& input_file);
Model load_model(const std::string& input_file, std::string& errors);
TriangleMesh load_mesh(const std::string& input_file);
} }
ENABLE_ENUM_BITMASK_OPERATORS(FileReader::LoadAttribute) ENABLE_ENUM_BITMASK_OPERATORS(FileReader::LoadAttribute)

View File

@ -14,35 +14,11 @@
#include "Model.hpp" #include "Model.hpp"
#include "ModelProcessing.hpp" #include "ModelProcessing.hpp"
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r::ModelProcessing { namespace Slic3r::ModelProcessing {
bool looks_like_multipart_object(const Model& model)
{
if (model.objects.size() <= 1)
return false;
BoundingBoxf3 tbb;
for (const ModelObject* obj : model.objects) {
if (obj->volumes.size() > 1 || obj->config.keys().size() > 1)
return false;
BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box();
// FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that
// case is performed only after this function is called. For now (shortly before the 2.7.2 release, let's
// just do this non-invasive check. Reordering all the functions could break it much more.
BoundingBoxf3 tbb_this = (!obj->instances.empty() ? obj->instances[0]->transform_bounding_box(bb_this) : bb_this);
if (!tbb.defined)
tbb = tbb_this;
else if (tbb.intersects(tbb_this) || tbb.shares_boundary(tbb_this))
// The volumes has intersects bounding boxes or share some boundary
return true;
}
return false;
}
// Generate next extruder ID string, in the range of (1, max_extruders). // Generate next extruder ID string, in the range of (1, max_extruders).
static inline int auto_extruder_id(unsigned int max_extruders, unsigned int& cntr) static inline int auto_extruder_id(unsigned int max_extruders, unsigned int& cntr)
{ {
@ -100,33 +76,6 @@ void convert_to_multipart_object(Model& model, unsigned int max_extruders)
model.add_object(*object); model.add_object(*object);
} }
static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
bool looks_like_imperial_units(const Model& model)
{
if (model.objects.empty())
return false;
for (ModelObject* obj : model.objects)
if (get_object_mesh_stats(obj).volume < volume_threshold_inches) {
if (!obj->is_cut())
return true;
bool all_cut_parts_look_like_imperial_units = true;
for (ModelObject* obj_other : model.objects) {
if (obj_other == obj)
continue;
if (obj_other->cut_id.is_equal(obj->cut_id) && get_object_mesh_stats(obj_other).volume >= volume_threshold_inches) {
all_cut_parts_look_like_imperial_units = false;
break;
}
}
if (all_cut_parts_look_like_imperial_units)
return true;
}
return false;
}
void convert_from_imperial_units(Model& model, bool only_small_volumes) void convert_from_imperial_units(Model& model, bool only_small_volumes)
{ {
static constexpr const float in_to_mm = 25.4f; static constexpr const float in_to_mm = 25.4f;
@ -148,20 +97,6 @@ void convert_from_imperial_units(ModelVolume* volume)
volume->source.is_converted_from_inches = true; volume->source.is_converted_from_inches = true;
} }
static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1
bool looks_like_saved_in_meters(const Model& model)
{
if (model.objects.size() == 0)
return false;
for (ModelObject* obj : model.objects)
if (get_object_mesh_stats(obj).volume < volume_threshold_meters)
return true;
return false;
}
void convert_from_meters(Model& model, bool only_small_volumes) void convert_from_meters(Model& model, bool only_small_volumes)
{ {
static constexpr const double m_to_mm = 1000; static constexpr const double m_to_mm = 1000;
@ -244,23 +179,6 @@ void convert_units(Model& model_to, ModelObject* object_from, ConversionType con
BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end"; BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end";
} }
static constexpr const double zero_volume = 0.0000000001;
int removed_objects_with_zero_volume(Model& model)
{
if (model.objects.size() == 0)
return 0;
int removed = 0;
for (int i = int(model.objects.size()) - 1; i >= 0; i--)
if (get_object_mesh_stats(model.objects[i]).volume < zero_volume) {
model.delete_object(size_t(i));
removed++;
}
return removed;
}
TriangleMeshStats get_object_mesh_stats(const ModelObject* object) TriangleMeshStats get_object_mesh_stats(const ModelObject* object)
{ {
TriangleMeshStats full_stats; TriangleMeshStats full_stats;

View File

@ -30,21 +30,19 @@ enum class ConversionType : int {
namespace ModelProcessing namespace ModelProcessing
{ {
bool looks_like_multipart_object(const Model& model); static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1
void convert_to_multipart_object(Model& model, unsigned int max_extruders); void convert_to_multipart_object(Model& model, unsigned int max_extruders);
bool looks_like_imperial_units(const Model& model);
void convert_from_imperial_units(Model& model, bool only_small_volumes); void convert_from_imperial_units(Model& model, bool only_small_volumes);
void convert_from_imperial_units(ModelVolume* volume); void convert_from_imperial_units(ModelVolume* volume);
bool looks_like_saved_in_meters(const Model& model);
void convert_from_meters(Model& model, bool only_small_volumes); void convert_from_meters(Model& model, bool only_small_volumes);
void convert_from_meters(ModelVolume* volume); void convert_from_meters(ModelVolume* volume);
void convert_units(Model& model_to, ModelObject* object_from, ConversionType conv_type, std::vector<int> volume_idxs); void convert_units(Model& model_to, ModelObject* object_from, ConversionType conv_type, std::vector<int> volume_idxs);
int removed_objects_with_zero_volume(Model& model);
// Get full stl statistics for all object's meshes // Get full stl statistics for all object's meshes
TriangleMeshStats get_object_mesh_stats(const ModelObject* object); TriangleMeshStats get_object_mesh_stats(const ModelObject* object);
// Get count of errors in the mesh // Get count of errors in the mesh

View File

@ -800,9 +800,7 @@ void GUI_App::post_init()
if (plater()->load_files(fns) && this->init_params->input_files.size() == 1) { if (plater()->load_files(fns) && this->init_params->input_files.size() == 1) {
// Update application titlebar when opening a project file // Update application titlebar when opening a project file
const std::string& filename = this->init_params->input_files.front(); const std::string& filename = this->init_params->input_files.front();
if (boost::algorithm::iends_with(filename, ".amf") || if (boost::algorithm::iends_with(filename, ".3mf"))
boost::algorithm::iends_with(filename, ".amf.xml") ||
boost::algorithm::iends_with(filename, ".3mf"))
this->plater()->set_project_filename(from_u8(filename)); this->plater()->set_project_filename(from_u8(filename));
} }
if (this->init_params->delete_after_load) { if (this->init_params->delete_after_load) {

View File

@ -612,11 +612,9 @@ private:
bool show_warning_dialog { false }; bool show_warning_dialog { false };
}; };
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|3mf)", std::regex::icase);
const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase); const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase);
const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase);
const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase); const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase);
const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase); const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase);
@ -1241,7 +1239,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
wxBusyCursor busy; wxBusyCursor busy;
auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); auto *extra_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
std::vector<size_t> obj_idxs; std::vector<size_t> obj_idxs;
boost::optional<Semver> prusaslicer_generator_version; boost::optional<Semver> prusaslicer_generator_version;
@ -1270,9 +1268,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
const bool type_3mf = std::regex_match(path.string(), pattern_3mf) || std::regex_match(path.string(), pattern_zip); const bool type_3mf = std::regex_match(path.string(), pattern_3mf) || std::regex_match(path.string(), pattern_zip);
const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf);
const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf); const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf);
const bool type_prusa = std::regex_match(path.string(), pattern_prusa);
const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest); const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest);
if (type_printRequest && printer_technology != ptSLA) { if (type_printRequest && printer_technology != ptSLA) {
@ -1283,10 +1279,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
Slic3r::Model model; Slic3r::Model model;
bool is_project_file = type_prusa; bool is_project_file = false;
try {
if (type_3mf || type_zip_amf) {
#ifdef __linux__ #ifdef __linux__
// On Linux Constructor of the ProgressDialog calls DisableOtherWindows() function which causes a disabling of all children of the find_toplevel_parent(q) // On Linux Constructor of the ProgressDialog calls DisableOtherWindows() function which causes a disabling of all children of the find_toplevel_parent(q)
// And a destructor of the ProgressDialog calls ReenableOtherWindows() function which revert previously disabled children. // And a destructor of the ProgressDialog calls ReenableOtherWindows() function which revert previously disabled children.
@ -1299,19 +1293,37 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
progress_dlg = nullptr; progress_dlg = nullptr;
} }
#endif #endif
DynamicPrintConfig config; DynamicPrintConfig config;
PrinterTechnology loaded_printer_technology{ ptFFF }; PrinterTechnology loaded_printer_technology{ ptFFF };
{
DynamicPrintConfig config_loaded; DynamicPrintConfig config_loaded;
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable }; ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
model = FileReader::read_from_archive(
path.string(), if (!type_3mf)
&config_loaded, load_config = false; // just 3mf file contains config
&config_substitutions,
prusaslicer_generator_version, FileReader::LoadStats load_stats;
only_if(load_config, Model::LoadAttribute::CheckVersion)
); try {
if (load_config && !config_loaded.empty()) { if (load_config) {
model = FileReader::load_model_with_config(path.string(), &config_loaded, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::CheckVersion, &load_stats);
}
else if (load_model) {
model = FileReader::load_model(path.string(), FileReader::LoadAttributes{}, &load_stats);
}
} catch (const ConfigurationError &e) {
std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
GUI::show_error(q, message);
continue;
} catch (const std::exception &e) {
GUI::show_error(q, e.what());
continue;
}
if (load_config) {
if (!config_loaded.empty()) {
// Based on the printer technology field found in the loaded config, select the base for the config, // Based on the printer technology field found in the loaded config, select the base for the config,
loaded_printer_technology = Preset::printer_technology(config_loaded); loaded_printer_technology = Preset::printer_technology(config_loaded);
@ -1326,19 +1338,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (!config_substitutions.empty()) if (!config_substitutions.empty())
show_substitutions_info(config_substitutions.substitutions, filename.string()); show_substitutions_info(config_substitutions.substitutions, filename.string());
if (load_config) {
this->model.get_custom_gcode_per_print_z_vector() = model.get_custom_gcode_per_print_z_vector();
this->model.get_wipe_tower_vector() = model.get_wipe_tower_vector();
}
}
if (load_config) {
if (!config.empty()) { if (!config.empty()) {
const auto* post_process = config.opt<ConfigOptionStrings>("post_process"); const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
if (post_process != nullptr && !post_process->values.empty()) { if (post_process != nullptr && !post_process->values.empty()) {
// TRN The placeholder is either "3MF" or "AMF" wxString msg = _L("The selected 3MF file contains a post-processing script.\n"
wxString msg = GUI::format_wxstr(_L("The selected %1% file contains a post-processing script.\n" "Please review the script carefully before exporting G-code.");
"Please review the script carefully before exporting G-code."), type_3mf ? "3MF" : "AMF" );
std::string text; std::string text;
for (const std::string& s : post_process->values) for (const std::string& s : post_process->values)
text += s; text += s;
@ -1348,15 +1352,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
msg_dlg.ShowModal(); msg_dlg.ShowModal();
} }
Preset::normalize(config); Preset::normalize(config); //???
PresetBundle* preset_bundle = wxGetApp().preset_bundle; PresetBundle* preset_bundle = wxGetApp().preset_bundle;
preset_bundle->load_config_model(filename.string(), std::move(config)); preset_bundle->load_config_model(filename.string(), std::move(config));
q->notify_about_installed_presets(); q->notify_about_installed_presets();
//if (loaded_printer_technology == ptFFF) //if (loaded_printer_technology == ptFFF && load_model)
// CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z(), &preset_bundle->project_config); // CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z(), &preset_bundle->project_config);
// For exporting from the amf/3mf we shouldn't check printer_presets for the containing information about "Print Host upload" // For exporting from the 3mf we shouldn't check printer_presets for the containing information about "Print Host upload"
wxGetApp().load_current_presets(false); wxGetApp().load_current_presets(false);
// Update filament colors for the MM-printer profile in the full config // Update filament colors for the MM-printer profile in the full config
// to avoid black (default) colors for Extruders in the ObjectList, // to avoid black (default) colors for Extruders in the ObjectList,
@ -1364,52 +1368,33 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
q->update_filament_colors_in_full_config(); q->update_filament_colors_in_full_config();
is_project_file = true; is_project_file = true;
} }
this->model.get_custom_gcode_per_print_z_vector() = model.get_custom_gcode_per_print_z_vector();
this->model.get_wipe_tower_vector() = model.get_wipe_tower_vector();
if (!in_temp) if (!in_temp)
wxGetApp().app_config->update_config_dir(path.parent_path().string()); wxGetApp().app_config->update_config_dir(path.parent_path().string());
} }
}
else {
model = FileReader::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, FileReader::LoadAttribute::CheckVersion));
for (auto obj : model.objects)
if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string();
}
} catch (const ConfigurationError &e) {
std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
GUI::show_error(q, message);
continue;
} catch (const std::exception &e) {
GUI::show_error(q, e.what());
continue;
}
if (load_model) { if (load_model) {
// The model should now be initialized
auto convert_from_imperial_units = [](Model& model, bool only_small_volumes) {
ModelProcessing::convert_from_imperial_units(model, only_small_volumes);
// wxGetApp().app_config->set("use_inches", "1");
// wxGetApp().sidebar().update_ui_from_settings();
};
if (!is_project_file) { if (!is_project_file) {
if (int deleted_objects = removed_objects_with_zero_volume(model); deleted_objects > 0) {
if (load_stats.deleted_objects_cnt > 0) {
MessageDialog(q, format_wxstr(_L_PLURAL( MessageDialog(q, format_wxstr(_L_PLURAL(
"Object size from file %s appears to be zero.\n" "Object size from file %s appears to be zero.\n"
"This object has been removed from the model", "This object has been removed from the model",
"Objects size from file %s appears to be zero.\n" "Objects size from file %s appears to be zero.\n"
"These objects have been removed from the model", deleted_objects), from_path(filename)) + "\n", "These objects have been removed from the model", load_stats.deleted_objects_cnt), from_path(filename)) + "\n",
_L("The size of the object is zero"), wxICON_INFORMATION | wxOK).ShowModal(); _L("The size of the object is zero"), wxICON_INFORMATION | wxOK).ShowModal();
} }
if (imperial_units)
if (imperial_units) {
// Convert even if the object is big. // Convert even if the object is big.
convert_from_imperial_units(model, false); // It's a case, when we load model from STL, saved in imperial units
else if (!type_3mf && looks_like_saved_in_meters(model)) { ModelProcessing::convert_from_imperial_units(model, false);
auto convert_model_if = [](Model& model, bool condition) { }
if (condition) else if (load_stats.looks_like_saved_in_meters) {
//FIXME up-scale only the small parts?
convert_from_meters(model, true);
};
if (answer_convert_from_meters == wxOK_DEFAULT) { if (answer_convert_from_meters == wxOK_DEFAULT) {
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in meters.\n" "The dimensions of the object from file %s seem to be defined in meters.\n"
@ -1421,17 +1406,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
int answer = dlg.ShowModal(); int answer = dlg.ShowModal();
if (dlg.IsCheckBoxChecked()) if (dlg.IsCheckBoxChecked())
answer_convert_from_meters = answer; answer_convert_from_meters = answer;
else
convert_model_if(model, answer == wxID_YES);
} }
convert_model_if(model, answer_convert_from_meters == wxID_YES); if (answer_convert_from_meters == wxID_YES)
}
else if (!type_3mf && looks_like_imperial_units(model)) {
auto convert_model_if = [convert_from_imperial_units](Model& model, bool condition) {
if (condition)
//FIXME up-scale only the small parts? //FIXME up-scale only the small parts?
convert_from_imperial_units(model, true); ModelProcessing::convert_from_meters(model, true);
}; }
else if (load_stats.looks_like_imperial_units) {
if (answer_convert_from_imperial_units == wxOK_DEFAULT) { if (answer_convert_from_imperial_units == wxOK_DEFAULT) {
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in inches.\n" "The dimensions of the object from file %s seem to be defined in inches.\n"
@ -1443,13 +1423,13 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
int answer = dlg.ShowModal(); int answer = dlg.ShowModal();
if (dlg.IsCheckBoxChecked()) if (dlg.IsCheckBoxChecked())
answer_convert_from_imperial_units = answer; answer_convert_from_imperial_units = answer;
else
convert_model_if(model, answer == wxID_YES);
} }
convert_model_if(model, answer_convert_from_imperial_units == wxID_YES); if (answer_convert_from_imperial_units == wxID_YES)
//FIXME up-scale only the small parts?
ModelProcessing::convert_from_imperial_units(model, true);
} }
if (!type_printRequest && looks_like_multipart_object(model)) { if (load_stats.looks_like_multipart_object) {
if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) { if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) {
RichMessageDialog dlg(q, _L( RichMessageDialog dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n" "This file contains several objects positioned at multiple heights.\n"
@ -1460,14 +1440,13 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
int answer = dlg.ShowModal(); int answer = dlg.ShowModal();
if (dlg.IsCheckBoxChecked()) if (dlg.IsCheckBoxChecked())
answer_consider_as_multi_part_objects = answer; answer_consider_as_multi_part_objects = answer;
if (answer == wxID_YES)
convert_to_multipart_object(model, nozzle_dmrs->size());
} }
else if (answer_consider_as_multi_part_objects == wxID_YES) if (/*!type_printRequest && */answer_consider_as_multi_part_objects == wxID_YES) //! type_printRequest is no need here, SLA allow multipart object now
convert_to_multipart_object(model, nozzle_dmrs->size()); ModelProcessing::convert_to_multipart_object(model, nozzle_dmrs->size());
} }
} }
if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
if ((wxGetApp().get_mode() == comSimple) && type_3mf && model_has_advanced_features(model)) {
MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n",
_L("Detected advanced data"), wxICON_WARNING | wxOK | wxCANCEL); _L("Detected advanced data"), wxICON_WARNING | wxOK | wxCANCEL);
if (msg_dlg.ShowModal() == wxID_OK) { if (msg_dlg.ShowModal() == wxID_OK) {
@ -1475,11 +1454,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
view3D->set_as_dirty(); view3D->set_as_dirty();
} }
else else
return obj_idxs; continue;// return obj_idxs;
} }
for (ModelObject* model_object : model.objects) { for (ModelObject* model_object : model.objects) {
if (!type_3mf && !type_zip_amf) { if (!type_3mf) {
model_object->center_around_origin(false); model_object->center_around_origin(false);
if (type_any_amf && model_object->instances.empty()) { if (type_any_amf && model_object->instances.empty()) {
ModelInstance* instance = model_object->add_instance(); ModelInstance* instance = model_object->add_instance();
@ -1494,12 +1473,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
} }
} }
if (type_printRequest ) { if (type_printRequest ) {
assert(model.materials.size()); assert(model.materials.size());
for (const auto& material : model.materials) { for (const auto& material : model.materials) {
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, material.first);
Preset::remove_suffix_modified(material.first));
Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false);
if (!prst) { //did not find compatible profile if (!prst) { //did not find compatible profile
// try find alias of material comaptible with another print profile - if exists, use the print profile // try find alias of material comaptible with another print profile - if exists, use the print profile
@ -1511,8 +1490,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (it->name != edited_print_name) { if (it->name != edited_print_name) {
BOOST_LOG_TRIVIAL(error) << it->name; BOOST_LOG_TRIVIAL(error) << it->name;
wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false); wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false);
preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, material.first);
Preset::remove_suffix_modified(material.first));
prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false);
if (prst) { if (prst) {
found = true; found = true;
@ -1542,14 +1520,14 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
if (one_by_one) { if (one_by_one) {
if ((type_3mf && !is_project_file) || (type_any_amf && !type_zip_amf)) if ((type_3mf && !is_project_file) || type_any_amf)
model.center_instances_around_point(this->bed.build_volume().bed_center()); model.center_instances_around_point(this->bed.build_volume().bed_center());
auto loaded_idxs = load_model_objects(model.objects, is_project_file); auto loaded_idxs = load_model_objects(model.objects, is_project_file);
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
} else { } else {
// This must be an .stl or .obj file, which may contain a maximum of one volume. // This must be an .stl or .obj file, which may contain a maximum of one volume.
for (const ModelObject* model_object : model.objects) { for (const ModelObject* model_object : model.objects) {
new_model->add_object(*model_object); extra_model->add_object(*model_object);
} }
} }
@ -1558,17 +1536,17 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
} }
if (new_model != nullptr && new_model->objects.size() > 1) { if (extra_model != nullptr && extra_model->objects.size() > 1) {
MessageDialog msg_dlg(q, _L( MessageDialog msg_dlg(q, _L(
"Multiple objects were loaded for a multi-material printer.\n" "Multiple objects were loaded for a multi-material printer.\n"
"Instead of considering them as multiple objects, should I consider\n" "Instead of considering them as multiple objects, should I consider\n"
"these files to represent a single object having multiple parts?") + "\n", "these files to represent a single object having multiple parts?") + "\n",
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO); _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) { if (msg_dlg.ShowModal() == wxID_YES) {
convert_to_multipart_object(*new_model, nozzle_dmrs->values.size()); ModelProcessing::convert_to_multipart_object(*extra_model, nozzle_dmrs->values.size());
} }
auto loaded_idxs = load_model_objects(new_model->objects); auto loaded_idxs = load_model_objects(extra_model->objects);
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
} }
@ -2677,9 +2655,9 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters);
if (old_volume->source.is_converted_from_inches) if (old_volume->source.is_converted_from_inches)
convert_from_imperial_units(new_volume); ModelProcessing::convert_from_imperial_units(new_volume);
else if (old_volume->source.is_converted_from_meters) else if (old_volume->source.is_converted_from_meters)
convert_from_meters(new_volume); ModelProcessing::convert_from_meters(new_volume);
if (old_volume->mesh().its == new_volume->mesh().its) { if (old_volume->mesh().its == new_volume->mesh().its) {
// This function is called both from reload_from_disk and replace_with_stl. // This function is called both from reload_from_disk and replace_with_stl.
@ -2972,9 +2950,9 @@ void Plater::priv::reload_from_disk()
new_volume->source.volume_idx = old_volume->source.volume_idx; new_volume->source.volume_idx = old_volume->source.volume_idx;
assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters);
if (old_volume->source.is_converted_from_inches) if (old_volume->source.is_converted_from_inches)
convert_from_imperial_units(new_volume); ModelProcessing::convert_from_imperial_units(new_volume);
else if (old_volume->source.is_converted_from_meters) else if (old_volume->source.is_converted_from_meters)
convert_from_meters(new_volume); ModelProcessing::convert_from_meters(new_volume);
std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back()); std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1); old_model_object->delete_volume(old_model_object->volumes.size() - 1);
if (!sinking) if (!sinking)
@ -5051,86 +5029,6 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
{ {
return false; return false;
} }
#if 0
// 1 project, 0 models - behave like drag n drop
if (project_paths.size() == 1 && non_project_paths.empty())
{
wxArrayString aux;
aux.Add(from_u8(project_paths.front().string()));
load_files(aux);
//load_files(project_paths, true, true);
boost::system::error_code ec;
fs::remove(project_paths.front(), ec);
if (ec)
BOOST_LOG_TRIVIAL(error) << ec.message();
return true;
}
// 1 model (or more and other instances are not allowed), 0 projects - open geometry
if (project_paths.empty() && (non_project_paths.size() == 1 || wxGetApp().app_config->get_bool("single_instance")))
{
load_files(non_project_paths, true, false);
boost::system::error_code ec;
fs::remove(non_project_paths.front(), ec);
if (ec)
BOOST_LOG_TRIVIAL(error) << ec.message();
return true;
}
bool delete_after = true;
LoadProjectsDialog dlg(project_paths);
if (dlg.ShowModal() == wxID_OK) {
LoadProjectsDialog::LoadProjectOption option = static_cast<LoadProjectsDialog::LoadProjectOption>(dlg.get_action());
switch (option)
{
case LoadProjectsDialog::LoadProjectOption::AllGeometry: {
load_files(project_paths, true, false);
load_files(non_project_paths, true, false);
break;
}
case LoadProjectsDialog::LoadProjectOption::AllNewWindow: {
delete_after = false;
for (const fs::path& path : project_paths) {
wxString f = from_path(path);
start_new_slicer(&f, false);
}
for (const fs::path& path : non_project_paths) {
wxString f = from_path(path);
start_new_slicer(&f, false);
}
break;
}
case LoadProjectsDialog::LoadProjectOption::OneProject: {
int pos = dlg.get_selected();
assert(pos >= 0 && pos < project_paths.size());
if (wxGetApp().can_load_project())
load_project(from_path(project_paths[pos]));
project_paths.erase(project_paths.begin() + pos);
load_files(project_paths, true, false);
load_files(non_project_paths, true, false);
break;
}
case LoadProjectsDialog::LoadProjectOption::OneConfig: {
int pos = dlg.get_selected();
assert(pos >= 0 && pos < project_paths.size());
std::vector<fs::path> aux;
aux.push_back(project_paths[pos]);
load_files(aux, false, true);
project_paths.erase(project_paths.begin() + pos);
load_files(project_paths, true, false);
load_files(non_project_paths, true, false);
break;
}
case LoadProjectsDialog::LoadProjectOption::Unknown:
default:
assert(false);
break;
}
}
if (!delete_after)
return true;
#else
// 1 project file and some models - behave like drag n drop of 3mf and then load models // 1 project file and some models - behave like drag n drop of 3mf and then load models
if (project_paths.size() == 1) if (project_paths.size() == 1)
{ {
@ -5157,7 +5055,6 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
// load all projects and all models as geometry // load all projects and all models as geometry
load_files(project_paths, true, false); load_files(project_paths, true, false);
load_files(non_project_paths, true, false); load_files(non_project_paths, true, false);
#endif // 0
for (const fs::path& path : project_paths) { for (const fs::path& path : project_paths) {
@ -5311,7 +5208,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) { for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) {
std::string filename = (*it).filename().string(); std::string filename = (*it).filename().string();
bool handle_as_project = (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")); bool handle_as_project = boost::algorithm::iends_with(filename, ".3mf");
if (boost::algorithm::iends_with(filename, ".zip") && is_project_3mf(it->string())) { if (boost::algorithm::iends_with(filename, ".zip") && is_project_3mf(it->string())) {
BOOST_LOG_TRIVIAL(warning) << "File with .zip extension is 3mf project, opening as it would have .3mf extension: " << *it; BOOST_LOG_TRIVIAL(warning) << "File with .zip extension is 3mf project, opening as it would have .3mf extension: " << *it;
handle_as_project = true; handle_as_project = true;
@ -5319,8 +5216,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
if (handle_as_project && load_just_one_file) { if (handle_as_project && load_just_one_file) {
ProjectDropDialog::LoadType load_type = ProjectDropDialog::LoadType::Unknown; 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") && !is_project_3mf(it->string()))
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf")))
load_type = ProjectDropDialog::LoadType::LoadGeometry; load_type = ProjectDropDialog::LoadType::LoadGeometry;
else { else {
if (wxGetApp().app_config->get_bool("show_drop_project_dialog")) { if (wxGetApp().app_config->get_bool("show_drop_project_dialog")) {