Merge remote-tracking branch 'origin/master' into ys_cp_improvements

This commit is contained in:
YuSanka 2020-01-15 11:40:54 +01:00
commit 1844fca780
52 changed files with 1433 additions and 922 deletions

View File

@ -72,6 +72,9 @@ if (MSVC)
# error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater
# Generate symbols at every build target, even for the release.
add_compile_options(-bigobj -Zm520 /Zi)
# Disable STL4007: Many result_type typedefs and all argument_type, first_argument_type, and second_argument_type typedefs are deprecated in C++17.
#FIXME Remove this line after eigen library adapts to the new C++17 adaptor rules.
add_compile_options(-D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING)
endif ()
if (MINGW)

View File

@ -18,6 +18,7 @@ config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/Prusa
name = Creality Ender-3
variants = 0.4
technology = FFF
default_materials = Creality PLA @ENDER3; Prusament PLA @ENDER3
# All presets starting with asterisk, for example *common*, are intermediate and they will
# not make it into the user interface.

View File

@ -22,72 +22,84 @@ name = Original Prusa MINI
variants = 0.4; 0.25; 0.6
technology = FFF
family = MINI
default_materials = Prusament PLA; Prusament PETG @MINI
[printer_model:MK3S]
name = Original Prusa i3 MK3S
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK3
default_materials = Prusament PLA; Prusament PETG
[printer_model:MK3]
name = Original Prusa i3 MK3
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK3
default_materials = Prusament PLA; Prusament PETG
[printer_model:MK3SMMU2S]
name = Original Prusa i3 MK3S MMU2S
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK3
default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
[printer_model:MK3MMU2]
name = Original Prusa i3 MK3 MMU2
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK3
default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
[printer_model:MK2.5S]
name = Original Prusa i3 MK2.5S
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK2.5
default_materials = Prusament PLA; Prusament PETG
[printer_model:MK2.5]
name = Original Prusa i3 MK2.5
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK2.5
default_materials = Prusament PLA; Prusament PETG
[printer_model:MK2.5SMMU2S]
name = Original Prusa i3 MK2.5S MMU2S
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK2.5
default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
[printer_model:MK2.5MMU2]
name = Original Prusa i3 MK2.5 MMU2
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK2.5
default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2
[printer_model:MK2S]
name = Original Prusa i3 MK2S
variants = 0.4; 0.25; 0.6
technology = FFF
family = MK2
default_materials = Prusament PLA; Prusament PETG
[printer_model:MK2SMM]
name = Original Prusa i3 MK2S MMU1
variants = 0.4; 0.6
technology = FFF
family = MK2
default_materials = Prusament PLA; Prusament PETG @MMU1
[printer_model:SL1]
name = Original Prusa SL1
variants = default
technology = SLA
family = SL1
default_materials = Prusa Transparent Tough @0.05
# All presets starting with asterisk, for example *common*, are intermediate and they will
# not make it into the user interface.

View File

@ -641,10 +641,18 @@ bool CLI::export_models(IO::ExportFormat format)
const std::string path = this->output_filepath(model, format);
bool success = false;
switch (format) {
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr, false); break;
#else
case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break;
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break;
case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break;
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break;
#else
case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break;
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
default: assert(false); break;
}
if (success)

View File

@ -24,7 +24,7 @@ set(LIBNEST2D_SRCFILES
src/libnest2d.cpp
)
add_library(libnest2d ${LIBNEST2D_SRCFILES})
add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES})
target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost)

View File

@ -1116,12 +1116,8 @@ private:
for(Item& item : items_) item.translate(d);
}
void setInitialPosition(Item& item) {
auto sh = item.rawShape();
sl::translate(sh, item.translation());
sl::rotate(sh, item.rotation());
Box bb = sl::boundingBox(sh);
void setInitialPosition(Item& item) {
Box bb = item.boundingBox();
Vertex ci, cb;
auto bbin = sl::boundingBox(bin_);

View File

@ -500,7 +500,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
std::runtime_error("Deserializing nil into a non-nullable object");
throw std::runtime_error("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@ -525,9 +525,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
std::runtime_error("Serializing NaN");
throw std::runtime_error("Serializing NaN");
} else
std::runtime_error("Serializing invalid number");
throw std::runtime_error("Serializing invalid number");
}
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
if (NULLABLE) {
@ -646,7 +646,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
std::runtime_error("Deserializing nil into a non-nullable object");
throw std::runtime_error("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int value;
@ -663,7 +663,7 @@ private:
if (NULLABLE)
ss << "nil";
else
std::runtime_error("Serializing NaN");
throw std::runtime_error("Serializing NaN");
} else
ss << v;
}
@ -1126,7 +1126,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
std::runtime_error("Deserializing nil into a non-nullable object");
throw std::runtime_error("Deserializing nil into a non-nullable object");
} else
this->values.push_back(item_str.compare("1") == 0);
}
@ -1139,7 +1139,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
std::runtime_error("Serializing NaN");
throw std::runtime_error("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@ -1638,7 +1638,7 @@ class DynamicConfig : public virtual ConfigBase
public:
DynamicConfig() {}
DynamicConfig(const DynamicConfig &rhs) { *this = rhs; }
DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); }
DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); }
explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys);
explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {}
virtual ~DynamicConfig() override { clear(); }
@ -1656,7 +1656,7 @@ public:
// Move a content of one DynamicConfig to another DynamicConfig.
// If rhs.def() is not null, then it has to be equal to this->def().
DynamicConfig& operator=(DynamicConfig &&rhs)
DynamicConfig& operator=(DynamicConfig &&rhs) noexcept
{
assert(this->def() == nullptr || this->def() == rhs.def());
this->clear();

View File

@ -19,7 +19,7 @@ class ExPolygon
public:
ExPolygon() {}
ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
ExPolygon(ExPolygon &&other) noexcept : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
explicit ExPolygon(const Polygon &contour) : contour(contour) {}
explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {}
explicit ExPolygon(const Points &contour) : contour(contour) {}
@ -32,7 +32,7 @@ public:
ExPolygon(std::initializer_list<Point> contour, std::initializer_list<Point> hole) : contour(contour), holes({ hole }) {}
ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; }
ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
ExPolygon& operator=(ExPolygon &&other) noexcept { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
Polygon contour;
Polygons holes;

View File

@ -48,9 +48,6 @@ public:
double retract_length_toolchange() const;
double retract_restart_extra_toolchange() const;
// Constructor for a key object, to be used by the stdlib search functions.
static Extruder key(unsigned int id) { return Extruder(id); }
private:
// Private constructor to create a key for a search in std::set.
Extruder(unsigned int id) : m_id(id) {}

View File

@ -6,6 +6,26 @@
namespace Slic3r {
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
{
if (role != erMixed) {
auto first = extrusion_entities.begin();
auto last = extrusion_entities.end();
auto result = first;
while (first != last) {
// The caller wants only paths with a specific extrusion role.
auto role2 = (*first)->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
*result = *first;
++ result;
}
++ first;
}
}
}
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
: no_sort(false)
{
@ -74,31 +94,16 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role)
{
ExtrusionEntityCollection out;
if (this->no_sort) {
out = *this;
} else {
if (role == erMixed)
out = *this;
else {
for (const ExtrusionEntity *ee : this->entities) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = ee->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
continue;
}
}
out.entities.emplace_back(ee->clone());
}
}
chain_and_reorder_extrusion_entities(out.entities, &start_near);
}
return out;
// Return a filtered copy of the collection.
ExtrusionEntityCollection out;
out.entities = filter_by_extrusion_role(extrusion_entities, role);
// Clone the extrusion entities.
for (auto &ptr : out.entities)
ptr = ptr->clone();
chain_and_reorder_extrusion_entities(out.entities, &start_near);
return out;
}
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const

View File

@ -6,6 +6,21 @@
namespace Slic3r {
// Remove those items from extrusion_entities, that do not match role.
// Do nothing if role is mixed.
// Removed elements are NOT being deleted.
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role);
// Return new vector of ExtrusionEntities* with only those items from input extrusion_entities, that match role.
// Return all extrusion entities if role is mixed.
// Returned extrusion entities are shared with the source vector, they are NOT cloned, they are considered to be owned by extrusion_entities.
inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
{
ExtrusionEntitiesPtr out { extrusion_entities };
filter_by_extrusion_role_in_place(out, role);
return out;
}
class ExtrusionEntityCollection : public ExtrusionEntity
{
public:
@ -65,7 +80,9 @@ public:
}
void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i);
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const;
static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = erMixed);
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
void reverse();
const Point& first_point() const { return this->entities.front()->first_point(); }
const Point& last_point() const { return this->entities.back()->last_point(); }
@ -105,6 +122,6 @@ public:
}
};
}
} // namespace Slic3r
#endif

View File

@ -107,9 +107,9 @@ double Flow::mm3_per_mm() const
{
float res = this->bridge ?
// Area of a circle with dmr of this->width.
(this->width * this->width) * 0.25 * PI :
float((this->width * this->width) * 0.25 * PI) :
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
this->height * (this->width - this->height * (1. - 0.25 * PI));
float(this->height * (this->width - this->height * (1. - 0.25 * PI)));
//assert(res > 0.);
if (res <= 0.)
throw std::runtime_error("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?");

View File

@ -1876,12 +1876,24 @@ namespace Slic3r {
typedef std::vector<BuildItem> BuildItemsList;
typedef std::map<int, ObjectData> IdToObjectDataMap;
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
bool m_fullpath_sources{ true };
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
public:
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
#if ENABLE_THUMBNAIL_GENERATOR
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
#else
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources);
#endif // ENABLE_THUMBNAIL_GENERATOR
#else
#if ENABLE_THUMBNAIL_GENERATOR
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
#else
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
private:
#if ENABLE_THUMBNAIL_GENERATOR
@ -1906,6 +1918,22 @@ namespace Slic3r {
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
};
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
#if ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
{
clear_errors();
m_fullpath_sources = fullpath_sources;
return _save_model_to_file(filename, model, config, thumbnail_data);
}
#else
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources)
{
clear_errors();
return _save_model_to_file(filename, model, config);
}
#endif // ENABLE_THUMBNAIL_GENERATOR
#else
#if ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
{
@ -1919,6 +1947,7 @@ namespace Slic3r {
return _save_model_to_file(filename, model, config);
}
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
#if ENABLE_THUMBNAIL_GENERATOR
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
@ -2557,7 +2586,12 @@ namespace Slic3r {
// stores volume's source data
if (!volume->source.input_file.empty())
{
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
#else
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n";
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
@ -2646,21 +2680,37 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c
return res;
}
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
#if ENABLE_THUMBNAIL_GENERATOR
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
#else
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
#endif // ENABLE_THUMBNAIL_GENERATOR
#else
#if ENABLE_THUMBNAIL_GENERATOR
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
#else
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config)
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
{
if ((path == nullptr) || (model == nullptr))
return false;
_3MF_Exporter exporter;
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
#if ENABLE_THUMBNAIL_GENERATOR
bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data);
#else
bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources);
#endif // ENABLE_THUMBNAIL_GENERATOR
#else
#if ENABLE_THUMBNAIL_GENERATOR
bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data);
#else
bool res = exporter.save_model_to_file(path, *model, config);
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
if (!res)
exporter.log_errors();

View File

@ -31,11 +31,19 @@ namespace Slic3r {
// Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
#if ENABLE_THUMBNAIL_GENERATOR
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
#else
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
#endif // ENABLE_THUMBNAIL_GENERATOR
#else
#if ENABLE_THUMBNAIL_GENERATOR
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
#else
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config);
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
}; // namespace Slic3r

View File

@ -1019,7 +1019,11 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
return false;
}
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
#else
bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
{
if ((path == nullptr) || (model == nullptr))
return false;
@ -1177,7 +1181,12 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
stream << "</metadata>\n";
if (!volume->source.input_file.empty())
{
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
stream << " <metadata type=\"slic3r.source_file\">" << input_file << "</metadata>\n";
#else
stream << " <metadata type=\"slic3r.source_file\">" << xml_escape(volume->source.input_file) << "</metadata>\n";
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
stream << " <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
stream << " <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";

View File

@ -11,7 +11,11 @@ extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model,
// Save the given model and the config data into an amf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
#else
extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config);
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
}; // namespace Slic3r

File diff suppressed because it is too large Load Diff

View File

@ -197,23 +197,25 @@ public:
// append full config to the given string
static void append_full_config(const Print& print, std::string& str);
protected:
// Object and support extrusions of the same PrintObject at the same print_z.
// public, so that it could be accessed by free helper functions from GCode.cpp
struct LayerToPrint
{
LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
const Layer* object_layer;
const SupportLayer* support_layer;
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
private:
#if ENABLE_THUMBNAIL_GENERATOR
void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb);
void _do_export(Print &print, FILE *file, ThumbnailsGeneratorCallback thumbnail_cb);
#else
void _do_export(Print &print, FILE *file);
#endif //ENABLE_THUMBNAIL_GENERATOR
// Object and support extrusions of the same PrintObject at the same print_z.
struct LayerToPrint
{
LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
const Layer *object_layer;
const SupportLayer *support_layer;
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
void process_layer(
@ -239,7 +241,6 @@ protected:
std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.);
std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.);
typedef std::vector<int> ExtruderPerCopy;
// Extruding multiple objects with soluble / non-soluble / combined supports
// on a multi-material printer, trying to minimize tool switches.
// Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
@ -253,21 +254,29 @@ protected:
struct Island
{
struct Region {
ExtrusionEntityCollection perimeters;
ExtrusionEntityCollection infills;
// Non-owned references to LayerRegion::perimeters::entities
// std::vector<const ExtrusionEntity*> would be better here, but there is no way in C++ to convert from std::vector<T*> std::vector<const T*> without copying.
ExtrusionEntitiesPtr perimeters;
// Non-owned references to LayerRegion::fills::entities
ExtrusionEntitiesPtr infills;
std::vector<const ExtruderPerCopy*> infills_overrides;
std::vector<const ExtruderPerCopy*> perimeters_overrides;
std::vector<const WipingExtrusions::ExtruderPerCopy*> infills_overrides;
std::vector<const WipingExtrusions::ExtruderPerCopy*> perimeters_overrides;
enum Type {
PERIMETERS,
INFILL,
};
// Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping
void append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copy_extruders, size_t object_copies_num);
void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders);
};
std::vector<Region> by_region; // all extrusions for this island, grouped by regions
const std::vector<Region>& by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities = false); // returns reference to subvector of by_region
private:
std::vector<Region> by_region_per_copy_cache; // caches vector generated by function above to avoid copying and recalculating
std::vector<Region> by_region; // all extrusions for this island, grouped by regions
// Fills in by_region_per_copy_cache and returns its reference.
const std::vector<Region>& by_region_per_copy(std::vector<Region> &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const;
};
std::vector<Island> islands;
};
@ -277,7 +286,9 @@ protected:
InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
ObjectByExtruder &object_by_extruder;
// Repository
ObjectByExtruder &object_by_extruder;
// Index into std::vector<LayerToPrint>, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
const size_t layer_id;
const PrintObject &print_object;
// Instance idx of the copy of a print object.
@ -285,7 +296,8 @@ protected:
};
std::vector<InstanceToPrint> sort_print_object_instances(
std::vector<ObjectByExtruder> &objects_by_extruder,
std::vector<ObjectByExtruder> &objects_by_extruder,
// Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances.
const std::vector<LayerToPrint> &layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<std::pair<size_t, size_t>> *ordering,
@ -354,7 +366,7 @@ protected:
#endif /* HAS_PRESSURE_EQUALIZER */
std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
// Heights at which the skirt has already been extruded.
// Heights (print_z) at which the skirt has already been extruded.
std::vector<coordf_t> m_skirt_done;
// Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print.
bool m_brim_done;
@ -362,11 +374,6 @@ protected:
bool m_second_layer_things_done;
// Index of a last object copy extruded.
std::pair<const PrintObject*, Point> m_last_obj_copy;
// Extensions for colorprint - now it's not a just color_print_heights,
// there can be some custom gcode.
// Updated before the export and erased during the process,
// so no toolchange occurs twice.
std::vector<Model::CustomGCode> m_custom_gcode_per_print_z;
// Time estimators
GCodeTimeEstimator m_normal_time_estimator;

View File

@ -311,24 +311,22 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
{
auto axis_absolute_position = [this](GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float
{
float current_absolute_position = _get_axis_position(axis);
float current_origin = _get_axis_origin(axis);
float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
bool is_relative = (_get_global_positioning_type() == Relative);
if (axis == E)
is_relative |= (_get_e_local_positioning_type() == Relative);
if (lineG1.has(Slic3r::Axis(axis)))
{
float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret + current_origin;
return is_relative ? _get_axis_position(axis) + ret : _get_axis_origin(axis) + ret;
}
else
return current_absolute_position;
return _get_axis_position(axis);
};
// updates axes positions from line
float new_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
@ -352,7 +350,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
if (delta_pos[E] < 0.0f)
{
if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
type = GCodeMove::Move;
type = GCodeMove::Move;
else
type = GCodeMove::Retract;
}
@ -440,7 +438,9 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
if (line.has_e())
{
_set_axis_origin(E, _get_axis_position(E) - line.e() * lengthsScaleFactor);
// extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
// we set the value taken from the G92 line as the new current position for it
_set_axis_position(E, line.e() * lengthsScaleFactor);
anyFound = true;
}
@ -956,7 +956,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
GCodePreviewData::Extrusion::Path &path = paths.back();
path.polyline = polyline;
path.extrusion_role = data.extrusion_role;
path.mm3_per_mm = data.mm3_per_mm;
path.mm3_per_mm = float(data.mm3_per_mm);
path.width = data.width;
path.height = data.height;
path.feedrate = data.feedrate;

View File

@ -303,8 +303,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
unsigned int extruder_id = extruders[i].id();
adj.extruder_id = extruder_id;
adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
adj.min_print_speed = config.min_print_speed.get_at(extruder_id);
adj.slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(extruder_id));
adj.min_print_speed = float(config.min_print_speed.get_at(extruder_id));
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
}

View File

@ -29,7 +29,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
{
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width));
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@ -43,7 +43,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@ -57,7 +57,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);

View File

@ -13,13 +13,17 @@
#include <cassert>
#include <limits>
#include <libslic3r.h>
#include "../GCodeWriter.hpp"
namespace Slic3r {
// Returns true in case that extruder a comes before b (b does not have to be present). False otherwise.
bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
{
if (a==b)
if (a == b)
return false;
for (auto extruder : extruders) {
@ -32,6 +36,39 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
return false;
}
// Return a zero based extruder from the region, or extruder_override if overriden.
unsigned int LayerTools::perimeter_extruder(const PrintRegion &region) const
{
assert(region.config().perimeter_extruder.value > 0);
return ((this->extruder_override == 0) ? region.config().perimeter_extruder.value : this->extruder_override) - 1;
}
unsigned int LayerTools::infill_extruder(const PrintRegion &region) const
{
assert(region.config().infill_extruder.value > 0);
return ((this->extruder_override == 0) ? region.config().infill_extruder.value : this->extruder_override) - 1;
}
unsigned int LayerTools::solid_infill_extruder(const PrintRegion &region) const
{
assert(region.config().solid_infill_extruder.value > 0);
return ((this->extruder_override == 0) ? region.config().solid_infill_extruder.value : this->extruder_override) - 1;
}
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion &region) const
{
assert(region.config().perimeter_extruder.value > 0);
assert(region.config().infill_extruder.value > 0);
assert(region.config().solid_infill_extruder.value > 0);
// 1 based extruder ID.
unsigned int extruder = ((this->extruder_override == 0) ?
(is_infill(extrusions.role()) ?
(is_solid_infill(extrusions.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) :
region.config().perimeter_extruder.value) :
this->extruder_override);
return (extruder == 0) ? 0 : extruder - 1;
}
// For the use case when each object is printed separately
// (print.config().complete_objects is true).
@ -52,7 +89,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
}
// Collect extruders reuqired to print the layers.
this->collect_extruders(object);
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
// Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder);
@ -89,9 +126,15 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
this->initialize_layers(zs);
}
// Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object.
// Do it only if all the objects were configured to be printed with a single extruder.
std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
if (print.object_extruders().size() == 1)
per_layer_extruder_switches = custom_tool_changes(print.model(), (unsigned int)print.config().nozzle_diameter.size());
// Collect extruders reuqired to print the layers.
for (auto object : print.objects())
this->collect_extruders(*object);
this->collect_extruders(*object, per_layer_extruder_switches);
// Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder);
@ -99,6 +142,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
this->fill_wipe_tower_partitions(print.config(), object_bottom_z);
this->collect_extruder_statistics(prime_multi_material);
// Assign custom G-code actions from Model::custom_gcode_per_print_z to their respecive layers,
// ignoring the extruder switches, which were processed above, and ignoring color changes for extruders,
// that do not print above their respective print_z.
this->assign_custom_gcodes(print);
}
void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
@ -111,13 +159,13 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
coordf_t zmax = zs[i] + EPSILON;
for (; j < zs.size() && zs[j] <= zmax; ++ j) ;
// Assign an average print_z to the set of layers with nearly equal print_z.
m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr));
m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1])));
i = j;
}
}
// Collect extruders reuqired to print layers.
void ToolOrdering::collect_extruders(const PrintObject &object)
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
{
// Collect the support extruders.
for (auto support_layer : object.support_layers()) {
@ -134,9 +182,23 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
if (has_support || has_interface)
layer_tools.has_support = true;
}
// Extruder overrides are ordered by print_z.
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
it_per_layer_extruder_override = per_layer_extruder_switches.begin();
unsigned int extruder_override = 0;
// Collect the object extruders.
for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
// Override extruder with the next
for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override)
extruder_override = (int)it_per_layer_extruder_override->second;
// Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it.
layer_tools.extruder_override = extruder_override;
// What extruders are required to print this object layer?
for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) {
const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr;
@ -150,19 +212,18 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
something_nonoverriddable = false;
for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
something_nonoverriddable = true;
break;
}
}
if (something_nonoverriddable)
layer_tools.extruders.push_back(region.config().perimeter_extruder.value);
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().perimeter_extruder.value : extruder_override);
layer_tools.has_object = true;
}
bool has_infill = false;
bool has_solid_infill = false;
bool something_nonoverriddable = false;
@ -176,17 +237,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
has_infill = true;
if (m_print_config_ptr) {
if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region))
if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region))
something_nonoverriddable = true;
}
}
if (something_nonoverriddable || !m_print_config_ptr)
{
if (has_solid_infill)
layer_tools.extruders.push_back(region.config().solid_infill_extruder);
if (has_infill)
layer_tools.extruders.push_back(region.config().infill_extruder);
if (something_nonoverriddable || !m_print_config_ptr) {
if (extruder_override == 0) {
if (has_solid_infill)
layer_tools.extruders.emplace_back(region.config().solid_infill_extruder);
if (has_infill)
layer_tools.extruders.emplace_back(region.config().infill_extruder);
} else if (has_solid_infill || has_infill)
layer_tools.extruders.emplace_back(extruder_override);
}
if (has_solid_infill || has_infill)
layer_tools.has_object = true;
@ -199,7 +262,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
if (layer.extruders.empty() && layer.has_object)
layer.extruders.push_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
layer.extruders.emplace_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
}
}
@ -254,11 +317,9 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
for (unsigned int &extruder_id : lt.extruders) {
assert(extruder_id > 0);
-- extruder_id;
}
}
}
void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z)
{
if (m_layer_tools.empty())
@ -394,6 +455,46 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
}
}
// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools.
// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer.
// If multiple events are planned over a span of a single layer, use the last one.
void ToolOrdering::assign_custom_gcodes(const Print &print)
{
const std::vector<Model::CustomGCode> &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
if (custom_gcode_per_print_z.empty())
return;
unsigned int num_extruders = *std::max_element(m_all_printing_extruders.begin(), m_all_printing_extruders.end()) + 1;
std::vector<unsigned char> extruder_printing_above(num_extruders, false);
auto custom_gcode_it = custom_gcode_per_print_z.rbegin();
// From the last layer to the first one:
for (auto it_lt = m_layer_tools.rbegin(); it_lt != m_layer_tools.rend(); ++ it_lt) {
LayerTools &lt = *it_lt;
// Add the extruders of the current layer to the set of extruders printing at and above this print_z.
for (unsigned int i : lt.extruders)
extruder_printing_above[i] = true;
// Skip all custom G-codes above this layer and skip all extruder switches.
for (; custom_gcode_it != custom_gcode_per_print_z.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->gcode == ExtruderChangeCode); ++ custom_gcode_it);
if (custom_gcode_it == custom_gcode_per_print_z.rend())
// Custom G-codes were processed.
break;
// Some custom G-code is configured for this layer or a layer below.
const Model::CustomGCode &custom_gcode = *custom_gcode_it;
// print_z of the layer below the current layer.
coordf_t print_z_below = 0.;
if (auto it_lt_below = it_lt; ++ it_lt_below != m_layer_tools.rend())
print_z_below = it_lt_below->print_z;
if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) {
// The custom G-code applies to the current layer.
if (custom_gcode.gcode != ColorChangeCode || extruder_printing_above[unsigned(custom_gcode.extruder - 1)])
// If it is color change, it will actually be useful as the exturder above will print.
lt.custom_gcode = &custom_gcode;
// Consume that custom G-code event.
++ custom_gcode_it;
}
}
}
const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
{
auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON));
@ -411,14 +512,13 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const
}
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies)
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies)
{
something_overridden = true;
auto entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first; // (add and) return iterator
auto& copies_vector = entity_map_it->second;
if (copies_vector.size() < num_of_copies)
copies_vector.resize(num_of_copies, -1);
auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator
ExtruderPerCopy& copies_vector = entity_map_it->second;
copies_vector.resize(num_of_copies, -1);
if (copies_vector[copy_id] != -1)
std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen.
@ -426,7 +526,6 @@ void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsi
copies_vector[copy_id] = extruder;
}
// Finds first non-soluble extruder on the layer
int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
{
@ -449,11 +548,10 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print
return (-1);
}
// Decides whether this entity could be overridden
bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const
{
if (print_config.filament_soluble.get_at(Print::get_extruder(eec, region)))
if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region)))
return false;
if (object.config().wipe_into_objects)
@ -465,7 +563,6 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con
return true;
}
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
// and returns volume that is left to be wiped on the wipe tower.
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
@ -473,8 +570,8 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
const LayerTools& lt = *m_layer_tools;
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it
if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it
// we will sort objects so that dedicated for wiping are at the beginning:
PrintObjectPtrs object_list = print.objects();
@ -497,13 +594,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
const PrintObject* object = object_list[i];
// Finds this layer:
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
if (this_layer_it == object->layers().end())
continue;
const Layer* this_layer = *this_layer_it;
const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
if (this_layer == nullptr)
continue;
size_t num_of_copies = object->copies().size();
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
// iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves
for (unsigned int copy = 0; copy < num_of_copies; ++copy) {
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
const auto& region = *object->print()->regions()[region_id];
@ -511,51 +608,48 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
continue;
if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) {
bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill;
if (print.config().infill_first != perimeters_done || wipe_into_infill_only) {
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region))
continue;
if (volume_to_wipe<=0)
continue;
if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill)
if (wipe_into_infill_only && ! print.config().infill_first)
// In this case we must check that the original extruder is used on this layer before the one we are overridding
// (and the perimeters will be finished before the infill is printed):
if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder))
if (!lt.is_extruder_order(lt.perimeter_extruder(region), new_extruder))
continue;
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder
set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= float(fill->total_volume());
if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
// More material was purged already than asked for.
return 0.f;
}
}
}
// Now the same for perimeters - see comments above for explanation:
if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done))
if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done)
{
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region))
continue;
if (volume_to_wipe<=0)
continue;
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {
if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) {
set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= float(fill->total_volume());
if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f)
// More material was purged already than asked for.
return 0.f;
}
}
}
}
}
}
return std::max(0.f, volume_to_wipe);
// Some purge remains to be done on the Wipe Tower.
assert(volume_to_wipe > 0.);
return volume_to_wipe;
}
@ -566,16 +660,18 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
// them again and make sure we override it.
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
{
if (! this->something_overridable)
return;
const LayerTools& lt = *m_layer_tools;
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
for (const PrintObject* object : print.objects()) {
// Finds this layer:
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
if (this_layer_it == object->layers().end())
continue;
const Layer* this_layer = *this_layer_it;
const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON);
if (this_layer == nullptr)
continue;
size_t num_of_copies = object->copies().size();
for (size_t copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
@ -598,9 +694,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
// Either way, we will now force-override it with something suitable:
if (print.config().infill_first
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|| lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
)
|| lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME)
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
else {
// In this case we can (and should) leave it to be printed normally.
@ -611,42 +706,31 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
// Now the same for perimeters - see comments above for explanation:
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region)
|| is_entity_overridden(fill, copy) )
continue;
set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy))
set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
}
}
}
}
}
// Following function is called from process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
// It first makes sure the pointer is valid (creates the vector if it does not exist) and contains a record for each copy
// It also modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
// so -1 was used as "print as usual".
// The resulting vector has to keep track of which extrusions are the ones that were overridden and which were not. In the extruder is used as overridden,
// its number is saved as it is (zero-based index). Usual extrusions are saved as -number-1 (unfortunately there is no negative zero).
const std::vector<int>* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
// If this extrusion does not have any override, nullptr is returned.
// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
// so -1 was used as "print as usual").
// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden,
// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero).
const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies)
{
ExtruderPerCopy *overrides = nullptr;
auto entity_map_it = entity_map.find(entity);
if (entity_map_it == entity_map.end())
entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first;
// Now the entity_map_it should be valid, let's make sure the vector is long enough:
entity_map_it->second.resize(num_of_copies, -1);
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
std::replace(entity_map_it->second.begin(), entity_map_it->second.end(), -1, -correct_extruder_id-1);
return &(entity_map_it->second);
if (entity_map_it != entity_map.end()) {
overrides = &entity_map_it->second;
overrides->resize(num_of_copies, -1);
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1);
}
return overrides;
}

View File

@ -7,6 +7,8 @@
#include <utility>
#include <boost/container/small_vector.hpp>
namespace Slic3r {
class Print;
@ -25,8 +27,19 @@ public:
return something_overridden;
}
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
class ExtruderOverrides
{
public:
ExtruderOverrides(const ExtruderPerCopy *overrides, const int correct_extruder_id) : m_overrides(overrides) {}
private:
const ExtruderPerCopy *m_overrides;
};
// This is called from GCode::process_layer - see implementation for further comments:
const std::vector<int>* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);
// This function goes through all infill entities, decides which ones will be used for wiping and
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
@ -35,6 +48,11 @@ public:
void ensure_perimeters_infills_order(const Print& print);
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
bool out = this->is_overriddable(ee, print_config, object, region);
this->something_overridable |= out;
return out;
}
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
@ -43,14 +61,16 @@ private:
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
// This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
void set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies);
void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies);
// Returns true in case that entity is not printed with its usual extruder for a given copy:
bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const {
return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1);
auto it = entity_map.find(entity);
return it == entity_map.end() ? false : it->second[copy_id] != -1;
}
std::map<const ExtrusionEntity*, std::vector<int>> entity_map; // to keep track of who prints what
std::map<const ExtrusionEntity*, ExtruderPerCopy> entity_map; // to keep track of who prints what
bool something_overridable = false;
bool something_overridden = false;
const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to
};
@ -60,13 +80,7 @@ private:
class LayerTools
{
public:
LayerTools(const coordf_t z, const PrintConfig* print_config_ptr = nullptr) :
print_z(z),
has_object(false),
has_support(false),
has_wipe_tower(false),
wipe_tower_partitions(0),
wipe_tower_layer_height(0.) {}
LayerTools(const coordf_t z) : print_z(z) {}
// Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
// In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
@ -74,20 +88,33 @@ public:
bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
bool is_extruder_order(unsigned int a, unsigned int b) const;
bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); }
coordf_t print_z;
bool has_object;
bool has_support;
// Return a zero based extruder from the region, or extruder_override if overriden.
unsigned int perimeter_extruder(const PrintRegion &region) const;
unsigned int infill_extruder(const PrintRegion &region) const;
unsigned int solid_infill_extruder(const PrintRegion &region) const;
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion &region) const;
coordf_t print_z = 0.;
bool has_object = false;
bool has_support = false;
// Zero based extruder IDs, ordered to minimize tool switches.
std::vector<unsigned int> extruders;
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
// If not overriden, it is set to 0.
unsigned int extruder_override = 0;
// Will there be anything extruded on this layer for the wipe tower?
// Due to the support layers possibly interleaving the object layers,
// wipe tower will be disabled for some support only layers.
bool has_wipe_tower;
bool has_wipe_tower = false;
// Number of wipe tower partitions to support the required number of tool switches
// and to support the wipe tower partitions above this one.
size_t wipe_tower_partitions;
coordf_t wipe_tower_layer_height;
size_t wipe_tower_partitions = 0;
coordf_t wipe_tower_layer_height = 0.;
// Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print.
const Model::CustomGCode *custom_gcode = nullptr;
WipingExtrusions& wiping_extrusions() {
m_wiping_extrusions.set_layer_tools_ptr(this);
@ -108,11 +135,11 @@ public:
// For the use case when each object is printed separately
// (print.config.complete_objects is true).
ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false);
// For the use case when all objects are printed at once.
// (print.config.complete_objects is false).
ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material = false);
void clear() { m_layer_tools.clear(); }
@ -139,10 +166,11 @@ public:
private:
void initialize_layers(std::vector<coordf_t> &zs);
void collect_extruders(const PrintObject &object);
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
void reorder_extruders(unsigned int last_extruder_id);
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z);
void collect_extruder_statistics(bool prime_multi_material);
void assign_custom_gcodes(const Print &print);
std::vector<LayerTools> m_layer_tools;
// First printing extruder, including the multi-material priming sequence.
@ -152,7 +180,6 @@ private:
// All extruders, which extrude some material over m_layer_tools.
std::vector<unsigned int> m_all_printing_extruders;
const PrintConfig* m_print_config_ptr = nullptr;
};

View File

@ -1261,7 +1261,9 @@ namespace Slic3r {
if (line.has_e())
{
set_axis_origin(E, get_axis_position(E) - line.e() * lengthsScaleFactor);
// extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments,
// we set the value taken from the G92 line as the new current position for it
set_axis_position(E, line.e() * lengthsScaleFactor);
anyFound = true;
}
else

View File

@ -19,12 +19,13 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
this->config.apply(print_config, true);
m_extrusion_axis = this->config.get_extrusion_axis();
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
m_max_acceleration = (print_config.gcode_flavor.value == gcfMarlin) ?
print_config.machine_max_acceleration_extruding.values.front() : 0;
m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin) ?
print_config.machine_max_acceleration_extruding.values.front() : 0);
}
void GCodeWriter::set_extruders(const std::vector<unsigned int> &extruder_ids)
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
{
std::sort(extruder_ids.begin(), extruder_ids.end());
m_extruders.clear();
m_extruders.reserve(extruder_ids.size());
for (unsigned int extruder_id : extruder_ids)
@ -247,9 +248,9 @@ std::string GCodeWriter::toolchange_prefix() const
std::string GCodeWriter::toolchange(unsigned int extruder_id)
{
// set the new extruder
auto it_extruder = std::lower_bound(m_extruders.begin(), m_extruders.end(), Extruder::key(extruder_id));
assert(it_extruder != m_extruders.end());
m_extruder = const_cast<Extruder*>(&*it_extruder);
auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; });
assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id);
m_extruder = &*it_extruder;
// return the toolchange command
// if we are running a single-extruder setup, just set the extruder and return nothing

View File

@ -33,7 +33,7 @@ public:
std::string extrusion_axis() const { return m_extrusion_axis; }
void apply_print_config(const PrintConfig &print_config);
// Extruders are expected to be sorted in an increasing order.
void set_extruders(const std::vector<unsigned int> &extruder_ids);
void set_extruders(std::vector<unsigned int> extruder_ids);
const std::vector<Extruder>& extruders() const { return m_extruders; }
std::vector<unsigned int> extruder_ids() const {
std::vector<unsigned int> out;
@ -74,7 +74,8 @@ public:
Vec3d get_position() const { return m_pos; }
private:
std::vector<Extruder> m_extruders;
// Extruders are sorted by their ID, so that binary search is possible.
std::vector<Extruder> m_extruders;
std::string m_extrusion_axis;
bool m_single_extruder_multi_material;
Extruder* m_extruder;

View File

@ -598,21 +598,6 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte
return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string();
}
std::vector<std::pair<double, DynamicPrintConfig>> Model::get_custom_tool_changes(double default_layer_height, size_t num_extruders) const
{
std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes;
for (const CustomGCode& custom_gcode : custom_gcode_per_print_z)
if (custom_gcode.gcode == ExtruderChangeCode) {
DynamicPrintConfig config;
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
config.set_key_value("extruder", new ConfigOptionInt(custom_gcode.extruder > num_extruders ? 0 : custom_gcode.extruder));
// For correct extruders(tools) changing, we should decrease custom_gcode.height value by one default layer height
custom_tool_changes.push_back({ custom_gcode.print_z - default_layer_height, config });
}
return custom_tool_changes;
}
ModelObject::~ModelObject()
{
this->clear_volumes();
@ -1856,6 +1841,19 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
return ret;
}
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
// print_z corresponds to the first layer printed with the new extruder.
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Model &model, size_t num_extruders)
{
std::vector<std::pair<double, unsigned int>> custom_tool_changes;
for (const Model::CustomGCode &custom_gcode : model.custom_gcode_per_print_z)
if (custom_gcode.gcode == ExtruderChangeCode) {
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(custom_gcode.extruder > num_extruders ? 1 : custom_gcode.extruder));
}
return custom_tool_changes;
}
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
bool model_object_list_equal(const Model &model_old, const Model &model_new)

View File

@ -838,9 +838,6 @@ public:
// Propose an output path, replace extension. The new_extension shall contain the initial dot.
std::string propose_export_file_name_and_path(const std::string &new_extension) const;
// from custom_gcode_per_print_z get just tool_change codes
std::vector<std::pair<double, DynamicPrintConfig>> get_custom_tool_changes(double default_layer_height, size_t num_extruders) const;
private:
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); };
void assign_new_unique_ids_recursive();
@ -857,6 +854,10 @@ private:
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
// print_z corresponds to the first layer printed with the new extruder.
extern std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Model &model, size_t num_extruders);
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
extern bool model_object_list_equal(const Model &model_old, const Model &model_new);

View File

@ -638,48 +638,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
}
// Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs,
// considering custom_tool_change values
void assign(const t_layer_config_ranges &in, const std::vector<std::pair<double, DynamicPrintConfig>> &custom_tool_changes) {
m_ranges.clear();
m_ranges.reserve(in.size());
// Input ranges are sorted lexicographically. First range trims the other ranges.
coordf_t last_z = 0;
for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in)
if (range.first.second > last_z) {
coordf_t min_z = std::max(range.first.first, 0.);
if (min_z > last_z + EPSILON) {
m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr);
last_z = min_z;
}
if (range.first.second > last_z + EPSILON) {
const DynamicPrintConfig* cfg = &range.second;
m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg);
last_z = range.first.second;
}
}
// add ranges for extruder changes from custom_tool_changes
for (size_t i = 0; i < custom_tool_changes.size(); i++) {
const DynamicPrintConfig* cfg = &custom_tool_changes[i].second;
coordf_t cur_Z = custom_tool_changes[i].first;
coordf_t next_Z = i == custom_tool_changes.size()-1 ? DBL_MAX : custom_tool_changes[i+1].first;
if (cur_Z > last_z + EPSILON) {
if (i==0)
m_ranges.emplace_back(t_layer_height_range(last_z, cur_Z), nullptr);
m_ranges.emplace_back(t_layer_height_range(cur_Z, next_Z), cfg);
}
else if (next_Z > last_z + EPSILON)
m_ranges.emplace_back(t_layer_height_range(last_z, next_Z), cfg);
}
if (m_ranges.empty())
m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr);
else if (m_ranges.back().second == nullptr)
m_ranges.back().first.second = DBL_MAX;
else if (m_ranges.back().first.second != DBL_MAX)
m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr);
}
const DynamicPrintConfig* config(const t_layer_height_range &range) const {
auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr));
// #ys_FIXME_COLOR
@ -733,17 +691,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
for (const ModelObject *model_object : m_model.objects)
model_object_status.emplace(model_object->id(), ModelObjectStatus::New);
} else {
if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
// If custom gcode per layer height was changed, we should stop background processing.
update_apply_status(this->invalidate_steps({ psWipeTower, psGCodeExport }));
m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
}
if (model_object_list_equal(m_model, model)) {
// The object list did not change.
for (const ModelObject *model_object : m_model.objects)
model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
// But if custom gcode per layer height was changed
if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) {
// we should stop background processing
update_apply_status(this->invalidate_step(psGCodeExport));
m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
}
} else if (model_object_list_extended(m_model, model)) {
// Add new objects. Their volumes and configs will be synchronized later.
update_apply_status(this->invalidate_step(psGCodeExport));
@ -835,9 +791,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
for (PrintObject *print_object : m_objects)
print_object_status.emplace(PrintObjectStatus(print_object));
std::vector<std::pair<double, DynamicPrintConfig>> custom_tool_changes =
m_model.get_custom_tool_changes(m_default_object_config.layer_height, num_extruders);
// 3) Synchronize ModelObjects & PrintObjects.
for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) {
ModelObject &model_object = *m_model.objects[idx_model_object];
@ -845,9 +798,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
assert(it_status != model_object_status.end());
assert(it_status->status != ModelObjectStatus::Deleted);
const ModelObject& model_object_new = *model.objects[idx_model_object];
// ys_FIXME_COLOR
// const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges, custom_tool_changes);
const_cast<ModelObjectStatus&>(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges);
if (it_status->status == ModelObjectStatus::New)
// PrintObject instances will be added in the next loop.
continue;
@ -1015,8 +966,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
PrintRegionConfig this_region_config;
bool this_region_config_set = false;
for (PrintObject *print_object : m_objects) {
if(m_force_update_print_regions && !custom_tool_changes.empty())
goto print_object_end;
const LayerRanges *layer_ranges;
{
auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id()));
@ -1963,8 +1912,8 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l
// If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default.
if (! is_step_done(psWipeTower) && extruders_cnt !=0) {
float width = m_config.wipe_tower_width;
float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4);
float width = float(m_config.wipe_tower_width);
float brim_spacing = float(nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4));
const_cast<Print*>(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1);
const_cast<Print*>(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing;
@ -1990,6 +1939,7 @@ void Print::_make_wipe_tower()
// Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
if (! m_wipe_tower_data.tool_ordering.has_wipe_tower())
// Don't generate any wipe tower.
return;
@ -2107,13 +2057,6 @@ void Print::_make_wipe_tower()
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
}
// Returns extruder this eec should be printed with, according to PrintRegion config
int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region)
{
return is_infill(fill.role()) ? std::max<int>(0, (is_solid_infill(fill.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) :
std::max<int>(region.config().perimeter_extruder.value - 1, 0);
}
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics.
// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized).

View File

@ -15,6 +15,8 @@
#include "GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r.h"
namespace Slic3r {
class Print;
@ -50,7 +52,7 @@ public:
// Average diameter of nozzles participating on extruding this region.
coordf_t bridging_height_avg(const PrintConfig &print_config) const;
// Collect extruder indices used to print this region's object.
// Collect 0-based extruder indices used to print this region's object.
void collect_object_printing_extruders(std::vector<unsigned int> &object_extruders) const;
static void collect_object_printing_extruders(const PrintConfig &print_config, const PrintRegionConfig &region_config, std::vector<unsigned int> &object_extruders);
@ -116,8 +118,21 @@ public:
size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
size_t layer_count() const { return m_layers.size(); }
void clear_layers();
Layer* get_layer(int idx) { return m_layers[idx]; }
const Layer* get_layer(int idx) const { return m_layers[idx]; }
const Layer* get_layer(int idx) const { return m_layers[idx]; }
Layer* get_layer(int idx) { return m_layers[idx]; }
// Get a layer exactly at print_z.
const Layer* get_layer_at_printz(coordf_t print_z) const {
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; });
return (it == m_layers.end() || (*it)->print_z != print_z) ? nullptr : *it;
}
Layer* get_layer_at_printz(coordf_t print_z) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z)); }
// Get a layer approximately at print_z.
const Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const {
coordf_t limit = print_z + epsilon;
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; });
return (it == m_layers.end() || (*it)->print_z < print_z - epsilon) ? nullptr : *it;
}
Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); }
// print_z: top of the layer; slice_z: center of the layer.
Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
@ -345,6 +360,7 @@ public:
const PrintConfig& config() const { return m_config; }
const PrintObjectConfig& default_object_config() const { return m_default_object_config; }
const PrintRegionConfig& default_region_config() const { return m_default_region_config; }
//FIXME returning const vector to non-const PrintObject*, caller could modify PrintObjects!
const PrintObjectPtrs& objects() const { return m_objects; }
PrintObject* get_object(size_t idx) { return m_objects[idx]; }
const PrintObject* get_object(size_t idx) const { return m_objects[idx]; }
@ -353,9 +369,6 @@ public:
// If zero, then the print is empty and the print shall not be executed.
unsigned int num_object_instances() const;
// Returns extruder this eec should be printed with, according to PrintRegion config:
static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region);
const ExtrusionEntityCollection& skirt() const { return m_skirt; }
const ExtrusionEntityCollection& brim() const { return m_brim; }
@ -370,9 +383,6 @@ public:
// Accessed by SupportMaterial
const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; }
// force update of PrintRegions, when custom_tool_change is not empty and (Re)Slicing is started
void set_force_update_print_regions(bool force_update_print_regions) { m_force_update_print_regions = force_update_print_regions; }
protected:
// methods for handling regions
PrintRegion* get_region(size_t idx) { return m_regions[idx]; }
@ -415,9 +425,6 @@ private:
// Estimated print time, filament consumed.
PrintStatistics m_print_statistics;
// flag used
bool m_force_update_print_regions = false;
// To allow GCode to set the Print's GCodeExport step status.
friend class GCode;
// Allow PrintObject to access m_mutex and m_cancel_callback.

View File

@ -71,5 +71,8 @@
// Enable a modified version of the toolbar textures where all the icons are separated by 1 pixel
#define ENABLE_MODIFIED_TOOLBAR_TEXTURES (1 && ENABLE_2_2_0_BETA1)
// Enable configurable paths export (fullpath or not) to 3mf and amf
#define ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF (1 && ENABLE_2_2_0_BETA1)
#endif // _technologies_h_

View File

@ -158,6 +158,53 @@ inline std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// Variant of std::lower_bound() with compare predicate, but without the key.
// This variant is very useful in case that the T type is large or it does not even have a public constructor.
template<class ForwardIt, class LowerThanKeyPredicate>
ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key)
{
ForwardIt it;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
count = std::distance(first, last);
while (count > 0) {
it = first;
step = count / 2;
std::advance(it, step);
if (lower_thank_key(*it)) {
first = ++it;
count -= step + 1;
}
else
count = step;
}
return first;
}
// from https://en.cppreference.com/w/cpp/algorithm/lower_bound
template<class ForwardIt, class T, class Compare=std::less<>>
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={})
{
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
first = std::lower_bound(first, last, value, comp);
return first != last && !comp(value, *first) ? first : last;
}
// from https://en.cppreference.com/w/cpp/algorithm/lower_bound
template<class ForwardIt, class LowerThanKeyPredicate, class EqualToKeyPredicate>
ForwardIt binary_find_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key, EqualToKeyPredicate equal_to_key)
{
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
first = lower_bound_by_predicate(first, last, lower_thank_key);
return first != last && equal_to_key(*first) ? first : last;
}
template<typename T>
static inline T sqr(T x)
{

View File

@ -63,6 +63,7 @@
#include <boost/bind.hpp>
#include <boost/config.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem.hpp>

View File

@ -341,24 +341,10 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
printf("Unable to create bed grid lines\n");
}
static const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
{
const VendorProfile::PrinterModel *out = nullptr;
if (preset.vendor != nullptr) {
auto *printer_model = preset.config.opt<ConfigOptionString>("printer_model");
if (printer_model != nullptr && ! printer_model->value.empty()) {
auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; });
if (it != preset.vendor->models.end())
out = &(*it);
}
}
return out;
}
static std::string system_print_bed_model(const Preset &preset)
{
std::string out;
const VendorProfile::PrinterModel *pm = system_printer_model(preset);
const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset);
if (pm != nullptr && ! pm->bed_model.empty())
out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model;
return out;
@ -367,7 +353,7 @@ static std::string system_print_bed_model(const Preset &preset)
static std::string system_print_bed_texture(const Preset &preset)
{
std::string out;
const VendorProfile::PrinterModel *pm = system_printer_model(preset);
const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset);
if (pm != nullptr && ! pm->bed_texture.empty())
out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
return out;

View File

@ -877,13 +877,10 @@ bool can_export_to_obj(const GLVolume& volume)
if (!volume.is_active || !volume.is_extrusion_path)
return false;
if (volume.indexed_vertex_array.triangle_indices.empty() && (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) == 0))
return false;
bool has_triangles = !volume.indexed_vertex_array.triangle_indices.empty() || (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) > 0);
bool has_quads = !volume.indexed_vertex_array.quad_indices.empty() || (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) > 0);
if (volume.indexed_vertex_array.quad_indices.empty() && (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) == 0))
return false;
return true;
return has_triangles || has_quads;
}
bool GLVolumeCollection::has_toolpaths_to_export() const

View File

@ -61,6 +61,11 @@ void AppConfig::set_defaults()
if (get("preset_update").empty())
set("preset_update", "1");
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
if (get("export_sources_full_pathnames").empty())
set("export_sources_full_pathnames", "0");
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
// remove old 'use_legacy_opengl' parameter from this config, if present
if (!get("use_legacy_opengl").empty())
erase("", "use_legacy_opengl");

View File

@ -95,14 +95,6 @@ void BackgroundSlicingProcess::process_fff()
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
#endif // ENABLE_THUMBNAIL_GENERATOR
/* #ys_FIXME_no_exported_codes
if (m_fff_print->model().custom_gcode_per_print_z != GUI::wxGetApp().model().custom_gcode_per_print_z) {
GUI::wxGetApp().model().custom_gcode_per_print_z = m_fff_print->model().custom_gcode_per_print_z;
GUI::show_info(nullptr, _(L("To except of redundant tool manipulation, \n"
"Color change(s) for unused extruder(s) was(were) deleted")), _(L("Info")));
}
*/
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
//FIXME localize the messages

View File

@ -132,11 +132,6 @@ public:
// This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
// and it does not account for the OctoPrint scheduling.
bool finished() const { return m_print->finished(); }
void set_force_update_print_regions(bool force_update_print_regions) {
if (m_fff_print)
m_fff_print->set_force_update_print_regions(force_update_print_regions);
}
private:
void thread_proc();

View File

@ -1,6 +1,7 @@
#include "BitmapCache.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/filesystem.hpp>
#if ! defined(WIN32) && ! defined(__APPLE__)
#define BROKEN_ALPHA
@ -15,7 +16,7 @@
#include "nanosvg/nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
#include "GUI_App.hpp"
//#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
@ -226,7 +227,7 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width,
}
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,
float scale /* = 1.0f */, const bool grayscale/* = false*/)
float scale /* = 1.0f */, const bool grayscale/* = false*/, const bool dark_mode/* = false*/)
{
std::string bitmap_key = bitmap_name + ( target_height !=0 ?
"-h" + std::to_string(target_height) :
@ -234,16 +235,45 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_
+ (scale != 1.0f ? "-s" + std::to_string(scale) : "")
+ (grayscale ? "-gs" : "");
target_height != 0 ? target_height *= scale : target_width *= scale;
/* For the Dark mode of any platform, we should draw icons in respect to OS background
* Note: All standard(regular) icons are collected in "icons" folder,
* SVG-icons, which have "Dark mode" variant, are collected in "icons/white" folder
*/
std::string folder;
if (dark_mode)
{
#ifdef __WXMSW__
folder = "white\\";
#else
folder = "white/";
#endif
auto it = m_map.find(folder + bitmap_key);
if (it != m_map.end())
return it->second;
else {
it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
}
auto it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg")))
folder.clear();
else
bitmap_key = folder + bitmap_key;
}
else
{
auto it = m_map.find(bitmap_key);
if (it != m_map.end())
return it->second;
}
NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f);
NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f);
if (image == nullptr)
return nullptr;
target_height != 0 ? target_height *= scale : target_width *= scale;
float svg_scale = target_height != 0 ?
(float)target_height / image->height : target_width != 0 ?
(float)target_width / image->width : 1;

View File

@ -34,7 +34,7 @@ public:
// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
wxBitmap* load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false);
// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false);
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false, const bool dark_mode = false);
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }

View File

@ -350,6 +350,21 @@ bool PrinterPicker::any_selected() const
return false;
}
std::set<std::string> PrinterPicker::get_selected_models() const
{
std::set<std::string> ret_set;
for (const auto& cb : cboxes)
if (cb->GetValue())
ret_set.emplace(cb->model);
for (const auto& cb : cboxes_alt)
if (cb->GetValue())
ret_set.emplace(cb->model);
return ret_set;
}
void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
{
PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
@ -500,6 +515,19 @@ bool PagePrinters::any_selected() const
return false;
}
std::set<std::string> PagePrinters::get_selected_models()
{
std::set<std::string> ret_set;
for (const auto *picker : printer_pickers)
{
std::set<std::string> tmp_models = picker->get_selected_models();
ret_set.insert(tmp_models.begin(), tmp_models.end());
}
return ret_set;
}
void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
{
if (technology == T_FFF
@ -765,6 +793,23 @@ PageUpdate::PageUpdate(ConfigWizard *parent)
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
}
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent)
: ConfigWizardPage(parent, _(L("Reload from disk")), _(L("Reload from disk")))
, full_pathnames(false)
{
auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _(L("Export full pathnames of models and parts sources into 3mf and amf files")));
box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1");
append(box_pathnames);
append_text(_(L(
"If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n"
"If not enabled, the Reload from disk command will ask to select each file using an open file dialog."
)));
box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); });
}
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
PageMode::PageMode(ConfigWizard *parent)
: ConfigWizardPage(parent, _(L("View mode")), _(L("View mode")))
{
@ -1356,6 +1401,9 @@ void ConfigWizard::priv::load_pages()
btn_finish->Enable(any_fff_selected || any_sla_selected);
index->add_page(page_update);
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
index->add_page(page_reload_from_disk);
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
index->add_page(page_mode);
index->go_to(former_active); // Will restore the active item/page if possible
@ -1580,6 +1628,10 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
preset.is_visible = evt.enable;
}
}
// if at list one printer is selected but there in no one selected material,
// select materials which is default for selected printer(s)
select_default_materials_if_needed(pair.second.vendor_profile, page->technology, evt.model_id);
}
if (page->technology & T_FFF) {
@ -1589,6 +1641,57 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker
}
}
void ConfigWizard::priv::select_default_materials_for_printer_model(const std::vector<VendorProfile::PrinterModel>& models, Technology technology, const std::string& model_id)
{
PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials;
auto it = std::find_if(models.begin(), models.end(), [model_id](VendorProfile::PrinterModel model) {return model_id == model.id; });
if (it != models.end())
for (const std::string& material : it->default_materials)
appconfig_new.set(page_materials->materials->appconfig_section(), material, "1");
}
void ConfigWizard::priv::select_default_materials_if_needed(VendorProfile* vendor_profile, Technology technology, const std::string& model_id)
{
if ((technology & T_FFF && !any_fff_selected) ||
(technology & T_SLA && !any_sla_selected) ||
check_materials_in_config(technology, false))
return;
select_default_materials_for_printer_model(vendor_profile->models, technology, model_id);
}
void ConfigWizard::priv::selected_default_materials(Technology technology)
{
auto select_default_materials_for_printer_page = [this](PagePrinters * page_printers, Technology technology)
{
std::set<std::string> selected_models = page_printers->get_selected_models();
const std::string vendor_id = page_printers->get_vendor_id();
for (auto& pair : bundles)
{
if (pair.first != vendor_id)
continue;
for (const std::string& model_id : selected_models)
select_default_materials_for_printer_model(pair.second.vendor_profile->models, technology, model_id);
}
};
PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla;
select_default_materials_for_printer_page(page_printers, technology);
for (const auto& printer : pages_3rdparty)
{
page_printers = technology & T_FFF ? printer.second.first : printer.second.second;
if (page_printers)
select_default_materials_for_printer_page(page_printers, technology);
}
update_materials(technology);
(technology& T_FFF ? page_filaments : page_sla_materials)->reload_presets();
}
void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install)
{
auto it = pages_3rdparty.find(vendor->id);
@ -1625,7 +1728,7 @@ bool ConfigWizard::priv::on_bnt_finish()
return check_materials_in_config(T_ANY);
}
bool ConfigWizard::priv::check_materials_in_config(Technology technology)
bool ConfigWizard::priv::check_materials_in_config(Technology technology, bool show_info_msg)
{
const auto exist_preset = [this](const std::string& section, const Materials& materials)
{
@ -1640,15 +1743,32 @@ bool ConfigWizard::priv::check_materials_in_config(Technology technology)
return false;
};
const auto ask_and_selected_default_materials = [this](wxString message, Technology technology)
{
wxMessageDialog msg(q, message, _(L("Notice")), wxYES_NO);
if (msg.ShowModal() == wxID_YES)
selected_default_materials(technology);
};
if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments))
{
show_info(q, _(L("You have to select at least one filament for selected printers")), "");
if (show_info_msg)
{
wxString message = _(L("You have to select at least one filament for selected printers")) + "\n\n\t" +
_(L("Do you want to automatic select default filaments?"));
ask_and_selected_default_materials(message, T_FFF);
}
return false;
}
if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials))
{
show_info(q, _(L("You have to select at least one material for selected printers")), "");
if (show_info_msg)
{
wxString message = _(L("You have to select at least one material for selected printers")) + "\n\n\t" +
_(L("Do you want to automatic select default materials?"));
ask_and_selected_default_materials(message, T_SLA);
}
return false;
}
@ -1730,6 +1850,11 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
}
app_config->set("version_check", page_update->version_check ? "1" : "0");
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
page_mode->serialize_mode(app_config);
std::string preferred_model;
@ -1885,6 +2010,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->add_page(p->page_custom = new PageCustom(this));
p->add_page(p->page_update = new PageUpdate(this));
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
p->add_page(p->page_mode = new PageMode(this));
p->add_page(p->page_firmware = new PageFirmware(this));
p->add_page(p->page_bed = new PageBedShape(this));

View File

@ -149,6 +149,7 @@ struct PrinterPicker: wxPanel
void select_all(bool select, bool alternates = false);
void select_one(size_t i, bool select);
bool any_selected() const;
std::set<std::string> get_selected_models() const ;
int get_width() const { return width; }
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
@ -215,6 +216,9 @@ struct PagePrinters: ConfigWizardPage
void select_all(bool select, bool alternates = false);
int get_width() const;
bool any_selected() const;
std::set<std::string> get_selected_models();
std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
};
@ -301,6 +305,17 @@ struct PageUpdate: ConfigWizardPage
PageUpdate(ConfigWizard *parent);
};
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
struct PageReloadFromDisk : ConfigWizardPage
{
bool full_pathnames;
PageReloadFromDisk(ConfigWizard* parent);
};
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
struct PageMode: ConfigWizardPage
{
wxRadioButton *radio_simple;
@ -455,6 +470,11 @@ struct ConfigWizard::priv
PageMaterials *page_sla_materials = nullptr;
PageCustom *page_custom = nullptr;
PageUpdate *page_update = nullptr;
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
PageReloadFromDisk *page_reload_from_disk = nullptr;
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
PageMode *page_mode = nullptr;
PageVendors *page_vendors = nullptr;
Pages3rdparty pages_3rdparty;
@ -487,10 +507,17 @@ struct ConfigWizard::priv
void on_custom_setup();
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
void select_default_materials_for_printer_model(const std::vector<VendorProfile::PrinterModel> &models,
Technology technology,
const std::string & model_id);
void select_default_materials_if_needed(VendorProfile* vendor_profile,
Technology technology,
const std::string &model_id);
void selected_default_materials(Technology technology);
void on_3rdparty_install(const VendorProfile *vendor, bool install);
bool on_bnt_finish();
bool check_materials_in_config(Technology technology);
bool check_materials_in_config(Technology technology, bool show_info_msg = true);
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);

View File

@ -886,7 +886,7 @@ void GLGizmosManager::do_render_overlay() const
#else
float du = (float)(tex_width - 1) / (3.0f * (float)tex_width); // 3 is the number of possible states if the icons
#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE
float dv = (float)(tex_height - 1) / (float)(selectable_idxs.size() * tex_height);
float dv = (float)(tex_height - 1) / (float)(m_gizmos.size() * tex_height);
// tiles in the texture are spaced by 1 pixel
float u_offset = 1.0f / (float)tex_width;

View File

@ -631,13 +631,16 @@ bool Mouse3DController::connect_device()
if (m_device != nullptr)
{
std::vector<wchar_t> manufacturer(1024, 0);
hid_get_manufacturer_string(m_device, manufacturer.data(), 1024);
m_device_str = boost::nowide::narrow(manufacturer.data());
wchar_t buffer[1024];
hid_get_manufacturer_string(m_device, buffer, 1024);
m_device_str = boost::nowide::narrow(buffer);
// #3479 seems to show that sometimes an extra whitespace is added, so we remove it
boost::algorithm::trim(m_device_str);
std::vector<wchar_t> product(1024, 0);
hid_get_product_string(m_device, product.data(), 1024);
m_device_str += "/" + boost::nowide::narrow(product.data());
hid_get_product_string(m_device, buffer, 1024);
m_device_str += "/" + boost::nowide::narrow(buffer);
// #3479 seems to show that sometimes an extra whitespace is added, so we remove it
boost::algorithm::trim(m_device_str);
BOOST_LOG_TRIVIAL(info) << "Connected 3DConnexion device:";
BOOST_LOG_TRIVIAL(info) << "Manufacturer/product: " << m_device_str;

View File

@ -68,7 +68,7 @@ class Mouse3DController
CustomParameters<double> m_translation_params;
CustomParameters<float> m_rotation_params;
#if ENABLE_3DCONNEXION_Y_AS_ZOOM
CustomParameters<float> m_zoom_params;
CustomParameters<double> m_zoom_params;
#endif // ENABLE_3DCONNEXION_Y_AS_ZOOM
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.

View File

@ -3022,7 +3022,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
this->update_print_volume_state();
// Apply new config to the possibly running background task.
bool was_running = this->background_process.running();
this->background_process.set_force_update_print_regions(force_validation);
Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config());
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
@ -4909,7 +4908,12 @@ void Plater::export_amf()
wxBusyCursor wait;
bool export_config = true;
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) {
#else
if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path));
} else {
@ -4938,6 +4942,16 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
#if ENABLE_THUMBNAIL_GENERATOR
ThumbnailData thumbnail_data;
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) {
#else
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) {
#endif // ENABLE_THUMBNAIL_GENERATOR
#else
#if ENABLE_THUMBNAIL_GENERATOR
ThumbnailData thumbnail_data;
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
@ -4945,6 +4959,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
#else
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
#endif // ENABLE_THUMBNAIL_GENERATOR
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
// Success
p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path));
p->set_project_filename(path);

View File

@ -73,6 +73,16 @@ void PreferencesDialog::build()
option = Option (def, "version_check");
m_optgroup->append_single_option_line(option);
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
// Please keep in sync with ConfigWizard
def.label = L("Export sources full pathnames to 3mf and amf");
def.type = coBool;
def.tooltip = L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked.");
def.set_default_value(new ConfigOptionBool(app_config->get("export_sources_full_pathnames") == "1"));
option = Option(def, "export_sources_full_pathnames");
m_optgroup->append_single_option_line(option);
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
// Please keep in sync with ConfigWizard
def.label = L("Update built-in Presets automatically");
def.type = coBool;

View File

@ -184,6 +184,14 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
} else {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field;
}
auto default_materials_field = section.second.get<std::string>("default_materials", "");
if (default_materials_field.empty())
default_materials_field = section.second.get<std::string>("default_filaments", "");
if (Slic3r::unescape_strings_cstyle(default_materials_field, model.default_materials)) {
Slic3r::sort_remove_duplicates(model.default_materials);
} else {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed default_materials field: `%2%`") % id % default_materials_field;
}
model.bed_model = section.second.get<std::string>("bed_model", "");
model.bed_texture = section.second.get<std::string>("bed_texture", "");
if (! model.id.empty() && ! model.variants.empty())
@ -1502,4 +1510,20 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model
return it != cend() ? &*it : nullptr;
}
namespace PresetUtils {
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
{
const VendorProfile::PrinterModel *out = nullptr;
if (preset.vendor != nullptr) {
auto *printer_model = preset.config.opt<ConfigOptionString>("printer_model");
if (printer_model != nullptr && ! printer_model->value.empty()) {
auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; });
if (it != preset.vendor->models.end())
out = &(*it);
}
}
return out;
}
} // namespace PresetUtils
} // namespace Slic3r

View File

@ -61,6 +61,7 @@ public:
PrinterTechnology technology;
std::string family;
std::vector<PrinterVariant> variants;
std::vector<std::string> default_materials;
// Vendor & Printer Model specific print bed model & texture.
std::string bed_model;
std::string bed_texture;
@ -563,6 +564,11 @@ public:
const Preset* find_by_model_id(const std::string &model_id) const;
};
namespace PresetUtils {
// PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile.
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
} // namespace PresetUtils
} // namespace Slic3r
#endif /* slic3r_Preset_hpp_ */

View File

@ -13,9 +13,7 @@
#include <wx/numformatter.h>
#include <wx/colordlg.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/nowide/cstdio.hpp>
#include "BitmapCache.hpp"
#include "GUI.hpp"
@ -426,28 +424,6 @@ static float get_svg_scale_factor(wxWindow *win)
#endif
}
// in the Dark mode of any platform, we should draw icons in respect to OS background
static std::string icon_name_respected_to_mode(const std::string& bmp_name_in)
{
#ifdef __WXMSW__
const std::string folder = "white\\";
#else
const std::string folder = "white/";
#endif
std::string bmp_name;
if (Slic3r::GUI::wxGetApp().dark_mode()) {
bmp_name = folder + bmp_name_in;
boost::replace_last(bmp_name, ".png", "");
if (! boost::filesystem::exists(Slic3r::var(bmp_name + ".svg")))
bmp_name.clear();
}
if (bmp_name.empty()) {
bmp_name = bmp_name_in;
boost::replace_last(bmp_name, ".png", "");
}
return bmp_name;
}
// If an icon has horizontal orientation (width > height) call this function with is_horizontal = true
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/)
@ -474,13 +450,13 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in,
scale_base = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f);
// std::string bmp_name = bmp_name_in;
// boost::replace_last(bmp_name, ".png", "");
std::string bmp_name = bmp_name_in;
boost::replace_last(bmp_name, ".png", "");
std::string bmp_name = icon_name_respected_to_mode(bmp_name_in);
// std::string bmp_name = icon_name_respected_to_mode(bmp_name_in);
// Try loading an SVG first, then PNG if SVG is not found:
wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale);
wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale, Slic3r::GUI::wxGetApp().dark_mode());
if (bmp == nullptr) {
bmp = cache.load_png(bmp_name, width, height, grayscale);
}

View File

@ -363,10 +363,17 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
ModelObject *model_object = model.add_object();
model_object->add_volume(*volumes[ivolume]);
model_object->add_instance();
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) {
boost::filesystem::remove(path_src);
throw std::runtime_error(L("Export of a temporary 3mf file failed"));
}
#else
if (! Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr)) {
boost::filesystem::remove(path_src);
throw std::runtime_error(L("Export of a temporary 3mf file failed"));
}
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
model.clear_objects();
model.clear_materials();
boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();

View File

@ -51,7 +51,11 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
WHEN("model is saved+loaded to/from 3mf file") {
// save the model to 3mf file
std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/prusa.3mf";
#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
store_3mf(test_file.c_str(), &src_model, nullptr, false);
#else
store_3mf(test_file.c_str(), &src_model, nullptr);
#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF
// load back the model from the 3mf file
Model dst_model;