mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-26 18:42:00 +08:00
258 lines
10 KiB
C++
258 lines
10 KiB
C++
///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
|
|
///|/ Copyright (c) 2021 Boleslaw Ciesielski
|
|
///|/ Copyright (c) 2019 John Drake @foxox
|
|
///|/ Copyright (c) 2019 Sijmen Schoon
|
|
///|/ Copyright (c) Slic3r 2014 - 2016 Alessandro Ranellucci @alranel
|
|
///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard
|
|
///|/
|
|
///|/ ported from lib/Slic3r/Model.pm:
|
|
///|/ Copyright (c) Prusa Research 2016 - 2022 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
|
|
///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel
|
|
///|/
|
|
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
|
///|/
|
|
#include "FileReader.hpp"
|
|
#include "Model.hpp"
|
|
#include "ModelProcessing.hpp"
|
|
#include "TriangleMesh.hpp"
|
|
|
|
#include "Format/AMF.hpp"
|
|
#include "Format/OBJ.hpp"
|
|
#include "Format/STL.hpp"
|
|
#include "Format/3mf.hpp"
|
|
#include "Format/STEP.hpp"
|
|
#include "Format/SVG.hpp"
|
|
#include "Format/PrintRequest.hpp"
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#include "I18N.hpp"
|
|
|
|
namespace Slic3r::FileReader
|
|
{
|
|
|
|
bool is_project_file(const std::string& input_file)
|
|
{
|
|
return boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip");
|
|
}
|
|
|
|
// 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_model_from_file(const std::string& input_file, LoadAttributes options)
|
|
{
|
|
Model model;
|
|
|
|
DynamicPrintConfig temp_config;
|
|
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
|
|
|
|
bool result = false;
|
|
if (boost::algorithm::iends_with(input_file, ".stl"))
|
|
result = load_stl(input_file.c_str(), &model);
|
|
else if (boost::algorithm::iends_with(input_file, ".obj"))
|
|
result = load_obj(input_file.c_str(), &model);
|
|
else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp"))
|
|
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"))
|
|
//? 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")) {
|
|
//FIXME options & LoadAttribute::CheckVersion ?
|
|
boost::optional<Semver> 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"))
|
|
result = load_svg(input_file, model);
|
|
else if (boost::ends_with(input_file, ".printRequest"))
|
|
result = load_printRequest(input_file.c_str(), &model);
|
|
else
|
|
throw Slic3r::RuntimeError(L("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."));
|
|
|
|
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"));
|
|
|
|
if (!boost::ends_with(input_file, ".printRequest"))
|
|
for (ModelObject* o : model.objects)
|
|
o->input_file = input_file;
|
|
|
|
if (options & LoadAttribute::AddDefaultInstances)
|
|
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)
|
|
{
|
|
assert(is_project_file(input_file));
|
|
assert(config != nullptr);
|
|
assert(config_substitutions != nullptr);
|
|
|
|
Model model;
|
|
|
|
bool result = false;
|
|
if (is_project_file(input_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()) {
|
|
CustomGCode::update_custom_gcode_per_print_z_from_config(info, config);
|
|
CustomGCode::check_mode_for_custom_gcode_per_print_z(info);
|
|
}
|
|
sort_remove_duplicates(config_substitutions->substitutions);
|
|
return model;
|
|
}
|
|
|
|
TriangleMesh load_mesh(const std::string& 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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|