mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-02 00:48:43 +03:00
Prusa 2.7.3
This commit is contained in:
@@ -215,6 +215,7 @@ set(SLIC3R_SOURCES
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
Layer.hpp
|
||||
LayerRegion.hpp
|
||||
LayerRegion.cpp
|
||||
libslic3r.h
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
//w21
|
||||
#include "../ShortestPath.hpp"
|
||||
//w11
|
||||
|
||||
#include "LayerRegion.hpp"
|
||||
|
||||
#define NARROW_INFILL_AREA_THRESHOLD 3
|
||||
#define NARROW_INFILL_AREA_THRESHOLD_MIN 0.5
|
||||
namespace Slic3r {
|
||||
@@ -228,14 +231,14 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
|
||||
//w21
|
||||
fill.region_id_group.push_back(region_id);
|
||||
fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons;
|
||||
//fill.no_overlap_expolygons = layerm.fill_no_overlap_expolygons;
|
||||
} else {
|
||||
//w21
|
||||
fill.expolygons.emplace_back(surface.expolygon);
|
||||
auto t = find(fill.region_id_group.begin(), fill.region_id_group.end(), region_id);
|
||||
if (t == fill.region_id_group.end()) {
|
||||
fill.region_id_group.push_back(region_id);
|
||||
fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons);
|
||||
//fill.no_overlap_expolygons = union_ex(fill.no_overlap_expolygons, layerm.fill_no_overlap_expolygons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,11 @@ const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt
|
||||
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/QIDI_Slicer_custom_gcode_per_print_z.xml";
|
||||
const std::string CUT_INFORMATION_FILE = "Metadata/QIDI_Slicer_cut_information.xml";
|
||||
|
||||
static constexpr const char *RELATIONSHIP_TAG = "Relationship";
|
||||
|
||||
static constexpr const char* TARGET_ATTR = "Target";
|
||||
static constexpr const char* RELS_TYPE_ATTR = "Type";
|
||||
|
||||
static constexpr const char* MODEL_TAG = "model";
|
||||
static constexpr const char* RESOURCES_TAG = "resources";
|
||||
static constexpr const char* OBJECT_TAG = "object";
|
||||
@@ -113,6 +118,7 @@ static constexpr const char* Z_ATTR = "z";
|
||||
static constexpr const char* V1_ATTR = "v1";
|
||||
static constexpr const char* V2_ATTR = "v2";
|
||||
static constexpr const char* V3_ATTR = "v3";
|
||||
static constexpr const char* PPATH_ATTR = "p:path";
|
||||
static constexpr const char* OBJECTID_ATTR = "objectid";
|
||||
static constexpr const char* TRANSFORM_ATTR = "transform";
|
||||
static constexpr const char* PRINTABLE_ATTR = "printable";
|
||||
@@ -329,18 +335,20 @@ namespace Slic3r {
|
||||
|
||||
class _3MF_Importer : public _3MF_Base
|
||||
{
|
||||
typedef std::pair<std::string, int> PathId;
|
||||
struct Component
|
||||
{
|
||||
int object_id;
|
||||
PathId object_id;
|
||||
std::string path;
|
||||
Transform3d transform;
|
||||
|
||||
explicit Component(int object_id)
|
||||
explicit Component(PathId object_id)
|
||||
: object_id(object_id)
|
||||
, transform(Transform3d::Identity())
|
||||
{
|
||||
}
|
||||
|
||||
Component(int object_id, const Transform3d& transform)
|
||||
Component(PathId object_id, const Transform3d &transform)
|
||||
: object_id(object_id)
|
||||
, transform(transform)
|
||||
{
|
||||
@@ -458,11 +466,11 @@ namespace Slic3r {
|
||||
};
|
||||
|
||||
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
|
||||
typedef std::map<int, int> IdToModelObjectMap;
|
||||
typedef std::map<int, ComponentsList> IdToAliasesMap;
|
||||
typedef std::map<PathId, int> IdToModelObjectMap;
|
||||
typedef std::map<PathId, ComponentsList> IdToAliasesMap;
|
||||
typedef std::vector<Instance> InstancesList;
|
||||
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
|
||||
typedef std::map<int, Geometry> IdToGeometryMap;
|
||||
typedef std::map<PathId, Geometry> IdToGeometryMap;
|
||||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
|
||||
@@ -503,6 +511,8 @@ namespace Slic3r {
|
||||
std::string m_curr_metadata_name;
|
||||
std::string m_curr_characters;
|
||||
std::string m_name;
|
||||
std::string m_start_part_path;
|
||||
std::string m_model_path;
|
||||
|
||||
public:
|
||||
_3MF_Importer();
|
||||
@@ -526,8 +536,9 @@ namespace Slic3r {
|
||||
}
|
||||
|
||||
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
|
||||
bool _extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
bool _extract_model_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
bool _is_svg_shape_file(const std::string &filename) const;
|
||||
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
@@ -540,6 +551,9 @@ namespace Slic3r {
|
||||
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
|
||||
void _extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat);
|
||||
|
||||
// handlers to parse the .rels file
|
||||
void _handle_start_relationships_element(const char* name, const char** attributes);
|
||||
bool _handle_start_relationship(const char **attributes, unsigned int num_attributes);
|
||||
// handlers to parse the .model file
|
||||
void _handle_start_model_xml_element(const char* name, const char** attributes);
|
||||
void _handle_end_model_xml_element(const char* name);
|
||||
@@ -591,7 +605,7 @@ namespace Slic3r {
|
||||
bool _handle_start_text_configuration(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_start_shape_configuration(const char **attributes, unsigned int num_attributes);
|
||||
|
||||
bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
||||
bool _create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
||||
|
||||
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
|
||||
|
||||
@@ -611,6 +625,8 @@ namespace Slic3r {
|
||||
|
||||
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
|
||||
|
||||
// callbacks to parse the .rels file
|
||||
static void XMLCALL _handle_start_relationships_element(void *userData, const char *name, const char **attributes);
|
||||
// callbacks to parse the .model file
|
||||
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
|
||||
static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name);
|
||||
@@ -660,6 +676,7 @@ namespace Slic3r {
|
||||
m_sla_support_points.clear();
|
||||
m_curr_metadata_name.clear();
|
||||
m_curr_characters.clear();
|
||||
m_start_part_path = MODEL_FILE; // set default value for invalid .rel file
|
||||
clear_errors();
|
||||
|
||||
return _load_model_from_file(filename, model, config, config_substitutions);
|
||||
@@ -699,23 +716,28 @@ namespace Slic3r {
|
||||
|
||||
m_name = boost::filesystem::path(filename).stem().string();
|
||||
|
||||
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
|
||||
int index = mz_zip_reader_locate_file(&archive, RELATIONSHIPS_FILE.c_str(), nullptr, 0);
|
||||
if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat))
|
||||
return false;
|
||||
|
||||
mz_zip_archive_file_stat start_part_stat{std::numeric_limits<mz_uint32>::max()};
|
||||
m_model_path = MODEL_FILE;
|
||||
_extract_relationships_from_archive(archive, stat);
|
||||
bool found_model = false;
|
||||
// we first loop the entries to read from the .model files which are not root
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
std::string name(stat.m_filename);
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
|
||||
if (boost::algorithm::iends_with(name, MODEL_EXTENSION)) {
|
||||
if(found_model){
|
||||
close_zip_reader(&archive);
|
||||
add_error("3mf contain multiple .model files and it is not supported yet.");
|
||||
return false;
|
||||
// valid model name -> extract model
|
||||
m_model_path = "/" + name;
|
||||
if (m_model_path == m_start_part_path) {
|
||||
start_part_stat = stat;
|
||||
continue;
|
||||
}
|
||||
found_model = true;
|
||||
try
|
||||
{
|
||||
// valid model name -> extract model
|
||||
try {
|
||||
if (!_extract_model_from_archive(archive, stat)) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Archive does not contain a valid model");
|
||||
@@ -728,9 +750,26 @@ namespace Slic3r {
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
found_model = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read root model file
|
||||
if (start_part_stat.m_file_index < num_entries) {
|
||||
try {
|
||||
m_model_path.clear();
|
||||
if (!_extract_model_from_archive(archive, start_part_stat)) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Archive does not contain a valid model");
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
found_model = true;
|
||||
}
|
||||
if (!found_model) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Not valid 3mf. There is missing .model file.");
|
||||
@@ -869,7 +908,7 @@ namespace Slic3r {
|
||||
ObjectMetadata::VolumeMetadataList volumes;
|
||||
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
|
||||
|
||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
|
||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
|
||||
if (obj_metadata != m_objects_metadata.end()) {
|
||||
// config data has been found, this model was saved using slic3r pe
|
||||
|
||||
@@ -950,8 +989,55 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
// // fixes the min z of the model if negative
|
||||
// model.adjust_min_z();
|
||||
// We support our 3mf contains only configuration without mesh,
|
||||
// others MUST contain mesh (triangles and vertices).
|
||||
if (!m_qidislicer_generator_version.has_value() && model.objects.empty()) {
|
||||
const std::string msg = (boost::format(_u8L("The 3MF file does not contain a valid mesh.\n\n\"%1%\"")) % filename).str();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_extract_relationships_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
|
||||
{
|
||||
if (stat.m_uncomp_size == 0 ||
|
||||
stat.m_uncomp_size > 10000000 // Prevent overloading by big Relations file(>10MB). there is no reason to be soo big
|
||||
) {
|
||||
add_error("Found invalid size");
|
||||
return false;
|
||||
}
|
||||
|
||||
_destroy_xml_parser();
|
||||
|
||||
m_xml_parser = XML_ParserCreate(nullptr);
|
||||
if (m_xml_parser == nullptr) {
|
||||
add_error("Unable to create parser");
|
||||
return false;
|
||||
}
|
||||
|
||||
XML_SetUserData(m_xml_parser, (void *) this);
|
||||
XML_SetStartElementHandler(m_xml_parser, _handle_start_relationships_element);
|
||||
|
||||
void *parser_buffer = XML_GetBuffer(m_xml_parser, (int) stat.m_uncomp_size);
|
||||
if (parser_buffer == nullptr) {
|
||||
add_error("Unable to create buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t) stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
add_error("Error while reading config data to buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!XML_ParseBuffer(m_xml_parser, (int) stat.m_uncomp_size, 1)) {
|
||||
char error_buf[1024];
|
||||
::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)),
|
||||
(int) XML_GetCurrentLineNumber(m_xml_parser));
|
||||
add_error(error_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1515,6 +1601,39 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL _3MF_Importer::_handle_start_relationships_element(void *userData, const char *name, const char **attributes)
|
||||
{
|
||||
_3MF_Importer *importer = (_3MF_Importer *) userData;
|
||||
if (importer != nullptr)
|
||||
importer->_handle_start_relationships_element(name, attributes);
|
||||
}
|
||||
|
||||
void _3MF_Importer::_handle_start_relationships_element(const char *name, const char **attributes)
|
||||
{
|
||||
if (m_xml_parser == nullptr)
|
||||
return;
|
||||
|
||||
bool res = true;
|
||||
unsigned int num_attributes = (unsigned int) XML_GetSpecifiedAttributeCount(m_xml_parser);
|
||||
|
||||
if (::strcmp(RELATIONSHIP_TAG, name) == 0)
|
||||
res = _handle_start_relationship(attributes, num_attributes);
|
||||
|
||||
m_curr_characters.clear();
|
||||
if (!res)
|
||||
_stop_xml_parser();
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_handle_start_relationship(const char **attributes, unsigned int num_attributes)
|
||||
{
|
||||
std::string type = get_attribute_value_string(attributes, num_attributes, RELS_TYPE_ATTR);
|
||||
// only exactly that string type mean root model file
|
||||
if (type == "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel") {
|
||||
std::string path = get_attribute_value_string(attributes, num_attributes, TARGET_ATTR);
|
||||
m_start_part_path = path;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
|
||||
{
|
||||
if (m_xml_parser == nullptr)
|
||||
@@ -1654,6 +1773,8 @@ namespace Slic3r {
|
||||
|
||||
bool _3MF_Importer::_handle_end_model()
|
||||
{
|
||||
if (!m_model_path.empty())
|
||||
return true;
|
||||
// deletes all non-built or non-instanced objects
|
||||
for (const IdToModelObjectMap::value_type& object : m_objects) {
|
||||
if (object.second >= int(m_model->objects.size())) {
|
||||
@@ -1722,6 +1843,7 @@ namespace Slic3r {
|
||||
bool _3MF_Importer::_handle_end_object()
|
||||
{
|
||||
if (m_curr_object.object != nullptr) {
|
||||
PathId object_id{m_model_path, m_curr_object.id};
|
||||
if (m_curr_object.geometry.empty()) {
|
||||
// no geometry defined
|
||||
// remove the object from the model
|
||||
@@ -1729,26 +1851,26 @@ namespace Slic3r {
|
||||
|
||||
if (m_curr_object.components.empty()) {
|
||||
// no components defined -> invalid object, delete it
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id);
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
|
||||
if (object_item != m_objects.end())
|
||||
m_objects.erase(object_item);
|
||||
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id);
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
|
||||
if (alias_item != m_objects_aliases.end())
|
||||
m_objects_aliases.erase(alias_item);
|
||||
}
|
||||
else
|
||||
// adds components to aliases
|
||||
m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components });
|
||||
m_objects_aliases.insert({ object_id, m_curr_object.components });
|
||||
}
|
||||
else {
|
||||
// geometry defined, store it for later use
|
||||
m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) });
|
||||
m_geometries.insert({ object_id, std::move(m_curr_object.geometry) });
|
||||
|
||||
// stores the object for later use
|
||||
if (m_objects.find(m_curr_object.id) == m_objects.end()) {
|
||||
m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx });
|
||||
m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself
|
||||
if (m_objects.find(object_id) == m_objects.end()) {
|
||||
m_objects.insert({ object_id, m_curr_object.model_object_idx });
|
||||
m_objects_aliases.insert({object_id, {1, Component(object_id)}}); // aliases itself
|
||||
}
|
||||
else {
|
||||
add_error("Found object with duplicate id");
|
||||
@@ -1859,19 +1981,22 @@ namespace Slic3r {
|
||||
|
||||
bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||
if (path.empty()) path = m_model_path;
|
||||
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
||||
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
|
||||
PathId path_id { path, object_id };
|
||||
IdToModelObjectMap::iterator object_item = m_objects.find(path_id);
|
||||
if (object_item == m_objects.end()) {
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
|
||||
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(path_id);
|
||||
if (alias_item == m_objects_aliases.end()) {
|
||||
add_error("Found component with invalid object id");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_curr_object.components.emplace_back(object_id, transform);
|
||||
m_curr_object.components.emplace_back(path_id, transform);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1905,9 +2030,11 @@ namespace Slic3r {
|
||||
|
||||
int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
||||
Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
||||
std::string path = get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
||||
if (path.empty()) path = m_model_path;
|
||||
int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
||||
|
||||
return _create_object_instance(object_id, transform, printable, 1);
|
||||
return _create_object_instance({path, object_id}, transform, printable, 1);
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_handle_end_item()
|
||||
@@ -2063,7 +2190,7 @@ namespace Slic3r {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
||||
bool _3MF_Importer::_create_object_instance(PathId object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
||||
{
|
||||
static const unsigned int MAX_RECURSIONS = 10;
|
||||
|
||||
|
||||
@@ -552,7 +552,6 @@ GCodeGenerator::GCodeGenerator(const Print* print) :
|
||||
m_brim_done(false),
|
||||
m_second_layer_things_done(false),
|
||||
m_silent_time_estimator_enabled(false),
|
||||
m_current_instance({nullptr, -1}),
|
||||
m_print(print)
|
||||
{}
|
||||
void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
@@ -876,6 +875,30 @@ static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print&
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline bool is_mk2_or_mk3(const std::string &printer_model) {
|
||||
if (boost::starts_with(printer_model, "MK2")) {
|
||||
return true;
|
||||
} else if (boost::starts_with(printer_model, "MK3") && (printer_model.size() <= 3 || printer_model[3] != '.')) {
|
||||
// Ignore MK3.5 and MK3.9.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline std::optional<std::string> find_M84(const std::string &gcode) {
|
||||
std::istringstream gcode_is(gcode);
|
||||
std::string gcode_line;
|
||||
while (std::getline(gcode_is, gcode_line)) {
|
||||
boost::trim(gcode_line);
|
||||
|
||||
if (gcode_line == "M84" || boost::starts_with(gcode_line, "M84 ") || boost::starts_with(gcode_line, "M84;")) {
|
||||
return gcode_line;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
{
|
||||
const bool export_to_binary_gcode = print.full_print_config().option<ConfigOptionBool>("binary_gcode")->value;
|
||||
@@ -1109,7 +1132,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
this->print_machine_envelope(file, print);
|
||||
|
||||
// Label all objects so printer knows about them since the start.
|
||||
m_label_objects.init(print);
|
||||
m_label_objects.init(print.objects(), print.config().gcode_label_objects, print.config().gcode_flavor);
|
||||
//B41
|
||||
// file.write(m_label_objects.all_objects_header());
|
||||
// Update output variables after the extruders were initialized.
|
||||
@@ -1228,9 +1251,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
// Move to the origin position for the copy we're going to print.
|
||||
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
|
||||
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
file.write(this->retract_and_wipe());
|
||||
file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object"));
|
||||
file.write(m_label_objects.maybe_stop_instance());
|
||||
const double last_z{this->writer().get_position().z()};
|
||||
file.write(this->writer().get_travel_to_z_gcode(last_z, "ensure z position"));
|
||||
file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object", [](){return "";}));
|
||||
m_enable_cooling_markers = true;
|
||||
// Disable motion planner when traveling to first object point.
|
||||
m_avoid_crossing_perimeters.disable_once();
|
||||
@@ -1259,6 +1285,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
m_second_layer_things_done = false;
|
||||
prev_object = &object;
|
||||
}
|
||||
file.write(m_label_objects.maybe_stop_instance());
|
||||
} else {
|
||||
// Sort layers by Z.
|
||||
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
||||
@@ -1313,6 +1340,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
// and export G-code into file.
|
||||
this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print,
|
||||
smooth_path_cache_global, file);
|
||||
file.write(m_label_objects.maybe_stop_instance());
|
||||
if (m_wipe_tower)
|
||||
// Purge the extruder, pull out the active filament.
|
||||
file.write(m_wipe_tower->finalize(*this));
|
||||
@@ -1382,8 +1410,10 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
m_processor.get_binary_data()
|
||||
);
|
||||
|
||||
if (!export_to_binary_gcode)
|
||||
if (!export_to_binary_gcode) {
|
||||
file.write_format("; objects_info = %s\n", m_label_objects.all_objects_header_singleline_json().c_str());
|
||||
file.write(filament_stats_string_out);
|
||||
}
|
||||
if (export_to_binary_gcode) {
|
||||
bgcode::binarize::BinaryData& binary_data = m_processor.get_binary_data();
|
||||
if (print.m_print_statistics.total_toolchanges > 0)
|
||||
@@ -1391,6 +1421,8 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
char buf[1024];
|
||||
sprintf(buf, "%.2lf", m_max_layer_z);
|
||||
binary_data.printer_metadata.raw_data.emplace_back("max_layer_z", buf);
|
||||
// Now the objects info.
|
||||
binary_data.printer_metadata.raw_data.emplace_back("objects_info", m_label_objects.all_objects_header_singleline_json());
|
||||
}
|
||||
else {
|
||||
// if exporting gcode in ascii format, statistics export is done here
|
||||
@@ -1426,7 +1458,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
append_full_config(*m_print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
file.write("; qidislicer_config = end\n");
|
||||
file.write("; qidislicer_config = end\n");
|
||||
}
|
||||
|
||||
if (std::optional<std::string> line_M84 = find_M84(print.config().end_gcode);
|
||||
is_mk2_or_mk3(print.config().printer_model) && line_M84.has_value()) {
|
||||
file.writeln(*line_M84);
|
||||
}
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
@@ -1502,12 +1539,13 @@ void GCodeGenerator::process_layers(
|
||||
});
|
||||
// The pipeline is variable: The vase mode filter is optional.
|
||||
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[spiral_vase = this->m_spiral_vase.get()](LayerResult in) -> LayerResult {
|
||||
[spiral_vase = this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult {
|
||||
if (in.nop_layer_result)
|
||||
return in;
|
||||
|
||||
spiral_vase->enable(in.spiral_vase_enable);
|
||||
return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
|
||||
bool last_layer = in.layer_id == layers_to_print.size() - 1;
|
||||
return { spiral_vase->process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
|
||||
});
|
||||
const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
|
||||
@@ -1596,11 +1634,12 @@ void GCodeGenerator::process_layers(
|
||||
});
|
||||
// The pipeline is variable: The vase mode filter is optional.
|
||||
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[spiral_vase = this->m_spiral_vase.get()](LayerResult in)->LayerResult {
|
||||
[spiral_vase = this->m_spiral_vase.get(), &layers_to_print](LayerResult in)->LayerResult {
|
||||
if (in.nop_layer_result)
|
||||
return in;
|
||||
spiral_vase->enable(in.spiral_vase_enable);
|
||||
return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
|
||||
bool last_layer = in.layer_id == layers_to_print.size() - 1;
|
||||
return { spiral_vase->process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
|
||||
});
|
||||
const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||
[pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
|
||||
@@ -2107,18 +2146,61 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector<unsigned int>&
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) {
|
||||
const Polyline xy_path{
|
||||
this->gcode_to_point(from.head<2>()),
|
||||
this->gcode_to_point(to.head<2>())
|
||||
};
|
||||
Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d &to) {
|
||||
|
||||
bool could_be_wipe_disabled{false};
|
||||
const bool needs_retraction{true};
|
||||
|
||||
const Point saved_last_position{*this->last_position};
|
||||
const bool saved_use_external_mp{this->m_avoid_crossing_perimeters.use_external_mp_once};
|
||||
const Vec2d saved_origin{this->origin()};
|
||||
const Layer* saved_layer{this->layer()};
|
||||
|
||||
this->m_avoid_crossing_perimeters.use_external_mp_once = m_layer_change_used_external_mp;
|
||||
if (this->m_layer_change_origin) {
|
||||
this->m_origin = *this->m_layer_change_origin;
|
||||
}
|
||||
this->m_layer = m_layer_change_layer;
|
||||
this->m_avoid_crossing_perimeters.init_layer(*this->m_layer);
|
||||
|
||||
const Point start_point{this->gcode_to_point(from.head<2>())};
|
||||
const Point end_point{this->gcode_to_point(to.head<2>())};
|
||||
this->last_position = start_point;
|
||||
|
||||
Polyline xy_path{
|
||||
this->generate_travel_xy_path(start_point, end_point, needs_retraction, could_be_wipe_disabled)};
|
||||
std::vector<Vec2d> gcode_xy_path;
|
||||
gcode_xy_path.reserve(xy_path.size());
|
||||
for (const Point &point : xy_path.points) {
|
||||
gcode_xy_path.push_back(this->point_to_gcode(point));
|
||||
}
|
||||
|
||||
this->last_position = saved_last_position;
|
||||
this->m_avoid_crossing_perimeters.use_external_mp_once = saved_use_external_mp;
|
||||
this->m_origin = saved_origin;
|
||||
this->m_layer = saved_layer;
|
||||
|
||||
Polyline result;
|
||||
for (const Vec2d& point : gcode_xy_path) {
|
||||
result.points.push_back(gcode_to_point(point));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GCode::Impl::Travels::ElevatedTravelParams get_ramping_layer_change_params(
|
||||
const Vec3d &from,
|
||||
const Vec3d &to,
|
||||
const Polyline &xy_path,
|
||||
const FullPrintConfig &config,
|
||||
const unsigned extruder_id,
|
||||
const GCode::TravelObstacleTracker &obstacle_tracker
|
||||
) {
|
||||
using namespace GCode::Impl::Travels;
|
||||
|
||||
ElevatedTravelParams elevation_params{
|
||||
get_elevated_traval_params(xy_path, this->m_config, extruder_id, this->m_travel_obstacle_tracker)};
|
||||
get_elevated_traval_params(xy_path, config, extruder_id, obstacle_tracker)};
|
||||
|
||||
const double initial_elevation = from.z();
|
||||
const double z_change = to.z() - from.z();
|
||||
elevation_params.lift_height = std::max(z_change, elevation_params.lift_height);
|
||||
|
||||
@@ -2132,6 +2214,25 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3
|
||||
elevation_params.slope_end = path_length;
|
||||
}
|
||||
|
||||
return elevation_params;
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::get_ramping_layer_change_gcode(const Vec3d &from, const Vec3d &to, const unsigned extruder_id) {
|
||||
const Polyline xy_path{this->get_layer_change_xy_path(from, to)};
|
||||
|
||||
const GCode::Impl::Travels::ElevatedTravelParams elevation_params{
|
||||
get_ramping_layer_change_params(
|
||||
from, to, xy_path, m_config, extruder_id, m_travel_obstacle_tracker
|
||||
)};
|
||||
return this->generate_ramping_layer_change_gcode(xy_path, from.z(), elevation_params);
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::generate_ramping_layer_change_gcode(
|
||||
const Polyline &xy_path,
|
||||
const double initial_elevation,
|
||||
const GCode::Impl::Travels::ElevatedTravelParams &elevation_params
|
||||
) const {
|
||||
using namespace GCode::Impl::Travels;
|
||||
const std::vector<double> ensure_points_at_distances = linspace(
|
||||
elevation_params.slope_end - elevation_params.blend_width / 2.0,
|
||||
elevation_params.slope_end + elevation_params.blend_width / 2.0,
|
||||
@@ -2147,7 +2248,8 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3
|
||||
Vec3d previous_point{this->point_to_gcode(travel.front())};
|
||||
for (const Vec3crd& point : travel) {
|
||||
const Vec3d gcode_point{this->point_to_gcode(point)};
|
||||
travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change");
|
||||
travel_gcode += this->m_writer
|
||||
.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change");
|
||||
previous_point = gcode_point;
|
||||
}
|
||||
return travel_gcode;
|
||||
@@ -2199,6 +2301,10 @@ LayerResult GCodeGenerator::process_layer(
|
||||
bool first_layer = layer.id() == 0;
|
||||
unsigned int first_extruder_id = layer_tools.extruders.front();
|
||||
|
||||
const std::vector<InstanceToPrint> instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)};
|
||||
const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]};
|
||||
m_label_objects.update(first_instance);
|
||||
|
||||
//B36
|
||||
m_writer.set_is_first_layer(first_layer);
|
||||
|
||||
@@ -2347,6 +2453,10 @@ LayerResult GCodeGenerator::process_layer(
|
||||
gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config());
|
||||
}
|
||||
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
|
||||
if (!this->m_config.complete_objects.value) {
|
||||
gcode += this->m_label_objects.maybe_stop_instance();
|
||||
}
|
||||
this->m_label_objects.update(nullptr);
|
||||
const std::pair<size_t, size_t> loops = loops_it->second;
|
||||
this->set_origin(0., 0.);
|
||||
m_avoid_crossing_perimeters.use_external_mp();
|
||||
@@ -2368,6 +2478,10 @@ LayerResult GCodeGenerator::process_layer(
|
||||
|
||||
// Extrude brim with the extruder of the 1st region.
|
||||
if (! m_brim_done) {
|
||||
if (!this->m_config.complete_objects.value) {
|
||||
gcode += this->m_label_objects.maybe_stop_instance();
|
||||
}
|
||||
this->m_label_objects.update(nullptr);
|
||||
this->set_origin(0., 0.);
|
||||
m_avoid_crossing_perimeters.use_external_mp();
|
||||
for (const ExtrusionEntity *ee : print.brim().entities)
|
||||
@@ -2378,7 +2492,7 @@ LayerResult GCodeGenerator::process_layer(
|
||||
m_avoid_crossing_perimeters.disable_once();
|
||||
}
|
||||
|
||||
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx);
|
||||
this->m_label_objects.update(first_instance);
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
||||
bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden();
|
||||
@@ -2420,7 +2534,9 @@ LayerResult GCodeGenerator::process_layer(
|
||||
if (first_layer) {
|
||||
layer_change_gcode = ""; // Explicit for readability.
|
||||
} else if (do_ramping_layer_change) {
|
||||
layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id);
|
||||
const Vec3d &from{*m_previous_layer_last_position};
|
||||
const Vec3d &to{*m_current_layer_first_position};
|
||||
layer_change_gcode = this->get_ramping_layer_change_gcode(from, to, *m_layer_change_extruder_id);
|
||||
} else {
|
||||
layer_change_gcode = this->writer().get_travel_to_z_gcode(print_z, "simple layer change");
|
||||
}
|
||||
@@ -2451,7 +2567,7 @@ LayerResult GCodeGenerator::process_layer(
|
||||
const std::size_t end{end_tag_start + retraction_end_tag.size()};
|
||||
gcode.replace(start, end - start, "");
|
||||
|
||||
layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id);
|
||||
layer_change_gcode = this->get_ramping_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id);
|
||||
|
||||
removed_retraction = true;
|
||||
}
|
||||
@@ -2500,7 +2616,7 @@ void GCodeGenerator::process_layer_single_object(
|
||||
{
|
||||
bool first = true;
|
||||
// Delay layer initialization as many layers may not print with all extruders.
|
||||
auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() {
|
||||
auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first]() {
|
||||
if (first) {
|
||||
first = false;
|
||||
const PrintObject &print_object = print_instance.print_object;
|
||||
@@ -2512,11 +2628,12 @@ void GCodeGenerator::process_layer_single_object(
|
||||
// When starting a new object, use the external motion planner for the first travel move.
|
||||
const Point &offset = print_object.instances()[print_instance.instance_id].shift;
|
||||
GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)};
|
||||
if (m_current_instance != next_instance)
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
if (m_current_instance != next_instance) {
|
||||
m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
}
|
||||
m_current_instance = next_instance;
|
||||
this->set_origin(unscale(offset));
|
||||
gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No);
|
||||
m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2698,8 +2815,6 @@ void GCodeGenerator::process_layer_single_object(
|
||||
}
|
||||
}
|
||||
|
||||
if (! first)
|
||||
gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]);
|
||||
|
||||
}
|
||||
|
||||
@@ -2786,6 +2901,9 @@ std::string GCodeGenerator::change_layer(
|
||||
// Increment a progress bar indicator.
|
||||
gcode += m_writer.update_progress(++ m_layer_index, m_layer_count);
|
||||
|
||||
if (m_writer.multiple_extruders) {
|
||||
gcode += m_label_objects.maybe_change_instance(m_writer);
|
||||
}
|
||||
if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) {
|
||||
gcode += this->retract_and_wipe();
|
||||
} else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){
|
||||
@@ -2793,8 +2911,9 @@ std::string GCodeGenerator::change_layer(
|
||||
std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} :
|
||||
std::nullopt;
|
||||
gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start);
|
||||
gcode += this->retract_and_wipe();
|
||||
gcode += this->retract_and_wipe(false, false);
|
||||
gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End);
|
||||
gcode += m_writer.reset_e();
|
||||
}
|
||||
|
||||
Vec3d new_position = this->writer().get_position();
|
||||
@@ -2868,7 +2987,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC
|
||||
if (m_wipe.enabled()) {
|
||||
|
||||
// Wipe will hide the seam.
|
||||
m_wipe.set_path(std::move(smooth_path), false);
|
||||
m_wipe.set_path(std::move(smooth_path));
|
||||
} else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) {
|
||||
|
||||
// Only wipe inside if the wipe along the perimeter is disabled.
|
||||
@@ -2912,7 +3031,7 @@ std::string GCodeGenerator::extrude_skirt(
|
||||
gcode += m_writer.set_print_acceleration(fast_round_up<unsigned int>(m_config.default_acceleration.value));
|
||||
if (m_wipe.enabled())
|
||||
// Wipe will hide the seam.
|
||||
m_wipe.set_path(std::move(smooth_path), false);
|
||||
m_wipe.set_path(std::move(smooth_path));
|
||||
|
||||
return gcode;
|
||||
}
|
||||
@@ -2931,7 +3050,8 @@ std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipa
|
||||
std::string gcode;
|
||||
for (GCode::SmoothPathElement &el : smooth_path)
|
||||
gcode += this->_extrude(el.path_attributes, el.path, description, speed);
|
||||
m_wipe.set_path(std::move(smooth_path), true);
|
||||
GCode::reverse(smooth_path);
|
||||
m_wipe.set_path(std::move(smooth_path));
|
||||
// reset acceleration
|
||||
gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5));
|
||||
return gcode;
|
||||
@@ -3064,11 +3184,22 @@ void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...)
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z) {
|
||||
std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const double from_z, const ExtrusionRole role, const std::function<std::string()>& insert_gcode) {
|
||||
std::string gcode;
|
||||
|
||||
const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z()));
|
||||
|
||||
if (!EXTRUDER_CONFIG(travel_ramping_lift) && this->last_position) {
|
||||
Vec3d writer_position{this->writer().get_position()};
|
||||
writer_position.z() = 0.0; // Endofrce z generation!
|
||||
this->writer().update_position(writer_position);
|
||||
gcode = this->travel_to(
|
||||
*this->last_position, point.head<2>(), role, "travel to first layer point", insert_gcode
|
||||
);
|
||||
} else {
|
||||
this->m_layer_change_used_external_mp = this->m_avoid_crossing_perimeters.use_external_mp_once;
|
||||
this->m_layer_change_layer = this->layer();
|
||||
this->m_layer_change_origin = this->origin();
|
||||
double lift{
|
||||
EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) :
|
||||
EXTRUDER_CONFIG(retract_lift)};
|
||||
@@ -3079,19 +3210,22 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const
|
||||
lift = 0.0;
|
||||
}
|
||||
|
||||
if (EXTRUDER_CONFIG(retract_length) > 0 && (!this->last_position || (!EXTRUDER_CONFIG(travel_ramping_lift)))) {
|
||||
if (EXTRUDER_CONFIG(retract_length) > 0 && !this->last_position) {
|
||||
if (!this->last_position || EXTRUDER_CONFIG(retract_before_travel) < (this->point_to_gcode(*this->last_position) - gcode_point.head<2>()).norm()) {
|
||||
gcode += this->writer().retract();
|
||||
gcode += this->writer().get_travel_to_z_gcode(from_z + lift, "lift");
|
||||
}
|
||||
}
|
||||
this->last_position = point.head<2>();
|
||||
this->writer().update_position(gcode_point);
|
||||
const std::string comment{"move to first layer point"};
|
||||
|
||||
std::string comment{"move to first layer point"};
|
||||
gcode += insert_gcode();
|
||||
gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), comment);
|
||||
gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), comment);
|
||||
|
||||
this->m_avoid_crossing_perimeters.reset_once_modifiers();
|
||||
this->last_position = point.head<2>();
|
||||
this->writer().update_position(gcode_point);
|
||||
}
|
||||
m_current_layer_first_position = gcode_point;
|
||||
return gcode;
|
||||
}
|
||||
@@ -3118,15 +3252,22 @@ std::string GCodeGenerator::_extrude(
|
||||
std::string gcode;
|
||||
const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv;
|
||||
|
||||
const bool has_active_instance{m_label_objects.has_active_instance()};
|
||||
if (m_writer.multiple_extruders && has_active_instance) {
|
||||
gcode += m_label_objects.maybe_change_instance(m_writer);
|
||||
}
|
||||
if (!m_current_layer_first_position) {
|
||||
const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z));
|
||||
gcode += this->travel_to_first_position(point, unscaled(point.z()));
|
||||
gcode += this->travel_to_first_position(point, unscaled(point.z()), path_attr.role, [&](){
|
||||
return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer);
|
||||
});
|
||||
} else {
|
||||
// go to first point of extrusion path
|
||||
if (!this->last_position) {
|
||||
const double z = this->m_last_layer_z;
|
||||
const std::string comment{"move to print after unknown position"};
|
||||
gcode += this->retract_and_wipe();
|
||||
gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer);
|
||||
gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment);
|
||||
gcode += this->m_writer.get_travel_to_z_gcode(z, comment);
|
||||
} else if ( this->last_position != path.front().point) {
|
||||
@@ -3134,7 +3275,9 @@ std::string GCodeGenerator::_extrude(
|
||||
comment += description;
|
||||
comment += description_bridge;
|
||||
comment += " point";
|
||||
const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)};
|
||||
const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [&](){
|
||||
return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer);
|
||||
})};
|
||||
gcode += travel_gcode;
|
||||
}
|
||||
}
|
||||
@@ -3148,6 +3291,12 @@ std::string GCodeGenerator::_extrude(
|
||||
} else {
|
||||
this->m_already_unretracted = true;
|
||||
gcode += "FIRST_UNRETRACT" + this->unretract();
|
||||
//First unretract may or may not be removed thus we must start from E0.
|
||||
gcode += this->writer().reset_e();
|
||||
}
|
||||
|
||||
if (m_writer.multiple_extruders && !has_active_instance) {
|
||||
gcode += m_label_objects.maybe_change_instance(m_writer);
|
||||
}
|
||||
|
||||
if (!m_pending_pre_extrusion_gcode.empty()) {
|
||||
@@ -3368,7 +3517,8 @@ std::string GCodeGenerator::_extrude(
|
||||
|
||||
std::string GCodeGenerator::generate_travel_gcode(
|
||||
const Points3& travel,
|
||||
const std::string& comment
|
||||
const std::string& comment,
|
||||
const std::function<std::string()>& insert_gcode
|
||||
) {
|
||||
std::string gcode;
|
||||
const unsigned acceleration =(unsigned)(m_config.travel_acceleration.value + 0.5);
|
||||
@@ -3383,9 +3533,15 @@ std::string GCodeGenerator::generate_travel_gcode(
|
||||
gcode += this->m_writer.set_travel_acceleration(acceleration);
|
||||
|
||||
Vec3d previous_point{this->point_to_gcode(travel.front())};
|
||||
for (const Vec3crd& point : travel) {
|
||||
bool already_inserted{false};
|
||||
for (std::size_t i{0}; i < travel.size(); ++i) {
|
||||
const Vec3crd& point{travel[i]};
|
||||
const Vec3d gcode_point{this->point_to_gcode(point)};
|
||||
|
||||
if (travel.size() - i <= 2 && !already_inserted) {
|
||||
gcode += insert_gcode();
|
||||
already_inserted = true;
|
||||
}
|
||||
gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment);
|
||||
this->last_position = point.head<2>();
|
||||
previous_point = gcode_point;
|
||||
@@ -3481,7 +3637,11 @@ Polyline GCodeGenerator::generate_travel_xy_path(
|
||||
|
||||
// This method accepts &point in print coordinates.
|
||||
std::string GCodeGenerator::travel_to(
|
||||
const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment
|
||||
const Point &start_point,
|
||||
const Point &end_point,
|
||||
ExtrusionRole role,
|
||||
const std::string &comment,
|
||||
const std::function<std::string()>& insert_gcode
|
||||
) {
|
||||
|
||||
// check whether a straight travel move would need retraction
|
||||
@@ -3541,10 +3701,10 @@ std::string GCodeGenerator::travel_to(
|
||||
)
|
||||
);
|
||||
|
||||
return wipe_retract_gcode + generate_travel_gcode(travel, comment);
|
||||
return wipe_retract_gcode + generate_travel_gcode(travel, comment, insert_gcode);
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::retract_and_wipe(bool toolchange)
|
||||
std::string GCodeGenerator::retract_and_wipe(bool toolchange, bool reset_e)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
@@ -3563,7 +3723,9 @@ std::string GCodeGenerator::retract_and_wipe(bool toolchange)
|
||||
length is honored in case wipe path was too short. */
|
||||
gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
|
||||
|
||||
if (reset_e) {
|
||||
gcode += m_writer.reset_e();
|
||||
}
|
||||
|
||||
return gcode;
|
||||
}
|
||||
@@ -3594,8 +3756,12 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string gcode{};
|
||||
if (!this->m_config.complete_objects.value) {
|
||||
gcode += this->m_label_objects.maybe_stop_instance();
|
||||
}
|
||||
// prepend retraction on the current extruder
|
||||
std::string gcode = this->retract_and_wipe(true);
|
||||
gcode += this->retract_and_wipe(true);
|
||||
|
||||
// Always reset the extrusion path, even if the tool change retract is set to zero.
|
||||
m_wipe.reset_path();
|
||||
|
||||
@@ -96,7 +96,7 @@ struct PrintObjectInstance
|
||||
int instance_idx = -1;
|
||||
|
||||
bool operator==(const PrintObjectInstance &other) const {return print_object == other.print_object && instance_idx == other.instance_idx; }
|
||||
bool operator!=(const PrintObjectInstance &other) const { return *this == other; }
|
||||
bool operator!=(const PrintObjectInstance &other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
@@ -210,8 +210,15 @@ private:
|
||||
static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object);
|
||||
static std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> collect_layers_to_print(const Print &print);
|
||||
|
||||
Polyline get_layer_change_xy_path(const Vec3d &from, const Vec3d &to);
|
||||
|
||||
std::string get_ramping_layer_change_gcode(const Vec3d &from, const Vec3d &to, const unsigned extruder_id);
|
||||
/** @brief Generates ramping travel gcode for layer change. */
|
||||
std::string get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id);
|
||||
std::string generate_ramping_layer_change_gcode(
|
||||
const Polyline &xy_path,
|
||||
const double initial_elevation,
|
||||
const GCode::Impl::Travels::ElevatedTravelParams &elevation_params
|
||||
) const;
|
||||
LayerResult process_layer(
|
||||
const Print &print,
|
||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||
@@ -302,7 +309,8 @@ private:
|
||||
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
|
||||
std::string generate_travel_gcode(
|
||||
const Points3& travel,
|
||||
const std::string& comment
|
||||
const std::string& comment,
|
||||
const std::function<std::string()>& insert_gcode
|
||||
);
|
||||
Polyline generate_travel_xy_path(
|
||||
const Point& start,
|
||||
@@ -314,10 +322,11 @@ private:
|
||||
const Point &start_point,
|
||||
const Point &end_point,
|
||||
ExtrusionRole role,
|
||||
const std::string &comment
|
||||
const std::string &comment,
|
||||
const std::function<std::string()>& insert_gcode
|
||||
);
|
||||
|
||||
std::string travel_to_first_position(const Vec3crd& point, const double from_z);
|
||||
std::string travel_to_first_position(const Vec3crd& point, const double from_z, const ExtrusionRole role, const std::function<std::string()>& insert_gcode);
|
||||
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
|
||||
|
||||
//B41
|
||||
@@ -328,7 +337,7 @@ private:
|
||||
int unique_id;
|
||||
};
|
||||
std::unordered_map<const PrintInstance *, LabelData> m_label_data;
|
||||
std::string retract_and_wipe(bool toolchange = false);
|
||||
std::string retract_and_wipe(bool toolchange = false, bool reset_e = true);
|
||||
std::string unretract() { return m_writer.unretract(); }
|
||||
std::string set_extruder(unsigned int extruder_id, double print_z);
|
||||
bool line_distancer_is_required(const std::vector<unsigned int>& extruder_ids);
|
||||
@@ -419,6 +428,9 @@ private:
|
||||
// This needs to be populated during the layer processing!
|
||||
std::optional<Vec3d> m_current_layer_first_position;
|
||||
std::optional<unsigned> m_layer_change_extruder_id;
|
||||
bool m_layer_change_used_external_mp{false};
|
||||
const Layer* m_layer_change_layer{nullptr};
|
||||
std::optional<Vec2d> m_layer_change_origin;
|
||||
bool m_already_unretracted{false};
|
||||
|
||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||
|
||||
@@ -1171,7 +1171,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons
|
||||
{
|
||||
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
|
||||
// Otherwise perform the path planning in the coordinate system of the active object.
|
||||
bool use_external = m_use_external_mp || m_use_external_mp_once;
|
||||
bool use_external = m_use_external_mp || use_external_mp_once;
|
||||
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
|
||||
const Point start = *gcodegen.last_position + scaled_origin;
|
||||
const Point end = point + scaled_origin;
|
||||
|
||||
@@ -17,11 +17,10 @@ class AvoidCrossingPerimeters
|
||||
public:
|
||||
// Routing around the objects vs. inside a single object.
|
||||
void use_external_mp(bool use = true) { m_use_external_mp = use; };
|
||||
void use_external_mp_once() { m_use_external_mp_once = true; }
|
||||
bool used_external_mp_once() { return m_use_external_mp_once; }
|
||||
bool used_external_mp_once() { return use_external_mp_once; }
|
||||
void disable_once() { m_disabled_once = true; }
|
||||
bool disabled_once() const { return m_disabled_once; }
|
||||
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
|
||||
void reset_once_modifiers() { use_external_mp_once = false; m_disabled_once = false; }
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
@@ -50,10 +49,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// just for the next travel move
|
||||
bool use_external_mp_once { false };
|
||||
private:
|
||||
bool m_use_external_mp { false };
|
||||
// just for the next travel move
|
||||
bool m_use_external_mp_once { false };
|
||||
// this flag disables avoid_crossing_perimeters just for the next travel move
|
||||
// we enable it by default for the first travel move in print
|
||||
bool m_disabled_once { true };
|
||||
|
||||
@@ -3806,7 +3806,13 @@ void GCodeProcessor::post_process()
|
||||
struct LineData
|
||||
{
|
||||
std::string line;
|
||||
float time;
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> times{ 0.0f, 0.0f };
|
||||
};
|
||||
|
||||
enum ETimeMode
|
||||
{
|
||||
Normal = static_cast<int>(PrintEstimatedStatistics::ETimeMode::Normal),
|
||||
Stealth = static_cast<int>(PrintEstimatedStatistics::ETimeMode::Stealth)
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -3836,10 +3842,10 @@ void GCodeProcessor::post_process()
|
||||
#endif // NDEBUG
|
||||
|
||||
EWriteType m_write_type{ EWriteType::BySize };
|
||||
// Time machine containing g1 times cache
|
||||
TimeMachine& m_machine;
|
||||
// Time machines containing g1 times cache
|
||||
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines;
|
||||
// Current time
|
||||
float m_time{ 0.0f };
|
||||
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_times{ 0.0f, 0.0f };
|
||||
// Current size in bytes
|
||||
size_t m_size{ 0 };
|
||||
|
||||
@@ -3855,11 +3861,12 @@ void GCodeProcessor::post_process()
|
||||
|
||||
bgcode::binarize::Binarizer& m_binarizer;
|
||||
public:
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine)
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type,
|
||||
const std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines)
|
||||
#ifndef NDEBUG
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
|
||||
#else
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machines(machines) {}
|
||||
#endif // NDEBUG
|
||||
|
||||
// return: number of internal G1 lines (from G2/G3 splitting) processed
|
||||
@@ -3876,9 +3883,9 @@ void GCodeProcessor::post_process()
|
||||
else
|
||||
return ret;
|
||||
|
||||
auto init_it = m_machine.g1_times_cache.begin() + m_times_cache_id;
|
||||
auto init_it = m_machines[Normal].g1_times_cache.begin() + m_times_cache_id;
|
||||
auto it = init_it;
|
||||
while (it != m_machine.g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
while (it != m_machines[Normal].g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
}
|
||||
@@ -3888,7 +3895,7 @@ void GCodeProcessor::post_process()
|
||||
|
||||
// search for internal G1 lines
|
||||
if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) {
|
||||
while (it != m_machine.g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
while (it != m_machines[Normal].g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
++g1_lines_counter;
|
||||
@@ -3896,14 +3903,17 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
}
|
||||
|
||||
if (it != m_machine.g1_times_cache.end() && it->id == g1_lines_counter)
|
||||
m_time = it->elapsed_time;
|
||||
if (it != m_machines[Normal].g1_times_cache.end() && it->id == g1_lines_counter) {
|
||||
m_times[Normal] = it->elapsed_time;
|
||||
if (!m_machines[Stealth].g1_times_cache.empty())
|
||||
m_times[Stealth] = (m_machines[Stealth].g1_times_cache.begin() + std::distance(m_machines[Normal].g1_times_cache.begin(), it))->elapsed_time;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// add the given gcode line to the cache
|
||||
void append_line(const std::string& line) {
|
||||
m_lines.push_back({ line, m_time });
|
||||
m_lines.push_back({ line, m_times });
|
||||
#ifndef NDEBUG
|
||||
m_statistics.add_line(line.length());
|
||||
#endif // NDEBUG
|
||||
@@ -3914,7 +3924,8 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
|
||||
// Insert the gcode lines required by the command cmd by backtracing into the cache
|
||||
void insert_lines(const Backtrace& backtrace, const std::string& cmd, std::function<std::string(unsigned int, float, float)> line_inserter,
|
||||
void insert_lines(const Backtrace& backtrace, const std::string& cmd,
|
||||
std::function<std::string(unsigned int, const std::vector<float>&)> line_inserter,
|
||||
std::function<std::string(const std::string&)> line_replacer) {
|
||||
assert(!m_lines.empty());
|
||||
const float time_step = backtrace.time_step();
|
||||
@@ -3922,13 +3933,13 @@ void GCodeProcessor::post_process()
|
||||
float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time
|
||||
for (unsigned int i = 0; i < backtrace.steps; ++i) {
|
||||
const float backtrace_time_i = (i + 1) * time_step;
|
||||
const float time_threshold_i = m_time - backtrace_time_i;
|
||||
const float time_threshold_i = m_times[Normal] - backtrace_time_i;
|
||||
auto rev_it = m_lines.rbegin() + rev_it_dist;
|
||||
auto start_rev_it = rev_it;
|
||||
|
||||
std::string curr_cmd = GCodeReader::GCodeLine::extract_cmd(rev_it->line);
|
||||
// backtrace into the cache to find the place where to insert the line
|
||||
while (rev_it != m_lines.rend() && rev_it->time > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
while (rev_it != m_lines.rend() && rev_it->times[Normal] > time_threshold_i && curr_cmd != cmd && curr_cmd != "G28" && curr_cmd != "G29") {
|
||||
rev_it->line = line_replacer(rev_it->line);
|
||||
++rev_it;
|
||||
if (rev_it != m_lines.rend())
|
||||
@@ -3940,11 +3951,15 @@ void GCodeProcessor::post_process()
|
||||
break;
|
||||
|
||||
// insert the line for the current step
|
||||
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->time != last_time_insertion) {
|
||||
last_time_insertion = rev_it->time;
|
||||
const std::string out_line = line_inserter(i + 1, last_time_insertion, m_time - last_time_insertion);
|
||||
if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) {
|
||||
last_time_insertion = rev_it->times[Normal];
|
||||
std::vector<float> time_diffs;
|
||||
time_diffs.push_back(m_times[Normal] - last_time_insertion);
|
||||
if (!m_machines[Stealth].g1_times_cache.empty())
|
||||
time_diffs.push_back(m_times[Stealth] - rev_it->times[Stealth]);
|
||||
const std::string out_line = line_inserter(i + 1, time_diffs);
|
||||
rev_it_dist = std::distance(m_lines.rbegin(), rev_it) + 1;
|
||||
m_lines.insert(rev_it.base(), { out_line, rev_it->time });
|
||||
m_lines.insert(rev_it.base(), { out_line, rev_it->times });
|
||||
#ifndef NDEBUG
|
||||
m_statistics.add_line(out_line.length());
|
||||
#endif // NDEBUG
|
||||
@@ -3970,7 +3985,7 @@ void GCodeProcessor::post_process()
|
||||
std::string out_string;
|
||||
if (!m_lines.empty()) {
|
||||
if (m_write_type == EWriteType::ByTime) {
|
||||
while (m_lines.front().time < m_time - backtrace_time) {
|
||||
while (m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) {
|
||||
const LineData& data = m_lines.front();
|
||||
out_string += data.line;
|
||||
m_size -= data.line.length();
|
||||
@@ -4055,7 +4070,8 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
};
|
||||
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize,
|
||||
m_time_processor.machines);
|
||||
|
||||
// replace placeholder lines with the proper final value
|
||||
// gcode_line is in/out parameter, to reduce expensive memory allocation
|
||||
@@ -4259,9 +4275,14 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
export_lines.insert_lines(backtrace, cmd,
|
||||
// line inserter
|
||||
[tool_number, this](unsigned int id, float time, float time_diff) {
|
||||
int temperature = int( m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
|
||||
const std::string out = "M104 T" + std::to_string(tool_number) + " P" + std::to_string(int(std::round(time_diff))) + " S" + std::to_string(temperature) + "\n";
|
||||
[tool_number, this](unsigned int id, const std::vector<float>& time_diffs) {
|
||||
const int temperature = int(m_layer_id != 1 ? m_extruder_temps_config[tool_number] : m_extruder_temps_first_layer_config[tool_number]);
|
||||
std::string out = "M104.1 T" + std::to_string(tool_number);
|
||||
if (time_diffs.size() > 0)
|
||||
out += " P" + std::to_string(int(std::round(time_diffs[0])));
|
||||
if (time_diffs.size() > 1)
|
||||
out += " Q" + std::to_string(int(std::round(time_diffs[1])));
|
||||
out += " S" + std::to_string(temperature) + "\n";
|
||||
return out;
|
||||
},
|
||||
// line replacer
|
||||
|
||||
@@ -198,6 +198,8 @@ public:
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec2d quantize(const Vec2f &pt)
|
||||
{ return { quantize(double(pt.x()), XYZF_EXPORT_DIGITS), quantize(double(pt.y()), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "LabelObjects.hpp"
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
|
||||
#include "boost/algorithm/string/replace.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
@@ -39,10 +41,10 @@ Polygon instance_outline(const PrintInstance* pi)
|
||||
}; // anonymous namespace
|
||||
|
||||
|
||||
void LabelObjects::init(const Print& print)
|
||||
void LabelObjects::init(const SpanOfConstPtrs<PrintObject>& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor)
|
||||
{
|
||||
m_label_objects_style = print.config().gcode_label_objects;
|
||||
m_flavor = print.config().gcode_flavor;
|
||||
m_label_objects_style = label_object_style;
|
||||
m_flavor = gcode_flavor;
|
||||
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return;
|
||||
@@ -51,7 +53,7 @@ void LabelObjects::init(const Print& print)
|
||||
|
||||
// Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which
|
||||
// belong to the same ModelObject.
|
||||
for (const PrintObject* po : print.objects())
|
||||
for (const PrintObject* po : objects)
|
||||
for (const PrintInstance& pi : po->instances())
|
||||
model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi);
|
||||
|
||||
@@ -87,13 +89,69 @@ void LabelObjects::init(const Print& print)
|
||||
}
|
||||
}
|
||||
|
||||
m_label_data.emplace(pi, LabelData{name, unique_id});
|
||||
// Now calculate the polygon and center for Cancel Object (this is not always used).
|
||||
Polygon outline = instance_outline(pi);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
char buffer[64];
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
std::string center_str(buffer);
|
||||
std::string polygon_str = std::string("[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
polygon_str += buffer;
|
||||
}
|
||||
polygon_str.pop_back();
|
||||
polygon_str += "]";
|
||||
|
||||
m_label_data.emplace_back(LabelData{pi, name, center_str, polygon_str, unique_id});
|
||||
++unique_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LabelObjects::update(const PrintInstance *instance) {
|
||||
if (this->last_operation_instance == instance) {
|
||||
return false;
|
||||
}
|
||||
this->last_operation_instance = instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_start_instance(GCodeWriter& writer) {
|
||||
if (current_instance == nullptr && last_operation_instance != nullptr) {
|
||||
current_instance = last_operation_instance;
|
||||
|
||||
std::string result{this->start_object(*current_instance, LabelObjects::IncludeName::No)};
|
||||
result += writer.reset_e(true);
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_stop_instance() {
|
||||
if (current_instance != nullptr) {
|
||||
const std::string result{this->stop_object(*current_instance)};
|
||||
current_instance = nullptr;
|
||||
return result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string LabelObjects::maybe_change_instance(GCodeWriter& writer) {
|
||||
if (last_operation_instance != current_instance) {
|
||||
const std::string stop_instance_gcode{this->maybe_stop_instance()};
|
||||
// Be carefull with refactoring: this->maybe_stop_instance() + this->maybe_start_instance()
|
||||
// may not be evaluated in order. The order is indeed undefined!
|
||||
return stop_instance_gcode + this->maybe_start_instance(writer);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool LabelObjects::has_active_instance() {
|
||||
return this->current_instance != nullptr;
|
||||
}
|
||||
|
||||
std::string LabelObjects::all_objects_header() const
|
||||
{
|
||||
@@ -102,38 +160,34 @@ std::string LabelObjects::all_objects_header() const
|
||||
|
||||
std::string out;
|
||||
|
||||
// Let's sort the values according to unique_id so they are in the same order in which they were added.
|
||||
std::vector<std::pair<const PrintInstance*, LabelData>> label_data_sorted;
|
||||
for (const auto& pi_and_label : m_label_data)
|
||||
label_data_sorted.emplace_back(pi_and_label);
|
||||
std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; });
|
||||
|
||||
out += "\n";
|
||||
for (const auto& [print_instance, label] : label_data_sorted) {
|
||||
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) {
|
||||
char buffer[64];
|
||||
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "'";
|
||||
Polygon outline = instance_outline(print_instance);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
out += buffer + std::string(" POLYGON=[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
out += buffer;
|
||||
}
|
||||
out.pop_back();
|
||||
out += "]\n";
|
||||
} else {
|
||||
out += start_object(*print_instance, IncludeName::Yes);
|
||||
out += stop_object(*print_instance);
|
||||
for (const LabelData& label : m_label_data) {
|
||||
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper)
|
||||
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "' CENTER=" + label.center + " POLYGON=" + label.polygon + "\n";
|
||||
else {
|
||||
out += start_object(*label.pi, IncludeName::Yes);
|
||||
out += stop_object(*label.pi);
|
||||
}
|
||||
}
|
||||
out += "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string LabelObjects::all_objects_header_singleline_json() const
|
||||
{
|
||||
std::string out;
|
||||
out = "{\"objects\":[";
|
||||
for (size_t i=0; i<m_label_data.size(); ++i) {
|
||||
const LabelData& label = m_label_data[i];
|
||||
out += std::string("{\"name\":\"") + label.name + "\",";
|
||||
out += "\"polygon\":" + label.polygon + "}";
|
||||
if (i != m_label_data.size() - 1)
|
||||
out += ",";
|
||||
}
|
||||
out += "]}";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const
|
||||
@@ -141,7 +195,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
@@ -170,7 +224,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define slic3r_GCode_LabelObjects_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -11,30 +13,49 @@ enum class LabelObjectsStyle;
|
||||
struct PrintInstance;
|
||||
class Print;
|
||||
|
||||
class GCodeWriter;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
|
||||
class LabelObjects {
|
||||
class LabelObjects
|
||||
{
|
||||
public:
|
||||
void init(const SpanOfConstPtrs<PrintObject>& objects, LabelObjectsStyle label_object_style, GCodeFlavor gcode_flavor);
|
||||
std::string all_objects_header() const;
|
||||
std::string all_objects_header_singleline_json() const;
|
||||
|
||||
bool update(const PrintInstance *instance);
|
||||
|
||||
std::string maybe_start_instance(GCodeWriter& writer);
|
||||
|
||||
std::string maybe_stop_instance();
|
||||
|
||||
std::string maybe_change_instance(GCodeWriter& writer);
|
||||
|
||||
bool has_active_instance();
|
||||
|
||||
private:
|
||||
struct LabelData
|
||||
{
|
||||
const PrintInstance* pi;
|
||||
std::string name;
|
||||
std::string center;
|
||||
std::string polygon;
|
||||
int unique_id;
|
||||
};
|
||||
enum class IncludeName {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
void init(const Print& print);
|
||||
std::string all_objects_header() const;
|
||||
std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const;
|
||||
std::string stop_object(const PrintInstance& print_instance) const;
|
||||
|
||||
private:
|
||||
struct LabelData {
|
||||
std::string name;
|
||||
int unique_id;
|
||||
};
|
||||
const PrintInstance* current_instance{nullptr};
|
||||
const PrintInstance* last_operation_instance{nullptr};
|
||||
|
||||
LabelObjectsStyle m_label_objects_style;
|
||||
GCodeFlavor m_flavor;
|
||||
std::unordered_map<const PrintInstance*, LabelData> m_label_data;
|
||||
std::vector<LabelData> m_label_data;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <memory.h>
|
||||
#include <cstring>
|
||||
#include <cfloat>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
@@ -27,6 +28,11 @@ static constexpr float max_segment_length = 5.f;
|
||||
// affect how distant will be propagated a flow rate adjustment.
|
||||
static constexpr int max_look_back_limit = 128;
|
||||
|
||||
// Max non-extruding XY distance (travel move) in mm between two continuous extrusions where we pretend
|
||||
// it's all one continuous extrusion line. Above this distance, we assume extruder pressure hits 0
|
||||
// This exists because often there are tiny travel moves between stuff like infill.
|
||||
// Lines where some extruder pressure will remain (so we should equalize between these small travels).
|
||||
static constexpr double max_ignored_gap_between_extruding_segments = 3.;
|
||||
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value)
|
||||
{
|
||||
// Preallocate some data, so that output_buffer.data() will return an empty string.
|
||||
@@ -59,8 +65,8 @@ PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_
|
||||
extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive;
|
||||
}
|
||||
|
||||
// Don't regulate the pressure before and after gap-fill and ironing.
|
||||
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::GapFill, GCodeExtrusionRole::Ironing}) {
|
||||
// Don't regulate the pressure before and after ironing.
|
||||
for (const GCodeExtrusionRole er : {GCodeExtrusionRole::Ironing}) {
|
||||
m_max_volumetric_extrusion_rate_slopes[size_t(er)].negative = 0;
|
||||
m_max_volumetric_extrusion_rate_slopes[size_t(er)].positive = 0;
|
||||
}
|
||||
@@ -97,6 +103,72 @@ void PressureEqualizer::process_layer(const std::string &gcode)
|
||||
}
|
||||
assert(!this->opened_extrude_set_speed_block);
|
||||
}
|
||||
// At this point, we have an entire layer of gcode lines loaded into m_gcode_lines.
|
||||
// Now, we will split the mix of travels and extrusions into segments of continuous extrusions and process them.
|
||||
// We skip over large travels, and pretend that small ones are part of a continuous extrusion segment.
|
||||
for (auto current_extrusion_end_it = m_gcode_lines.cbegin(); current_extrusion_end_it != m_gcode_lines.cend();) {
|
||||
// Find beginning of next extrusion segment from current position.
|
||||
const auto current_extrusion_begin_it = std::find_if(current_extrusion_end_it, m_gcode_lines.cend(), [](const GCodeLine &line) {
|
||||
return line.extruding();
|
||||
});
|
||||
|
||||
// We start with extrusion length of zero.
|
||||
current_extrusion_end_it = current_extrusion_begin_it;
|
||||
|
||||
// Inner loop extends the extrusion segment over small travel moves.
|
||||
while (current_extrusion_end_it != m_gcode_lines.cend()) {
|
||||
// Find the end of the current extrusion segment.
|
||||
const auto travel_begin_it = std::find_if(std::next(current_extrusion_end_it), m_gcode_lines.cend(), [](const GCodeLine &line) {
|
||||
return !line.extruding();
|
||||
});
|
||||
|
||||
current_extrusion_end_it = std::prev(travel_begin_it);
|
||||
|
||||
const auto next_extrusion_segment_it = advance_segment_beyond_small_gap(current_extrusion_end_it);
|
||||
if (std::distance(current_extrusion_end_it, next_extrusion_segment_it) > 0) {
|
||||
// Extend the continuous line over the small gap.
|
||||
current_extrusion_end_it = next_extrusion_segment_it;
|
||||
continue; // Keep going, loop again to find the new end of extrusion segment.
|
||||
} else {
|
||||
break; // Gap to next extrude is too big, stop looking forward. We've found the end of this segment.
|
||||
}
|
||||
}
|
||||
|
||||
// Now, run the pressure equalizer across the segment like a streamroller.
|
||||
// It operates on a sliding window that moves forward across gcode line by line.
|
||||
const std::ptrdiff_t current_extrusion_begin_idx = std::distance(m_gcode_lines.cbegin(), current_extrusion_begin_it);
|
||||
for (auto current_line_it = current_extrusion_begin_it; current_line_it != current_extrusion_end_it; ++current_line_it) {
|
||||
const std::ptrdiff_t current_line_idx = std::distance(m_gcode_lines.cbegin(), current_line_it);
|
||||
|
||||
// Feed pressure equalizer past lines, going back to max_look_back_limit (or start of segment).
|
||||
const size_t start_idx = size_t(std::max<std::ptrdiff_t>(current_extrusion_begin_idx, current_line_idx - max_look_back_limit));
|
||||
adjust_volumetric_rate(start_idx, size_t(current_line_idx));
|
||||
}
|
||||
|
||||
// Current extrusion is all done processing so advance beyond it for the next loop.
|
||||
if (current_extrusion_end_it != m_gcode_lines.cend())
|
||||
++current_extrusion_end_it;
|
||||
}
|
||||
}
|
||||
|
||||
PressureEqualizer::GCodeLinesConstIt PressureEqualizer::advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const {
|
||||
// This should only be run on the last extruding line before a gap.
|
||||
assert(last_extruding_line_it != m_gcode_lines.cend() && last_extruding_line_it->extruding());
|
||||
double travel_distance = 0.;
|
||||
// Start at the beginning of a gap, advance till extrusion found or gap too big.
|
||||
for (auto current_line_it = std::next(last_extruding_line_it); current_line_it != m_gcode_lines.cend(); ++current_line_it) {
|
||||
// Started extruding again! Return segment extension.
|
||||
if (current_line_it->extruding())
|
||||
return current_line_it;
|
||||
|
||||
travel_distance += current_line_it->dist_xy();
|
||||
// Gap too big, don't extend segment.
|
||||
if (travel_distance > max_ignored_gap_between_extruding_segments)
|
||||
return last_extruding_line_it;
|
||||
}
|
||||
|
||||
// Looped until the end of the layer and couldn't extend extrusion.
|
||||
return last_extruding_line_it;
|
||||
}
|
||||
|
||||
LayerResult PressureEqualizer::process_layer(LayerResult &&input)
|
||||
@@ -391,7 +463,6 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
|
||||
buf.extruder_id = m_current_extruder;
|
||||
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
|
||||
|
||||
adjust_volumetric_rate();
|
||||
#ifdef PRESSURE_EQUALIZER_DEBUG
|
||||
++line_idx;
|
||||
#endif
|
||||
@@ -506,16 +577,14 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx)
|
||||
}
|
||||
}
|
||||
|
||||
void PressureEqualizer::adjust_volumetric_rate()
|
||||
void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, const size_t last_line_idx)
|
||||
{
|
||||
if (m_gcode_lines.size() < 2)
|
||||
// Don't bother adjusting volumetric rate if there's no gcode to adjust.
|
||||
if (last_line_idx <= first_line_idx || last_line_idx - first_line_idx < 2)
|
||||
return;
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
size_t fist_line_idx = size_t(std::max<int>(0, int(m_gcode_lines.size()) - max_look_back_limit));
|
||||
const size_t last_line_idx = m_gcode_lines.size() - 1;
|
||||
size_t line_idx = last_line_idx;
|
||||
if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding())
|
||||
if (line_idx == first_line_idx || !m_gcode_lines[line_idx].extruding())
|
||||
// Nothing to do, the last move is not extruding.
|
||||
return;
|
||||
|
||||
@@ -523,13 +592,13 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max());
|
||||
feedrate_per_extrusion_role[int(m_gcode_lines[line_idx].extrusion_role)] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
|
||||
|
||||
while (line_idx != fist_line_idx) {
|
||||
while (line_idx != first_line_idx) {
|
||||
size_t idx_prev = line_idx - 1;
|
||||
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev);
|
||||
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != first_line_idx; --idx_prev);
|
||||
if (!m_gcode_lines[idx_prev].extruding())
|
||||
break;
|
||||
// Don't decelerate before ironing and gap-fill.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) {
|
||||
// Don't decelerate before ironing.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
line_idx = idx_prev;
|
||||
continue;
|
||||
}
|
||||
@@ -549,7 +618,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
// Limit by the succeeding volumetric flow rate.
|
||||
rate_end = rate_succ;
|
||||
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
// Don't alter the flow rate for these extrusion types.
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
rate_end = line.volumetric_extrusion_rate_end;
|
||||
} else if (line.volumetric_extrusion_rate_end > rate_end) {
|
||||
line.volumetric_extrusion_rate_end = rate_end;
|
||||
@@ -571,9 +641,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
line.modified = true;
|
||||
}
|
||||
}
|
||||
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
|
||||
// Don't store feed rate for ironing and gap-fill.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
|
||||
// Don't store feed rate for ironing.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing)
|
||||
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start;
|
||||
}
|
||||
}
|
||||
@@ -587,8 +656,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next);
|
||||
if (!m_gcode_lines[idx_next].extruding())
|
||||
break;
|
||||
// Don't accelerate after ironing and gap-fill.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing || m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::GapFill) {
|
||||
// Don't accelerate after ironing.
|
||||
if (m_gcode_lines[line_idx].extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
line_idx = idx_next;
|
||||
continue;
|
||||
}
|
||||
@@ -603,7 +672,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
continue; // The positive rate is unlimited or the rate for GCodeExtrusionRole iRole is unlimited.
|
||||
|
||||
float rate_start = feedrate_per_extrusion_role[iRole];
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::ExternalPerimeter || line.extrusion_role == GCodeExtrusionRole::GapFill || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
// Don't alter the flow rate for these extrusion types.
|
||||
if (!line.adjustable_flow || line.extrusion_role == GCodeExtrusionRole::BridgeInfill || line.extrusion_role == GCodeExtrusionRole::Ironing) {
|
||||
rate_start = line.volumetric_extrusion_rate_start;
|
||||
} else if (iRole == size_t(line.extrusion_role) && rate_prec < rate_start)
|
||||
rate_start = rate_prec;
|
||||
@@ -627,9 +697,8 @@ void PressureEqualizer::adjust_volumetric_rate()
|
||||
line.modified = true;
|
||||
}
|
||||
}
|
||||
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
|
||||
// Don't store feed rate for ironing and gap-fill.
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing && line.extrusion_role != GCodeExtrusionRole::GapFill)
|
||||
// Don't store feed rate for ironing
|
||||
if (line.extrusion_role != GCodeExtrusionRole::Ironing)
|
||||
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,8 @@ private:
|
||||
bool extrude_end_tag = false;
|
||||
};
|
||||
|
||||
using GCodeLines = std::vector<GCodeLine>;
|
||||
using GCodeLinesConstIt = GCodeLines::const_iterator;
|
||||
// Output buffer will only grow. It will not be reallocated over and over.
|
||||
std::vector<char> output_buffer;
|
||||
size_t output_buffer_length;
|
||||
@@ -182,9 +184,10 @@ private:
|
||||
bool process_line(const char *line, const char *line_end, GCodeLine &buf);
|
||||
void output_gcode_line(size_t line_idx);
|
||||
|
||||
GCodeLinesConstIt advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const;
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
void adjust_volumetric_rate();
|
||||
void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx);
|
||||
|
||||
// Push the text to the end of the output_buffer.
|
||||
inline void push_to_output(GCodeG1Formatter &formatter);
|
||||
|
||||
@@ -133,6 +133,11 @@ double clip_end(SmoothPath &path, double distance, double min_point_distance_thr
|
||||
return distance;
|
||||
}
|
||||
|
||||
void reverse(SmoothPath &path) {
|
||||
std::reverse(path.begin(), path.end());
|
||||
for (SmoothPathElement &path_element : path)
|
||||
Geometry::ArcWelder::reverse(path_element.path);
|
||||
}
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
|
||||
@@ -33,6 +33,7 @@ std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &pa
|
||||
// rather discard such a degenerate segment.
|
||||
double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold);
|
||||
|
||||
void reverse(SmoothPath &path);
|
||||
class SmoothPathCache
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#include "SpiralVase.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
static AABBTreeLines::LinesDistancer<Linef> get_layer_distancer(const std::vector<Vec2f> &layer_points)
|
||||
{
|
||||
Linesf lines;
|
||||
for (size_t idx = 1; idx < layer_points.size(); ++idx)
|
||||
lines.emplace_back(layer_points[idx - 1].cast<double>(), layer_points[idx].cast<double>());
|
||||
|
||||
return AABBTreeLines::LinesDistancer{std::move(lines)};
|
||||
}
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
|
||||
{
|
||||
/* This post-processor relies on several assumptions:
|
||||
- all layers are processed through it, including those that are not supposed
|
||||
@@ -22,8 +33,8 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
}
|
||||
|
||||
// Get total XY length for this layer by summing all extrusion moves.
|
||||
float total_layer_length = 0;
|
||||
float layer_height = 0;
|
||||
float total_layer_length = 0.f;
|
||||
float layer_height = 0.f;
|
||||
float z = 0.f;
|
||||
|
||||
{
|
||||
@@ -49,15 +60,21 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
// Remove layer height from initial Z.
|
||||
z -= layer_height;
|
||||
|
||||
std::string new_gcode;
|
||||
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
|
||||
// FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
const bool transition_out = last_layer && m_config.use_relative_e_distances.value;
|
||||
const bool smooth_spiral = m_smooth_spiral && m_config.use_relative_e_distances.value;
|
||||
|
||||
const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer);
|
||||
Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back();
|
||||
float len = 0.f;
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
||||
std::string new_gcode, transition_gcode;
|
||||
std::vector<Vec2f> current_layer;
|
||||
m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral, max_xy_smoothing = m_max_xy_smoothing,
|
||||
&len, &last_point, &new_gcode, &transition_gcode, ¤t_layer, &previous_layer_distancer]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
@@ -66,16 +83,52 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
line.set(reader, Z, z);
|
||||
new_gcode += line.raw() + '\n';
|
||||
return;
|
||||
} else if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position.
|
||||
if (const float dist_XY = line.dist_XY(reader); dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract
|
||||
len += dist_XY;
|
||||
const float factor = len / total_layer_length;
|
||||
if (transition_in)
|
||||
// Transition layer, interpolate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.e() * factor, 5);
|
||||
else if (transition_out) {
|
||||
// We want the last layer to ramp down extrusion, but without changing z height!
|
||||
// So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E
|
||||
// We add this new layer at the very end
|
||||
GCodeReader::GCodeLine transition_line(line);
|
||||
transition_line.set(reader, E, line.e() * (1.f - factor), 5);
|
||||
transition_gcode += transition_line.raw() + '\n';
|
||||
}
|
||||
|
||||
// This line is the core of Spiral Vase mode, ramp up the Z smoothly
|
||||
line.set(reader, Z, z + factor * layer_height);
|
||||
|
||||
bool emit_gcode_line = true;
|
||||
if (smooth_spiral) {
|
||||
// Now we also need to try to interpolate X and Y
|
||||
Vec2f p(line.x(), line.y()); // Get current x/y coordinates
|
||||
current_layer.emplace_back(p); // Store that point for later use on the next layer
|
||||
|
||||
auto [nearest_distance, idx, nearest_pt] = previous_layer_distancer.distance_from_lines_extra<false>(p.cast<double>());
|
||||
if (nearest_distance < max_xy_smoothing) {
|
||||
// Interpolate between the point on this layer and the point on the previous layer
|
||||
Vec2f target = nearest_pt.cast<float>() * (1.f - factor) + p * factor;
|
||||
|
||||
// We will emit a new g-code line only when XYZ positions differ from the previous g-code line.
|
||||
emit_gcode_line = GCodeFormatter::quantize(last_point) != GCodeFormatter::quantize(target);
|
||||
|
||||
line.set(reader, X, target.x());
|
||||
line.set(reader, Y, target.y());
|
||||
// We need to figure out the distance of this new line!
|
||||
float modified_dist_XY = (last_point - target).norm();
|
||||
// Scale the extrusion amount according to change in length
|
||||
line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5);
|
||||
last_point = target;
|
||||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
// horizontal move
|
||||
if (line.extruding(reader)) {
|
||||
len += dist_XY;
|
||||
line.set(reader, Z, z + len * layer_height_factor);
|
||||
if (transition && line.has(E))
|
||||
// Transition layer, modulate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.value(E) * len / total_layer_length);
|
||||
last_point = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (emit_gcode_line)
|
||||
new_gcode += line.raw() + '\n';
|
||||
}
|
||||
return;
|
||||
@@ -84,14 +137,18 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
cause a visible seam when loops are not aligned in XY; by skipping
|
||||
it we blend the first loop move in the XY plane (although the smoothness
|
||||
of such blend depend on how long the first segment is; maybe we should
|
||||
enforce some minimum length?). */
|
||||
enforce some minimum length?).
|
||||
When smooth_spiral is enabled, we're gonna end up exactly where the next layer should
|
||||
start anyway, so we don't need the travel move */
|
||||
}
|
||||
}
|
||||
}
|
||||
new_gcode += line.raw() + '\n';
|
||||
if (transition_out)
|
||||
transition_gcode += line.raw() + '\n';
|
||||
});
|
||||
|
||||
return new_gcode;
|
||||
m_previous_layer = std::move(current_layer);
|
||||
return new_gcode + transition_gcode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,28 +6,38 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SpiralVase {
|
||||
class SpiralVase
|
||||
{
|
||||
public:
|
||||
SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
SpiralVase() = delete;
|
||||
|
||||
explicit SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
{
|
||||
m_reader.z() = (float)m_config.z_offset;
|
||||
m_reader.apply_config(m_config);
|
||||
const double max_nozzle_diameter = *std::max_element(config.nozzle_diameter.values.begin(), config.nozzle_diameter.values.end());
|
||||
m_max_xy_smoothing = float(2. * max_nozzle_diameter);
|
||||
};
|
||||
|
||||
void enable(bool en) {
|
||||
m_transition_layer = en && ! m_enabled;
|
||||
m_enabled = en;
|
||||
void enable(bool enable)
|
||||
{
|
||||
m_transition_layer = enable && !m_enabled;
|
||||
m_enabled = enable;
|
||||
}
|
||||
|
||||
std::string process_layer(const std::string &gcode);
|
||||
std::string process_layer(const std::string &gcode, bool last_layer);
|
||||
|
||||
private:
|
||||
const PrintConfig &m_config;
|
||||
GCodeReader m_reader;
|
||||
float m_max_xy_smoothing = 0.f;
|
||||
|
||||
bool m_enabled = false;
|
||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||
bool m_transition_layer = false;
|
||||
// Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes
|
||||
bool m_smooth_spiral = true;
|
||||
std::vector<Vec2f> m_previous_layer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,27 +32,11 @@ void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extr
|
||||
this->enable(wipe_xy);
|
||||
}
|
||||
|
||||
void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
{
|
||||
void Wipe::set_path(SmoothPath &&path) {
|
||||
this->reset_path();
|
||||
|
||||
if (this->enabled() && ! path.empty()) {
|
||||
if (coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); reversed) {
|
||||
m_path = std::move(path.back().path);
|
||||
Geometry::ArcWelder::reverse(m_path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.rbegin()); len < wipe_len_max_scaled && it != path.rend(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.back().point);
|
||||
if (m_path.back().point != it->path.back().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
|
||||
}
|
||||
} else {
|
||||
const coord_t wipe_len_max_scaled = scaled(m_wipe_len_max);
|
||||
m_path = std::move(path.front().path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++ it) {
|
||||
@@ -67,7 +51,6 @@ void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void set_path(SmoothPath &&path);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
static float volume_to_length(float volume, float line_width, float layer_height)
|
||||
{
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
static float length_to_volume(float length, float line_width, float layer_height)
|
||||
{
|
||||
return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)));
|
||||
}
|
||||
class WipeTowerWriter
|
||||
{
|
||||
public:
|
||||
@@ -102,6 +112,10 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
WipeTowerWriter& switch_filament_monitoring(bool enable) {
|
||||
m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n";
|
||||
return *this;
|
||||
}
|
||||
// Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
|
||||
// filament loading and cooling moves from normal extrusion moves. Therefore the writer
|
||||
// is asked to suppres output of some lines, which look like extrusions.
|
||||
@@ -284,6 +298,24 @@ public:
|
||||
return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false);
|
||||
}
|
||||
|
||||
// Loads filament while also moving towards given point in x-axis. Unlike the previous function, this one respects
|
||||
// both the loading_speed and x_speed. Can shorten the move.
|
||||
WipeTowerWriter& load_move_x_advanced_there_and_back(float farthest_x, float e_dist, float e_speed, float x_speed)
|
||||
{
|
||||
float old_x = x();
|
||||
float time = std::abs(e_dist / e_speed); // time that the whole move must take
|
||||
float x_max_dist = std::abs(farthest_x - x()); // max x-distance that we can travel
|
||||
float x_dist = x_speed * time; // totel x-distance to travel during the move
|
||||
int n = int(x_dist / (2*x_max_dist) + 1.f); // how many there and back moves should we do
|
||||
float r = 2*n*x_max_dist / x_dist; // actual/required dist if the move is not shortened
|
||||
|
||||
float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_max_dist / r;
|
||||
for (int i=0; i<n; ++i) {
|
||||
extrude_explicit(end_point, y(), e_dist/(2.f*n), x_speed * 60.f, false, false);
|
||||
extrude_explicit(old_x, y(), e_dist/(2.f*n), x_speed * 60.f, false, false);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
// Elevate the extruder head above the current print_z position.
|
||||
WipeTowerWriter& z_hop(float hop, float f = 0.f)
|
||||
{
|
||||
@@ -326,6 +358,7 @@ public:
|
||||
// Set extruder temperature, don't wait by default.
|
||||
WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false)
|
||||
{
|
||||
m_gcode += "G4 S0\n"; // to flush planner queue
|
||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
||||
return *this;
|
||||
}
|
||||
@@ -523,7 +556,9 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)),
|
||||
m_wipe_tower_brim_width(float(config.wipe_tower_brim_width)),
|
||||
m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)),
|
||||
m_extra_spacing(float(config.wipe_tower_extra_spacing/100.)),
|
||||
m_extra_flow(float(config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing/100. * config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_ramming(float(config.wipe_tower_extra_spacing/100.)),
|
||||
m_y_shift(0.f),
|
||||
m_z_pos(0.f),
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
@@ -560,6 +595,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_set_extruder_trimpot = config.high_current_on_filament_swap;
|
||||
}
|
||||
|
||||
m_is_mk4mmu3 = boost::icontains(config.printer_notes.value, "PRINTER_MODEL_MK4") && boost::icontains(config.printer_notes.value, "MMU");
|
||||
// Calculate where the priming lines should be - very naive test not detecting parallelograms etc.
|
||||
const std::vector<Vec2d>& bed_points = config.bed_shape.values;
|
||||
BoundingBoxf bb(bed_points);
|
||||
@@ -594,6 +630,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != size_t(config.wipe_tower_extruder - 1));
|
||||
m_filpar[idx].temperature = config.temperature.get_at(idx);
|
||||
m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx);
|
||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||
|
||||
// If this is a single extruder MM printer, we will use all the SE-specific config values.
|
||||
// Otherwise, the defaults will be used to turn off the SE stuff.
|
||||
@@ -606,6 +643,8 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx);
|
||||
m_filpar[idx].cooling_initial_speed = float(config.filament_cooling_initial_speed.get_at(idx));
|
||||
m_filpar[idx].cooling_final_speed = float(config.filament_cooling_final_speed.get_at(idx));
|
||||
m_filpar[idx].filament_stamping_loading_speed = float(config.filament_stamping_loading_speed.get_at(idx));
|
||||
m_filpar[idx].filament_stamping_distance = float(config.filament_stamping_distance.get_at(idx));
|
||||
}
|
||||
|
||||
m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point
|
||||
@@ -719,7 +758,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f);
|
||||
box_coordinates box = cleaning_box;
|
||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
cleaning_box.translate(prime_section_width, 0.f);
|
||||
writer.travel(cleaning_box.ld, 7200);
|
||||
}
|
||||
@@ -766,7 +805,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
for (const auto &b : m_layer_info->tool_changes)
|
||||
if ( b.new_tool == tool ) {
|
||||
wipe_volume = b.wipe_volume;
|
||||
wipe_area = b.required_depth * m_layer_info->extra_spacing;
|
||||
wipe_area = b.required_depth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -807,14 +846,15 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
if (tool != (unsigned int)-1){ // This is not the last change.
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||
is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
|
||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||
(is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature));
|
||||
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
|
||||
toolchange_Load(writer, cleaning_box);
|
||||
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
++ m_num_tool_changes;
|
||||
} else
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature, m_filpar[m_current_tool].temperature);
|
||||
|
||||
m_depth_traversed += wipe_area;
|
||||
|
||||
@@ -841,13 +881,14 @@ void WipeTower::toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int old_temperature,
|
||||
const int new_temperature)
|
||||
{
|
||||
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
|
||||
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
|
||||
|
||||
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
|
||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
|
||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm
|
||||
|
||||
const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
|
||||
|
||||
@@ -860,10 +901,14 @@ void WipeTower::toolchange_Unload(
|
||||
float e_done = 0; // measures E move done from each segment
|
||||
|
||||
const bool do_ramming = m_semm || m_filpar[m_current_tool].multitool_ramming;
|
||||
const bool cold_ramming = m_is_mk4mmu3;
|
||||
|
||||
if (do_ramming) {
|
||||
writer.travel(ramming_start_pos); // move to starting position
|
||||
if (! m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
if (cold_ramming)
|
||||
writer.set_extruder_temp(old_temperature - 20);
|
||||
}
|
||||
else
|
||||
writer.set_position(ramming_start_pos);
|
||||
@@ -884,7 +929,7 @@ void WipeTower::toolchange_Unload(
|
||||
if (tch.old_tool == m_current_tool) {
|
||||
sum_of_depths += tch.ramming_depth;
|
||||
float ramming_end_y = sum_of_depths;
|
||||
ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
ramming_end_y -= (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f; // center of final ramming line
|
||||
|
||||
if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) ||
|
||||
(m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) )
|
||||
@@ -898,6 +943,10 @@ void WipeTower::toolchange_Unload(
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_mk4mmu3) {
|
||||
writer.switch_filament_monitoring(false);
|
||||
writer.wait(1.5f);
|
||||
}
|
||||
|
||||
// now the ramming itself:
|
||||
while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size())
|
||||
@@ -938,31 +987,66 @@ void WipeTower::toolchange_Unload(
|
||||
.retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
|
||||
.resume_preview();
|
||||
}
|
||||
const int& number_of_cooling_moves = m_filpar[m_current_tool].cooling_moves;
|
||||
const bool cooling_will_happen = m_semm && number_of_cooling_moves > 0;
|
||||
bool change_temp_later = false;
|
||||
// Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should
|
||||
// be already set and there is no need to change anything. Also, the temperature could be changed
|
||||
// for wrong extruder.
|
||||
if (m_semm) {
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer()) ) { // Set the extruder temperature, but don't wait.
|
||||
if (new_temperature != 0 && (new_temperature != m_old_temperature || is_first_layer() || cold_ramming) ) { // Set the extruder temperature, but don't wait.
|
||||
// If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
|
||||
// However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
|
||||
if (cold_ramming && cooling_will_happen)
|
||||
change_temp_later = true;
|
||||
else
|
||||
writer.set_extruder_temp(new_temperature, false);
|
||||
m_old_temperature = new_temperature;
|
||||
}
|
||||
}
|
||||
|
||||
// Cooling:
|
||||
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
|
||||
if (m_semm && number_of_moves > 0) {
|
||||
if (cooling_will_happen) {
|
||||
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
|
||||
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
|
||||
|
||||
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f);
|
||||
float speed_inc = (final_speed - initial_speed) / (2.f * number_of_cooling_moves - 1.f);
|
||||
|
||||
if (m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
|
||||
writer.suppress_preview()
|
||||
.travel(writer.x(), writer.y() + y_step);
|
||||
old_x = writer.x();
|
||||
turning_point = xr-old_x > old_x-xl ? xr : xl;
|
||||
for (int i=0; i<number_of_moves; ++i) {
|
||||
float stamping_dist_e = m_filpar[m_current_tool].filament_stamping_distance + m_cooling_tube_length / 2.f;
|
||||
|
||||
for (int i=0; i<number_of_cooling_moves; ++i) {
|
||||
|
||||
// Stamping - happens after every cooling move except for the last one.
|
||||
if (i>0 && m_filpar[m_current_tool].filament_stamping_distance != 0) {
|
||||
|
||||
// Stamping turning point shall be no farther than 20mm from the current nozzle position:
|
||||
float stamping_turning_point = std::clamp(old_x + 20.f * (turning_point - old_x > 0.f ? 1.f : -1.f), xl, xr);
|
||||
|
||||
// Only last 5mm will be done with the fast x travel. The point is to spread possible blobs
|
||||
// along the whole wipe tower.
|
||||
if (stamping_dist_e > 5) {
|
||||
float cent = writer.x();
|
||||
writer.load_move_x_advanced(stamping_turning_point, (stamping_dist_e - 5), m_filpar[m_current_tool].filament_stamping_loading_speed, 200);
|
||||
writer.load_move_x_advanced(cent, 5, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed);
|
||||
writer.travel(cent, writer.y());
|
||||
} else
|
||||
writer.load_move_x_advanced_there_and_back(stamping_turning_point, stamping_dist_e, m_filpar[m_current_tool].filament_stamping_loading_speed, m_travel_speed);
|
||||
|
||||
// Retract while the print head is stationary, so if there is a blob, it is not dragged along.
|
||||
writer.retract(stamping_dist_e, m_filpar[m_current_tool].unloading_speed * 60.f);
|
||||
}
|
||||
|
||||
if (i == number_of_cooling_moves - 1 && change_temp_later) {
|
||||
// If cold_ramming, the temperature change should be done before the last cooling move.
|
||||
writer.set_extruder_temp(new_temperature, false);
|
||||
}
|
||||
float speed = initial_speed + speed_inc * 2*i;
|
||||
writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
|
||||
speed += speed_inc;
|
||||
@@ -979,7 +1063,7 @@ void WipeTower::toolchange_Unload(
|
||||
|
||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
if (do_ramming)
|
||||
writer.travel(pos, 2400.f);
|
||||
else
|
||||
@@ -1004,6 +1088,8 @@ void WipeTower::toolchange_Change(
|
||||
//writer.append("[end_filament_gcode]\n");
|
||||
writer.append("[toolchange_gcode_from_wipe_tower_generator]\n");
|
||||
|
||||
if (m_is_mk4mmu3)
|
||||
writer.switch_filament_monitoring(true);
|
||||
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
||||
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
|
||||
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
|
||||
@@ -1064,20 +1150,23 @@ void WipeTower::toolchange_Wipe(
|
||||
const float& xl = cleaning_box.ld.x();
|
||||
const float& xr = cleaning_box.rd.x();
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
||||
const float line_width = m_perimeter_width * m_extra_flow;
|
||||
writer.change_analyzer_line_width(line_width);
|
||||
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
||||
// the ordered volume, even if it means violating the box. This can later be removed and simply
|
||||
// wipe until the end of the assigned area.
|
||||
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) * (is_first_layer() ? m_extra_spacing : 1.f);
|
||||
float dy = (is_first_layer() ? 1.f : m_extra_spacing) * m_perimeter_width; // Don't use the extra spacing for the first layer.
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
||||
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
// All the calculations in all other places take the spacing into account for all the layers.
|
||||
|
||||
const float target_speed = is_first_layer() ? m_first_layer_speed * 60.f : m_infill_speed * 60.f;
|
||||
float wipe_speed = 0.33f * target_speed;
|
||||
|
||||
// if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) {
|
||||
writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy);
|
||||
// if there is less than 2.5*line_width to the edge, advance straightaway (there is likely a blob anyway)
|
||||
if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*line_width) {
|
||||
writer.travel((m_left_to_right ? xr-line_width : xl+line_width),writer.y()+dy);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
@@ -1092,21 +1181,21 @@ void WipeTower::toolchange_Wipe(
|
||||
|
||||
float traversed_x = writer.x();
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed);
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
else
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*m_perimeter_width), writer.y(), wipe_speed);
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*m_perimeter_width)
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
||||
break; // in case next line would not fit
|
||||
|
||||
traversed_x -= writer.x();
|
||||
x_to_wipe -= std::abs(traversed_x);
|
||||
if (x_to_wipe < WT_EPSILON) {
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*m_perimeter_width : xr - 1.5f*m_perimeter_width, writer.y(), 7200);
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
||||
break;
|
||||
}
|
||||
// stepping to the next line:
|
||||
writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*m_perimeter_width, writer.y() + dy);
|
||||
writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5f*line_width, writer.y() + dy);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
@@ -1120,6 +1209,7 @@ void WipeTower::toolchange_Wipe(
|
||||
m_left_to_right = !m_left_to_right;
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
|
||||
writer.change_analyzer_line_width(m_perimeter_width);
|
||||
}
|
||||
|
||||
|
||||
@@ -1399,9 +1489,19 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
|
||||
// Extract purging volumes for each extruder pair:
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
|
||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||
for (size_t i = 0; i<number_of_extruders; ++i)
|
||||
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
|
||||
|
||||
// For SEMM printers, the project can be configured to use defaults from configuration,
|
||||
// in which case the custom matrix shall be ignored. We will overwrite the values.
|
||||
if (config.single_extruder_multi_material && ! config.wiping_volumes_use_custom_matrix) {
|
||||
for (size_t i = 0; i < number_of_extruders; ++i) {
|
||||
for (size_t j = 0; j < number_of_extruders; ++j) {
|
||||
if (i != j)
|
||||
wipe_volumes[i][j] = (i == j ? 0.f : config.multimaterial_purging.value * config.filament_purge_multiplier.get_at(j) / 100.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
|
||||
for (unsigned int i = 0; i<number_of_extruders; ++i)
|
||||
for (unsigned int j = 0; j<number_of_extruders; ++j)
|
||||
@@ -1410,6 +1510,13 @@ std::vector<std::vector<float>> WipeTower::extract_wipe_volumes(const PrintConfi
|
||||
return wipe_volumes;
|
||||
}
|
||||
|
||||
static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width)
|
||||
{
|
||||
float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow;
|
||||
length_to_extrude = std::max(length_to_extrude,0.f);
|
||||
|
||||
return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing;
|
||||
}
|
||||
// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
|
||||
void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool,
|
||||
unsigned int new_tool, float wipe_volume)
|
||||
@@ -1426,22 +1533,17 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
|
||||
return;
|
||||
|
||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||
float depth = 0.f;
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height_par);
|
||||
depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator);
|
||||
float ramming_depth = depth;
|
||||
length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width;
|
||||
float first_wipe_line = -length_to_extrude;
|
||||
length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par);
|
||||
length_to_extrude = std::max(length_to_extrude,0.f);
|
||||
float ramming_depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming;
|
||||
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||
|
||||
depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
|
||||
depth *= m_extra_spacing;
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
|
||||
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
}
|
||||
|
||||
|
||||
@@ -1491,14 +1593,14 @@ void WipeTower::save_on_last_wipe()
|
||||
|
||||
if (i == idx) {
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
float length_to_save = finish_layer().total_extrusion_length_in_plane();
|
||||
float length_to_wipe = volume_to_length(toolchange.wipe_volume,
|
||||
m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save;
|
||||
|
||||
length_to_wipe = std::max(length_to_wipe,0.f);
|
||||
float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) );
|
||||
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width);
|
||||
|
||||
toolchange.required_depth = (toolchange.ramming_depth + depth_to_wipe) * m_extra_spacing;
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
|
||||
#ifndef slic3r_GCode_WipeTower_hpp_
|
||||
#define slic3r_GCode_WipeTower_hpp_
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@@ -232,6 +231,8 @@ public:
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
float filament_stamping_loading_speed = 0.f;
|
||||
float filament_stamping_distance = 0.f;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
@@ -243,6 +244,7 @@ public:
|
||||
float filament_area;
|
||||
bool multitool_ramming;
|
||||
float multitool_ramming_time = 0.f;
|
||||
float filament_minimal_purge_on_wipe_tower = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -260,6 +262,7 @@ private:
|
||||
|
||||
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_is_mk4mmu3 = false;
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -309,8 +312,6 @@ private:
|
||||
// State of the wipe tower generator.
|
||||
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
||||
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
||||
///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
|
||||
bool m_print_brim = true;
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
@@ -319,7 +320,9 @@ private:
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
float m_extra_flow = 1.f;
|
||||
float m_extra_spacing_wipe = 1.f;
|
||||
float m_extra_spacing_ramming = 1.f;
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
@@ -331,16 +334,10 @@ private:
|
||||
return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area();
|
||||
}
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
@@ -355,19 +352,19 @@ private:
|
||||
float ramming_depth;
|
||||
float first_wipe_line;
|
||||
float wipe_volume;
|
||||
float wipe_volume_total;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f)
|
||||
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {}
|
||||
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv}, wipe_volume_total{wv} {}
|
||||
};
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
WipeTowerInfo(float z_par, float layer_height_par)
|
||||
: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
|
||||
: z{z_par}, height{layer_height_par}, depth{0} {}
|
||||
};
|
||||
|
||||
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
|
||||
@@ -390,6 +387,7 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int old_temperature,
|
||||
const int new_temperature);
|
||||
|
||||
void toolchange_Change(
|
||||
@@ -412,4 +410,4 @@ private:
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerQIDIMM_hpp_
|
||||
#endif // slic3r_GCode_WipeTower_hpp_
|
||||
|
||||
@@ -58,13 +58,14 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
|| will_go_down); // don't dig into the print
|
||||
if (should_travel_to_tower) {
|
||||
const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos);
|
||||
gcode += gcodegen.m_label_objects.maybe_stop_instance();
|
||||
gcode += gcodegen.retract_and_wipe();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
const std::string comment{"Travel to a Wipe Tower"};
|
||||
if (gcodegen.m_current_layer_first_position) {
|
||||
if (gcodegen.last_position) {
|
||||
gcode += gcodegen.travel_to(
|
||||
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment
|
||||
*gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";}
|
||||
);
|
||||
} else {
|
||||
gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment);
|
||||
@@ -72,7 +73,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
}
|
||||
} else {
|
||||
const Vec3crd point = to_3d(xy_point, scaled(z));
|
||||
gcode += gcodegen.travel_to_first_position(point, current_z);
|
||||
gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";});
|
||||
}
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
@@ -93,12 +94,10 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower) {
|
||||
if (tcr.priming) {
|
||||
const double return_to_z{tcr.print_z + gcodegen.config().z_offset.value};
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(return_to_z, "set priming layer Z");
|
||||
} else {
|
||||
deretraction_str += gcodegen.writer().travel_to_z(z, "restore layer Z");
|
||||
}
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z");
|
||||
Vec3d position{gcodegen.writer().get_position()};
|
||||
position.z() = z;
|
||||
gcodegen.writer().update_position(position);
|
||||
deretraction_str += gcodegen.unretract();
|
||||
|
||||
}
|
||||
@@ -111,7 +110,10 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str);
|
||||
std::string tcr_gcode;
|
||||
unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode);
|
||||
if (gcodegen.config().default_acceleration > 0)
|
||||
gcode += gcodegen.writer().set_print_acceleration(fast_round_up<unsigned int>(gcodegen.config().wipe_tower_acceleration.value));
|
||||
gcode += tcr_gcode;
|
||||
gcode += gcodegen.writer().set_print_acceleration(fast_round_up<unsigned int>(gcodegen.config().default_acceleration.value));
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
@@ -136,7 +138,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
|
||||
return gcode;
|
||||
}
|
||||
|
||||
@@ -262,10 +264,11 @@ std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extr
|
||||
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
const double purge_z{m_final_purge.print_z + gcodegen.config().z_offset.value};
|
||||
if (std::abs(gcodegen.writer().get_position().z() - purge_z) > EPSILON)
|
||||
gcode += gcodegen.generate_travel_gcode(
|
||||
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}},
|
||||
"move to safe place for purging"
|
||||
{{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(purge_z)}},
|
||||
"move to safe place for purging", [](){return "";}
|
||||
);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
|
||||
@@ -620,8 +620,6 @@ void Layer::make_perimeters()
|
||||
std::vector<uint32_t> layer_region_ids;
|
||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> perimeter_and_gapfill_ranges;
|
||||
ExPolygons fill_expolygons;
|
||||
//w21
|
||||
ExPolygons fill_no_overlap_expolygons;
|
||||
std::vector<ExPolygonRange> fill_expolygons_ranges;
|
||||
SurfacesPtr surfaces_to_merge;
|
||||
SurfacesPtr surfaces_to_merge_temp;
|
||||
@@ -652,8 +650,6 @@ void Layer::make_perimeters()
|
||||
fill_expolygons.clear();
|
||||
fill_expolygons_ranges.clear();
|
||||
surfaces_to_merge.clear();
|
||||
//w21
|
||||
//fill_no_overlap_expolygons.clear();
|
||||
|
||||
// find compatible regions
|
||||
layer_region_ids.clear();
|
||||
@@ -694,8 +690,7 @@ void Layer::make_perimeters()
|
||||
}
|
||||
|
||||
if (layer_region_ids.size() == 1) { // optimization
|
||||
//w21
|
||||
(*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges,(*layerm)->fill_no_overlap_expolygons);
|
||||
(*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
|
||||
this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
|
||||
} else {
|
||||
SurfaceCollection new_slices;
|
||||
@@ -730,24 +725,8 @@ void Layer::make_perimeters()
|
||||
}
|
||||
}
|
||||
// make perimeters
|
||||
//w21
|
||||
ExPolygons fill_no_overlap;
|
||||
layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges,fill_no_overlap);
|
||||
layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
|
||||
this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
|
||||
//w21
|
||||
if (!new_slices.surfaces.empty()) {
|
||||
for (size_t idx : layer_region_ids) {
|
||||
// Separate the fill surfaces.
|
||||
LayerRegion &layer = *m_regions[idx];
|
||||
ExPolygons expp = intersection_ex(new_slices.surfaces, layer.slices().surfaces);
|
||||
layer.m_fill_expolygons = std::move(expp);
|
||||
if (!layer.m_fill_expolygons.empty()) {
|
||||
layer.m_fill_surfaces.set(std::move(layer.m_fill_expolygons), layer.slices().surfaces.front());
|
||||
layer.fill_no_overlap_expolygons = intersection_ex(layer.slices().surfaces, fill_no_overlap);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Flow.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "LayerRegion.hpp"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
@@ -29,41 +30,6 @@ namespace FillLightning {
|
||||
class Generator;
|
||||
};
|
||||
|
||||
// Range of indices, providing support for range based loops.
|
||||
template<typename T>
|
||||
class IndexRange
|
||||
{
|
||||
public:
|
||||
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
|
||||
IndexRange() = default;
|
||||
|
||||
// Just a bare minimum functionality iterator required by range-for loop.
|
||||
class Iterator {
|
||||
public:
|
||||
T operator*() const { return m_idx; }
|
||||
bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; }
|
||||
void operator++() { ++ m_idx; }
|
||||
private:
|
||||
friend class IndexRange<T>;
|
||||
Iterator(T idx) : m_idx(idx) {}
|
||||
T m_idx;
|
||||
};
|
||||
|
||||
Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); };
|
||||
Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); };
|
||||
|
||||
bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; }
|
||||
T size() const { assert(m_begin <= m_end); return m_end - m_begin; }
|
||||
|
||||
private:
|
||||
// Index of the first extrusion in LayerRegion.
|
||||
T m_begin { 0 };
|
||||
// Index of the last extrusion in LayerRegion.
|
||||
T m_end { 0 };
|
||||
};
|
||||
|
||||
using ExtrusionRange = IndexRange<uint32_t>;
|
||||
using ExPolygonRange = IndexRange<uint32_t>;
|
||||
|
||||
// Range of extrusions, referencing the source region by an index.
|
||||
class LayerExtrusionRange : public ExtrusionRange
|
||||
@@ -91,150 +57,6 @@ using LayerExtrusionRanges =
|
||||
std::vector<LayerExtrusionRange>;
|
||||
#endif // NDEBUG
|
||||
|
||||
class LayerRegion
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] Layer* layer() { return m_layer; }
|
||||
[[nodiscard]] const Layer* layer() const { return m_layer; }
|
||||
[[nodiscard]] const PrintRegion& region() const { return *m_region; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
|
||||
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
[[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; }
|
||||
// and their bounding boxes
|
||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; }
|
||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||
// Not used for a plain single material print with no infill modifiers.
|
||||
[[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; }
|
||||
// and their bounding boxes
|
||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
[[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; }
|
||||
|
||||
// collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
[[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; }
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
[[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; }
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; }
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
|
||||
//w21
|
||||
ExPolygons fill_no_overlap_expolygons;
|
||||
|
||||
Flow flow(FlowRole role) const;
|
||||
Flow flow(FlowRole role, double layer_height) const;
|
||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
|
||||
void make_perimeters(
|
||||
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
||||
const SurfaceCollection &slices,
|
||||
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
||||
// newly created extrusions stored at this LayerRegion.
|
||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||
// All fill areas produced for all input slices above.
|
||||
ExPolygons &fill_expolygons,
|
||||
// Ranges of fill areas above per input slice.
|
||||
std::vector<ExPolygonRange> &fill_expolygons_ranges,
|
||||
//w21
|
||||
ExPolygons &fill_no_overlap_expolygons);
|
||||
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||
double infill_area_threshold() const;
|
||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||
void trim_surfaces(const Polygons &trimming_polygons);
|
||||
// Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer.
|
||||
// Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit.
|
||||
void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons);
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
|
||||
|
||||
protected:
|
||||
friend class Layer;
|
||||
friend class PrintObject;
|
||||
|
||||
LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {}
|
||||
~LayerRegion() = default;
|
||||
|
||||
private:
|
||||
// Modifying m_slices
|
||||
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
||||
template<typename ThrowOnCancel>
|
||||
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
|
||||
|
||||
Layer *m_layer;
|
||||
const PrintRegion *m_region;
|
||||
|
||||
// Backed up slices before they are split into top/bottom/internal.
|
||||
// Only backed up for multi-region layers or layers with elephant foot compensation.
|
||||
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
|
||||
ExPolygons m_raw_slices;
|
||||
|
||||
//FIXME make m_slices public for unit tests
|
||||
public:
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
SurfaceCollection m_slices;
|
||||
|
||||
private:
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
ExPolygons m_fill_expolygons;
|
||||
// and their bounding boxes
|
||||
BoundingBoxes m_fill_expolygons_bboxes;
|
||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||
// Not used for a plain single material print with no infill modifiers.
|
||||
ExPolygons m_fill_expolygons_composite;
|
||||
// and their bounding boxes
|
||||
BoundingBoxes m_fill_expolygons_composite_bboxes;
|
||||
|
||||
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
|
||||
SurfaceCollection m_fill_surfaces;
|
||||
|
||||
// Collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
ExtrusionEntityCollection m_thin_fills;
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
Polylines m_unsupported_bridge_edges;
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_perimeters;
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_fills;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
// Polygons bridged;
|
||||
//w21
|
||||
ExPolygons m_fill_no_overlap_expolygons;
|
||||
};
|
||||
|
||||
// LayerSlice contains one or more LayerIsland objects,
|
||||
// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters
|
||||
|
||||
@@ -73,9 +73,7 @@ void LayerRegion::make_perimeters(
|
||||
// All fill areas produced for all input slices above.
|
||||
ExPolygons &fill_expolygons,
|
||||
// Ranges of fill areas above per input slice.
|
||||
std::vector<ExPolygonRange> &fill_expolygons_ranges,
|
||||
//w21
|
||||
ExPolygons &fill_no_overlap_expolygons)
|
||||
std::vector<ExPolygonRange> &fill_expolygons_ranges)
|
||||
{
|
||||
m_perimeters.clear();
|
||||
m_thin_fills.clear();
|
||||
@@ -108,11 +106,8 @@ void LayerRegion::make_perimeters(
|
||||
|
||||
// Cummulative sum of polygons over all the regions.
|
||||
const ExPolygons *lower_slices = this->layer()->lower_layer ? &this->layer()->lower_layer->lslices : nullptr;
|
||||
//w16
|
||||
const ExPolygons *upper_slices = this->layer()->upper_layer ? &this->layer()->upper_layer->lslices : nullptr;
|
||||
// Cache for offsetted lower_slices
|
||||
Polygons lower_layer_polygons_cache;
|
||||
Polygons upper_layer_polygons_cache;
|
||||
|
||||
for (const Surface &surface : slices) {
|
||||
auto perimeters_begin = uint32_t(m_perimeters.size());
|
||||
@@ -120,37 +115,16 @@ void LayerRegion::make_perimeters(
|
||||
auto fill_expolygons_begin = uint32_t(fill_expolygons.size());
|
||||
if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase)
|
||||
|
||||
//w16
|
||||
if (this->layer()->object()->config().top_one_wall_type == TopOneWallType::Alltop)
|
||||
PerimeterGenerator::process_with_one_wall_arachne(
|
||||
// input:
|
||||
params,
|
||||
surface,
|
||||
lower_slices,
|
||||
//w16
|
||||
upper_slices,
|
||||
lower_layer_polygons_cache,
|
||||
upper_layer_polygons_cache,
|
||||
// output:
|
||||
m_perimeters,
|
||||
m_thin_fills,
|
||||
fill_expolygons,
|
||||
//w21
|
||||
fill_no_overlap_expolygons);
|
||||
else
|
||||
PerimeterGenerator::process_arachne(
|
||||
// input:
|
||||
params,
|
||||
surface,
|
||||
lower_slices,
|
||||
upper_slices,
|
||||
lower_layer_polygons_cache,
|
||||
// output:
|
||||
m_perimeters,
|
||||
m_thin_fills,
|
||||
fill_expolygons,
|
||||
//w21
|
||||
fill_no_overlap_expolygons);
|
||||
fill_expolygons);
|
||||
|
||||
else
|
||||
PerimeterGenerator::process_classic(
|
||||
@@ -158,16 +132,11 @@ void LayerRegion::make_perimeters(
|
||||
params,
|
||||
surface,
|
||||
lower_slices,
|
||||
//w16
|
||||
upper_slices,
|
||||
lower_layer_polygons_cache,
|
||||
upper_layer_polygons_cache,
|
||||
// output:
|
||||
m_perimeters,
|
||||
m_thin_fills,
|
||||
fill_expolygons,
|
||||
//w21
|
||||
fill_no_overlap_expolygons);
|
||||
fill_expolygons);
|
||||
perimeter_and_gapfill_ranges.emplace_back(
|
||||
ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) },
|
||||
ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) });
|
||||
@@ -197,61 +166,17 @@ static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::init
|
||||
return out;
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||
// detect bridges.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
Surfaces expand_bridges_detect_orientations(
|
||||
Surfaces &surfaces,
|
||||
ExPolygons &shells,
|
||||
const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill,
|
||||
ExPolygons &sparse,
|
||||
const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill,
|
||||
const float closing_radius)
|
||||
{
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
double thickness;
|
||||
ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness);
|
||||
if (bridges_ex.empty())
|
||||
return {};
|
||||
|
||||
// Calculate bridge anchors and their expansions in their respective shell region.
|
||||
WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params_into_solid_infill.tiny_expansion, true);
|
||||
std::vector<RegionExpansionEx> bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params_into_solid_infill);
|
||||
bool expanded_into_shells = ! bridge_expansions.empty();
|
||||
bool expanded_into_sparse = false;
|
||||
{
|
||||
WaveSeeds bridge_anchors_sparse = wave_seeds(bridges_ex, sparse, expansion_params_into_sparse_infill.tiny_expansion, true);
|
||||
std::vector<RegionExpansionEx> bridge_expansions_sparse = propagate_waves_ex(bridge_anchors_sparse, sparse, expansion_params_into_sparse_infill);
|
||||
if (! bridge_expansions_sparse.empty()) {
|
||||
expanded_into_sparse = true;
|
||||
for (WaveSeed &seed : bridge_anchors_sparse)
|
||||
seed.boundary += uint32_t(shells.size());
|
||||
for (RegionExpansionEx &expansion : bridge_expansions_sparse)
|
||||
expansion.boundary_id += uint32_t(shells.size());
|
||||
append(bridge_anchors, std::move(bridge_anchors_sparse));
|
||||
append(bridge_expansions, std::move(bridge_expansions_sparse));
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
|
||||
struct Bridge {
|
||||
ExPolygon expolygon;
|
||||
uint32_t group_id;
|
||||
std::vector<RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
||||
double angle = -1;
|
||||
std::vector<Algorithm::RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
||||
std::optional<double> angle{std::nullopt};
|
||||
};
|
||||
std::vector<Bridge> bridges;
|
||||
{
|
||||
bridges.reserve(bridges_ex.size());
|
||||
uint32_t group_id = 0;
|
||||
for (ExPolygon &ex : bridges_ex)
|
||||
bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() });
|
||||
bridges_ex.clear();
|
||||
}
|
||||
|
||||
// Group the bridge surfaces by overlaps.
|
||||
auto group_id = [&bridges](uint32_t src_id) {
|
||||
uint32_t group_id(std::vector<Bridge> &bridges, uint32_t src_id) {
|
||||
uint32_t group_id = bridges[src_id].group_id;
|
||||
while (group_id != src_id) {
|
||||
src_id = group_id;
|
||||
@@ -261,107 +186,148 @@ Surfaces expand_bridges_detect_orientations(
|
||||
return group_id;
|
||||
};
|
||||
|
||||
std::vector<Bridge> get_grouped_bridges(
|
||||
ExPolygons&& bridge_expolygons,
|
||||
const std::vector<Algorithm::RegionExpansionEx>& bridge_expansions
|
||||
) {
|
||||
using namespace Algorithm;
|
||||
|
||||
std::vector<Bridge> result;
|
||||
{
|
||||
// Cache of bboxes per expansion boundary.
|
||||
std::vector<BoundingBox> bboxes;
|
||||
result.reserve(bridge_expansions.size());
|
||||
uint32_t group_id = 0;
|
||||
using std::move_iterator;
|
||||
for (ExPolygon& expolygon : bridge_expolygons)
|
||||
result.push_back({ std::move(expolygon), group_id ++, bridge_expansions.end() });
|
||||
}
|
||||
// Detect overlaps of bridge anchors inside their respective shell regions.
|
||||
// bridge_expansions are sorted by boundary id and source id.
|
||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) {
|
||||
// For each boundary region:
|
||||
auto it_begin = it;
|
||||
auto it_end = std::next(it_begin);
|
||||
for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ;
|
||||
bboxes.clear();
|
||||
bboxes.reserve(it_end - it_begin);
|
||||
for (auto it2 = it_begin; it2 != it_end; ++ it2)
|
||||
bboxes.emplace_back(get_extents(it2->expolygon.contour));
|
||||
for (auto expansion_iterator = bridge_expansions.begin(); expansion_iterator != bridge_expansions.end();) {
|
||||
auto boundary_region_begin = expansion_iterator;
|
||||
auto boundary_region_end = std::find_if(
|
||||
next(expansion_iterator),
|
||||
bridge_expansions.end(),
|
||||
[&](const RegionExpansionEx& expansion){
|
||||
return expansion.boundary_id != expansion_iterator->boundary_id;
|
||||
}
|
||||
);
|
||||
|
||||
// Cache of bboxes per expansion boundary.
|
||||
std::vector<BoundingBox> bounding_boxes;
|
||||
bounding_boxes.reserve(std::distance(boundary_region_begin, boundary_region_end));
|
||||
std::transform(
|
||||
boundary_region_begin,
|
||||
boundary_region_end,
|
||||
std::back_inserter(bounding_boxes),
|
||||
[](const RegionExpansionEx& expansion){
|
||||
return get_extents(expansion.expolygon.contour);
|
||||
}
|
||||
);
|
||||
// For each bridge anchor of the current source:
|
||||
for (; it != it_end; ++ it) {
|
||||
// A grup id for this bridge.
|
||||
for (auto it2 = std::next(it); it2 != it_end; ++ it2)
|
||||
if (it->src_id != it2->src_id &&
|
||||
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
|
||||
for (;expansion_iterator != boundary_region_end; ++expansion_iterator) {
|
||||
auto candidate_iterator = std::next(expansion_iterator);
|
||||
for (;candidate_iterator != boundary_region_end; ++candidate_iterator) {
|
||||
const BoundingBox& current_bounding_box{
|
||||
bounding_boxes[expansion_iterator - boundary_region_begin]
|
||||
};
|
||||
const BoundingBox& candidate_bounding_box{
|
||||
bounding_boxes[candidate_iterator - boundary_region_begin]
|
||||
};
|
||||
if (
|
||||
expansion_iterator->src_id != candidate_iterator->src_id
|
||||
&& current_bounding_box.overlap(candidate_bounding_box)
|
||||
// One may ignore holes, they are irrelevant for intersection test.
|
||||
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
|
||||
&& !intersection(expansion_iterator->expolygon.contour, candidate_iterator->expolygon.contour).empty()
|
||||
) {
|
||||
// The two bridge regions intersect. Give them the same (lower) group id.
|
||||
uint32_t id = group_id(it->src_id);
|
||||
uint32_t id2 = group_id(it2->src_id);
|
||||
uint32_t id = group_id(result, expansion_iterator->src_id);
|
||||
uint32_t id2 = group_id(result, candidate_iterator->src_id);
|
||||
if (id < id2)
|
||||
bridges[id2].group_id = id;
|
||||
result[id2].group_id = id;
|
||||
else
|
||||
bridges[id].group_id = id2;
|
||||
result[id].group_id = id2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Detect bridge directions.
|
||||
{
|
||||
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
|
||||
void detect_bridge_directions(
|
||||
const Algorithm::WaveSeeds& bridge_anchors,
|
||||
std::vector<Bridge>& bridges,
|
||||
const std::vector<ExpansionZone>& expansion_zones
|
||||
) {
|
||||
if (expansion_zones.empty()) {
|
||||
throw std::runtime_error("At least one expansion zone must exist!");
|
||||
}
|
||||
auto it_bridge_anchor = bridge_anchors.begin();
|
||||
Lines lines;
|
||||
Polygons anchor_areas;
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||
Bridge &bridge = bridges[bridge_id];
|
||||
// lines.clear();
|
||||
anchor_areas.clear();
|
||||
Polygons anchor_areas;
|
||||
int32_t last_anchor_id = -1;
|
||||
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
|
||||
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
|
||||
last_anchor_id = int(it_bridge_anchor->boundary);
|
||||
append(anchor_areas, to_polygons(last_anchor_id < int32_t(shells.size()) ? shells[last_anchor_id] : sparse[last_anchor_id - int32_t(shells.size())]));
|
||||
unsigned start_index{};
|
||||
unsigned end_index{};
|
||||
for (const ExpansionZone& expansion_zone: expansion_zones) {
|
||||
end_index += expansion_zone.expolygons.size();
|
||||
if (last_anchor_id < static_cast<int64_t>(end_index)) {
|
||||
append(anchor_areas, to_polygons(expansion_zone.expolygons[last_anchor_id - start_index]));
|
||||
break;
|
||||
}
|
||||
start_index += expansion_zone.expolygons.size();
|
||||
}
|
||||
// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) {
|
||||
// reserve_more_power_of_2(lines, polyline.size() - 1);
|
||||
// for (size_t i = 1; i < polyline.size(); ++ i)
|
||||
// lines.push_back({ polyline[i - 1], polyline[1] });
|
||||
// }
|
||||
}
|
||||
lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))));
|
||||
}
|
||||
Lines lines{to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))))};
|
||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
|
||||
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||
#if 0
|
||||
if constexpr (false) {
|
||||
coordf_t stroke_width = scale_(0.06);
|
||||
BoundingBox bbox = get_extents(anchor_areas);
|
||||
bbox.merge(get_extents(bridge.expolygon));
|
||||
bbox.offset(scale_(1.));
|
||||
::Slic3r::SVG
|
||||
svg(debug_out_path(("bridge" + std::to_string(bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()),
|
||||
svg(debug_out_path(("bridge" + std::to_string(*bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()),
|
||||
bbox);
|
||||
svg.draw(bridge.expolygon, "cyan");
|
||||
svg.draw(lines, "green", stroke_width);
|
||||
svg.draw(anchor_areas, "red");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
|
||||
Surfaces out;
|
||||
{
|
||||
Polygons acc;
|
||||
Surface templ{ stBottomBridge, {} };
|
||||
std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) {
|
||||
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
|
||||
});
|
||||
Surfaces merge_bridges(
|
||||
std::vector<Bridge>& bridges,
|
||||
const std::vector<Algorithm::RegionExpansionEx>& bridge_expansions,
|
||||
const float closing_radius
|
||||
) {
|
||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
|
||||
bridges[it->src_id].bridge_expansion_begin = it;
|
||||
uint32_t src_id = it->src_id;
|
||||
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
|
||||
}
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id)
|
||||
if (group_id(bridge_id) == bridge_id) {
|
||||
Surfaces result;
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||
if (group_id(bridges, bridge_id) == bridge_id) {
|
||||
// Head of the group.
|
||||
acc.clear();
|
||||
Polygons acc;
|
||||
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
|
||||
if (group_id(bridge_id2) == bridge_id) {
|
||||
if (group_id(bridges, bridge_id2) == bridge_id) {
|
||||
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
|
||||
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
|
||||
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
|
||||
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
|
||||
append(acc, to_polygons(std::move(it_bridge_expansion->expolygon)));
|
||||
append(acc, to_polygons(it_bridge_expansion->expolygon));
|
||||
}
|
||||
//FIXME try to be smart and pick the best bridging angle for all?
|
||||
templ.bridge_angle = bridges[bridge_id].angle;
|
||||
if (!bridges[bridge_id].angle) {
|
||||
assert(false && "Bridge angle must be pre-calculated!");
|
||||
}
|
||||
Surface templ{ stBottomBridge, {} };
|
||||
templ.bridge_angle = bridges[bridge_id].angle ? *bridges[bridge_id].angle : -1;
|
||||
//NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy)
|
||||
// without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface.
|
||||
// look for narrow_ensure_vertical_wall_thickness_region_radius filter.
|
||||
@@ -369,29 +335,105 @@ Surfaces expand_bridges_detect_orientations(
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
// union_safety_offset_ex(acc)
|
||||
for (ExPolygon &ex : final)
|
||||
out.emplace_back(templ, std::move(ex));
|
||||
result.emplace_back(templ, std::move(ex));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ExpansionResult {
|
||||
Algorithm::WaveSeeds anchors;
|
||||
std::vector<Algorithm::RegionExpansionEx> expansions;
|
||||
};
|
||||
|
||||
ExpansionResult expand_expolygons(
|
||||
const ExPolygons& expolygons,
|
||||
std::vector<ExpansionZone>& expansion_zones
|
||||
) {
|
||||
using namespace Algorithm;
|
||||
WaveSeeds bridge_anchors;
|
||||
std::vector<RegionExpansionEx> bridge_expansions;
|
||||
|
||||
unsigned processed_bridges_count = 0;
|
||||
for (ExpansionZone& expansion_zone : expansion_zones) {
|
||||
WaveSeeds seeds{wave_seeds(
|
||||
expolygons,
|
||||
expansion_zone.expolygons,
|
||||
expansion_zone.parameters.tiny_expansion,
|
||||
true
|
||||
)};
|
||||
std::vector<RegionExpansionEx> expansions{propagate_waves_ex(
|
||||
seeds,
|
||||
expansion_zone.expolygons,
|
||||
expansion_zone.parameters
|
||||
)};
|
||||
|
||||
for (WaveSeed &seed : seeds)
|
||||
seed.boundary += processed_bridges_count;
|
||||
for (RegionExpansionEx &expansion : expansions)
|
||||
expansion.boundary_id += processed_bridges_count;
|
||||
|
||||
expansion_zone.expanded_into = ! expansions.empty();
|
||||
|
||||
append(bridge_anchors, std::move(seeds));
|
||||
append(bridge_expansions, std::move(expansions));
|
||||
|
||||
processed_bridges_count += expansion_zone.expolygons.size();
|
||||
}
|
||||
return {bridge_anchors, bridge_expansions};
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||
// detect bridges.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
Surfaces expand_bridges_detect_orientations(
|
||||
Surfaces &surfaces,
|
||||
std::vector<ExpansionZone>& expansion_zones,
|
||||
const float closing_radius
|
||||
)
|
||||
{
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
double thickness;
|
||||
ExPolygons bridge_expolygons = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness);
|
||||
if (bridge_expolygons.empty())
|
||||
return {};
|
||||
|
||||
// Calculate bridge anchors and their expansions in their respective shell region.
|
||||
ExpansionResult expansion_result{expand_expolygons(
|
||||
bridge_expolygons,
|
||||
expansion_zones
|
||||
)};
|
||||
|
||||
std::vector<Bridge> bridges{get_grouped_bridges(
|
||||
std::move(bridge_expolygons),
|
||||
expansion_result.expansions
|
||||
)};
|
||||
bridge_expolygons.clear();
|
||||
|
||||
std::sort(expansion_result.anchors.begin(), expansion_result.anchors.end(), Algorithm::lower_by_src_and_boundary);
|
||||
detect_bridge_directions(expansion_result.anchors, bridges, expansion_zones);
|
||||
|
||||
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
|
||||
std::sort(expansion_result.expansions.begin(), expansion_result.expansions.end(), [](auto &l, auto &r) {
|
||||
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
|
||||
});
|
||||
Surfaces out{merge_bridges(bridges, expansion_result.expansions, closing_radius)};
|
||||
|
||||
// Clip by the expanded bridges.
|
||||
if (expanded_into_shells)
|
||||
shells = diff_ex(shells, out);
|
||||
if (expanded_into_sparse)
|
||||
sparse = diff_ex(sparse, out);
|
||||
for (ExpansionZone& expansion_zone : expansion_zones)
|
||||
if (expansion_zone.expanded_into)
|
||||
expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
static Surfaces expand_merge_surfaces(
|
||||
Surfaces expand_merge_surfaces(
|
||||
Surfaces &surfaces,
|
||||
SurfaceType surface_type,
|
||||
ExPolygons &shells,
|
||||
const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill,
|
||||
ExPolygons &sparse,
|
||||
const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill,
|
||||
std::vector<ExpansionZone>& expansion_zones,
|
||||
const float closing_radius,
|
||||
const double bridge_angle = -1.)
|
||||
const double bridge_angle
|
||||
)
|
||||
{
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
@@ -400,17 +442,17 @@ static Surfaces expand_merge_surfaces(
|
||||
if (src.empty())
|
||||
return {};
|
||||
|
||||
std::vector<RegionExpansion> expansions = propagate_waves(src, shells, expansion_params_into_solid_infill);
|
||||
bool expanded_into_shells = !expansions.empty();
|
||||
bool expanded_into_sparse = false;
|
||||
{
|
||||
std::vector<RegionExpansion> expansions2 = propagate_waves(src, sparse, expansion_params_into_sparse_infill);
|
||||
if (! expansions2.empty()) {
|
||||
expanded_into_sparse = true;
|
||||
for (RegionExpansion &expansion : expansions2)
|
||||
expansion.boundary_id += uint32_t(shells.size());
|
||||
append(expansions, std::move(expansions2));
|
||||
}
|
||||
unsigned processed_expolygons_count = 0;
|
||||
std::vector<RegionExpansion> expansions;
|
||||
for (ExpansionZone& expansion_zone : expansion_zones) {
|
||||
std::vector<RegionExpansion> zone_expansions = propagate_waves(src, expansion_zone.expolygons, expansion_zone.parameters);
|
||||
expansion_zone.expanded_into = !zone_expansions.empty();
|
||||
|
||||
for (RegionExpansion &expansion : zone_expansions)
|
||||
expansion.boundary_id += processed_expolygons_count;
|
||||
|
||||
processed_expolygons_count += expansion_zone.expolygons.size();
|
||||
append(expansions, std::move(zone_expansions));
|
||||
}
|
||||
|
||||
std::vector<ExPolygon> expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions));
|
||||
@@ -418,11 +460,10 @@ static Surfaces expand_merge_surfaces(
|
||||
// without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface.
|
||||
// look for narrow_ensure_vertical_wall_thickness_region_radius filter.
|
||||
expanded = closing_ex(expanded, closing_radius);
|
||||
// Trim the shells by the expanded expolygons.
|
||||
if (expanded_into_shells)
|
||||
shells = diff_ex(shells, expanded);
|
||||
if (expanded_into_sparse)
|
||||
sparse = diff_ex(sparse, expanded);
|
||||
// Trim the zones by the expanded expolygons.
|
||||
for (ExpansionZone& expansion_zone : expansion_zones)
|
||||
if (expansion_zone.expanded_into)
|
||||
expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, expanded);
|
||||
|
||||
Surface templ{ surface_type, {} };
|
||||
templ.bridge_angle = bridge_angle;
|
||||
@@ -471,16 +512,23 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
double layer_thickness;
|
||||
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternalSolid }, layer_thickness));
|
||||
ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternal }, layer_thickness));
|
||||
ExPolygons top_expolygons = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stTop }, layer_thickness));
|
||||
const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps);
|
||||
const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
||||
|
||||
std::vector<ExpansionZone> expansion_zones{
|
||||
ExpansionZone{std::move(shells), expansion_params_into_solid_infill},
|
||||
ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill},
|
||||
ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill},
|
||||
};
|
||||
|
||||
SurfaceCollection bridges;
|
||||
const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps);
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
||||
const double custom_angle = this->region().config().bridge_angle.value;
|
||||
const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
||||
bridges.surfaces = custom_angle > 0 ?
|
||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, Geometry::deg2rad(custom_angle)) :
|
||||
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) :
|
||||
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, expansion_zones, closing_radius);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
#if 0
|
||||
{
|
||||
@@ -490,25 +538,36 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
#endif
|
||||
}
|
||||
|
||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
|
||||
RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
|
||||
RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
m_fill_surfaces.remove_types({ stTop });
|
||||
{
|
||||
Surface top_templ(stTop, {});
|
||||
top_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ);
|
||||
}
|
||||
|
||||
expansion_zones.pop_back();
|
||||
|
||||
expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps);
|
||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius);
|
||||
|
||||
expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps);
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, expansion_zones, closing_radius);
|
||||
|
||||
// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid });
|
||||
m_fill_surfaces.clear();
|
||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size());
|
||||
unsigned zones_expolygons_count = 0;
|
||||
for (const ExpansionZone& zone : expansion_zones)
|
||||
zones_expolygons_count += zone.expolygons.size();
|
||||
reserve_more(m_fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size());
|
||||
{
|
||||
Surface solid_templ(stInternalSolid, {});
|
||||
solid_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(shells), solid_templ);
|
||||
m_fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ);
|
||||
}
|
||||
{
|
||||
Surface sparse_templ(stInternal, {});
|
||||
sparse_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(sparse), sparse_templ);
|
||||
m_fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ);
|
||||
}
|
||||
m_fill_surfaces.append(std::move(bridges.surfaces));
|
||||
m_fill_surfaces.append(std::move(bottoms));
|
||||
|
||||
224
src/libslic3r/LayerRegion.hpp
Normal file
224
src/libslic3r/LayerRegion.hpp
Normal file
@@ -0,0 +1,224 @@
|
||||
#ifndef slic3r_LayerRegion_hpp_
|
||||
#define slic3r_LayerRegion_hpp_
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "libslic3r/Algorithm/RegionExpansion.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Layer;
|
||||
using LayerPtrs = std::vector<Layer*>;
|
||||
class PrintRegion;
|
||||
|
||||
// Range of indices, providing support for range based loops.
|
||||
template<typename T>
|
||||
class IndexRange
|
||||
{
|
||||
public:
|
||||
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
|
||||
IndexRange() = default;
|
||||
|
||||
// Just a bare minimum functionality iterator required by range-for loop.
|
||||
class Iterator {
|
||||
public:
|
||||
T operator*() const { return m_idx; }
|
||||
bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; }
|
||||
void operator++() { ++ m_idx; }
|
||||
private:
|
||||
friend class IndexRange<T>;
|
||||
Iterator(T idx) : m_idx(idx) {}
|
||||
T m_idx;
|
||||
};
|
||||
|
||||
Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); };
|
||||
Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); };
|
||||
|
||||
bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; }
|
||||
T size() const { assert(m_begin <= m_end); return m_end - m_begin; }
|
||||
|
||||
private:
|
||||
// Index of the first extrusion in LayerRegion.
|
||||
T m_begin { 0 };
|
||||
// Index of the last extrusion in LayerRegion.
|
||||
T m_end { 0 };
|
||||
};
|
||||
|
||||
template class IndexRange<uint32_t>;
|
||||
|
||||
using ExtrusionRange = IndexRange<uint32_t>;
|
||||
using ExPolygonRange = IndexRange<uint32_t>;
|
||||
|
||||
class LayerRegion
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] Layer* layer() { return m_layer; }
|
||||
[[nodiscard]] const Layer* layer() const { return m_layer; }
|
||||
[[nodiscard]] const PrintRegion& region() const { return *m_region; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
|
||||
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
[[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; }
|
||||
// and their bounding boxes
|
||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; }
|
||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||
// Not used for a plain single material print with no infill modifiers.
|
||||
[[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; }
|
||||
// and their bounding boxes
|
||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
[[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; }
|
||||
|
||||
// collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
[[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; }
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
[[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; }
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; }
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
|
||||
|
||||
Flow flow(FlowRole role) const;
|
||||
Flow flow(FlowRole role, double layer_height) const;
|
||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
|
||||
void make_perimeters(
|
||||
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
||||
const SurfaceCollection &slices,
|
||||
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
||||
// newly created extrusions stored at this LayerRegion.
|
||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||
// All fill areas produced for all input slices above.
|
||||
ExPolygons &fill_expolygons,
|
||||
// Ranges of fill areas above per input slice.
|
||||
std::vector<ExPolygonRange> &fill_expolygons_ranges);
|
||||
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||
double infill_area_threshold() const;
|
||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||
void trim_surfaces(const Polygons &trimming_polygons);
|
||||
// Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer.
|
||||
// Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit.
|
||||
void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons);
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
|
||||
|
||||
protected:
|
||||
friend class Layer;
|
||||
friend class PrintObject;
|
||||
|
||||
LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {}
|
||||
~LayerRegion() = default;
|
||||
|
||||
private:
|
||||
// Modifying m_slices
|
||||
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
||||
template<typename ThrowOnCancel>
|
||||
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
|
||||
|
||||
Layer *m_layer;
|
||||
const PrintRegion *m_region;
|
||||
|
||||
// Backed up slices before they are split into top/bottom/internal.
|
||||
// Only backed up for multi-region layers or layers with elephant foot compensation.
|
||||
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
|
||||
ExPolygons m_raw_slices;
|
||||
|
||||
//FIXME make m_slices public for unit tests
|
||||
public:
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
SurfaceCollection m_slices;
|
||||
|
||||
private:
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
ExPolygons m_fill_expolygons;
|
||||
// and their bounding boxes
|
||||
BoundingBoxes m_fill_expolygons_bboxes;
|
||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||
// Not used for a plain single material print with no infill modifiers.
|
||||
ExPolygons m_fill_expolygons_composite;
|
||||
// and their bounding boxes
|
||||
BoundingBoxes m_fill_expolygons_composite_bboxes;
|
||||
|
||||
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
|
||||
SurfaceCollection m_fill_surfaces;
|
||||
|
||||
// Collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
ExtrusionEntityCollection m_thin_fills;
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
Polylines m_unsupported_bridge_edges;
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_perimeters;
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_fills;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
// Polygons bridged;
|
||||
};
|
||||
|
||||
struct ExpansionZone {
|
||||
ExPolygons expolygons;
|
||||
Algorithm::RegionExpansionParameters parameters;
|
||||
bool expanded_into = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||
* detect bridges.
|
||||
* Trim "shells" by the expanded bridges.
|
||||
*/
|
||||
Surfaces expand_bridges_detect_orientations(
|
||||
Surfaces &surfaces,
|
||||
std::vector<ExpansionZone>& expansion_zones,
|
||||
const float closing_radius
|
||||
);
|
||||
|
||||
/**
|
||||
* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
||||
* Trim "shells" by the expanded bridges.
|
||||
*/
|
||||
Surfaces expand_merge_surfaces(
|
||||
Surfaces &surfaces,
|
||||
SurfaceType surface_type,
|
||||
std::vector<ExpansionZone>& expansion_zones,
|
||||
const float closing_radius,
|
||||
const double bridge_angle = -1
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#endif // slic3r_LayerRegion_hpp_
|
||||
@@ -1069,39 +1069,6 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
return {extra_perims, diff(inset_overhang_area, inset_overhang_area_left_unfilled)};
|
||||
}
|
||||
|
||||
//w16
|
||||
void PerimeterGenerator::add_infill_contour_for_arachne(ExPolygons infill_contour,
|
||||
int loops,
|
||||
coord_t ext_perimeter_spacing,
|
||||
coord_t perimeter_spacing,
|
||||
coord_t min_perimeter_infill_spacing,
|
||||
coord_t spacing,
|
||||
bool is_inner_part,
|
||||
const Parameters ¶ms,
|
||||
ExPolygons &infill_areas,
|
||||
ExPolygons & out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons & out_fill_no_overlap)
|
||||
{
|
||||
if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) {
|
||||
infill_contour.clear();
|
||||
}
|
||||
coord_t insert = (loops < 0) ? 0 : ext_perimeter_spacing;
|
||||
if (is_inner_part || loops > 0)
|
||||
insert = perimeter_spacing;
|
||||
|
||||
insert = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(insert))));
|
||||
Polygons inner_pp;
|
||||
for (ExPolygon &ex : infill_contour)
|
||||
ex.simplify_p(params.scaled_resolution, &inner_pp);
|
||||
ExPolygons inner_union = union_ex(inner_pp);
|
||||
float offset1 = -min_perimeter_infill_spacing / 2.;
|
||||
float offset2 = insert + min_perimeter_infill_spacing / 2.;
|
||||
infill_areas = offset2_ex(inner_union, offset1, offset2);
|
||||
append(out_fill_expolygons, offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(insert + min_perimeter_infill_spacing / 2.)));
|
||||
//w21
|
||||
append(out_fill_no_overlap,offset2_ex(inner_union, float(-min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.)));
|
||||
}
|
||||
|
||||
|
||||
// Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper
|
||||
@@ -1111,7 +1078,6 @@ void PerimeterGenerator::process_arachne(
|
||||
const Parameters ¶ms,
|
||||
const Surface &surface,
|
||||
const ExPolygons *lower_slices,
|
||||
const ExPolygons *upper_slices,
|
||||
// Cache:
|
||||
Polygons &lower_slices_polygons_cache,
|
||||
// Output:
|
||||
@@ -1120,9 +1086,7 @@ void PerimeterGenerator::process_arachne(
|
||||
// Gaps without the thin walls
|
||||
ExtrusionEntityCollection & /* out_gap_fill */,
|
||||
// Infills without the gap fills
|
||||
ExPolygons &out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons &out_fill_no_overlap)
|
||||
ExPolygons &out_fill_expolygons)
|
||||
{
|
||||
// other perimeters
|
||||
coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing();
|
||||
@@ -1149,9 +1113,6 @@ void PerimeterGenerator::process_arachne(
|
||||
ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
|
||||
Polygons last_p = to_polygons(last);
|
||||
|
||||
//w16
|
||||
if (upper_slices == nullptr && params.object_config.top_one_wall_type == TopOneWallType::Onlytopmost)
|
||||
loop_number = 0;
|
||||
|
||||
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
|
||||
@@ -1308,21 +1269,10 @@ void PerimeterGenerator::process_arachne(
|
||||
out_loops.append(extrusion_coll);
|
||||
|
||||
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
|
||||
//w17
|
||||
ExPolygons the_layer_surface = infill_contour;
|
||||
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||
if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
|
||||
infill_contour.clear(); // Infill region is too small, so let's filter it out.
|
||||
if (params.object_config.top_one_wall_type != TopOneWallType::Disable) {
|
||||
coord_t perimeter_width = params.perimeter_flow.scaled_width();
|
||||
double min_width_top_surface = (params.object_config.top_area_threshold / 100) *
|
||||
std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4));
|
||||
infill_contour = offset2_ex(infill_contour, -min_width_top_surface, min_width_top_surface + perimeter_width);
|
||||
ExPolygons surface_not_export_to_top = diff_ex(the_layer_surface, infill_contour);
|
||||
}
|
||||
|
||||
// BBS: get real top surface
|
||||
infill_contour = intersection_ex(infill_contour, the_layer_surface);
|
||||
// create one more offset to be used as boundary for fill
|
||||
// we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||
// and then we offset back and forth by half the infill spacing to only consider the
|
||||
@@ -1366,411 +1316,24 @@ void PerimeterGenerator::process_arachne(
|
||||
infill_areas = diff_ex(infill_areas, filled_area);
|
||||
}
|
||||
}
|
||||
//w21
|
||||
append(out_fill_no_overlap, offset2_ex(union_ex(pp),float(-min_perimeter_infill_spacing / 2.), float( min_perimeter_infill_spacing / 2.)));
|
||||
append(out_fill_expolygons, std::move(infill_areas));
|
||||
}
|
||||
|
||||
void PerimeterGenerator::process_with_one_wall_arachne(
|
||||
// Inputs:
|
||||
const Parameters ¶ms,
|
||||
const Surface &surface,
|
||||
const ExPolygons *lower_slices,
|
||||
//w16
|
||||
const ExPolygons *upper_slices,
|
||||
// Cache:
|
||||
Polygons &lower_slices_polygons_cache,
|
||||
Polygons &upper_slices_polygons_cache,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection &out_loops,
|
||||
// Gaps without the thin walls
|
||||
ExtrusionEntityCollection & /* out_gap_fill */,
|
||||
// Infills without the gap fills
|
||||
ExPolygons &out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons &out_fill_no_overlap)
|
||||
{
|
||||
// other perimeters
|
||||
coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing();
|
||||
// external perimeters
|
||||
coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width();
|
||||
coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing();
|
||||
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing()));
|
||||
// solid infill
|
||||
coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing();
|
||||
|
||||
// prepare grown lower layer slices for overhang detection
|
||||
if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) {
|
||||
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
// in the current layer
|
||||
double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1);
|
||||
lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2)));
|
||||
}
|
||||
if (params.config.overhangs && upper_slices != nullptr && upper_slices_polygons_cache.empty()) {
|
||||
double upper_nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder - 1);
|
||||
upper_slices_polygons_cache = offset(*upper_slices, float(scale_(EPSILON)));
|
||||
}
|
||||
|
||||
|
||||
// we need to process each island separately because we might have different
|
||||
// extra perimeters for each one
|
||||
// detect how many perimeters must be generated for this island
|
||||
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
||||
ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
|
||||
Polygons last_p = to_polygons(last);
|
||||
|
||||
int remain_loops = -1;
|
||||
if (params.object_config.top_one_wall_type == TopOneWallType::Alltop) {
|
||||
if (upper_slices != nullptr)
|
||||
remain_loops = loop_number - 1;
|
||||
|
||||
loop_number = 0;
|
||||
}
|
||||
|
||||
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
|
||||
loop_number = int(perimeters.size()) - 1;
|
||||
|
||||
|
||||
|
||||
//w16
|
||||
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
|
||||
ExPolygons inner_infill_contour;
|
||||
|
||||
if (remain_loops >= 0) {
|
||||
|
||||
ExPolygons the_layer_surface = infill_contour;
|
||||
BoundingBox infill_contour_box = get_extents(infill_contour);
|
||||
infill_contour_box.offset(SCALED_EPSILON);
|
||||
Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slices_polygons_cache,
|
||||
infill_contour_box);
|
||||
|
||||
infill_contour = diff_ex(infill_contour, upper_polygons_series_clipped);
|
||||
|
||||
coord_t perimeter_width = params.perimeter_flow.scaled_width();
|
||||
if (lower_slices != nullptr) {
|
||||
BoundingBox infill_contour_box = get_extents(infill_contour);
|
||||
infill_contour_box.offset(SCALED_EPSILON);
|
||||
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache,
|
||||
infill_contour_box);
|
||||
|
||||
ExPolygons bridge_area = offset_ex(diff_ex(infill_contour, lower_polygons_series_clipped),
|
||||
std::max(ext_perimeter_spacing, perimeter_width));
|
||||
infill_contour = diff_ex(infill_contour, bridge_area);
|
||||
}
|
||||
//w17
|
||||
// double min_width_top_surface = std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4));
|
||||
double min_width_top_surface = (params.object_config.top_area_threshold / 100) * std::max(double(ext_perimeter_spacing / 4 + 10), double(perimeter_width / 4));
|
||||
infill_contour = offset2_ex(infill_contour, -min_width_top_surface, min_width_top_surface + perimeter_width);
|
||||
|
||||
ExPolygons surface_not_export_to_top = diff_ex(the_layer_surface, infill_contour);
|
||||
|
||||
infill_contour = intersection_ex(infill_contour, the_layer_surface);
|
||||
Polygons surface_not_export_to_top_p = to_polygons(surface_not_export_to_top);
|
||||
Arachne::WallToolPaths innerWallToolPaths(surface_not_export_to_top_p, perimeter_spacing, perimeter_spacing,
|
||||
coord_t(remain_loops + 1), 0, params.layer_height, params.object_config, params.print_config);
|
||||
|
||||
std::vector<Arachne::VariableWidthLines> perimeters_inner = innerWallToolPaths.getToolPaths();
|
||||
remain_loops = int(perimeters_inner.size()) - 1;
|
||||
if (!perimeters.empty()) {
|
||||
for (int perimeter_idx = 0; perimeter_idx < perimeters_inner.size(); perimeter_idx++) {
|
||||
if (perimeters_inner[perimeter_idx].empty())
|
||||
continue;
|
||||
|
||||
for (Arachne::ExtrusionLine &wall : perimeters_inner[perimeter_idx]) {
|
||||
wall.inset_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
perimeters.insert(perimeters.end(), perimeters_inner.begin(), perimeters_inner.end());
|
||||
|
||||
inner_infill_contour = union_ex(innerWallToolPaths.getInnerContour());
|
||||
}
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
|
||||
}
|
||||
#endif
|
||||
|
||||
// All closed ExtrusionLine should have the same the first and the last point.
|
||||
// But in rare cases, Arachne produce ExtrusionLine marked as closed but without
|
||||
// equal the first and the last point.
|
||||
assert([&perimeters = std::as_const(perimeters)]() -> bool {
|
||||
for (const Arachne::VariableWidthLines &perimeter : perimeters)
|
||||
for (const Arachne::ExtrusionLine &el : perimeter)
|
||||
if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
|
||||
return false;
|
||||
return true;
|
||||
}());
|
||||
|
||||
int start_perimeter = int(perimeters.size()) - 1;
|
||||
int end_perimeter = -1;
|
||||
int direction = -1;
|
||||
|
||||
if (params.config.external_perimeters_first) {
|
||||
start_perimeter = 0;
|
||||
end_perimeter = int(perimeters.size());
|
||||
direction = 1;
|
||||
}
|
||||
|
||||
std::vector<Arachne::ExtrusionLine *> all_extrusions;
|
||||
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
|
||||
if (perimeters[perimeter_idx].empty())
|
||||
continue;
|
||||
for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx])
|
||||
all_extrusions.emplace_back(&wall);
|
||||
}
|
||||
|
||||
// Find topological order with constraints from extrusions_constrains.
|
||||
std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
|
||||
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
|
||||
ankerl::unordered_dense::map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx;
|
||||
for (size_t idx = 0; idx < all_extrusions.size(); idx++)
|
||||
map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
|
||||
|
||||
Arachne::WallToolPaths::ExtrusionLineSet extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first);
|
||||
for (auto [before, after] : extrusions_constrains) {
|
||||
auto after_it = map_extrusion_to_idx.find(after);
|
||||
++blocked[after_it->second];
|
||||
blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
|
||||
}
|
||||
|
||||
std::vector<bool> processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
|
||||
Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
|
||||
std::vector<PerimeterGeneratorArachneExtrusion> ordered_extrusions; // To store our result in. At the end we'll std::swap.
|
||||
ordered_extrusions.reserve(all_extrusions.size());
|
||||
|
||||
while (ordered_extrusions.size() < all_extrusions.size()) {
|
||||
size_t best_candidate = 0;
|
||||
double best_distance_sqr = std::numeric_limits<double>::max();
|
||||
bool is_best_closed = false;
|
||||
|
||||
std::vector<size_t> available_candidates;
|
||||
for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
|
||||
if (processed[candidate] || blocked[candidate])
|
||||
continue; // Not a valid candidate.
|
||||
available_candidates.push_back(candidate);
|
||||
}
|
||||
|
||||
std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
|
||||
return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
|
||||
});
|
||||
|
||||
for (const size_t candidate_path_idx : available_candidates) {
|
||||
auto& path = all_extrusions[candidate_path_idx];
|
||||
|
||||
if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end.
|
||||
if (best_distance_sqr == std::numeric_limits<double>::max()) {
|
||||
best_candidate = candidate_path_idx;
|
||||
is_best_closed = path->is_closed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const Point candidate_position = path->junctions.front().p;
|
||||
double distance_sqr = (current_position - candidate_position).cast<double>().norm();
|
||||
if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
|
||||
if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits<double>::max()) || (!path->is_closed && !is_best_closed)) {
|
||||
best_candidate = candidate_path_idx;
|
||||
best_distance_sqr = distance_sqr;
|
||||
is_best_closed = path->is_closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto &best_path = all_extrusions[best_candidate];
|
||||
ordered_extrusions.push_back({best_path, best_path->is_contour(), false});
|
||||
processed[best_candidate] = true;
|
||||
for (size_t unlocked_idx : blocking[best_candidate])
|
||||
blocked[unlocked_idx]--;
|
||||
|
||||
if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
|
||||
if(best_path->is_closed)
|
||||
current_position = best_path->junctions[0].p; //We end where we started.
|
||||
else
|
||||
current_position = best_path->junctions.back().p; //Pick the other end from where we started.
|
||||
}
|
||||
}
|
||||
|
||||
if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) {
|
||||
std::vector<PerimeterGeneratorArachneExtrusion *> closed_loop_extrusions;
|
||||
for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions)
|
||||
if (extrusion.extrusion->inset_idx == 0) {
|
||||
if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
|
||||
closed_loop_extrusions.emplace_back(&extrusion);
|
||||
} else {
|
||||
extrusion.fuzzify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.config.fuzzy_skin == FuzzySkinType::External) {
|
||||
ClipperLib_Z::Paths loops_paths;
|
||||
loops_paths.reserve(closed_loop_extrusions.size());
|
||||
for (const auto &cl_extrusion : closed_loop_extrusions) {
|
||||
assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back());
|
||||
size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front();
|
||||
ClipperLib_Z::Path loop_path;
|
||||
loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1);
|
||||
for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it)
|
||||
loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx);
|
||||
loops_paths.emplace_back(loop_path);
|
||||
}
|
||||
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true);
|
||||
ClipperLib_Z::PolyTree loops_polytree;
|
||||
clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
|
||||
|
||||
for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) {
|
||||
// The whole contour must have the same index.
|
||||
coord_t polygon_idx = child_node->Contour.front().z();
|
||||
bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(),
|
||||
[&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); });
|
||||
if (has_same_idx)
|
||||
closed_loop_extrusions[polygon_idx]->fuzzify = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty())
|
||||
out_loops.append(extrusion_coll);
|
||||
|
||||
|
||||
//w16
|
||||
if (remain_loops >= 0) {
|
||||
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||
if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
|
||||
infill_contour.clear();
|
||||
coord_t inset = (loop_number < 0) ? 0 :
|
||||
(loop_number == 0) ?
|
||||
// one loop
|
||||
ext_perimeter_spacing :
|
||||
// two or more loops?
|
||||
perimeter_spacing;
|
||||
|
||||
inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset))));
|
||||
Polygons pp;
|
||||
for (ExPolygon &ex : infill_contour)
|
||||
ex.simplify_p(params.scaled_resolution, &pp);
|
||||
// collapse too narrow infill areas
|
||||
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
||||
// append infill areas to fill_surfaces
|
||||
ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.),
|
||||
float(inset + min_perimeter_infill_spacing / 2.));
|
||||
// ExPolygons infill_areas;
|
||||
|
||||
if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs &&
|
||||
params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) {
|
||||
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
|
||||
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, lower_slices_polygons_cache,
|
||||
loop_number + 1, params.overhang_flow,
|
||||
params.scaled_resolution, params.object_config,
|
||||
params.print_config);
|
||||
if (!extra_perimeters.empty()) {
|
||||
ExtrusionEntityCollection &this_islands_perimeters = static_cast<ExtrusionEntityCollection &>(*out_loops.entities.back());
|
||||
ExtrusionEntitiesPtr old_entities;
|
||||
old_entities.swap(this_islands_perimeters.entities);
|
||||
for (ExtrusionPaths &paths : extra_perimeters)
|
||||
this_islands_perimeters.append(std::move(paths));
|
||||
append(this_islands_perimeters.entities, old_entities);
|
||||
infill_areas = diff_ex(infill_areas, filled_area);
|
||||
}
|
||||
}
|
||||
|
||||
inset = (loop_number < 0) ? 0 :
|
||||
(loop_number == 0) ?
|
||||
// one loop
|
||||
ext_perimeter_spacing :
|
||||
// two or more loops?
|
||||
perimeter_spacing;
|
||||
|
||||
inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset))));
|
||||
for (ExPolygon &ex : infill_contour)
|
||||
ex.simplify_p(params.scaled_resolution, &pp);
|
||||
// collapse too narrow infill areas
|
||||
|
||||
|
||||
if (remain_loops >= 0) {
|
||||
//w21
|
||||
add_infill_contour_for_arachne(infill_contour, loop_number, ext_perimeter_spacing, perimeter_spacing,
|
||||
min_perimeter_infill_spacing, spacing, true, params, infill_areas, out_fill_expolygons,out_fill_no_overlap);
|
||||
}
|
||||
|
||||
|
||||
if (remain_loops >= 0) {
|
||||
if (!inner_infill_contour.empty())
|
||||
//w21
|
||||
add_infill_contour_for_arachne(inner_infill_contour, remain_loops, ext_perimeter_spacing, perimeter_spacing,
|
||||
min_perimeter_infill_spacing, spacing, true, params, infill_areas, out_fill_expolygons,out_fill_no_overlap);
|
||||
}
|
||||
//w21
|
||||
append(out_fill_no_overlap,
|
||||
offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.)));
|
||||
append(out_fill_expolygons, std::move(infill_areas));
|
||||
} else {
|
||||
infill_contour = union_ex(wallToolPaths.getInnerContour());
|
||||
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||
if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
|
||||
infill_contour.clear(); // Infill region is too small, so let's filter it out.
|
||||
|
||||
coord_t inset = (loop_number < 0) ? 0 : (loop_number == 0) ? ext_perimeter_spacing : perimeter_spacing;
|
||||
|
||||
inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset))));
|
||||
Polygons pp;
|
||||
for (ExPolygon &ex : infill_contour)
|
||||
ex.simplify_p(params.scaled_resolution, &pp);
|
||||
// collapse too narrow infill areas
|
||||
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
||||
// append infill areas to fill_surfaces
|
||||
ExPolygons infill_areas = offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.),
|
||||
float(inset + min_perimeter_infill_spacing / 2.));
|
||||
|
||||
if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs &&
|
||||
params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) {
|
||||
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
|
||||
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, lower_slices_polygons_cache,
|
||||
loop_number + 1, params.overhang_flow,
|
||||
params.scaled_resolution, params.object_config,
|
||||
params.print_config);
|
||||
if (!extra_perimeters.empty()) {
|
||||
ExtrusionEntityCollection &this_islands_perimeters = static_cast<ExtrusionEntityCollection &>(*out_loops.entities.back());
|
||||
ExtrusionEntitiesPtr old_entities;
|
||||
old_entities.swap(this_islands_perimeters.entities);
|
||||
for (ExtrusionPaths &paths : extra_perimeters)
|
||||
this_islands_perimeters.append(std::move(paths));
|
||||
append(this_islands_perimeters.entities, old_entities);
|
||||
infill_areas = diff_ex(infill_areas, filled_area);
|
||||
}
|
||||
}
|
||||
//w21
|
||||
append(out_fill_no_overlap,offset2_ex(union_ex(pp), float(-min_perimeter_infill_spacing / 2.), float(min_perimeter_infill_spacing / 2.)));
|
||||
append(out_fill_expolygons, std::move(infill_areas));
|
||||
}
|
||||
}
|
||||
|
||||
void PerimeterGenerator::process_classic(
|
||||
// Inputs:
|
||||
const Parameters ¶ms,
|
||||
const Surface &surface,
|
||||
const ExPolygons *lower_slices,
|
||||
//w16
|
||||
const ExPolygons *upper_slices,
|
||||
// Cache:
|
||||
Polygons &lower_layer_polygons_cache,
|
||||
Polygons &upper_layer_polygons_cache,
|
||||
Polygons &lower_slices_polygons_cache,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection &out_loops,
|
||||
// Gaps without the thin walls
|
||||
ExtrusionEntityCollection &out_gap_fill,
|
||||
// Infills without the gap fills
|
||||
ExPolygons &out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons &out_fill_no_overlap)
|
||||
ExPolygons &out_fill_expolygons)
|
||||
{
|
||||
// other perimeters
|
||||
coord_t perimeter_width = params.perimeter_flow.scaled_width();
|
||||
@@ -1797,12 +1360,12 @@ void PerimeterGenerator::process_classic(
|
||||
bool has_gap_fill = params.config.gap_fill_enabled.value && params.config.gap_fill_speed.value > 0;
|
||||
|
||||
// prepare grown lower layer slices for overhang detection
|
||||
if (params.config.overhangs && lower_slices != nullptr && lower_layer_polygons_cache.empty()) {
|
||||
if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) {
|
||||
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
// in the current layer
|
||||
double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1);
|
||||
lower_layer_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter / 2)));
|
||||
lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2)));
|
||||
}
|
||||
|
||||
// we need to process each island separately because we might have different
|
||||
@@ -1811,17 +1374,6 @@ void PerimeterGenerator::process_classic(
|
||||
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution));
|
||||
ExPolygons gaps;
|
||||
//w16
|
||||
ExPolygons fill_clip;
|
||||
ExPolygons top_fills;
|
||||
//w16
|
||||
|
||||
if (params.config.overhangs && upper_slices != nullptr && upper_layer_polygons_cache.empty()) {
|
||||
double upper_nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder - 1);
|
||||
upper_layer_polygons_cache = offset(*upper_slices, float(scale_(+upper_nozzle_diameter / 2)));
|
||||
}
|
||||
if (loop_number > 0 && params.object_config.top_one_wall_type != TopOneWallType::Disable && upper_slices == nullptr)
|
||||
loop_number = 0;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||
@@ -1861,22 +1413,20 @@ void PerimeterGenerator::process_classic(
|
||||
//FIXME Is this offset correct if the line width of the inner perimeters differs
|
||||
// from the line width of the infill?
|
||||
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||
//w16
|
||||
//offsets = params.config.thin_walls ?
|
||||
offsets = params.config.thin_walls ?
|
||||
// This path will ensure, that the perimeters do not overfill, as in
|
||||
// qidi3d/Slic3r GH #32, but with the cost of rounding the perimeters
|
||||
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
|
||||
// excessively, creating gaps, which then need to be filled in by the not very
|
||||
// reliable gap fill algorithm.
|
||||
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
|
||||
// the original.
|
||||
//offset2_ex(last,
|
||||
// - float(distance + min_spacing / 2. - 1.),
|
||||
// float(min_spacing / 2. - 1.)) :
|
||||
offset2_ex(last,
|
||||
- float(distance + min_spacing / 2. - 1.),
|
||||
float(min_spacing / 2. - 1.)) :
|
||||
// If "detect thin walls" is not enabled, this paths will be entered, which
|
||||
// leads to overflows, as in qidi3d/Slic3r GH #32
|
||||
// offset_ex(last, - float(distance));
|
||||
// leads to overflows, as in prusa3d/Slic3r GH #32
|
||||
offset_ex(last, - float(distance));
|
||||
// look for gaps
|
||||
offsets = offset2_ex(last, -float(distance + min_spacing / 2. - 1.), float(min_spacing / 2. - 1.));
|
||||
if (has_gap_fill)
|
||||
// not using safety offset here would "detect" very narrow gaps
|
||||
// (but still long enough to escape the area threshold) that gap fill
|
||||
@@ -1915,51 +1465,6 @@ void PerimeterGenerator::process_classic(
|
||||
}
|
||||
last = std::move(offsets);
|
||||
|
||||
//w16
|
||||
if (i == 0 && i != loop_number && params.object_config.top_one_wall_type == TopOneWallType::Alltop &&
|
||||
upper_slices != nullptr) {
|
||||
coord_t offset_top_surface = scale_(
|
||||
1.5 * (params.config.perimeters.value == 0 ?
|
||||
0. :
|
||||
unscaled(double(ext_perimeter_width + perimeter_spacing * int(int(params.config.perimeters.value) - int(1))))));
|
||||
if (offset_top_surface > 0.9 * (params.config.perimeters.value <= 1 ? 0. : (perimeter_spacing * (params.config.perimeters.value - 1))))
|
||||
offset_top_surface -= coord_t(
|
||||
0.9 * (params.config.perimeters.value <= 1 ? 0. : (perimeter_spacing * (params.config.perimeters.value - 1))));
|
||||
else
|
||||
offset_top_surface = 0;
|
||||
//w17
|
||||
double min_width_top_surface = (params.object_config.top_area_threshold/100 ) *
|
||||
std::max(double(ext_perimeter_spacing / 2 + 10), 1.0 * (double(perimeter_width)));
|
||||
BoundingBox last_box = get_extents(last);
|
||||
last_box.offset(SCALED_EPSILON);
|
||||
Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_layer_polygons_cache,
|
||||
last_box);
|
||||
upper_polygons_series_clipped = offset(upper_polygons_series_clipped, min_width_top_surface);
|
||||
fill_clip = offset_ex(last, -double(ext_perimeter_spacing));
|
||||
ExPolygons bridge_checker;
|
||||
if (lower_slices != nullptr) {
|
||||
Polygons lower_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_layer_polygons_cache,
|
||||
last_box);
|
||||
|
||||
double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width)));
|
||||
bridge_checker = offset_ex(diff_ex(last, lower_polygons_series_clipped, ApplySafetyOffset::Yes), 1.5 * bridge_offset);
|
||||
}
|
||||
ExPolygons delete_bridge = diff_ex(last, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
ExPolygons temp_gap = diff_ex(top_polygons, fill_clip);
|
||||
ExPolygons inner_polygons = diff_ex(last,
|
||||
offset_ex(top_polygons, offset_top_surface + min_width_top_surface -
|
||||
double(ext_perimeter_spacing / 2)),
|
||||
ApplySafetyOffset::Yes);
|
||||
top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes);
|
||||
top_fills = union_ex(top_fills, top_polygons);
|
||||
double infill_spacing_unscaled = params.config.infill_extrusion_width.value; // this->config->sparse_infill_line_width.value;
|
||||
fill_clip = offset_ex(last, double(ext_perimeter_spacing / 2) - scale_(infill_spacing_unscaled / 2));
|
||||
last = intersection_ex(inner_polygons, last);
|
||||
if (has_gap_fill)
|
||||
last = union_ex(last, temp_gap);
|
||||
}
|
||||
|
||||
|
||||
if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) {
|
||||
@@ -2024,7 +1529,7 @@ void PerimeterGenerator::process_classic(
|
||||
}
|
||||
}
|
||||
// at this point, all loops should be in contours[0]
|
||||
ExtrusionEntityCollection entities = traverse_loops_classic(params, lower_layer_polygons_cache, contours.front(), thin_walls);
|
||||
ExtrusionEntityCollection entities = traverse_loops_classic(params, lower_slices_polygons_cache, contours.front(), thin_walls);
|
||||
// if brim will be printed, reverse the order of perimeters so that
|
||||
// we continue inwards after having finished the brim
|
||||
// TODO: add test for perimeter order
|
||||
@@ -2075,14 +1580,9 @@ void PerimeterGenerator::process_classic(
|
||||
ext_perimeter_spacing / 2 :
|
||||
// two or more loops?
|
||||
perimeter_spacing / 2;
|
||||
//w21
|
||||
coord_t infill_peri_overlap = 0;
|
||||
// only apply infill overlap if we actually have one perimeter
|
||||
//w21
|
||||
if (inset > 0) {
|
||||
infill_peri_overlap = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>( solid_infill_spacing / 2))));
|
||||
inset -= infill_peri_overlap;
|
||||
}
|
||||
if (inset > 0)
|
||||
inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
|
||||
// simplify infill contours according to resolution
|
||||
Polygons pp;
|
||||
for (ExPolygon &ex : last)
|
||||
@@ -2096,34 +1596,12 @@ void PerimeterGenerator::process_classic(
|
||||
float(- inset - min_perimeter_infill_spacing / 2.),
|
||||
float(min_perimeter_infill_spacing / 2.));
|
||||
|
||||
ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
|
||||
//w21
|
||||
if (!top_fills.empty()) {
|
||||
infill_areas = union_ex(infill_areas, offset_ex(top_infill_exp, double(infill_peri_overlap)));
|
||||
}
|
||||
append(out_fill_expolygons, std::move(top_infill_exp));
|
||||
//w21
|
||||
{
|
||||
ExPolygons polyWithoutOverlap;
|
||||
if (min_perimeter_infill_spacing / 2 > infill_peri_overlap)
|
||||
polyWithoutOverlap = offset2_ex(
|
||||
union_ex(pp),
|
||||
float(-inset - min_perimeter_infill_spacing / 2.),
|
||||
float(min_perimeter_infill_spacing / 2 - infill_peri_overlap));
|
||||
else
|
||||
polyWithoutOverlap = offset_ex(
|
||||
union_ex(pp),
|
||||
double(-inset - infill_peri_overlap));
|
||||
if (!top_fills.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp);
|
||||
out_fill_no_overlap.insert(out_fill_no_overlap.end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
||||
}
|
||||
|
||||
if (lower_slices != nullptr && params.config.overhangs && params.config.extra_perimeters_on_overhangs &&
|
||||
params.config.perimeters > 0 && params.layer_id > params.object_config.raft_layers) {
|
||||
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
|
||||
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
|
||||
lower_layer_polygons_cache,
|
||||
lower_slices_polygons_cache,
|
||||
loop_number + 1,
|
||||
params.overhang_flow, params.scaled_resolution,
|
||||
params.object_config, params.print_config);
|
||||
|
||||
@@ -70,28 +70,21 @@ void process_classic(
|
||||
const Parameters ¶ms,
|
||||
const Surface &surface,
|
||||
const ExPolygons *lower_slices,
|
||||
//w16
|
||||
const ExPolygons *upper_slices,
|
||||
// Cache:
|
||||
Polygons &lower_layer_polygons_cache,
|
||||
Polygons &upper_layer_polygons_cache,
|
||||
Polygons &lower_slices_polygons_cache,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection &out_loops,
|
||||
// Gaps without the thin walls
|
||||
ExtrusionEntityCollection &out_gap_fill,
|
||||
// Infills without the gap fills
|
||||
ExPolygons &out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons &out_fill_no_overlap);
|
||||
ExPolygons &out_fill_expolygons);
|
||||
|
||||
void process_arachne(
|
||||
// Inputs:
|
||||
const Parameters ¶ms,
|
||||
const Surface & surface,
|
||||
const ExPolygons *lower_slices,
|
||||
//w16
|
||||
const ExPolygons *upper_slices,
|
||||
// Cache:
|
||||
Polygons &lower_slices_polygons_cache,
|
||||
// Output:
|
||||
@@ -100,43 +93,7 @@ void process_arachne(
|
||||
// Gaps without the thin walls
|
||||
ExtrusionEntityCollection &out_gap_fill,
|
||||
// Infills without the gap fills
|
||||
ExPolygons &out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons &out_fill_no_overlap);
|
||||
|
||||
void process_with_one_wall_arachne(
|
||||
// Inputs:
|
||||
const Parameters ¶ms,
|
||||
const Surface &surface,
|
||||
const ExPolygons *lower_slices,
|
||||
//w16
|
||||
const ExPolygons *upper_slices,
|
||||
// Cache:
|
||||
Polygons &lower_slices_polygons_cache,
|
||||
Polygons &upper_slices_polygons_cache,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection &out_loops,
|
||||
// Gaps without the thin walls
|
||||
ExtrusionEntityCollection &out_gap_fill,
|
||||
// Infills without the gap fills
|
||||
ExPolygons &out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons &out_fill_no_overlap);
|
||||
|
||||
//w16
|
||||
void add_infill_contour_for_arachne(ExPolygons infill_contour,
|
||||
int loops,
|
||||
coord_t ext_perimeter_spacing,
|
||||
coord_t perimeter_spacing,
|
||||
coord_t min_perimeter_infill_spacing,
|
||||
coord_t spacing,
|
||||
bool is_inner_part,
|
||||
const Parameters ¶ms,
|
||||
ExPolygons & infill_areas,
|
||||
ExPolygons & out_fill_expolygons,
|
||||
//w21
|
||||
ExPolygons & out_fill_no_overlap);
|
||||
ExPolygons &out_fill_expolygons);
|
||||
|
||||
ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance);
|
||||
|
||||
|
||||
@@ -442,7 +442,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3",
|
||||
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
|
||||
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration",
|
||||
"external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", "travel_acceleration",
|
||||
"external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", "travel_acceleration", "wipe_tower_acceleration",
|
||||
"bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
|
||||
"min_skirt_length", "brim_width", "brim_separation", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
@@ -486,8 +486,8 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
static std::vector<std::string> s_Preset_filament_options {
|
||||
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
|
||||
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
|
||||
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
|
||||
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
|
||||
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_stamping_loading_speed", "filament_stamping_distance",
|
||||
"filament_cooling_initial_speed", "filament_purge_multiplier", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
|
||||
"filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow",
|
||||
"temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
|
||||
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
|
||||
@@ -536,8 +536,8 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
|
||||
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
|
||||
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
|
||||
"default_print_profile", "inherits",
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "multimaterial_purging",
|
||||
"max_print_height", "default_print_profile", "inherits",
|
||||
"remaining_times", "silent_mode",
|
||||
"machine_limits_usage", "thumbnails", "thumbnails_format",
|
||||
//Y20 //B52
|
||||
@@ -2276,6 +2276,13 @@ const std::string& ExtruderFilaments::get_preset_name_by_alias(const std::string
|
||||
return alias;
|
||||
}
|
||||
|
||||
void ExtruderFilaments::select_filament(size_t idx)
|
||||
{
|
||||
assert(idx == size_t(-1) || idx < m_extr_filaments.size());
|
||||
// Check idx befor saving it's value to m_idx_selected.
|
||||
// Invalidate m_idx_selected, if idx is out of range m_extr_filaments
|
||||
m_idx_selected = (idx == size_t(-1) || idx < m_extr_filaments.size()) ? idx : size_t(-1);
|
||||
}
|
||||
bool ExtruderFilaments::select_filament(const std::string &name_w_suffix, bool force/*= false*/)
|
||||
{
|
||||
std::string name = Preset::remove_suffix_modified(name_w_suffix);
|
||||
|
||||
@@ -894,7 +894,7 @@ public:
|
||||
// Select filament by the full filament name, which contains name of filament, separator and name of selected preset
|
||||
// If full_name doesn't contain name of selected preset, then select first preset in the list for this filament
|
||||
bool select_filament(const std::string& name, bool force = false);
|
||||
void select_filament(size_t idx) { m_idx_selected = idx; }
|
||||
void select_filament(size_t idx);
|
||||
|
||||
std::string get_selected_preset_name() const { return m_idx_selected == size_t(-1) ? std::string() : m_extr_filaments[m_idx_selected].preset->name; }
|
||||
const Preset* get_selected_preset() const { return m_idx_selected == size_t(-1) ? nullptr : m_extr_filaments[m_idx_selected].preset; }
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Slic3r {
|
||||
|
||||
static std::vector<std::string> s_project_options {
|
||||
"colorprint_heights",
|
||||
"wiping_volumes_extruders",
|
||||
"wiping_volumes_matrix"
|
||||
"wiping_volumes_matrix",
|
||||
"wiping_volumes_use_custom_matrix"
|
||||
};
|
||||
|
||||
const char *PresetBundle::QIDI_BUNDLE = "QIDITechnology";
|
||||
@@ -1202,6 +1202,7 @@ ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
|
||||
load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset_name(), true);
|
||||
|
||||
this->extruders_filaments.clear();
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments));
|
||||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 1; i < std::min(tmp_bundle.extruders_filaments.size(), this->extruders_filaments.size()); ++i)
|
||||
this->extruders_filaments[i].select_filament(load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.extruders_filaments[i].get_selected_preset_name(), false));
|
||||
@@ -1691,6 +1692,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
|
||||
|
||||
// Extruder_filaments have to be recreated with new loaded filaments
|
||||
this->extruders_filaments.clear();
|
||||
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments));
|
||||
this->update_multi_material_filament_presets();
|
||||
for (size_t i = 0; i < std::min(this->extruders_filaments.size(), active_filaments.size()); ++ i)
|
||||
this->extruders_filaments[i].select_filament(filaments.find_preset(active_filaments[i], true)->name);
|
||||
@@ -1729,27 +1731,21 @@ void PresetBundle::update_multi_material_filament_presets()
|
||||
|
||||
// Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator):
|
||||
std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values;
|
||||
size_t old_number_of_extruders = size_t(sqrt(old_matrix.size())+EPSILON);
|
||||
size_t old_number_of_extruders = size_t(std::sqrt(old_matrix.size())+EPSILON);
|
||||
if (num_extruders != old_number_of_extruders) {
|
||||
// First verify if purging volumes presets for each extruder matches number of extruders
|
||||
std::vector<double>& extruders = this->project_config.option<ConfigOptionFloats>("wiping_volumes_extruders")->values;
|
||||
while (extruders.size() < 2*num_extruders) {
|
||||
extruders.push_back(extruders.size()>1 ? extruders[0] : 50.); // copy the values from the first extruder
|
||||
extruders.push_back(extruders.size()>1 ? extruders[1] : 50.);
|
||||
}
|
||||
while (extruders.size() > 2*num_extruders) {
|
||||
extruders.pop_back();
|
||||
extruders.pop_back();
|
||||
}
|
||||
// Extract the relevant config options, even values from possibly modified presets.
|
||||
const double default_purge = static_cast<const ConfigOptionFloat*>(this->printers.get_edited_preset().config.option("multimaterial_purging"))->value;
|
||||
const std::vector<double> filament_purging_multipliers = get_config_options_for_current_filaments<ConfigOptionPercents>("filament_purge_multiplier");
|
||||
|
||||
std::vector<double> new_matrix;
|
||||
for (unsigned int i=0;i<num_extruders;++i)
|
||||
for (unsigned int i=0;i<num_extruders;++i) {
|
||||
for (unsigned int j=0;j<num_extruders;++j) {
|
||||
// append the value for this pair from the old matrix (if it's there):
|
||||
if (i<old_number_of_extruders && j<old_number_of_extruders)
|
||||
new_matrix.push_back(old_matrix[i*old_number_of_extruders + j]);
|
||||
else
|
||||
new_matrix.push_back( i==j ? 0. : extruders[2*i]+extruders[2*j+1]); // so it matches new extruder volumes
|
||||
new_matrix.push_back( i==j ? 0. : default_purge * filament_purging_multipliers[j] / 100.);
|
||||
}
|
||||
}
|
||||
this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values = new_matrix;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,27 @@ public:
|
||||
void cache_extruder_filaments_names();
|
||||
void reset_extruder_filaments();
|
||||
|
||||
// Another hideous function related to current ExtruderFilaments hack. Returns a vector of values
|
||||
// of a given config option for all currently used filaments. Modified value is returned for modified preset.
|
||||
// Must be called with the vector ConfigOption type, e.g. ConfigOptionPercents.
|
||||
template <class T>
|
||||
auto get_config_options_for_current_filaments(const t_config_option_key& key)
|
||||
{
|
||||
decltype(T::values) out;
|
||||
const Preset& edited_preset = this->filaments.get_edited_preset();
|
||||
for (const ExtruderFilaments& extr_filament : this->extruders_filaments) {
|
||||
const Preset& selected_preset = *extr_filament.get_selected_preset();
|
||||
const Preset& preset = edited_preset.name == selected_preset.name ? edited_preset : selected_preset;
|
||||
const T* co = preset.config.opt<T>(key);
|
||||
if (co) {
|
||||
assert(co->values.size() == 1);
|
||||
out.push_back(co->values.back());
|
||||
} else {
|
||||
// Key is missing or type mismatch.
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
PresetCollection& get_presets(Preset::Type preset_type);
|
||||
|
||||
// The project configuration values are kept separated from the print/filament/printer preset,
|
||||
|
||||
@@ -159,6 +159,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"use_volumetric_e",
|
||||
"variable_layer_height",
|
||||
"wipe",
|
||||
"wipe_tower_acceleration",
|
||||
//w15
|
||||
"wipe_distance"
|
||||
};
|
||||
@@ -208,9 +209,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "filament_unloading_speed_start"
|
||||
|| opt_key == "filament_toolchange_delay"
|
||||
|| opt_key == "filament_cooling_moves"
|
||||
|| opt_key == "filament_stamping_loading_speed"
|
||||
|| opt_key == "filament_stamping_distance"
|
||||
|| opt_key == "filament_minimal_purge_on_wipe_tower"
|
||||
|| opt_key == "filament_cooling_initial_speed"
|
||||
|| opt_key == "filament_cooling_final_speed"
|
||||
|| opt_key == "filament_purge_multiplier"
|
||||
|| opt_key == "filament_ramming_parameters"
|
||||
|| opt_key == "filament_multitool_ramming"
|
||||
|| opt_key == "filament_multitool_ramming_volume"
|
||||
@@ -228,13 +232,16 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "wipe_tower_cone_angle"
|
||||
|| opt_key == "wipe_tower_bridging"
|
||||
|| opt_key == "wipe_tower_extra_spacing"
|
||||
|| opt_key == "wipe_tower_extra_flow"
|
||||
|| opt_key == "wipe_tower_no_sparse_layers"
|
||||
|| opt_key == "wipe_tower_extruder"
|
||||
|| opt_key == "wiping_volumes_matrix"
|
||||
|| opt_key == "wiping_volumes_use_custom_matrix"
|
||||
|| opt_key == "parking_pos_retraction"
|
||||
|| opt_key == "cooling_tube_retraction"
|
||||
|| opt_key == "cooling_tube_length"
|
||||
|| opt_key == "extra_loading_move"
|
||||
|| opt_key == "multimaterial_purging"
|
||||
|| opt_key == "travel_speed"
|
||||
|| opt_key == "travel_speed_z"
|
||||
|| opt_key == "first_layer_speed"
|
||||
|
||||
@@ -1117,6 +1117,22 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloats { 0. });
|
||||
|
||||
def = this->add("filament_stamping_loading_speed", coFloats);
|
||||
def->label = L("Stamping loading speed");
|
||||
def->tooltip = L("Speed used for stamping.");
|
||||
def->sidetext = L("mm/s");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloats { 20. });
|
||||
|
||||
def = this->add("filament_stamping_distance", coFloats);
|
||||
def->label = L("Stamping distance measured from the center of the cooling tube");
|
||||
def->tooltip = L("If set to nonzero value, filament is moved toward the nozzle between the individual cooling moves (\"stamping\"). "
|
||||
"This option configures how long this movement should be before the filament is retracted again.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloats { 0. });
|
||||
def = this->add("filament_cooling_moves", coInts);
|
||||
def->label = L("Number of cooling moves");
|
||||
def->tooltip = L("Filament is cooled by being moved back and forth in the "
|
||||
@@ -1153,6 +1169,15 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloats { 3.4 });
|
||||
|
||||
def = this->add("filament_purge_multiplier", coPercents);
|
||||
def->label = L("Purge volume multiplier");
|
||||
def->tooltip = L("Purging volume on the wipe tower is determined by 'multimaterial_purging' in Printer Settings. "
|
||||
"This option allows to modify the volume on filament level. "
|
||||
"Note that the project can override this by setting project-specific values.");
|
||||
def->sidetext = L("%");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionPercents { 100 });
|
||||
def = this->add("filament_load_time", coFloats);
|
||||
def->label = L("Filament load time");
|
||||
def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator.");
|
||||
@@ -1614,6 +1639,14 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
def = this->add("wipe_tower_acceleration", coFloat);
|
||||
def->label = L("Wipe tower");
|
||||
def->tooltip = L("This is the acceleration your printer will use for wipe tower. Set zero to disable "
|
||||
"acceleration control for the wipe tower.");
|
||||
def->sidetext = L("mm/s²");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
def = this->add("travel_acceleration", coFloat);
|
||||
def->label = L("Travel");
|
||||
def->tooltip = L("This is the acceleration your printer will use for travel moves. Set zero to disable "
|
||||
@@ -2214,6 +2247,13 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(-2.));
|
||||
|
||||
def = this->add("multimaterial_purging", coFloat);
|
||||
def->label = L("Purging volume");
|
||||
def->tooltip = L("Determines purging volume on the wipe tower. This can be modified in Filament Settings "
|
||||
"('filament_purge_multiplier') or overridden using project-specific settings.");
|
||||
def->sidetext = L("mm³");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(140.));
|
||||
def = this->add("perimeter_acceleration", coFloat);
|
||||
def->label = L("Perimeters");
|
||||
def->tooltip = L("This is the acceleration your printer will use for perimeters. "
|
||||
@@ -3380,12 +3420,6 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("wiping_volumes_extruders", coFloats);
|
||||
def->label = L("Purging volumes - load/unload volumes");
|
||||
def->tooltip = L("This vector saves required volumes to change from/to each tool used on the "
|
||||
"wipe tower. These values are used to simplify creation of the full purging "
|
||||
"volumes below.");
|
||||
def->set_default_value(new ConfigOptionFloats { 70., 70., 70., 70., 70., 70., 70., 70., 70., 70. });
|
||||
|
||||
def = this->add("wiping_volumes_matrix", coFloats);
|
||||
def->label = L("Purging volumes - matrix");
|
||||
@@ -3397,6 +3431,10 @@ void PrintConfigDef::init_fff_params()
|
||||
140., 140., 140., 0., 140.,
|
||||
140., 140., 140., 140., 0. });
|
||||
|
||||
def = this->add("wiping_volumes_use_custom_matrix", coBool);
|
||||
def->label = "";
|
||||
def->tooltip = "";
|
||||
def->set_default_value(new ConfigOptionBool{ false });
|
||||
def = this->add("wipe_tower_x", coFloat);
|
||||
def->label = L("Position X");
|
||||
def->tooltip = L("X coordinate of the left front corner of a wipe tower");
|
||||
@@ -3452,6 +3490,15 @@ void PrintConfigDef::init_fff_params()
|
||||
def->max = 300.;
|
||||
def->set_default_value(new ConfigOptionPercent(100.));
|
||||
|
||||
def = this->add("wipe_tower_extra_flow", coPercent);
|
||||
def->label = L("Extra flow for purging");
|
||||
def->tooltip = L("Extra flow used for the purging lines on the wipe tower. This makes the purging lines thicker or narrower "
|
||||
"than they normally would be. The spacing is adjusted automatically.");
|
||||
def->sidetext = L("%");
|
||||
def->mode = comExpert;
|
||||
def->min = 100.;
|
||||
def->max = 300.;
|
||||
def->set_default_value(new ConfigOptionPercent(100.));
|
||||
def = this->add("wipe_into_infill", coBool);
|
||||
def->category = L("Wipe options");
|
||||
def->label = L("Wipe into this object's infill");
|
||||
@@ -4564,7 +4611,8 @@ static std::set<std::string> PrintConfigDef_ignore = {
|
||||
"ensure_vertical_shell_thickness",
|
||||
// Disabled in 2.6.0-alpha6, this option is problematic
|
||||
"infill_only_where_needed",
|
||||
"gcode_binary" // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode).
|
||||
"gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode).
|
||||
"wiping_volumes_extruders" // Removed in 2.7.3-alpha1.
|
||||
};
|
||||
|
||||
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
|
||||
@@ -4695,6 +4743,25 @@ void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config)
|
||||
config.set_key_value("thumbnails", new ConfigOptionString(thumbnails_str));
|
||||
}
|
||||
}
|
||||
if (config.has("wiping_volumes_matrix") && !config.has("wiping_volumes_use_custom_matrix")) {
|
||||
// This is apparently some pre-2.7.3 config, where the wiping_volumes_matrix was always used.
|
||||
// The 2.7.3 introduced an option to use defaults derived from config. In case the matrix
|
||||
// contains only default values, switch it to default behaviour. The default values
|
||||
// were zeros on the diagonal and 140 otherwise.
|
||||
std::vector<double> matrix = config.opt<ConfigOptionFloats>("wiping_volumes_matrix")->values;
|
||||
int num_of_extruders = int(std::sqrt(matrix.size()) + 0.5);
|
||||
int i = -1;
|
||||
bool custom = false;
|
||||
for (int j = 0; j < int(matrix.size()); ++j) {
|
||||
if (j % num_of_extruders == 0)
|
||||
++i;
|
||||
if (i != j % num_of_extruders && !is_approx(matrix[j], 140.)) {
|
||||
custom = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
config.set_key_value("wiping_volumes_use_custom_matrix", new ConfigOptionBool(custom));
|
||||
}
|
||||
}
|
||||
|
||||
const PrintConfigDef print_config_def;
|
||||
|
||||
@@ -725,10 +725,13 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloats, filament_cooling_initial_speed))
|
||||
((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower))
|
||||
((ConfigOptionFloats, filament_cooling_final_speed))
|
||||
((ConfigOptionPercents, filament_purge_multiplier))
|
||||
((ConfigOptionStrings, filament_ramming_parameters))
|
||||
((ConfigOptionBools, filament_multitool_ramming))
|
||||
((ConfigOptionFloats, filament_multitool_ramming_volume))
|
||||
((ConfigOptionFloats, filament_multitool_ramming_flow))
|
||||
((ConfigOptionFloats, filament_stamping_loading_speed))
|
||||
((ConfigOptionFloats, filament_stamping_distance))
|
||||
((ConfigOptionBool, gcode_comments))
|
||||
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
|
||||
((ConfigOptionEnum<LabelObjectsStyle>, gcode_label_objects))
|
||||
@@ -781,6 +784,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, remaining_times))
|
||||
((ConfigOptionBool, silent_mode))
|
||||
((ConfigOptionFloat, extra_loading_move))
|
||||
((ConfigOptionFloat, multimaterial_purging))
|
||||
((ConfigOptionString, color_change_gcode))
|
||||
((ConfigOptionString, pause_print_gcode))
|
||||
((ConfigOptionString, template_custom_gcode))
|
||||
@@ -893,6 +897,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloat, travel_acceleration))
|
||||
((ConfigOptionBools, wipe))
|
||||
((ConfigOptionBool, wipe_tower))
|
||||
((ConfigOptionFloat, wipe_tower_acceleration))
|
||||
((ConfigOptionFloat, wipe_tower_x))
|
||||
((ConfigOptionFloat, wipe_tower_y))
|
||||
((ConfigOptionFloat, wipe_tower_width))
|
||||
@@ -901,10 +906,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloat, wipe_tower_brim_width))
|
||||
((ConfigOptionFloat, wipe_tower_cone_angle))
|
||||
((ConfigOptionPercent, wipe_tower_extra_spacing))
|
||||
((ConfigOptionPercent, wipe_tower_extra_flow))
|
||||
((ConfigOptionFloat, wipe_tower_bridging))
|
||||
((ConfigOptionInt, wipe_tower_extruder))
|
||||
((ConfigOptionFloats, wiping_volumes_matrix))
|
||||
((ConfigOptionFloats, wiping_volumes_extruders))
|
||||
((ConfigOptionBool, wiping_volumes_use_custom_matrix))
|
||||
((ConfigOptionFloat, z_offset))
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user