mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 02:09:02 +08:00
Merge branch 'ys_spe2523'
This commit is contained in:
commit
61618d63aa
@ -47,6 +47,8 @@
|
|||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ModelProcessing.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
#include "libslic3r/CutUtils.hpp"
|
#include "libslic3r/CutUtils.hpp"
|
||||||
#include <arrange-wrapper/ModelArrange.hpp>
|
#include <arrange-wrapper/ModelArrange.hpp>
|
||||||
#include "libslic3r/Platform.hpp"
|
#include "libslic3r/Platform.hpp"
|
||||||
@ -291,13 +293,14 @@ int CLI::run(int argc, char **argv)
|
|||||||
Model model;
|
Model model;
|
||||||
try {
|
try {
|
||||||
if (has_config_from_profiles)
|
if (has_config_from_profiles)
|
||||||
model = Model::read_from_file(file, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
|
model = FileReader::load_model(file);
|
||||||
else {
|
else {
|
||||||
// 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 = Model::read_from_file(file, &config, &config_substitutions, Model::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;
|
||||||
@ -563,7 +566,7 @@ int CLI::run(int argc, char **argv)
|
|||||||
size_t num_objects = model.objects.size();
|
size_t num_objects = model.objects.size();
|
||||||
for (size_t i = 0; i < num_objects; ++ i) {
|
for (size_t i = 0; i < num_objects; ++ i) {
|
||||||
ModelObjectPtrs new_objects;
|
ModelObjectPtrs new_objects;
|
||||||
model.objects.front()->split(&new_objects);
|
ModelProcessing::split(model.objects.front(), &new_objects);
|
||||||
model.delete_object(size_t(0));
|
model.delete_object(size_t(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,6 +262,10 @@ set(SLIC3R_SOURCES
|
|||||||
CutUtils.hpp
|
CutUtils.hpp
|
||||||
Model.cpp
|
Model.cpp
|
||||||
Model.hpp
|
Model.hpp
|
||||||
|
ModelProcessing.cpp
|
||||||
|
ModelProcessing.hpp
|
||||||
|
FileReader.cpp
|
||||||
|
FileReader.hpp
|
||||||
MultiMaterialSegmentation.cpp
|
MultiMaterialSegmentation.cpp
|
||||||
MultiMaterialSegmentation.hpp
|
MultiMaterialSegmentation.hpp
|
||||||
MeshNormals.hpp
|
MeshNormals.hpp
|
||||||
|
252
src/libslic3r/FileReader.cpp
Normal file
252
src/libslic3r/FileReader.cpp
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
///|/ 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
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
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()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
61
src/libslic3r/FileReader.hpp
Normal file
61
src/libslic3r/FileReader.hpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Vojtěch Král @vojtechkral
|
||||||
|
///|/ Copyright (c) 2019 John Drake @foxox
|
||||||
|
///|/ Copyright (c) 2019 Sijmen Schoon
|
||||||
|
///|/ Copyright (c) 2017 Eyal Soha @eyal0
|
||||||
|
///|/ Copyright (c) Slic3r 2014 - 2015 Alessandro Ranellucci @alranel
|
||||||
|
///|/
|
||||||
|
///|/ 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
|
||||||
|
///|/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PrintConfig.hpp"
|
||||||
|
#include "enum_bitmask.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class Model;
|
||||||
|
class TriangleMesh;
|
||||||
|
|
||||||
|
namespace FileReader
|
||||||
|
{
|
||||||
|
enum class LoadAttribute : int {
|
||||||
|
AddDefaultInstances,
|
||||||
|
CheckVersion
|
||||||
|
};
|
||||||
|
using LoadAttributes = enum_bitmask<LoadAttribute>;
|
||||||
|
|
||||||
|
struct LoadStats {
|
||||||
|
int deleted_objects_cnt { 0 };
|
||||||
|
bool looks_like_saved_in_meters { false };
|
||||||
|
bool looks_like_imperial_units { false };
|
||||||
|
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,
|
||||||
|
ConfigSubstitutionContext* config_substitutions,
|
||||||
|
boost::optional<Semver> &prusaslicer_generator_version,
|
||||||
|
LoadAttributes options,
|
||||||
|
LoadStats* statistics = nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
ENABLE_ENUM_BITMASK_OPERATORS(FileReader::LoadAttribute)
|
||||||
|
|
||||||
|
} // namespace Slic3r::ModelProcessing
|
@ -15,21 +15,13 @@
|
|||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "BuildVolume.hpp"
|
#include "BuildVolume.hpp"
|
||||||
#include "Exception.hpp"
|
#include "Exception.hpp"
|
||||||
#include "Model.hpp"
|
|
||||||
#include "Geometry/ConvexHull.hpp"
|
#include "Geometry/ConvexHull.hpp"
|
||||||
#include "MTUtils.hpp"
|
#include "MTUtils.hpp"
|
||||||
#include "TriangleMeshSlicer.hpp"
|
#include "TriangleMeshSlicer.hpp"
|
||||||
#include "TriangleSelector.hpp"
|
#include "TriangleSelector.hpp"
|
||||||
#include "MultipleBeds.hpp"
|
#include "MultipleBeds.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 <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
@ -148,109 +140,6 @@ const CustomGCode::Info& Model::custom_gcode_per_print_z() const
|
|||||||
{
|
{
|
||||||
return custom_gcode_per_print_z_vector[s_multiple_beds.get_active_bed()];
|
return custom_gcode_per_print_z_vector[s_multiple_beds.get_active_bed()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 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 Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
|
|
||||||
{
|
|
||||||
Model model;
|
|
||||||
|
|
||||||
DynamicPrintConfig temp_config;
|
|
||||||
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;
|
|
||||||
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(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
|
||||||
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(), *config, *config_substitutions, &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("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip).");
|
|
||||||
|
|
||||||
if (! result)
|
|
||||||
throw Slic3r::RuntimeError("Loading of a model file failed.");
|
|
||||||
|
|
||||||
if (model.objects.empty())
|
|
||||||
throw Slic3r::RuntimeError("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();
|
|
||||||
|
|
||||||
for (CustomGCode::Info& info : model.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
|
|
||||||
Model 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.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelObject* Model::add_object()
|
ModelObject* Model::add_object()
|
||||||
{
|
{
|
||||||
this->objects.emplace_back(new ModelObject(this));
|
this->objects.emplace_back(new ModelObject(this));
|
||||||
@ -472,169 +361,6 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::looks_like_multipart_object() const
|
|
||||||
{
|
|
||||||
if (this->objects.size() <= 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BoundingBoxf3 tbb;
|
|
||||||
|
|
||||||
for (const ModelObject *obj : this->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).
|
|
||||||
static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
|
|
||||||
{
|
|
||||||
int out = ++ cntr;
|
|
||||||
if (cntr == max_extruders)
|
|
||||||
cntr = 0;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Model::convert_multipart_object(unsigned int max_extruders)
|
|
||||||
{
|
|
||||||
assert(this->objects.size() >= 2);
|
|
||||||
if (this->objects.size() < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ModelObject* object = new ModelObject(this);
|
|
||||||
object->input_file = this->objects.front()->input_file;
|
|
||||||
object->name = boost::filesystem::path(this->objects.front()->input_file).stem().string();
|
|
||||||
//FIXME copy the config etc?
|
|
||||||
|
|
||||||
unsigned int extruder_counter = 0;
|
|
||||||
for (const ModelObject* o : this->objects)
|
|
||||||
for (const ModelVolume* v : o->volumes) {
|
|
||||||
// If there are more than one object, put all volumes together
|
|
||||||
// Each object may contain any number of volumes and instances
|
|
||||||
// The volumes transformations are relative to the object containing them...
|
|
||||||
Geometry::Transformation trafo_volume = v->get_transformation();
|
|
||||||
// Revert the centering operation.
|
|
||||||
trafo_volume.set_offset(trafo_volume.get_offset() - o->origin_translation);
|
|
||||||
int counter = 1;
|
|
||||||
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
|
|
||||||
assert(new_v != nullptr);
|
|
||||||
new_v->name = (counter > 1) ? o->name + "_" + std::to_string(counter++) : o->name;
|
|
||||||
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
|
||||||
return new_v;
|
|
||||||
};
|
|
||||||
if (o->instances.empty()) {
|
|
||||||
copy_volume(object->add_volume(*v))->set_transformation(trafo_volume);
|
|
||||||
} else {
|
|
||||||
for (const ModelInstance* i : o->instances)
|
|
||||||
// ...so, transform everything to a common reference system (world)
|
|
||||||
copy_volume(object->add_volume(*v))->set_transformation(i->get_transformation() * trafo_volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// commented-out to fix #2868
|
|
||||||
// object->add_instance();
|
|
||||||
// object->instances[0]->set_offset(object->raw_mesh_bounding_box().center());
|
|
||||||
|
|
||||||
this->clear_objects();
|
|
||||||
this->objects.push_back(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3;
|
|
||||||
|
|
||||||
bool Model::looks_like_imperial_units() const
|
|
||||||
{
|
|
||||||
if (this->objects.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (ModelObject* obj : this->objects)
|
|
||||||
if (obj->get_object_stl_stats().volume < volume_threshold_inches) {
|
|
||||||
if (!obj->is_cut())
|
|
||||||
return true;
|
|
||||||
bool all_cut_parts_look_like_imperial_units = true;
|
|
||||||
for (ModelObject* obj_other : this->objects) {
|
|
||||||
if (obj_other == obj)
|
|
||||||
continue;
|
|
||||||
if (obj_other->cut_id.is_equal(obj->cut_id) && obj_other->get_object_stl_stats().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 Model::convert_from_imperial_units(bool only_small_volumes)
|
|
||||||
{
|
|
||||||
static constexpr const float in_to_mm = 25.4f;
|
|
||||||
for (ModelObject* obj : this->objects)
|
|
||||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) {
|
|
||||||
obj->scale_mesh_after_creation(in_to_mm);
|
|
||||||
for (ModelVolume* v : obj->volumes) {
|
|
||||||
assert(! v->source.is_converted_from_meters);
|
|
||||||
v->source.is_converted_from_inches = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1
|
|
||||||
|
|
||||||
bool Model::looks_like_saved_in_meters() const
|
|
||||||
{
|
|
||||||
if (this->objects.size() == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (ModelObject* obj : this->objects)
|
|
||||||
if (obj->get_object_stl_stats().volume < volume_threshold_meters)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Model::convert_from_meters(bool only_small_volumes)
|
|
||||||
{
|
|
||||||
static constexpr const double m_to_mm = 1000;
|
|
||||||
for (ModelObject* obj : this->objects)
|
|
||||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) {
|
|
||||||
obj->scale_mesh_after_creation(m_to_mm);
|
|
||||||
for (ModelVolume* v : obj->volumes) {
|
|
||||||
assert(! v->source.is_converted_from_inches);
|
|
||||||
v->source.is_converted_from_meters = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr const double zero_volume = 0.0000000001;
|
|
||||||
|
|
||||||
int Model::removed_objects_with_zero_volume()
|
|
||||||
{
|
|
||||||
if (objects.size() == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int removed = 0;
|
|
||||||
for (int i = int(objects.size()) - 1; i >= 0; i--)
|
|
||||||
if (objects[i]->get_object_stl_stats().volume < zero_volume) {
|
|
||||||
delete_object(size_t(i));
|
|
||||||
removed++;
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Model::adjust_min_z()
|
void Model::adjust_min_z()
|
||||||
{
|
{
|
||||||
if (objects.empty())
|
if (objects.empty())
|
||||||
@ -833,6 +559,13 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&me
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModelVolume* ModelObject::insert_volume(size_t idx, const ModelVolume& other, TriangleMesh&& mesh)
|
||||||
|
{
|
||||||
|
ModelVolume* v = new ModelVolume(this, other, std::move(mesh));
|
||||||
|
this->volumes.insert(this->volumes.begin() + idx, v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
void ModelObject::delete_volume(size_t idx)
|
void ModelObject::delete_volume(size_t idx)
|
||||||
{
|
{
|
||||||
ModelVolumePtrs::iterator i = this->volumes.begin() + idx;
|
ModelVolumePtrs::iterator i = this->volumes.begin() + idx;
|
||||||
@ -1264,72 +997,6 @@ void ModelObject::scale_mesh_after_creation(const float scale)
|
|||||||
this->invalidate_bounding_box();
|
this->invalidate_bounding_box();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType conv_type, std::vector<int> volume_idxs)
|
|
||||||
{
|
|
||||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - start";
|
|
||||||
|
|
||||||
ModelObject* new_object = new_clone(*this);
|
|
||||||
|
|
||||||
float koef = conv_type == ConversionType::CONV_FROM_INCH ? 25.4f : conv_type == ConversionType::CONV_TO_INCH ? 0.0393700787f :
|
|
||||||
conv_type == ConversionType::CONV_FROM_METER ? 1000.f : conv_type == ConversionType::CONV_TO_METER ? 0.001f : 1.f;
|
|
||||||
|
|
||||||
new_object->set_model(nullptr);
|
|
||||||
new_object->sla_support_points.clear();
|
|
||||||
new_object->sla_drain_holes.clear();
|
|
||||||
new_object->sla_points_status = sla::PointsStatus::NoPoints;
|
|
||||||
new_object->clear_volumes();
|
|
||||||
new_object->input_file.clear();
|
|
||||||
|
|
||||||
int vol_idx = 0;
|
|
||||||
for (ModelVolume* volume : volumes) {
|
|
||||||
if (!volume->mesh().empty()) {
|
|
||||||
TriangleMesh mesh(volume->mesh());
|
|
||||||
|
|
||||||
ModelVolume* vol = new_object->add_volume(mesh);
|
|
||||||
vol->name = volume->name;
|
|
||||||
vol->set_type(volume->type());
|
|
||||||
// Don't copy the config's ID.
|
|
||||||
vol->config.assign_config(volume->config);
|
|
||||||
assert(vol->config.id().valid());
|
|
||||||
assert(vol->config.id() != volume->config.id());
|
|
||||||
vol->set_material(volume->material_id(), *volume->material());
|
|
||||||
vol->source.input_file = volume->source.input_file;
|
|
||||||
vol->source.object_idx = (int)new_objects.size();
|
|
||||||
vol->source.volume_idx = vol_idx;
|
|
||||||
vol->source.is_converted_from_inches = volume->source.is_converted_from_inches;
|
|
||||||
vol->source.is_converted_from_meters = volume->source.is_converted_from_meters;
|
|
||||||
vol->source.is_from_builtin_objects = volume->source.is_from_builtin_objects;
|
|
||||||
|
|
||||||
vol->supported_facets.assign(volume->supported_facets);
|
|
||||||
vol->seam_facets.assign(volume->seam_facets);
|
|
||||||
vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets);
|
|
||||||
vol->fuzzy_skin_facets.assign(volume->fuzzy_skin_facets);
|
|
||||||
|
|
||||||
// Perform conversion only if the target "imperial" state is different from the current one.
|
|
||||||
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
|
|
||||||
if (//vol->source.is_converted_from_inches != from_imperial &&
|
|
||||||
(volume_idxs.empty() ||
|
|
||||||
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end())) {
|
|
||||||
vol->scale_geometry_after_creation(koef);
|
|
||||||
vol->set_offset(Vec3d(koef, koef, koef).cwiseProduct(volume->get_offset()));
|
|
||||||
if (conv_type == ConversionType::CONV_FROM_INCH || conv_type == ConversionType::CONV_TO_INCH)
|
|
||||||
vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH;
|
|
||||||
if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER)
|
|
||||||
vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER;
|
|
||||||
assert(! vol->source.is_converted_from_inches || ! vol->source.is_converted_from_meters);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
vol->set_offset(volume->get_offset());
|
|
||||||
}
|
|
||||||
vol_idx ++;
|
|
||||||
}
|
|
||||||
new_object->invalidate_bounding_box();
|
|
||||||
|
|
||||||
new_objects.push_back(new_object);
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end";
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ModelObject::materials_count() const
|
size_t ModelObject::materials_count() const
|
||||||
{
|
{
|
||||||
std::set<t_model_material_id> material_ids;
|
std::set<t_model_material_id> material_ids;
|
||||||
@ -1420,111 +1087,6 @@ void ModelVolume::reset_extra_facets()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)
|
|
||||||
/// From Front(Z) Upper(Y) TopLeft(X) corner.
|
|
||||||
/// 1. Seraparate group not overlaped i Z axis
|
|
||||||
/// 2. Seraparate group not overlaped i Y axis
|
|
||||||
/// 3. Start earlier in X (More on left side)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="triangle_mesh1">Compare from</param>
|
|
||||||
/// <param name="triangle_mesh2">Compare to</param>
|
|
||||||
/// <returns>True when triangle mesh 1 is closer, upper or lefter than triangle mesh 2 other wise false</returns>
|
|
||||||
static bool is_front_up_left(const TriangleMesh &trinagle_mesh1, const TriangleMesh &triangle_mesh2)
|
|
||||||
{
|
|
||||||
// stats form t1
|
|
||||||
const Vec3f &min1 = trinagle_mesh1.stats().min;
|
|
||||||
const Vec3f &max1 = trinagle_mesh1.stats().max;
|
|
||||||
// stats from t2
|
|
||||||
const Vec3f &min2 = triangle_mesh2.stats().min;
|
|
||||||
const Vec3f &max2 = triangle_mesh2.stats().max;
|
|
||||||
// priority Z, Y, X
|
|
||||||
for (int axe = 2; axe > 0; --axe) {
|
|
||||||
if (max1[axe] < min2[axe])
|
|
||||||
return true;
|
|
||||||
if (min1[axe] > max2[axe])
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return min1.x() < min2.x();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelObject::split(ModelObjectPtrs* new_objects)
|
|
||||||
{
|
|
||||||
for (ModelVolume* volume : this->volumes) {
|
|
||||||
if (volume->type() != ModelVolumeType::MODEL_PART)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// splited volume should not be text object
|
|
||||||
if (volume->text_configuration.has_value())
|
|
||||||
volume->text_configuration.reset();
|
|
||||||
|
|
||||||
std::vector<TriangleMesh> meshes = volume->mesh().split();
|
|
||||||
std::sort(meshes.begin(), meshes.end(), is_front_up_left);
|
|
||||||
|
|
||||||
size_t counter = 1;
|
|
||||||
for (TriangleMesh &mesh : meshes) {
|
|
||||||
// FIXME: crashes if not satisfied
|
|
||||||
if (mesh.facets_count() < 3 || mesh.has_zero_volume())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
|
|
||||||
ModelObject* new_object = m_model->add_object();
|
|
||||||
if (meshes.size() == 1) {
|
|
||||||
new_object->name = volume->name;
|
|
||||||
// Don't copy the config's ID.
|
|
||||||
new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
new_object->name = this->name + (meshes.size() > 1 ? "_" + std::to_string(counter++) : "");
|
|
||||||
// Don't copy the config's ID.
|
|
||||||
new_object->config.assign_config(this->config);
|
|
||||||
}
|
|
||||||
assert(new_object->config.id().valid());
|
|
||||||
assert(new_object->config.id() != this->config.id());
|
|
||||||
new_object->instances.reserve(this->instances.size());
|
|
||||||
for (const ModelInstance* model_instance : this->instances)
|
|
||||||
new_object->add_instance(*model_instance);
|
|
||||||
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh));
|
|
||||||
|
|
||||||
// Invalidate extruder value in volume's config,
|
|
||||||
// otherwise there will no way to change extruder for object after splitting,
|
|
||||||
// because volume's extruder value overrides object's extruder value.
|
|
||||||
if (new_vol->config.has("extruder"))
|
|
||||||
new_vol->config.set_key_value("extruder", new ConfigOptionInt(0));
|
|
||||||
|
|
||||||
for (ModelInstance* model_instance : new_object->instances) {
|
|
||||||
const Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset();
|
|
||||||
model_instance->set_offset(model_instance->get_offset() + shift);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_vol->set_offset(Vec3d::Zero());
|
|
||||||
// reset the source to disable reload from disk
|
|
||||||
new_vol->source = ModelVolume::Source();
|
|
||||||
new_objects->emplace_back(new_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ModelObject::merge()
|
|
||||||
{
|
|
||||||
if (this->volumes.size() == 1) {
|
|
||||||
// We can't merge meshes if there's just one volume
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TriangleMesh mesh;
|
|
||||||
|
|
||||||
for (ModelVolume* volume : volumes)
|
|
||||||
if (!volume->mesh().empty())
|
|
||||||
mesh.merge(volume->mesh());
|
|
||||||
|
|
||||||
this->clear_volumes();
|
|
||||||
ModelVolume* vol = this->add_volume(mesh);
|
|
||||||
|
|
||||||
if (!vol)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||||
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||||
@ -1733,42 +1295,6 @@ std::string ModelObject::get_export_filename() const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
TriangleMeshStats ModelObject::get_object_stl_stats() const
|
|
||||||
{
|
|
||||||
TriangleMeshStats full_stats;
|
|
||||||
full_stats.volume = 0.f;
|
|
||||||
|
|
||||||
// fill full_stats from all objet's meshes
|
|
||||||
for (ModelVolume* volume : this->volumes)
|
|
||||||
{
|
|
||||||
const TriangleMeshStats& stats = volume->mesh().stats();
|
|
||||||
|
|
||||||
// initialize full_stats (for repaired errors)
|
|
||||||
full_stats.open_edges += stats.open_edges;
|
|
||||||
full_stats.repaired_errors.merge(stats.repaired_errors);
|
|
||||||
|
|
||||||
// another used satistics value
|
|
||||||
if (volume->is_model_part()) {
|
|
||||||
Transform3d trans = instances.empty() ? volume->get_matrix() : (volume->get_matrix() * instances[0]->get_matrix());
|
|
||||||
full_stats.volume += stats.volume * std::fabs(trans.matrix().block(0, 0, 3, 3).determinant());
|
|
||||||
full_stats.number_of_parts += stats.number_of_parts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return full_stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ModelObject::get_repaired_errors_count(const int vol_idx /*= -1*/) const
|
|
||||||
{
|
|
||||||
if (vol_idx >= 0)
|
|
||||||
return this->volumes[vol_idx]->get_repaired_errors_count();
|
|
||||||
|
|
||||||
const RepairedMeshErrors& stats = get_object_stl_stats().repaired_errors;
|
|
||||||
|
|
||||||
return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
|
|
||||||
stats.facets_reversed + stats.backwards_edges;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModelObject::has_solid_mesh() const
|
bool ModelObject::has_solid_mesh() const
|
||||||
{
|
{
|
||||||
for (const ModelVolume* volume : volumes)
|
for (const ModelVolume* volume : volumes)
|
||||||
@ -1849,14 +1375,6 @@ void ModelVolume::calculate_convex_hull()
|
|||||||
assert(m_convex_hull.get());
|
assert(m_convex_hull.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
int ModelVolume::get_repaired_errors_count() const
|
|
||||||
{
|
|
||||||
const RepairedMeshErrors &stats = this->mesh().stats().repaired_errors;
|
|
||||||
|
|
||||||
return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
|
|
||||||
stats.facets_reversed + stats.backwards_edges;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TriangleMesh& ModelVolume::get_convex_hull() const
|
const TriangleMesh& ModelVolume::get_convex_hull() const
|
||||||
{
|
{
|
||||||
return *m_convex_hull.get();
|
return *m_convex_hull.get();
|
||||||
@ -1897,68 +1415,6 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split this volume, append the result to the object owning this volume.
|
|
||||||
// Return the number of volumes created from this one.
|
|
||||||
// This is useful to assign different materials to different volumes of an object.
|
|
||||||
size_t ModelVolume::split(unsigned int max_extruders)
|
|
||||||
{
|
|
||||||
std::vector<TriangleMesh> meshes = this->mesh().split();
|
|
||||||
if (meshes.size() <= 1)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
std::sort(meshes.begin(), meshes.end(), is_front_up_left);
|
|
||||||
|
|
||||||
// splited volume should not be text object
|
|
||||||
if (text_configuration.has_value())
|
|
||||||
text_configuration.reset();
|
|
||||||
|
|
||||||
size_t idx = 0;
|
|
||||||
size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
|
|
||||||
const std::string& name = this->name;
|
|
||||||
|
|
||||||
unsigned int extruder_counter = 0;
|
|
||||||
const Vec3d offset = this->get_offset();
|
|
||||||
|
|
||||||
for (TriangleMesh &mesh : meshes) {
|
|
||||||
if (mesh.empty() || mesh.has_zero_volume())
|
|
||||||
// Repair may have removed unconnected triangles, thus emptying the mesh.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (idx == 0) {
|
|
||||||
this->set_mesh(std::move(mesh));
|
|
||||||
this->calculate_convex_hull();
|
|
||||||
// Assign a new unique ID, so that a new GLVolume will be generated.
|
|
||||||
this->set_new_unique_id();
|
|
||||||
// reset the source to disable reload from disk
|
|
||||||
this->source = ModelVolume::Source();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh)));
|
|
||||||
|
|
||||||
this->object->volumes[ivolume]->set_offset(Vec3d::Zero());
|
|
||||||
this->object->volumes[ivolume]->center_geometry_after_creation();
|
|
||||||
this->object->volumes[ivolume]->translate(offset);
|
|
||||||
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
|
|
||||||
this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
|
||||||
this->object->volumes[ivolume]->m_is_splittable = 0;
|
|
||||||
++ idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// discard volumes for which the convex hull was not generated or is degenerate
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < this->object->volumes.size()) {
|
|
||||||
const std::shared_ptr<const TriangleMesh> &hull = this->object->volumes[i]->get_convex_hull_shared_ptr();
|
|
||||||
if (hull == nullptr || hull->its.vertices.empty() || hull->its.indices.empty()) {
|
|
||||||
this->object->delete_volume(i);
|
|
||||||
--idx;
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelVolume::translate(const Vec3d& displacement)
|
void ModelVolume::translate(const Vec3d& displacement)
|
||||||
{
|
{
|
||||||
set_offset(get_offset() + displacement);
|
set_offset(get_offset() + displacement);
|
||||||
@ -2052,22 +1508,6 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand
|
|||||||
this->set_new_unique_id();
|
this->set_new_unique_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelVolume::convert_from_imperial_units()
|
|
||||||
{
|
|
||||||
assert(! this->source.is_converted_from_meters);
|
|
||||||
this->scale_geometry_after_creation(25.4f);
|
|
||||||
this->set_offset(Vec3d(0, 0, 0));
|
|
||||||
this->source.is_converted_from_inches = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelVolume::convert_from_meters()
|
|
||||||
{
|
|
||||||
assert(! this->source.is_converted_from_inches);
|
|
||||||
this->scale_geometry_after_creation(1000.f);
|
|
||||||
this->set_offset(Vec3d(0, 0, 0));
|
|
||||||
this->source.is_converted_from_meters = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<size_t> ModelVolume::get_extruders_from_multi_material_painting() const {
|
std::vector<size_t> ModelVolume::get_extruders_from_multi_material_painting() const {
|
||||||
if (!this->is_mm_painted())
|
if (!this->is_mm_painted())
|
||||||
return {};
|
return {};
|
||||||
|
@ -14,17 +14,14 @@
|
|||||||
#define slic3r_Model_hpp_
|
#define slic3r_Model_hpp_
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "enum_bitmask.hpp"
|
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "ObjectID.hpp"
|
#include "ObjectID.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "PrintConfig.hpp"
|
|
||||||
#include "Slicing.hpp"
|
#include "Slicing.hpp"
|
||||||
#include "SLA/SupportPoint.hpp"
|
#include "SLA/SupportPoint.hpp"
|
||||||
#include "SLA/Hollowing.hpp"
|
#include "SLA/Hollowing.hpp"
|
||||||
#include "TriangleMesh.hpp"
|
#include "TriangleMesh.hpp"
|
||||||
#include "CustomGCode.hpp"
|
#include "CustomGCode.hpp"
|
||||||
#include "enum_bitmask.hpp"
|
|
||||||
#include "TextConfiguration.hpp"
|
#include "TextConfiguration.hpp"
|
||||||
#include "EmbossShape.hpp"
|
#include "EmbossShape.hpp"
|
||||||
#include "TriangleSelector.hpp"
|
#include "TriangleSelector.hpp"
|
||||||
@ -47,7 +44,6 @@ namespace cereal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
enum class ConversionType;
|
|
||||||
|
|
||||||
class BuildVolume;
|
class BuildVolume;
|
||||||
class Model;
|
class Model;
|
||||||
@ -424,6 +420,7 @@ public:
|
|||||||
ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART);
|
ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART);
|
||||||
ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID);
|
ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID);
|
||||||
ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh);
|
ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh);
|
||||||
|
ModelVolume* insert_volume(size_t idx, const ModelVolume &volume, TriangleMesh &&mesh);
|
||||||
void delete_volume(size_t idx);
|
void delete_volume(size_t idx);
|
||||||
void clear_volumes();
|
void clear_volumes();
|
||||||
void sort_volumes(bool full_sort);
|
void sort_volumes(bool full_sort);
|
||||||
@ -509,7 +506,6 @@ public:
|
|||||||
|
|
||||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||||
void scale_mesh_after_creation(const float scale);
|
void scale_mesh_after_creation(const float scale);
|
||||||
void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector<int> volume_idxs);
|
|
||||||
|
|
||||||
size_t materials_count() const;
|
size_t materials_count() const;
|
||||||
size_t facets_count() const;
|
size_t facets_count() const;
|
||||||
@ -519,9 +515,6 @@ public:
|
|||||||
// delete volumes which are marked as connector for this object
|
// delete volumes which are marked as connector for this object
|
||||||
void delete_connectors();
|
void delete_connectors();
|
||||||
void clone_for_cut(ModelObject **obj);
|
void clone_for_cut(ModelObject **obj);
|
||||||
|
|
||||||
void split(ModelObjectPtrs*new_objects);
|
|
||||||
void merge();
|
|
||||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||||
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||||
// This situation is solved by baking in the instance transformation into the mesh vertices.
|
// This situation is solved by baking in the instance transformation into the mesh vertices.
|
||||||
@ -536,11 +529,6 @@ public:
|
|||||||
|
|
||||||
std::string get_export_filename() const;
|
std::string get_export_filename() const;
|
||||||
|
|
||||||
// Get full stl statistics for all object's meshes
|
|
||||||
TriangleMeshStats get_object_stl_stats() const;
|
|
||||||
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
|
|
||||||
int get_repaired_errors_count(const int vol_idx = -1) const;
|
|
||||||
|
|
||||||
// Detect if object has at least one solid mash
|
// Detect if object has at least one solid mash
|
||||||
bool has_solid_mesh() const;
|
bool has_solid_mesh() const;
|
||||||
// Detect if object has at least one negative volume mash
|
// Detect if object has at least one negative volume mash
|
||||||
@ -697,13 +685,6 @@ private:
|
|||||||
void update_min_max_z();
|
void update_min_max_z();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ConversionType : int {
|
|
||||||
CONV_TO_INCH,
|
|
||||||
CONV_FROM_INCH,
|
|
||||||
CONV_TO_METER,
|
|
||||||
CONV_FROM_METER,
|
|
||||||
};
|
|
||||||
|
|
||||||
class FacetsAnnotation final : public ObjectWithTimestamp {
|
class FacetsAnnotation final : public ObjectWithTimestamp {
|
||||||
public:
|
public:
|
||||||
// Assign the content if the timestamp differs, don't assign an ObjectID.
|
// Assign the content if the timestamp differs, don't assign an ObjectID.
|
||||||
@ -879,11 +860,8 @@ public:
|
|||||||
int extruder_id() const;
|
int extruder_id() const;
|
||||||
|
|
||||||
bool is_splittable() const;
|
bool is_splittable() const;
|
||||||
|
void discard_splittable() { m_is_splittable = 0; }
|
||||||
|
|
||||||
// Split this volume, append the result to the object owning this volume.
|
|
||||||
// Return the number of volumes created from this one.
|
|
||||||
// This is useful to assign different materials to different volumes of an object.
|
|
||||||
size_t split(unsigned int max_extruders);
|
|
||||||
void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
|
void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
|
||||||
void translate(const Vec3d& displacement);
|
void translate(const Vec3d& displacement);
|
||||||
void scale(const Vec3d& scaling_factors);
|
void scale(const Vec3d& scaling_factors);
|
||||||
@ -904,8 +882,6 @@ public:
|
|||||||
void calculate_convex_hull();
|
void calculate_convex_hull();
|
||||||
const TriangleMesh& get_convex_hull() const;
|
const TriangleMesh& get_convex_hull() const;
|
||||||
const std::shared_ptr<const TriangleMesh>& get_convex_hull_shared_ptr() const { return m_convex_hull; }
|
const std::shared_ptr<const TriangleMesh>& get_convex_hull_shared_ptr() const { return m_convex_hull; }
|
||||||
// Get count of errors in the mesh
|
|
||||||
int get_repaired_errors_count() const;
|
|
||||||
|
|
||||||
// Helpers for loading / storing into AMF / 3MF files.
|
// Helpers for loading / storing into AMF / 3MF files.
|
||||||
static ModelVolumeType type_from_string(const std::string &s);
|
static ModelVolumeType type_from_string(const std::string &s);
|
||||||
@ -940,8 +916,6 @@ public:
|
|||||||
|
|
||||||
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
|
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
|
||||||
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
|
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
|
||||||
void convert_from_imperial_units();
|
|
||||||
void convert_from_meters();
|
|
||||||
|
|
||||||
const Transform3d& get_matrix() const { return m_transformation.get_matrix(); }
|
const Transform3d& get_matrix() const { return m_transformation.get_matrix(); }
|
||||||
Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); }
|
Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); }
|
||||||
@ -1318,24 +1292,6 @@ public:
|
|||||||
|
|
||||||
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
|
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
|
||||||
|
|
||||||
enum class LoadAttribute : int {
|
|
||||||
AddDefaultInstances,
|
|
||||||
CheckVersion
|
|
||||||
};
|
|
||||||
using LoadAttributes = enum_bitmask<LoadAttribute>;
|
|
||||||
|
|
||||||
static Model read_from_file(
|
|
||||||
const std::string& input_file,
|
|
||||||
DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
|
|
||||||
LoadAttributes options = LoadAttribute::AddDefaultInstances);
|
|
||||||
static Model read_from_archive(
|
|
||||||
const std::string& input_file,
|
|
||||||
DynamicPrintConfig* config,
|
|
||||||
ConfigSubstitutionContext* config_substitutions,
|
|
||||||
boost::optional<Semver> &prusaslicer_generator_version,
|
|
||||||
LoadAttributes options = LoadAttribute::AddDefaultInstances
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
|
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
|
||||||
ModelObject* add_object();
|
ModelObject* add_object();
|
||||||
ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
|
ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
|
||||||
@ -1373,14 +1329,6 @@ public:
|
|||||||
// Croaks if the duplicated objects do not fit the print bed.
|
// Croaks if the duplicated objects do not fit the print bed.
|
||||||
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
|
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
|
||||||
|
|
||||||
bool looks_like_multipart_object() const;
|
|
||||||
void convert_multipart_object(unsigned int max_extruders);
|
|
||||||
bool looks_like_imperial_units() const;
|
|
||||||
void convert_from_imperial_units(bool only_small_volumes);
|
|
||||||
bool looks_like_saved_in_meters() const;
|
|
||||||
void convert_from_meters(bool only_small_volumes);
|
|
||||||
int removed_objects_with_zero_volume();
|
|
||||||
|
|
||||||
// Ensures that the min z of the model is not negative
|
// Ensures that the min z of the model is not negative
|
||||||
void adjust_min_z();
|
void adjust_min_z();
|
||||||
|
|
||||||
@ -1412,8 +1360,6 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
|
|
||||||
|
|
||||||
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
||||||
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
|
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
|
||||||
|
|
||||||
|
401
src/libslic3r/ModelProcessing.cpp
Normal file
401
src/libslic3r/ModelProcessing.cpp
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
///|/ 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 "Model.hpp"
|
||||||
|
#include "ModelProcessing.hpp"
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r::ModelProcessing {
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
int out = ++cntr;
|
||||||
|
if (cntr == max_extruders)
|
||||||
|
cntr = 0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_to_multipart_object(Model& model, unsigned int max_extruders)
|
||||||
|
{
|
||||||
|
assert(model.objects.size() >= 2);
|
||||||
|
if (model.objects.size() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Model tmp_model = Model();
|
||||||
|
tmp_model.add_object();
|
||||||
|
|
||||||
|
ModelObject* object = tmp_model.objects[0];
|
||||||
|
object->input_file = model.objects.front()->input_file;
|
||||||
|
object->name = boost::filesystem::path(model.objects.front()->input_file).stem().string();
|
||||||
|
//FIXME copy the config etc?
|
||||||
|
|
||||||
|
unsigned int extruder_counter = 0;
|
||||||
|
for (const ModelObject* o : model.objects)
|
||||||
|
for (const ModelVolume* v : o->volumes) {
|
||||||
|
// If there are more than one object, put all volumes together
|
||||||
|
// Each object may contain any number of volumes and instances
|
||||||
|
// The volumes transformations are relative to the object containing them...
|
||||||
|
Geometry::Transformation trafo_volume = v->get_transformation();
|
||||||
|
// Revert the centering operation.
|
||||||
|
trafo_volume.set_offset(trafo_volume.get_offset() - o->origin_translation);
|
||||||
|
int counter = 1;
|
||||||
|
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume* new_v) {
|
||||||
|
assert(new_v != nullptr);
|
||||||
|
new_v->name = (counter > 1) ? o->name + "_" + std::to_string(counter++) : o->name;
|
||||||
|
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||||
|
return new_v;
|
||||||
|
};
|
||||||
|
if (o->instances.empty()) {
|
||||||
|
copy_volume(object->add_volume(*v))->set_transformation(trafo_volume);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const ModelInstance* i : o->instances)
|
||||||
|
// ...so, transform everything to a common reference system (world)
|
||||||
|
copy_volume(object->add_volume(*v))->set_transformation(i->get_transformation() * trafo_volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commented-out to fix #2868
|
||||||
|
// object->add_instance();
|
||||||
|
// object->instances[0]->set_offset(object->raw_mesh_bounding_box().center());
|
||||||
|
|
||||||
|
model.clear_objects();
|
||||||
|
model.add_object(*object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_from_imperial_units(Model& model, bool only_small_volumes)
|
||||||
|
{
|
||||||
|
static constexpr const float in_to_mm = 25.4f;
|
||||||
|
for (ModelObject* obj : model.objects)
|
||||||
|
if (!only_small_volumes || get_object_mesh_stats(obj).volume < volume_threshold_inches) {
|
||||||
|
obj->scale_mesh_after_creation(in_to_mm);
|
||||||
|
for (ModelVolume* v : obj->volumes) {
|
||||||
|
assert(!v->source.is_converted_from_meters);
|
||||||
|
v->source.is_converted_from_inches = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_from_imperial_units(ModelVolume* volume)
|
||||||
|
{
|
||||||
|
assert(!volume->source.is_converted_from_meters);
|
||||||
|
volume->scale_geometry_after_creation(25.4f);
|
||||||
|
volume->set_offset(Vec3d(0, 0, 0));
|
||||||
|
volume->source.is_converted_from_inches = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_from_meters(Model& model, bool only_small_volumes)
|
||||||
|
{
|
||||||
|
static constexpr const double m_to_mm = 1000;
|
||||||
|
for (ModelObject* obj : model.objects)
|
||||||
|
if (!only_small_volumes || get_object_mesh_stats(obj).volume < volume_threshold_meters) {
|
||||||
|
obj->scale_mesh_after_creation(m_to_mm);
|
||||||
|
for (ModelVolume* v : obj->volumes) {
|
||||||
|
assert(!v->source.is_converted_from_inches);
|
||||||
|
v->source.is_converted_from_meters = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_from_meters(ModelVolume* volume)
|
||||||
|
{
|
||||||
|
assert(!volume->source.is_converted_from_inches);
|
||||||
|
volume->scale_geometry_after_creation(1000.f);
|
||||||
|
volume->set_offset(Vec3d(0, 0, 0));
|
||||||
|
volume->source.is_converted_from_meters = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_units(Model& model_to, ModelObject* object_from, ConversionType conv_type, std::vector<int> volume_idxs)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - start";
|
||||||
|
|
||||||
|
float koef = conv_type == ConversionType::CONV_FROM_INCH ? 25.4f : conv_type == ConversionType::CONV_TO_INCH ? 0.0393700787f :
|
||||||
|
conv_type == ConversionType::CONV_FROM_METER ? 1000.f : conv_type == ConversionType::CONV_TO_METER ? 0.001f : 1.f;
|
||||||
|
|
||||||
|
ModelObject* new_object = model_to.add_object(*object_from);
|
||||||
|
new_object->sla_support_points.clear();
|
||||||
|
new_object->sla_drain_holes.clear();
|
||||||
|
new_object->sla_points_status = sla::PointsStatus::NoPoints;
|
||||||
|
new_object->clear_volumes();
|
||||||
|
new_object->input_file.clear();
|
||||||
|
|
||||||
|
int vol_idx = 0;
|
||||||
|
for (ModelVolume* volume : object_from->volumes) {
|
||||||
|
if (!volume->mesh().empty()) {
|
||||||
|
TriangleMesh mesh(volume->mesh());
|
||||||
|
|
||||||
|
ModelVolume* vol = new_object->add_volume(mesh);
|
||||||
|
vol->name = volume->name;
|
||||||
|
vol->set_type(volume->type());
|
||||||
|
// Don't copy the config's ID.
|
||||||
|
vol->config.assign_config(volume->config);
|
||||||
|
assert(vol->config.id().valid());
|
||||||
|
assert(vol->config.id() != volume->config.id());
|
||||||
|
vol->set_material(volume->material_id(), *volume->material());
|
||||||
|
vol->source.input_file = volume->source.input_file;
|
||||||
|
vol->source.object_idx = (int)model_to.objects.size()-1;
|
||||||
|
vol->source.volume_idx = vol_idx;
|
||||||
|
vol->source.is_converted_from_inches = volume->source.is_converted_from_inches;
|
||||||
|
vol->source.is_converted_from_meters = volume->source.is_converted_from_meters;
|
||||||
|
vol->source.is_from_builtin_objects = volume->source.is_from_builtin_objects;
|
||||||
|
|
||||||
|
vol->supported_facets.assign(volume->supported_facets);
|
||||||
|
vol->seam_facets.assign(volume->seam_facets);
|
||||||
|
vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets);
|
||||||
|
|
||||||
|
// Perform conversion only if the target "imperial" state is different from the current one.
|
||||||
|
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
|
||||||
|
if (//vol->source.is_converted_from_inches != from_imperial &&
|
||||||
|
(volume_idxs.empty() ||
|
||||||
|
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end())) {
|
||||||
|
vol->scale_geometry_after_creation(koef);
|
||||||
|
vol->set_offset(Vec3d(koef, koef, koef).cwiseProduct(volume->get_offset()));
|
||||||
|
if (conv_type == ConversionType::CONV_FROM_INCH || conv_type == ConversionType::CONV_TO_INCH)
|
||||||
|
vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH;
|
||||||
|
if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER)
|
||||||
|
vol->source.is_converted_from_meters = conv_type == ConversionType::CONV_FROM_METER;
|
||||||
|
assert(!vol->source.is_converted_from_inches || !vol->source.is_converted_from_meters);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
vol->set_offset(volume->get_offset());
|
||||||
|
}
|
||||||
|
vol_idx++;
|
||||||
|
}
|
||||||
|
new_object->invalidate_bounding_box();
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end";
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleMeshStats get_object_mesh_stats(const ModelObject* object)
|
||||||
|
{
|
||||||
|
TriangleMeshStats full_stats;
|
||||||
|
full_stats.volume = 0.f;
|
||||||
|
|
||||||
|
// fill full_stats from all objet's meshes
|
||||||
|
for (ModelVolume* volume : object->volumes)
|
||||||
|
{
|
||||||
|
const TriangleMeshStats& stats = volume->mesh().stats();
|
||||||
|
|
||||||
|
// initialize full_stats (for repaired errors)
|
||||||
|
full_stats.open_edges += stats.open_edges;
|
||||||
|
full_stats.repaired_errors.merge(stats.repaired_errors);
|
||||||
|
|
||||||
|
// another used satistics value
|
||||||
|
if (volume->is_model_part()) {
|
||||||
|
Transform3d trans = object->instances.empty() ? volume->get_matrix() : (volume->get_matrix() * object->instances[0]->get_matrix());
|
||||||
|
full_stats.volume += stats.volume * std::fabs(trans.matrix().block(0, 0, 3, 3).determinant());
|
||||||
|
full_stats.number_of_parts += stats.number_of_parts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return full_stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_repaired_errors_count(const ModelVolume* volume)
|
||||||
|
{
|
||||||
|
const RepairedMeshErrors& errors = volume->mesh().stats().repaired_errors;
|
||||||
|
return errors.degenerate_facets +
|
||||||
|
errors.edges_fixed +
|
||||||
|
errors.facets_removed +
|
||||||
|
errors.facets_reversed +
|
||||||
|
errors.backwards_edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_repaired_errors_count(const ModelObject* object, const int vol_idx /*= -1*/)
|
||||||
|
{
|
||||||
|
if (vol_idx >= 0)
|
||||||
|
return get_repaired_errors_count(object->volumes[vol_idx]);
|
||||||
|
|
||||||
|
const RepairedMeshErrors& errors = get_object_mesh_stats(object).repaired_errors;
|
||||||
|
return errors.degenerate_facets +
|
||||||
|
errors.edges_fixed +
|
||||||
|
errors.facets_removed +
|
||||||
|
errors.facets_reversed +
|
||||||
|
errors.backwards_edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)
|
||||||
|
/// From Front(Z) Upper(Y) TopLeft(X) corner.
|
||||||
|
/// 1. Seraparate group not overlaped i Z axis
|
||||||
|
/// 2. Seraparate group not overlaped i Y axis
|
||||||
|
/// 3. Start earlier in X (More on left side)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="triangle_mesh1">Compare from</param>
|
||||||
|
/// <param name="triangle_mesh2">Compare to</param>
|
||||||
|
/// <returns>True when triangle mesh 1 is closer, upper or lefter than triangle mesh 2 other wise false</returns>
|
||||||
|
static bool is_front_up_left(const TriangleMesh &trinagle_mesh1, const TriangleMesh &triangle_mesh2)
|
||||||
|
{
|
||||||
|
// stats form t1
|
||||||
|
const Vec3f &min1 = trinagle_mesh1.stats().min;
|
||||||
|
const Vec3f &max1 = trinagle_mesh1.stats().max;
|
||||||
|
// stats from t2
|
||||||
|
const Vec3f &min2 = triangle_mesh2.stats().min;
|
||||||
|
const Vec3f &max2 = triangle_mesh2.stats().max;
|
||||||
|
// priority Z, Y, X
|
||||||
|
for (int axe = 2; axe > 0; --axe) {
|
||||||
|
if (max1[axe] < min2[axe])
|
||||||
|
return true;
|
||||||
|
if (min1[axe] > max2[axe])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return min1.x() < min2.x();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split this volume, append the result to the object owning this volume.
|
||||||
|
// Return the number of volumes created from this one.
|
||||||
|
// This is useful to assign different materials to different volumes of an object.
|
||||||
|
size_t split(ModelVolume* volume, unsigned int max_extruders)
|
||||||
|
{
|
||||||
|
std::vector<TriangleMesh> meshes = volume->mesh().split();
|
||||||
|
if (meshes.size() <= 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
std::sort(meshes.begin(), meshes.end(), is_front_up_left);
|
||||||
|
|
||||||
|
// splited volume should not be text object
|
||||||
|
if (volume->text_configuration.has_value())
|
||||||
|
volume->text_configuration.reset();
|
||||||
|
|
||||||
|
ModelObject* object = volume->get_object();
|
||||||
|
|
||||||
|
size_t idx = 0;
|
||||||
|
size_t ivolume = std::find(object->volumes.begin(), object->volumes.end(), volume) - object->volumes.begin();
|
||||||
|
const std::string& name = volume->name;
|
||||||
|
|
||||||
|
unsigned int extruder_counter = 0;
|
||||||
|
const Vec3d offset = volume->get_offset();
|
||||||
|
|
||||||
|
for (TriangleMesh &mesh : meshes) {
|
||||||
|
if (mesh.empty() || mesh.has_zero_volume())
|
||||||
|
// Repair may have removed unconnected triangles, thus emptying the mesh.
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (idx == 0) {
|
||||||
|
volume->set_mesh(std::move(mesh));
|
||||||
|
volume->calculate_convex_hull();
|
||||||
|
// Assign a new unique ID, so that a new GLVolume will be generated.
|
||||||
|
volume->set_new_unique_id();
|
||||||
|
// reset the source to disable reload from disk
|
||||||
|
volume->source = ModelVolume::Source();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
object->insert_volume((++ivolume), *volume, std::move(mesh));//object->volumes.insert(object->volumes.begin() + (++ivolume), new ModelVolume(object, *volume, std::move(mesh)));
|
||||||
|
|
||||||
|
object->volumes[ivolume]->set_offset(Vec3d::Zero());
|
||||||
|
object->volumes[ivolume]->center_geometry_after_creation();
|
||||||
|
object->volumes[ivolume]->translate(offset);
|
||||||
|
object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
|
||||||
|
object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||||
|
object->volumes[ivolume]->discard_splittable();
|
||||||
|
++ idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard volumes for which the convex hull was not generated or is degenerate
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < object->volumes.size()) {
|
||||||
|
const std::shared_ptr<const TriangleMesh> &hull = object->volumes[i]->get_convex_hull_shared_ptr();
|
||||||
|
if (hull == nullptr || hull->its.vertices.empty() || hull->its.indices.empty()) {
|
||||||
|
object->delete_volume(i);
|
||||||
|
--idx;
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void split(ModelObject* object, ModelObjectPtrs* new_objects)
|
||||||
|
{
|
||||||
|
for (ModelVolume* volume : object->volumes) {
|
||||||
|
if (volume->type() != ModelVolumeType::MODEL_PART)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// splited volume should not be text object
|
||||||
|
if (volume->text_configuration.has_value())
|
||||||
|
volume->text_configuration.reset();
|
||||||
|
|
||||||
|
std::vector<TriangleMesh> meshes = volume->mesh().split();
|
||||||
|
std::sort(meshes.begin(), meshes.end(), is_front_up_left);
|
||||||
|
|
||||||
|
size_t counter = 1;
|
||||||
|
for (TriangleMesh &mesh : meshes) {
|
||||||
|
// FIXME: crashes if not satisfied
|
||||||
|
if (mesh.facets_count() < 3 || mesh.has_zero_volume())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
|
||||||
|
ModelObject* new_object = object->get_model()->add_object();
|
||||||
|
if (meshes.size() == 1) {
|
||||||
|
new_object->name = volume->name;
|
||||||
|
// Don't copy the config's ID.
|
||||||
|
new_object->config.assign_config(object->config.size() > 0 ? object->config : volume->config);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new_object->name = object->name + (meshes.size() > 1 ? "_" + std::to_string(counter++) : "");
|
||||||
|
// Don't copy the config's ID.
|
||||||
|
new_object->config.assign_config(object->config);
|
||||||
|
}
|
||||||
|
assert(new_object->config.id().valid());
|
||||||
|
assert(new_object->config.id() != object->config.id());
|
||||||
|
new_object->instances.reserve(object->instances.size());
|
||||||
|
for (const ModelInstance* model_instance : object->instances)
|
||||||
|
new_object->add_instance(*model_instance);
|
||||||
|
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh));
|
||||||
|
|
||||||
|
// Invalidate extruder value in volume's config,
|
||||||
|
// otherwise there will no way to change extruder for object after splitting,
|
||||||
|
// because volume's extruder value overrides object's extruder value.
|
||||||
|
if (new_vol->config.has("extruder"))
|
||||||
|
new_vol->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||||
|
|
||||||
|
for (ModelInstance* model_instance : new_object->instances) {
|
||||||
|
const Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset();
|
||||||
|
model_instance->set_offset(model_instance->get_offset() + shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_vol->set_offset(Vec3d::Zero());
|
||||||
|
// reset the source to disable reload from disk
|
||||||
|
new_vol->source = ModelVolume::Source();
|
||||||
|
new_objects->emplace_back(new_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void merge(ModelObject* object)
|
||||||
|
{
|
||||||
|
if (object->volumes.size() == 1) {
|
||||||
|
// We can't merge meshes if there's just one volume
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleMesh mesh;
|
||||||
|
|
||||||
|
for (ModelVolume* volume : object->volumes)
|
||||||
|
if (!volume->mesh().empty())
|
||||||
|
mesh.merge(volume->mesh());
|
||||||
|
|
||||||
|
object->clear_volumes();
|
||||||
|
ModelVolume* vol = object->add_volume(mesh);
|
||||||
|
|
||||||
|
if (!vol)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
62
src/libslic3r/ModelProcessing.hpp
Normal file
62
src/libslic3r/ModelProcessing.hpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, David Kocík @kocikdav, Vojtěch Král @vojtechkral
|
||||||
|
///|/ Copyright (c) 2019 John Drake @foxox
|
||||||
|
///|/ Copyright (c) 2019 Sijmen Schoon
|
||||||
|
///|/ Copyright (c) 2017 Eyal Soha @eyal0
|
||||||
|
///|/ Copyright (c) Slic3r 2014 - 2015 Alessandro Ranellucci @alranel
|
||||||
|
///|/
|
||||||
|
///|/ 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
|
||||||
|
///|/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TriangleMesh.hpp"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class Model;
|
||||||
|
class ModelObject;
|
||||||
|
class ModelVolume;
|
||||||
|
|
||||||
|
enum class ConversionType : int {
|
||||||
|
CONV_TO_INCH,
|
||||||
|
CONV_FROM_INCH,
|
||||||
|
CONV_TO_METER,
|
||||||
|
CONV_FROM_METER,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ModelProcessing
|
||||||
|
{
|
||||||
|
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_from_imperial_units(Model& model, bool only_small_volumes);
|
||||||
|
void convert_from_imperial_units(ModelVolume* volume);
|
||||||
|
|
||||||
|
void convert_from_meters(Model& model, bool only_small_volumes);
|
||||||
|
void convert_from_meters(ModelVolume* volume);
|
||||||
|
|
||||||
|
void convert_units(Model& model_to, ModelObject* object_from, ConversionType conv_type, std::vector<int> volume_idxs);
|
||||||
|
|
||||||
|
// Get full stl statistics for all object's meshes
|
||||||
|
TriangleMeshStats get_object_mesh_stats(const ModelObject* object);
|
||||||
|
// Get count of errors in the mesh
|
||||||
|
int get_repaired_errors_count(const ModelVolume* volume);
|
||||||
|
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
|
||||||
|
int get_repaired_errors_count(const ModelObject* object, const int vol_idx = -1);
|
||||||
|
|
||||||
|
// Split this volume, append the result to the object owning this volume.
|
||||||
|
// Return the number of volumes created from this one.
|
||||||
|
// This is useful to assign different materials to different volumes of an object.
|
||||||
|
size_t split(ModelVolume* volume, unsigned int max_extruders);
|
||||||
|
|
||||||
|
void split(ModelObject* object, std::vector<ModelObject*>* new_objects);
|
||||||
|
void merge(ModelObject* object);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r::ModelProcessing
|
@ -334,7 +334,7 @@ void Preset::normalize(DynamicPrintConfig &config)
|
|||||||
first_layer_height->percent = false;
|
first_layer_height->percent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_legacy_sla(config);
|
// handle_legacy_sla(config); // it looks like the best place for call it, is handle_legacy_composite
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config)
|
std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config)
|
||||||
|
@ -5064,6 +5064,8 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
|
|||||||
}
|
}
|
||||||
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
|
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle_legacy_sla(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PrintConfigDef print_config_def;
|
const PrintConfigDef print_config_def;
|
||||||
|
@ -19,8 +19,9 @@
|
|||||||
#include <wx/tooltip.h>
|
#include <wx/tooltip.h>
|
||||||
|
|
||||||
#include "libslic3r/BoundingBox.hpp"
|
#include "libslic3r/BoundingBox.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
@ -535,17 +536,15 @@ void BedShapePanel::load_stl()
|
|||||||
}
|
}
|
||||||
|
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
|
TriangleMesh mesh;
|
||||||
Model model;
|
|
||||||
try {
|
try {
|
||||||
model = Model::read_from_file(file_name);
|
mesh = FileReader::load_mesh(file_name);
|
||||||
}
|
}
|
||||||
catch (std::exception &) {
|
catch (std::exception& e) {
|
||||||
show_error(this, _L("Error! Invalid model"));
|
show_error(this, e.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mesh = model.mesh();
|
|
||||||
auto expolygons = mesh.horizontal_projection();
|
auto expolygons = mesh.horizontal_projection();
|
||||||
|
|
||||||
if (expolygons.size() == 0) {
|
if (expolygons.size() == 0) {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#endif // ENABLE_GLMODEL_STATISTICS
|
#endif // ENABLE_GLMODEL_STATISTICS
|
||||||
|
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/FileReader.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
#include "libslic3r/BuildVolume.hpp"
|
#include "libslic3r/BuildVolume.hpp"
|
||||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||||
@ -653,15 +653,14 @@ bool GLModel::init_from_file(const std::string& filename)
|
|||||||
if (!boost::algorithm::iends_with(filename, ".stl"))
|
if (!boost::algorithm::iends_with(filename, ".stl"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Model model;
|
TriangleMesh mesh;
|
||||||
try {
|
try {
|
||||||
model = Model::read_from_file(filename);
|
mesh = FileReader::load_mesh(filename);
|
||||||
}
|
}
|
||||||
catch (std::exception&) {
|
catch (std::exception&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
init_from(mesh);
|
||||||
init_from(model.mesh());
|
|
||||||
|
|
||||||
m_filename = filename;
|
m_filename = filename;
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/ModelProcessing.hpp"
|
||||||
|
|
||||||
#include "GUI_Factories.hpp"
|
#include "GUI_Factories.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/TextConfiguration.hpp"
|
#include "libslic3r/TextConfiguration.hpp"
|
||||||
#include "libslic3r/BuildVolume.hpp" // IWYU pragma: keep
|
#include "libslic3r/BuildVolume.hpp" // IWYU pragma: keep
|
||||||
|
#include "libslic3r/ModelProcessing.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
#include "GUI_Factories.hpp"
|
#include "GUI_Factories.hpp"
|
||||||
#include "GUI_ObjectManipulation.hpp"
|
#include "GUI_ObjectManipulation.hpp"
|
||||||
@ -434,7 +436,7 @@ void ObjectList::get_selection_indexes(std::vector<int>& obj_idxs, std::vector<i
|
|||||||
|
|
||||||
int ObjectList::get_repaired_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const
|
int ObjectList::get_repaired_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const
|
||||||
{
|
{
|
||||||
return obj_idx >= 0 ? (*m_objects)[obj_idx]->get_repaired_errors_count(vol_idx) : 0;
|
return obj_idx >= 0 ? ModelProcessing::get_repaired_errors_count(object(obj_idx), vol_idx) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string get_warning_icon_name(const TriangleMeshStats& stats)
|
static std::string get_warning_icon_name(const TriangleMeshStats& stats)
|
||||||
@ -455,7 +457,7 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TriangleMeshStats& stats = vol_idx == -1 ?
|
const TriangleMeshStats& stats = vol_idx == -1 ?
|
||||||
(*m_objects)[obj_idx]->get_object_stl_stats() :
|
ModelProcessing::get_object_mesh_stats((*m_objects)[obj_idx]) :
|
||||||
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats();
|
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats();
|
||||||
|
|
||||||
if (!stats.repaired() && stats.manifold()) {
|
if (!stats.repaired() && stats.manifold()) {
|
||||||
@ -1607,10 +1609,10 @@ void ObjectList::load_from_files(const wxArrayString& input_files, ModelObject&
|
|||||||
|
|
||||||
Model model;
|
Model model;
|
||||||
try {
|
try {
|
||||||
model = Model::read_from_file(input_file);
|
model = FileReader::load_model(input_file);
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception& e) {
|
||||||
auto msg = _L("Error!") + " " + input_file + " : " + e.what() + ".";
|
auto msg = _L("Error!") + " " + input_file + " : " + _(e.what()) + ".";
|
||||||
show_error(parent, msg);
|
show_error(parent, msg);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -1886,7 +1888,7 @@ bool ObjectList::del_subobject_item(wxDataViewItem& item)
|
|||||||
|
|
||||||
// If last volume item with warning was deleted, unmark object item
|
// If last volume item with warning was deleted, unmark object item
|
||||||
if (type & itVolume) {
|
if (type & itVolume) {
|
||||||
const std::string& icon_name = get_warning_icon_name(object(obj_idx)->get_object_stl_stats());
|
const std::string& icon_name = get_warning_icon_name(ModelProcessing::get_object_mesh_stats(object(obj_idx)));
|
||||||
m_objects_model->UpdateWarningIcon(parent, icon_name);
|
m_objects_model->UpdateWarningIcon(parent, icon_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2148,7 +2150,7 @@ void ObjectList::split()
|
|||||||
wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multi-material painting were "
|
wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multi-material painting were "
|
||||||
"removed after splitting the object."));
|
"removed after splitting the object."));
|
||||||
|
|
||||||
volume->split(nozzle_dmrs_cnt);
|
ModelProcessing::split(volume, nozzle_dmrs_cnt);
|
||||||
|
|
||||||
(*m_objects)[obj_idx]->input_file.clear();
|
(*m_objects)[obj_idx]->input_file.clear();
|
||||||
|
|
||||||
@ -2343,7 +2345,7 @@ void ObjectList::merge(bool to_multipart_object)
|
|||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Merge all parts to the one single object"));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Merge all parts to the one single object"));
|
||||||
|
|
||||||
ModelObject* model_object = (*m_objects)[obj_idx];
|
ModelObject* model_object = (*m_objects)[obj_idx];
|
||||||
model_object->merge();
|
ModelProcessing::merge(model_object);
|
||||||
|
|
||||||
m_objects_model->DeleteVolumeChildren(item);
|
m_objects_model->DeleteVolumeChildren(item);
|
||||||
|
|
||||||
@ -3173,7 +3175,7 @@ bool ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
|
|||||||
m_objects_model->SetExtruder(extruder, parent);
|
m_objects_model->SetExtruder(extruder, parent);
|
||||||
}
|
}
|
||||||
// If last volume item with warning was deleted, unmark object item
|
// If last volume item with warning was deleted, unmark object item
|
||||||
m_objects_model->UpdateWarningIcon(parent, get_warning_icon_name(obj->get_object_stl_stats()));
|
m_objects_model->UpdateWarningIcon(parent, get_warning_icon_name(ModelProcessing::get_object_mesh_stats(obj)));
|
||||||
}
|
}
|
||||||
wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx, printer_technology() != ptSLA);
|
wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx, printer_technology() != ptSLA);
|
||||||
}
|
}
|
||||||
@ -4623,7 +4625,7 @@ void ObjectList::fix_through_winsdk()
|
|||||||
if (vol_idxs.empty()) {
|
if (vol_idxs.empty()) {
|
||||||
#if !FIX_THROUGH_WINSDK_ALWAYS
|
#if !FIX_THROUGH_WINSDK_ALWAYS
|
||||||
for (int i = int(obj_idxs.size())-1; i >= 0; --i)
|
for (int i = int(obj_idxs.size())-1; i >= 0; --i)
|
||||||
if (object(obj_idxs[i])->get_repaired_errors_count() == 0)
|
if (ModelProcessing::get_repaired_errors_count(object(obj_idxs[i])) == 0)
|
||||||
obj_idxs.erase(obj_idxs.begin()+i);
|
obj_idxs.erase(obj_idxs.begin()+i);
|
||||||
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
||||||
for (int obj_idx : obj_idxs)
|
for (int obj_idx : obj_idxs)
|
||||||
@ -4633,7 +4635,7 @@ void ObjectList::fix_through_winsdk()
|
|||||||
ModelObject* obj = object(obj_idxs.front());
|
ModelObject* obj = object(obj_idxs.front());
|
||||||
#if !FIX_THROUGH_WINSDK_ALWAYS
|
#if !FIX_THROUGH_WINSDK_ALWAYS
|
||||||
for (int i = int(vol_idxs.size()) - 1; i >= 0; --i)
|
for (int i = int(vol_idxs.size()) - 1; i >= 0; --i)
|
||||||
if (obj->get_repaired_errors_count(vol_idxs[i]) == 0)
|
if (ModelProcessing::get_repaired_errors_count(obj, vol_idxs[i]) == 0)
|
||||||
vol_idxs.erase(vol_idxs.begin() + i);
|
vol_idxs.erase(vol_idxs.begin() + i);
|
||||||
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
||||||
for (int vol_idx : vol_idxs)
|
for (int vol_idx : vol_idxs)
|
||||||
@ -4689,7 +4691,7 @@ void ObjectList::fix_through_winsdk()
|
|||||||
int vol_idx{ -1 };
|
int vol_idx{ -1 };
|
||||||
for (int obj_idx : obj_idxs) {
|
for (int obj_idx : obj_idxs) {
|
||||||
#if !FIX_THROUGH_WINSDK_ALWAYS
|
#if !FIX_THROUGH_WINSDK_ALWAYS
|
||||||
if (object(obj_idx)->get_repaired_errors_count(vol_idx) == 0)
|
if (ModelProcessing::get_repaired_errors_count(object(obj_idx), vol_idx) == 0)
|
||||||
continue;
|
continue;
|
||||||
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
||||||
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models))
|
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models))
|
||||||
@ -4734,7 +4736,7 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co
|
|||||||
{
|
{
|
||||||
auto obj = object(obj_idx);
|
auto obj = object(obj_idx);
|
||||||
if (wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx)) {
|
if (wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx)) {
|
||||||
const std::string& icon_name = get_warning_icon_name(obj->get_object_stl_stats());
|
const std::string& icon_name = get_warning_icon_name(ModelProcessing::get_object_mesh_stats(obj));
|
||||||
m_objects_model->UpdateWarningIcon(obj_item, icon_name);
|
m_objects_model->UpdateWarningIcon(obj_item, icon_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
#include "libslic3r/BuildVolume.hpp"
|
#include "libslic3r/BuildVolume.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||||
#include "libslic3r/Format/OBJ.hpp"
|
#include "libslic3r/Format/OBJ.hpp"
|
||||||
#include "libslic3r/MultipleBeds.hpp"
|
#include "libslic3r/MultipleBeds.hpp"
|
||||||
@ -64,7 +65,7 @@ bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& f
|
|||||||
// hides the system icon
|
// hides the system icon
|
||||||
this->MSWUpdateDragImageOnLeave();
|
this->MSWUpdateDragImageOnLeave();
|
||||||
#endif // WIN32
|
#endif // WIN32
|
||||||
return gallery_dlg ? gallery_dlg->load_files(filenames) : false;
|
return gallery_dlg ? gallery_dlg->add_files_to_custom_dir(filenames) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -281,7 +282,7 @@ static void generate_thumbnail_from_model(const std::string& filename)
|
|||||||
|
|
||||||
Model model;
|
Model model;
|
||||||
try {
|
try {
|
||||||
model = Model::read_from_file(filename);
|
model = FileReader::load_model(filename);
|
||||||
}
|
}
|
||||||
catch (std::exception&) {
|
catch (std::exception&) {
|
||||||
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()";
|
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()";
|
||||||
@ -343,11 +344,16 @@ void GalleryDialog::load_label_icon_list()
|
|||||||
|
|
||||||
std::vector<std::string> sorted_names;
|
std::vector<std::string> sorted_names;
|
||||||
for (auto& dir_entry : fs::directory_iterator(dir)) {
|
for (auto& dir_entry : fs::directory_iterator(dir)) {
|
||||||
TriangleMesh mesh;
|
if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) {
|
||||||
if ((is_gallery_file(dir_entry, ".stl") && mesh.ReadSTLFile(dir_entry.path().string().c_str())) ||
|
try {
|
||||||
(is_gallery_file(dir_entry, ".obj") && load_obj(dir_entry.path().string().c_str(), &mesh) ) )
|
Model model = FileReader::load_model(dir_entry.path().string());
|
||||||
|
}
|
||||||
|
catch (std::exception&) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
sorted_names.push_back(dir_entry.path().filename().string());
|
sorted_names.push_back(dir_entry.path().filename().string());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sort the filename case insensitive
|
// sort the filename case insensitive
|
||||||
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
|
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
|
||||||
@ -441,7 +447,7 @@ void GalleryDialog::add_custom_shapes(wxEvent& event)
|
|||||||
if (input_files.IsEmpty())
|
if (input_files.IsEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
load_files(input_files);
|
add_files_to_custom_dir(input_files);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GalleryDialog::del_custom_shapes()
|
void GalleryDialog::del_custom_shapes()
|
||||||
@ -558,7 +564,7 @@ void GalleryDialog::update()
|
|||||||
load_label_icon_list();
|
load_label_icon_list();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GalleryDialog::load_files(const wxArrayString& input_files)
|
bool GalleryDialog::add_files_to_custom_dir(const wxArrayString& input_files)
|
||||||
{
|
{
|
||||||
auto dest_dir = get_dir(false);
|
auto dest_dir = get_dir(false);
|
||||||
|
|
||||||
@ -577,16 +583,14 @@ bool GalleryDialog::load_files(const wxArrayString& input_files)
|
|||||||
// Iterate through the input files
|
// Iterate through the input files
|
||||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||||
std::string input_file = into_u8(input_files.Item(i));
|
std::string input_file = into_u8(input_files.Item(i));
|
||||||
|
if (is_gallery_file(input_file, ".stl") || is_gallery_file(input_file, ".obj")) {
|
||||||
TriangleMesh mesh;
|
try {
|
||||||
if (is_gallery_file(input_file, ".stl") && !mesh.ReadSTLFile(input_file.c_str())) {
|
Model model = FileReader::load_model(input_file);
|
||||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL");
|
}
|
||||||
|
catch (std::exception&) {
|
||||||
|
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), is_gallery_file(input_file, ".obj") ? "OBJ" : "STL");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_gallery_file(input_file, ".obj") && !load_obj(input_file.c_str(), &mesh)) {
|
|
||||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "OBJ");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -58,7 +58,7 @@ public:
|
|||||||
|
|
||||||
int show(bool show_from_menu = false);
|
int show(bool show_from_menu = false);
|
||||||
void get_input_files(wxArrayString& input_files);
|
void get_input_files(wxArrayString& input_files);
|
||||||
bool load_files(const wxArrayString& input_files);
|
bool add_files_to_custom_dir(const wxArrayString& input_files);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "slic3r/Utils/FixModelByWin10.hpp"
|
#include "slic3r/Utils/FixModelByWin10.hpp"
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||||
|
#include "libslic3r/ModelProcessing.hpp"
|
||||||
|
|
||||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
@ -1898,7 +1899,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor
|
|||||||
// split to parts
|
// split to parts
|
||||||
for (int id = int(volumes.size())-1; id >= 0; id--)
|
for (int id = int(volumes.size())-1; id >= 0; id--)
|
||||||
if (volumes[id]->is_splittable() && volumes[id]->is_model_part()) // we have to split just solid volumes
|
if (volumes[id]->is_splittable() && volumes[id]->is_model_part()) // we have to split just solid volumes
|
||||||
volumes[id]->split(1);
|
ModelProcessing::split(volumes[id], 1);
|
||||||
|
|
||||||
m_parts.clear();
|
m_parts.clear();
|
||||||
for (const ModelVolume* volume : volumes) {
|
for (const ModelVolume* volume : volumes) {
|
||||||
@ -3293,8 +3294,8 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects)
|
|||||||
if (connectors_count != connectors_names.size())
|
if (connectors_count != connectors_names.size())
|
||||||
err_objects_names.push_back(object->name);
|
err_objects_names.push_back(object->name);
|
||||||
|
|
||||||
// check manifol/repairs
|
// check manifold/repairs
|
||||||
auto stats = object->get_object_stl_stats();
|
auto stats = ModelProcessing::get_object_mesh_stats(object);
|
||||||
if (!stats.manifold() || stats.repaired())
|
if (!stats.manifold() || stats.repaired())
|
||||||
err_objects_idxs.push_back(obj_idx);
|
err_objects_idxs.push_back(obj_idx);
|
||||||
obj_idx++;
|
obj_idx++;
|
||||||
|
@ -76,6 +76,8 @@
|
|||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/miniz_extension.hpp"
|
#include "libslic3r/miniz_extension.hpp"
|
||||||
|
#include "libslic3r/ModelProcessing.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
#include "libslic3r/MultipleBeds.hpp"
|
#include "libslic3r/MultipleBeds.hpp"
|
||||||
|
|
||||||
// For stl export
|
// For stl export
|
||||||
@ -160,6 +162,7 @@ using Slic3r::GUI::format_wxstr;
|
|||||||
static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
|
static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
using namespace ModelProcessing;
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
// BackgroundSlicingProcess updates UI with slicing progress: Status bar / progress bar has to be updated, possibly scene has to be refreshed,
|
// BackgroundSlicingProcess updates UI with slicing progress: Status bar / progress bar has to be updated, possibly scene has to be refreshed,
|
||||||
@ -609,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);
|
||||||
|
|
||||||
@ -1236,10 +1237,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
wxProgressDialog* progress_dlg = &progress_dlg_stack;
|
wxProgressDialog* progress_dlg = &progress_dlg_stack;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@ -1268,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) {
|
||||||
@ -1281,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.
|
||||||
@ -1297,35 +1293,40 @@ 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 = Slic3r::Model::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);
|
||||||
|
|
||||||
// We can't to load SLA project if there is at least one multi-part object on the bed
|
|
||||||
if (loaded_printer_technology == ptSLA) {
|
|
||||||
const ModelObjectPtrs& objects = q->model().objects;
|
|
||||||
for (auto object : objects)
|
|
||||||
if (object->volumes.size() > 1) {
|
|
||||||
Slic3r::GUI::show_info(nullptr,
|
|
||||||
_L("You cannot load SLA project with a multi-part object on the bed") + "\n\n" +
|
|
||||||
_L("Please check your object list before preset changing."),
|
|
||||||
_L("Attention!"));
|
|
||||||
return obj_idxs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.apply(loaded_printer_technology == ptFFF ?
|
config.apply(loaded_printer_technology == ptFFF ?
|
||||||
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
|
static_cast<const ConfigBase&>(FullPrintConfig::defaults()) :
|
||||||
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
|
static_cast<const ConfigBase&>(SLAFullPrintConfig::defaults()));
|
||||||
@ -1337,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;
|
||||||
@ -1359,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,
|
||||||
@ -1375,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 = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::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) {
|
|
||||||
model.convert_from_imperial_units(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 = model.removed_objects_with_zero_volume(); 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 && model.looks_like_saved_in_meters()) {
|
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?
|
|
||||||
model.convert_from_meters(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"
|
||||||
@ -1432,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 && model.looks_like_imperial_units()) {
|
|
||||||
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"
|
||||||
@ -1454,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 && model.looks_like_multipart_object()) {
|
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"
|
||||||
@ -1471,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)
|
|
||||||
model.convert_multipart_object(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
|
||||||
model.convert_multipart_object(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) {
|
||||||
@ -1486,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();
|
||||||
@ -1505,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
|
||||||
@ -1522,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;
|
||||||
@ -1553,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1569,18 +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) {
|
||||||
//wxMessageDialog msg_dlg(q, _L(
|
|
||||||
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) {
|
||||||
new_model->convert_multipart_object(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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2066,7 +2032,7 @@ void Plater::priv::split_object()
|
|||||||
|
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
ModelObjectPtrs new_objects;
|
ModelObjectPtrs new_objects;
|
||||||
current_model_object->split(&new_objects);
|
ModelProcessing::split(current_model_object, &new_objects);
|
||||||
if (new_objects.size() == 1)
|
if (new_objects.size() == 1)
|
||||||
// #ysFIXME use notification
|
// #ysFIXME use notification
|
||||||
Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part."));
|
Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part."));
|
||||||
@ -2651,7 +2617,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
|
|||||||
|
|
||||||
Model new_model;
|
Model new_model;
|
||||||
try {
|
try {
|
||||||
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
|
new_model = FileReader::load_model(path);
|
||||||
for (ModelObject* model_object : new_model.objects) {
|
for (ModelObject* model_object : new_model.objects) {
|
||||||
model_object->center_around_origin();
|
model_object->center_around_origin();
|
||||||
model_object->ensure_on_bed();
|
model_object->ensure_on_bed();
|
||||||
@ -2689,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)
|
||||||
new_volume->convert_from_imperial_units();
|
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)
|
||||||
new_volume->convert_from_meters();
|
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.
|
||||||
@ -2901,7 +2867,7 @@ void Plater::priv::reload_from_disk()
|
|||||||
Model new_model;
|
Model new_model;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
|
new_model = FileReader::load_model(path);
|
||||||
for (ModelObject* model_object : new_model.objects) {
|
for (ModelObject* model_object : new_model.objects) {
|
||||||
model_object->center_around_origin();
|
model_object->center_around_origin();
|
||||||
model_object->ensure_on_bed();
|
model_object->ensure_on_bed();
|
||||||
@ -2984,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)
|
||||||
new_volume->convert_from_imperial_units();
|
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)
|
||||||
new_volume->convert_from_meters();
|
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)
|
||||||
@ -3910,14 +3876,14 @@ bool Plater::priv::can_fix_through_winsdk() const
|
|||||||
// Fixing only if the model is not manifold.
|
// Fixing only if the model is not manifold.
|
||||||
if (vol_idxs.empty()) {
|
if (vol_idxs.empty()) {
|
||||||
for (auto obj_idx : obj_idxs)
|
for (auto obj_idx : obj_idxs)
|
||||||
if (model.objects[obj_idx]->get_repaired_errors_count() > 0)
|
if (get_repaired_errors_count(model.objects[obj_idx]) > 0)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int obj_idx = obj_idxs.front();
|
int obj_idx = obj_idxs.front();
|
||||||
for (auto vol_idx : vol_idxs)
|
for (auto vol_idx : vol_idxs)
|
||||||
if (model.objects[obj_idx]->get_repaired_errors_count(vol_idx) > 0)
|
if (get_repaired_errors_count(model.objects[obj_idx], vol_idx) > 0)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
#endif // FIX_THROUGH_WINSDK_ALWAYS
|
||||||
@ -5063,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)
|
||||||
{
|
{
|
||||||
@ -5169,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) {
|
||||||
@ -5323,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;
|
||||||
@ -5331,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")) {
|
||||||
@ -5695,19 +5579,23 @@ void Plater::convert_unit(ConversionType conv_type)
|
|||||||
conv_type == ConversionType::CONV_FROM_METER ? _L("Convert from meters") : _L("Revert conversion from meters"));
|
conv_type == ConversionType::CONV_FROM_METER ? _L("Convert from meters") : _L("Revert conversion from meters"));
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
|
|
||||||
ModelObjectPtrs objects;
|
{
|
||||||
|
Model tmp_model;
|
||||||
for (int obj_idx : obj_idxs) {
|
for (int obj_idx : obj_idxs) {
|
||||||
ModelObject *object = p->model.objects[obj_idx];
|
//ModelObject *object = p->model.objects[obj_idx];
|
||||||
object->convert_units(objects, conv_type, volume_idxs);
|
//object->convert_units(objects, conv_type, volume_idxs);
|
||||||
|
ModelProcessing::convert_units(tmp_model, p->model.objects[obj_idx], conv_type, volume_idxs);
|
||||||
remove(obj_idx);
|
remove(obj_idx);
|
||||||
}
|
}
|
||||||
p->load_model_objects(objects);
|
p->load_model_objects(tmp_model.objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Selection& selection = p->view3D->get_canvas3d()->get_selection();
|
Selection& selection = p->view3D->get_canvas3d()->get_selection();
|
||||||
size_t last_obj_idx = p->model.objects.size() - 1;
|
size_t last_obj_idx = p->model.objects.size() - 1;
|
||||||
|
|
||||||
if (volume_idxs.empty()) {
|
if (volume_idxs.empty()) {
|
||||||
for (size_t i = 0; i < objects.size(); ++i)
|
for (size_t i = 0; i < obj_idxs.size(); ++i)
|
||||||
selection.add_object((unsigned int)(last_obj_idx - i), i == 0);
|
selection.add_object((unsigned int)(last_obj_idx - i), i == 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
|
#include "libslic3r/ModelProcessing.hpp"
|
||||||
|
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
@ -930,7 +931,7 @@ void Sidebar::show_info_sizer()
|
|||||||
Vec3d size = vol ? vol->mesh().transformed_bounding_box(t).size() : model_object->instance_bounding_box(inst_idx).size();
|
Vec3d size = vol ? vol->mesh().transformed_bounding_box(t).size() : model_object->instance_bounding_box(inst_idx).size();
|
||||||
m_object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f", size(0)*koef, size(1)*koef, size(2)*koef));
|
m_object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f", size(0)*koef, size(1)*koef, size(2)*koef));
|
||||||
|
|
||||||
const TriangleMeshStats& stats = vol ? vol->mesh().stats() : model_object->get_object_stl_stats();
|
const TriangleMeshStats& stats = vol ? vol->mesh().stats() : ModelProcessing::get_object_mesh_stats(model_object);
|
||||||
|
|
||||||
double volume_val = stats.volume;
|
double volume_val = stats.volume;
|
||||||
if (vol)
|
if (vol)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "libslic3r/Format/SLAArchiveFormatRegistry.hpp"
|
#include "libslic3r/Format/SLAArchiveFormatRegistry.hpp"
|
||||||
#include "libslic3r/Format/SLAArchiveWriter.hpp"
|
#include "libslic3r/Format/SLAArchiveWriter.hpp"
|
||||||
#include "libslic3r/Format/SLAArchiveReader.hpp"
|
#include "libslic3r/Format/SLAArchiveReader.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ TEST_CASE("Archive export test", "[sla_archives]") {
|
|||||||
SLAPrint print;
|
SLAPrint print;
|
||||||
SLAFullPrintConfig fullcfg;
|
SLAFullPrintConfig fullcfg;
|
||||||
|
|
||||||
auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(pname) + ".obj", nullptr);
|
auto m = FileReader::load_model(TEST_DATA_DIR PATH_SEPARATOR + std::string(pname) + ".obj");
|
||||||
|
|
||||||
fullcfg.printer_technology.setInt(ptSLA); // FIXME this should be ensured
|
fullcfg.printer_technology.setInt(ptSLA); // FIXME this should be ensured
|
||||||
fullcfg.set("sla_archive_format", entry.id);
|
fullcfg.set("sla_archive_format", entry.id);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "slic3r/GUI/Jobs/ArrangeJob2.hpp"
|
#include "slic3r/GUI/Jobs/ArrangeJob2.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/FileReader.hpp"
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Format/3mf.hpp"
|
#include "libslic3r/Format/3mf.hpp"
|
||||||
@ -84,7 +85,7 @@ TEST_CASE("Basic arrange with cube", "[arrangejob]") {
|
|||||||
DynamicPrintConfig cfg;
|
DynamicPrintConfig cfg;
|
||||||
cfg.load_from_ini(basepath + "default_fff.ini",
|
cfg.load_from_ini(basepath + "default_fff.ini",
|
||||||
ForwardCompatibilitySubstitutionRule::Enable);
|
ForwardCompatibilitySubstitutionRule::Enable);
|
||||||
Model m = Model::read_from_file(basepath + "20mm_cube.obj", &cfg);
|
Model m = FileReader::load_model(basepath + "20mm_cube.obj");
|
||||||
|
|
||||||
UIThreadWorker w;
|
UIThreadWorker w;
|
||||||
arr2::ArrangeSettings settings;
|
arr2::ArrangeSettings settings;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user