Prusa 2.7.3

This commit is contained in:
sunsets
2024-03-30 10:22:25 +08:00
parent 764ce01063
commit 5ccb55ff98
56 changed files with 2106 additions and 1483 deletions

View File

@@ -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"

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;
};

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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 &params)
{
double tolerance = params.tolerance;

View File

@@ -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:

View File

@@ -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, &current_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;
}
}

View File

@@ -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;
};
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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_

View File

@@ -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;

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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));

View 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_

View File

@@ -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 &params,
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 &params,
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 &params,
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 &params,
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);

View File

@@ -70,28 +70,21 @@ void process_classic(
const Parameters &params,
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 &params,
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 &params,
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 &params,
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);

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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"

View File

@@ -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;

View File

@@ -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))
)