mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 04:22:04 +08:00
Merge branch 'master_27x'
This commit is contained in:
commit
9dc7b70084
@ -46,7 +46,7 @@ Other major features are:
|
||||
* several infill patterns including honeycomb, spirals, Hilbert curves
|
||||
* support material, raft, brim, skirt
|
||||
* **standby temperature** and automatic wiping for multi-extruder printing
|
||||
* [customizable **G-code macros**](https://github.com/prusa3d/PrusaSlicer/wiki/Slic3r-Prusa-Edition-Macro-Language) and output filename with variable placeholders
|
||||
* [customizable **G-code macros**](https://github.com/prusa3d/PrusaSlicer/wiki/PrusaSlicer-Macro-Language) and output filename with variable placeholders
|
||||
* support for **post-processing scripts**
|
||||
* **cooling logic** controlling fan speed and dynamic print speed
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
resources/localization/sl/PrusaSlicer.mo
Normal file
BIN
resources/localization/sl/PrusaSlicer.mo
Normal file
Binary file not shown.
27787
resources/localization/sl/PrusaSlicer.po
Normal file
27787
resources/localization/sl/PrusaSlicer.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,30 @@
|
||||
min_slic3r_version = 2.7.3-beta1
|
||||
1.13.0 Updated version for PrusaSlicer 2.7.3
|
||||
1.13.0-beta3 Reduced number of top/bottom layers (0.6). Updated cooling thresholds.
|
||||
1.13.0-beta2 Disabled ramping lift for MK3.5.
|
||||
1.13.0-beta1 Updated overhang slowdown speeds in selected print profiles (0.6 nozzle). Slightly reduced bed temperature for some PETG filaments (MINI).
|
||||
1.13.0-beta0 Updated version for 2.7.3-beta1.
|
||||
min_slic3r_version = 2.7.3-alpha0
|
||||
1.13.0-alpha4 Updated start g-code. Updated purging volumes.
|
||||
1.13.0-alpha3 Enabled metrics. Updated FW version notification.
|
||||
1.13.0-alpha2 Updated load/unload times. Removed toolchange retraction. Enabled stabilisation cone.
|
||||
1.13.0-alpha1 Further adjusted the range of supported filament profiles (MK4 MMU3).
|
||||
1.13.0-alpha0 Added initial profiles for MK4/MK3.9 MMU3. Updated start g-code sequence for Prusa XL.
|
||||
min_slic3r_version = 2.7.2-beta1
|
||||
1.12.3 Updated start g-code for MK3.5 MMU3.
|
||||
1.12.2 Added new print profile for 0.25mm nozzle. Updated PA values in selected filament profiles. Updated FW version notification. Updated some filament and print profiles.
|
||||
1.12.1 Updated printer profile for "XLIS 5T 0.3mm nozzle" to fix issues with configuration wizard.
|
||||
1.12.0 Added profiles for Original Prusa MK3.5 MMU3. Enabled ramping z-hop for MK3.9 and MK4. Added filament profile for Fiberlogy BVOH.
|
||||
1.12.0-beta0 Bumped up version for beta.
|
||||
min_slic3r_version = 2.7.2-alpha2
|
||||
1.12.0-alpha2 Reverted loading distance for MK3.5MMU.
|
||||
1.12.0-alpha1 Updated MK3.5MMU3 profiles. Decreased loading distance.
|
||||
1.12.0-alpha0 Enabled ramping z-hop for MK4/MK3.9. Added initial profiles for Original Prusa MK3.5 MMU3.
|
||||
min_slic3r_version = 2.7.0-beta1
|
||||
1.11.16 Updated start g-code for MK3.5 MMU3.
|
||||
1.11.15 Added new print profile for 0.25mm nozzle. Updated PA values in selected filament profiles. Updated FW version notification. Updated some filament and print profiles.
|
||||
1.11.14 Updated printer profile for "XLIS 5T 0.3mm nozzle" to fix issues with configuration wizard.
|
||||
1.11.13 Added profiles for Original Prusa MK3.5 MMU3. Added filament profile for Fiberlogy BVOH.
|
||||
1.11.12 Added profiles for Original Prusa MK3.5.
|
||||
1.11.11 Decreased speed of sparse infill in STRUCTURAL profiles. Decreased max volumetric speed for some PETG filaments for better sparse infill quality and reliability (XL/MK4/3.9/MINI - 0.4mm nozzle).
|
||||
1.11.10 Updated FW version notification (XL/MINI/MK4/MK3.9).
|
||||
|
File diff suppressed because one or more lines are too long
BIN
resources/profiles/PrusaResearch/MK3.5MMU3_thumbnail.png
Normal file
BIN
resources/profiles/PrusaResearch/MK3.5MMU3_thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
BIN
resources/profiles/PrusaResearch/MK3.9MMU3_thumbnail.png
Normal file
BIN
resources/profiles/PrusaResearch/MK3.9MMU3_thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
BIN
resources/profiles/PrusaResearch/MK39IS_thumbnail_v2.png
Normal file
BIN
resources/profiles/PrusaResearch/MK39IS_thumbnail_v2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
resources/profiles/PrusaResearch/MK4ISMMU3_thumbnail.png
Normal file
BIN
resources/profiles/PrusaResearch/MK4ISMMU3_thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
@ -1,3 +1,5 @@
|
||||
min_slic3r_version = 2.7.0
|
||||
1.0.3 Updated flavor. Updated start g-code.
|
||||
min_slic3r_version = 2.4.1-alpha0
|
||||
1.0.2 Updated start g-code.
|
||||
1.0.1 Various fixes and improvements. Commented filament sensor initialisation for v-Minion (optional HW).
|
||||
|
@ -9,7 +9,7 @@
|
||||
name = RatRig
|
||||
# Configuration version of this file. Config file will only be installed, if the config_version differs.
|
||||
# This means, the server may force the Slic3r configuration to be downgraded.
|
||||
config_version = 1.0.2
|
||||
config_version = 1.0.3
|
||||
# Where to get the updates from?
|
||||
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/RatRig/
|
||||
# The printer models will be shown by the Configuration Wizard in this order,
|
||||
@ -392,7 +392,7 @@ deretract_speed = 40
|
||||
end_gcode = END_PRINT\n
|
||||
extra_loading_move = -2
|
||||
extruder_colour = ""
|
||||
gcode_flavor = marlin
|
||||
gcode_flavor = klipper
|
||||
high_current_on_filament_swap = 0
|
||||
layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z]\n
|
||||
machine_limits_usage = time_estimate_only
|
||||
@ -433,7 +433,7 @@ retract_restart_extra_toolchange = 0
|
||||
retract_speed = 40
|
||||
silent_mode = 0
|
||||
single_extruder_multi_material = 0
|
||||
start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSET_GCODE_VARIABLE MACRO=RatOS VARIABLE=relative_extrusion VALUE=True\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature]\n;enable this if you have a BTT Smart Filament Sensor\nSET_FILAMENT_SENSOR SENSOR=my_sensor ENABLE=0\n
|
||||
start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSET_GCODE_VARIABLE MACRO=RatOS VARIABLE=relative_extrusion VALUE=True\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature] X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}
|
||||
thumbnails = 64x64,400x300
|
||||
toolchange_gcode =
|
||||
use_firmware_retraction = 0
|
||||
@ -496,7 +496,7 @@ retract_restart_extra_toolchange = 0
|
||||
retract_speed = 40
|
||||
silent_mode = 0
|
||||
single_extruder_multi_material = 0
|
||||
start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSET_GCODE_VARIABLE MACRO=RatOS VARIABLE=relative_extrusion VALUE=True\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature]\n;enable this if you have a BTT Smart Filament Sensor\nSET_FILAMENT_SENSOR SENSOR=my_sensor ENABLE=0\n
|
||||
start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSET_GCODE_VARIABLE MACRO=RatOS VARIABLE=relative_extrusion VALUE=True\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature] X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}
|
||||
start_gcode_manual = 0
|
||||
template_custom_gcode =
|
||||
thumbnails = 64x64,400x300
|
||||
|
@ -223,6 +223,7 @@ set(SLIC3R_SOURCES
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
Layer.hpp
|
||||
LayerRegion.hpp
|
||||
LayerRegion.cpp
|
||||
libslic3r.h
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
||||
|
@ -91,6 +91,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/Prusa_Slicer_custom_gcode_per_print_z.xml";
|
||||
const std::string CUT_INFORMATION_FILE = "Metadata/Prusa_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";
|
||||
@ -118,6 +123,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";
|
||||
@ -336,18 +342,21 @@ 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)
|
||||
{
|
||||
@ -465,11 +474,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;
|
||||
@ -509,6 +518,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();
|
||||
@ -532,8 +543,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);
|
||||
@ -546,6 +558,10 @@ 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);
|
||||
@ -597,7 +613,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);
|
||||
|
||||
@ -617,6 +633,9 @@ 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);
|
||||
@ -666,6 +685,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);
|
||||
@ -705,24 +725,29 @@ 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;
|
||||
}
|
||||
found_model = true;
|
||||
|
||||
try
|
||||
{
|
||||
// valid model name -> extract model
|
||||
m_model_path = "/" + name;
|
||||
if (m_model_path == m_start_part_path) {
|
||||
start_part_stat = stat;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (!_extract_model_from_archive(archive, stat)) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Archive does not contain a valid model");
|
||||
@ -735,9 +760,27 @@ 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.");
|
||||
@ -876,7 +919,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
|
||||
|
||||
@ -957,8 +1000,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_prusaslicer_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;
|
||||
}
|
||||
@ -1524,7 +1614,41 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
|
||||
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)
|
||||
return;
|
||||
@ -1663,6 +1787,9 @@ 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())) {
|
||||
@ -1731,6 +1858,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
|
||||
@ -1738,26 +1866,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");
|
||||
@ -1868,19 +1996,23 @@ 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;
|
||||
}
|
||||
@ -1914,9 +2046,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()
|
||||
@ -2072,7 +2206,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;
|
||||
|
||||
|
@ -573,7 +573,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)
|
||||
{}
|
||||
|
||||
@ -899,6 +898,31 @@ 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;
|
||||
@ -1130,7 +1154,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);
|
||||
file.write(m_label_objects.all_objects_header());
|
||||
|
||||
// Update output variables after the extruders were initialized.
|
||||
@ -1240,9 +1264,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();
|
||||
@ -1271,6 +1298,8 @@ 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.
|
||||
@ -1327,6 +1356,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));
|
||||
@ -1386,8 +1416,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();
|
||||
@ -1396,6 +1428,9 @@ 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
|
||||
@ -1418,6 +1453,11 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
file.write(full_config);
|
||||
file.write("; prusaslicer_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();
|
||||
}
|
||||
@ -1493,11 +1533,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 {
|
||||
@ -1586,11 +1627,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 {
|
||||
@ -2102,18 +2144,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);
|
||||
|
||||
@ -2127,6 +2212,26 @@ 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,
|
||||
@ -2140,9 +2245,10 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3
|
||||
|
||||
std::string travel_gcode;
|
||||
Vec3d previous_point{this->point_to_gcode(travel.front())};
|
||||
for (const Vec3crd& point : travel) {
|
||||
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;
|
||||
@ -2195,6 +2301,11 @@ 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);
|
||||
|
||||
|
||||
// Initialize config with the 1st object to be printed at this layer.
|
||||
m_config.apply(layer.object()->config(), true);
|
||||
|
||||
@ -2249,7 +2360,7 @@ LayerResult GCodeGenerator::process_layer(
|
||||
print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
|
||||
+ "\n";
|
||||
}
|
||||
gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index
|
||||
gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index
|
||||
m_layer = &layer;
|
||||
if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr)
|
||||
m_travel_obstacle_tracker.init_layer(layer, layers);
|
||||
@ -2341,6 +2452,11 @@ LayerResult GCodeGenerator::process_layer(
|
||||
}
|
||||
|
||||
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();
|
||||
@ -2362,6 +2478,12 @@ 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)
|
||||
@ -2371,8 +2493,7 @@ LayerResult GCodeGenerator::process_layer(
|
||||
// Allow a straight travel move to the first object point.
|
||||
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();
|
||||
@ -2415,7 +2536,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");
|
||||
}
|
||||
@ -2433,7 +2556,7 @@ LayerResult GCodeGenerator::process_layer(
|
||||
}};
|
||||
|
||||
bool removed_retraction{false};
|
||||
if (this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id)) {
|
||||
if (this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) && !result.spiral_vase_enable) {
|
||||
const std::string retraction_start_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start);
|
||||
const std::string retraction_end_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End);
|
||||
|
||||
@ -2446,7 +2569,8 @@ 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;
|
||||
}
|
||||
}
|
||||
@ -2495,7 +2619,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;
|
||||
@ -2507,11 +2631,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]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -2691,8 +2816,6 @@ void GCodeGenerator::process_layer_single_object(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! first)
|
||||
gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]);
|
||||
}
|
||||
|
||||
void GCodeGenerator::apply_print_config(const PrintConfig &print_config)
|
||||
@ -2768,22 +2891,28 @@ std::string GCodeGenerator::preamble()
|
||||
// called by GCodeGenerator::process_layer()
|
||||
std::string GCodeGenerator::change_layer(
|
||||
coordf_t previous_layer_z,
|
||||
coordf_t print_z
|
||||
coordf_t print_z,
|
||||
bool vase_mode
|
||||
) {
|
||||
std::string gcode;
|
||||
if (m_layer_count > 0)
|
||||
// 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)){
|
||||
} else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){
|
||||
m_previous_layer_last_position_before_wipe = this->last_position ?
|
||||
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();
|
||||
@ -2856,7 +2985,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.
|
||||
// Make a little move inwards before leaving loop.
|
||||
@ -2904,7 +3033,7 @@ std::string GCodeGenerator::extrude_skirt(
|
||||
|
||||
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;
|
||||
}
|
||||
@ -2923,7 +3052,10 @@ 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;
|
||||
@ -3056,11 +3188,23 @@ void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...)
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point) {
|
||||
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)};
|
||||
@ -3071,20 +3215,26 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point) {
|
||||
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(gcode_point.z() + lift, "lift");
|
||||
gcode += this->writer().get_travel_to_z_gcode(from_z + lift, "lift");
|
||||
}
|
||||
}
|
||||
this->last_position = point.head<2>();
|
||||
this->writer().update_position(gcode_point);
|
||||
|
||||
std::string comment{"move to first layer point"};
|
||||
const 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;
|
||||
}
|
||||
|
||||
@ -3111,15 +3261,23 @@ 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);
|
||||
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) {
|
||||
@ -3127,7 +3285,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;
|
||||
}
|
||||
}
|
||||
@ -3138,6 +3298,13 @@ 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()) {
|
||||
@ -3345,7 +3512,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;
|
||||
|
||||
@ -3360,9 +3528,16 @@ 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;
|
||||
@ -3458,7 +3633,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
|
||||
|
||||
@ -3516,10 +3695,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;
|
||||
|
||||
@ -3537,7 +3716,10 @@ std::string GCodeGenerator::retract_and_wipe(bool toolchange)
|
||||
methods even if we performed wipe, since this will ensure the entire retraction
|
||||
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;
|
||||
}
|
||||
@ -3568,8 +3750,13 @@ 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();
|
||||
|
@ -110,7 +110,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
|
||||
@ -226,8 +226,16 @@ 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,
|
||||
@ -266,7 +274,8 @@ private:
|
||||
std::string preamble();
|
||||
std::string change_layer(
|
||||
coordf_t previous_layer_z,
|
||||
coordf_t print_z
|
||||
coordf_t print_z,
|
||||
bool vase_mode
|
||||
);
|
||||
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
@ -318,7 +327,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,
|
||||
@ -330,14 +340,15 @@ 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);
|
||||
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);
|
||||
|
||||
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);
|
||||
@ -425,6 +436,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;
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
|
@ -1175,7 +1175,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;
|
||||
|
@ -21,11 +21,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);
|
||||
|
||||
@ -54,10 +53,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 };
|
||||
|
@ -685,7 +685,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
m_extra_loading_move = float(config.extra_loading_move);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i);
|
||||
m_time_processor.machines[i].max_acceleration = max_acceleration;
|
||||
m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION;
|
||||
@ -3815,7 +3815,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
|
||||
@ -3845,10 +3851,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 };
|
||||
|
||||
@ -3865,11 +3871,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
|
||||
@ -3886,9 +3893,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;
|
||||
}
|
||||
@ -3898,7 +3905,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;
|
||||
@ -3906,15 +3913,18 @@ 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
|
||||
@ -3925,7 +3935,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();
|
||||
@ -3933,13 +3944,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())
|
||||
@ -3951,11 +3962,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
|
||||
@ -3981,7 +3996,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();
|
||||
@ -4066,7 +4081,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
|
||||
@ -4269,9 +4285,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
|
||||
|
@ -176,6 +176,8 @@ public:
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec2d quantize(const Vec2f &pt)
|
||||
{ return { quantize(double(pt.x()), XYZF_EXPORT_DIGITS), quantize(double(pt.y()), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
#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 +42,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 +54,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 +90,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,35 +161,31 @@ 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 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;
|
||||
}
|
||||
|
||||
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;
|
||||
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.pop_back();
|
||||
out += "]\n";
|
||||
} else {
|
||||
out += start_object(*print_instance, IncludeName::Yes);
|
||||
out += stop_object(*print_instance);
|
||||
}
|
||||
}
|
||||
out += "\n";
|
||||
out += "]}";
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -141,7 +196,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 +225,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
const LabelData& label = *std::find_if(m_label_data.begin(), m_label_data.end(), [&print_instance](const LabelData& ld) { return ld.pi == &print_instance; });
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
|
@ -2,7 +2,9 @@
|
||||
#define slic3r_GCode_LabelObjects_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -10,35 +12,52 @@ enum GCodeFlavor : unsigned char;
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <memory.h>
|
||||
#include <cstring>
|
||||
#include <cfloat>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
@ -32,6 +33,12 @@ 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.
|
||||
@ -64,8 +71,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;
|
||||
}
|
||||
@ -102,6 +109,73 @@ 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)
|
||||
@ -148,7 +222,7 @@ static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); }
|
||||
static void eatws(const char *&line)
|
||||
{
|
||||
while (is_ws(*line))
|
||||
++ line;
|
||||
++line;
|
||||
}
|
||||
|
||||
// Parse an int starting at the current position of a line.
|
||||
@ -395,8 +469,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
|
||||
@ -511,16 +583,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;
|
||||
|
||||
@ -528,13 +598,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;
|
||||
}
|
||||
@ -554,7 +624,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;
|
||||
@ -576,9 +647,9 @@ 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;
|
||||
}
|
||||
}
|
||||
@ -592,8 +663,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;
|
||||
}
|
||||
@ -608,7 +679,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;
|
||||
@ -632,9 +704,9 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,9 @@ 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;
|
||||
@ -187,9 +190,11 @@ private:
|
||||
bool process_line(const char *line, const char *line_end, GCodeLine &buf);
|
||||
void output_gcode_line(size_t line_idx);
|
||||
|
||||
GCodeLinesConstIt advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const;
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
void adjust_volumetric_rate();
|
||||
void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx);
|
||||
|
||||
// Push the text to the end of the output_buffer.
|
||||
inline void push_to_output(GCodeG1Formatter &formatter);
|
||||
|
@ -133,6 +133,12 @@ double clip_end(SmoothPath &path, double distance, double min_point_distance_thr
|
||||
return distance;
|
||||
}
|
||||
|
||||
void reverse(SmoothPath &path) {
|
||||
std::reverse(path.begin(), path.end());
|
||||
for (SmoothPathElement &path_element : path)
|
||||
Geometry::ArcWelder::reverse(path_element.path);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
|
@ -33,6 +33,8 @@ 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:
|
||||
|
@ -9,10 +9,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
|
||||
@ -24,14 +35,14 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
|
||||
// If we're not going to modify G-code, just feed it to the reader
|
||||
// in order to update positions.
|
||||
if (! m_enabled) {
|
||||
if (!m_enabled) {
|
||||
m_reader.parse_buffer(gcode);
|
||||
return 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;
|
||||
|
||||
{
|
||||
@ -54,18 +65,25 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
});
|
||||
}
|
||||
|
||||
// Remove layer height from initial Z.
|
||||
// Remove layer height from initial Z.
|
||||
z -= layer_height;
|
||||
|
||||
std::string new_gcode;
|
||||
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
|
||||
// FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
const bool transition_out = last_layer && m_config.use_relative_e_distances.value;
|
||||
const bool smooth_spiral = m_smooth_spiral && m_config.use_relative_e_distances.value;
|
||||
|
||||
const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer);
|
||||
Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back();
|
||||
float len = 0.f;
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
||||
|
||||
std::string new_gcode, transition_gcode;
|
||||
std::vector<Vec2f> current_layer;
|
||||
m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral, max_xy_smoothing = m_max_xy_smoothing,
|
||||
&len, &last_point, &new_gcode, &transition_gcode, ¤t_layer, &previous_layer_distancer]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
@ -74,32 +92,72 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
||||
line.set(reader, Z, z);
|
||||
new_gcode += line.raw() + '\n';
|
||||
return;
|
||||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
// horizontal move
|
||||
if (line.extruding(reader)) {
|
||||
} 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;
|
||||
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);
|
||||
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 {
|
||||
last_point = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (emit_gcode_line)
|
||||
new_gcode += line.raw() + '\n';
|
||||
}
|
||||
return;
|
||||
|
||||
/* Skip travel moves: the move to first perimeter point will
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,31 +14,40 @@
|
||||
|
||||
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.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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // slic3r_SpiralVase_hpp_
|
||||
|
@ -32,30 +32,14 @@ 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 {
|
||||
if (this->enabled() && !path.empty()) {
|
||||
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) {
|
||||
for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
@ -67,7 +51,6 @@ void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ public:
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void set_path(SmoothPath &&path);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
@ -29,6 +29,20 @@
|
||||
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:
|
||||
@ -94,6 +108,11 @@ 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.
|
||||
@ -268,6 +287,25 @@ 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)
|
||||
{
|
||||
@ -310,6 +348,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;
|
||||
}
|
||||
@ -504,7 +543,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)),
|
||||
@ -541,6 +582,8 @@ 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);
|
||||
@ -575,6 +618,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.
|
||||
@ -587,6 +631,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
|
||||
@ -700,7 +746,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);
|
||||
}
|
||||
@ -747,7 +793,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;
|
||||
}
|
||||
}
|
||||
@ -788,14 +834,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;
|
||||
|
||||
@ -822,13 +869,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);
|
||||
|
||||
@ -841,10 +889,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);
|
||||
@ -865,7 +917,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 ) )
|
||||
@ -879,6 +931,11 @@ 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())
|
||||
@ -919,31 +976,69 @@ 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;
|
||||
@ -960,7 +1055,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
|
||||
@ -985,6 +1080,9 @@ 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.
|
||||
@ -1046,20 +1144,24 @@ 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;
|
||||
}
|
||||
|
||||
@ -1074,21 +1176,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;
|
||||
}
|
||||
|
||||
@ -1102,6 +1204,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);
|
||||
}
|
||||
|
||||
|
||||
@ -1381,9 +1484,20 @@ 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)
|
||||
@ -1392,6 +1506,17 @@ 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)
|
||||
@ -1408,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));
|
||||
}
|
||||
|
||||
|
||||
@ -1474,14 +1594,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1526,7 +1646,7 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
||||
return;
|
||||
|
||||
plan_tower();
|
||||
for (int i=0;i<5;++i) {
|
||||
for (int i = 0; i<5; ++i) {
|
||||
save_on_last_wipe();
|
||||
plan_tower();
|
||||
}
|
||||
|
@ -236,6 +236,10 @@ 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;
|
||||
@ -247,6 +251,7 @@ public:
|
||||
float filament_area;
|
||||
bool multitool_ramming;
|
||||
float multitool_ramming_time = 0.f;
|
||||
float filament_minimal_purge_on_wipe_tower = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@ -264,6 +269,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
|
||||
@ -313,8 +319,7 @@ 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;
|
||||
@ -323,7 +328,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; }
|
||||
|
||||
@ -335,17 +342,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();
|
||||
|
||||
@ -359,19 +359,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(...))
|
||||
@ -394,6 +394,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(
|
||||
|
@ -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);
|
||||
gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";});
|
||||
}
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
@ -93,8 +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) {
|
||||
const double retract_to_z = tcr.priming ? tcr.print_z + gcodegen.config().z_offset.value : z;
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(retract_to_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();
|
||||
}
|
||||
}
|
||||
@ -106,7 +109,11 @@ 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>());
|
||||
@ -131,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;
|
||||
}
|
||||
|
||||
@ -257,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;
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "Flow.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "LayerRegion.hpp"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
@ -39,42 +40,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
|
||||
{
|
||||
@ -101,144 +66,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; }
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// LayerSlice contains one or more LayerIsland objects,
|
||||
// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters
|
||||
|
@ -169,61 +169,16 @@ 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 {
|
||||
// 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<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();
|
||||
}
|
||||
std::vector<Algorithm::RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
||||
std::optional<double> angle{std::nullopt};
|
||||
};
|
||||
|
||||
// Group the bridge surfaces by overlaps.
|
||||
auto group_id = [&bridges](uint32_t src_id) {
|
||||
// Group the bridge surfaces by overlaps.
|
||||
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;
|
||||
@ -231,109 +186,156 @@ Surfaces expand_bridges_detect_orientations(
|
||||
}
|
||||
bridges[src_id].group_id = group_id;
|
||||
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 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]) &&
|
||||
// One may ignore holes, they are irrelevant for intersection test.
|
||||
! intersection(it->expolygon.contour, it2->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);
|
||||
if (id < id2)
|
||||
bridges[id2].group_id = id;
|
||||
else
|
||||
bridges[id].group_id = id2;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
// Detect bridge directions.
|
||||
{
|
||||
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
|
||||
// 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 (;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(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(result, expansion_iterator->src_id);
|
||||
uint32_t id2 = group_id(result, candidate_iterator->src_id);
|
||||
if (id < id2)
|
||||
result[id2].group_id = id;
|
||||
else
|
||||
result[id].group_id = id2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// 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] });
|
||||
// }
|
||||
start_index += expansion_zone.expolygons.size();
|
||||
}
|
||||
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.
|
||||
@ -341,29 +343,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;
|
||||
|
||||
@ -372,17 +450,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));
|
||||
@ -390,11 +468,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;
|
||||
@ -443,16 +520,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
|
||||
{
|
||||
@ -462,25 +546,36 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
#endif
|
||||
}
|
||||
|
||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
|
||||
RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
|
||||
RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps),
|
||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
||||
m_fill_surfaces.remove_types({ stTop });
|
||||
{
|
||||
Surface top_templ(stTop, {});
|
||||
top_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ);
|
||||
}
|
||||
|
||||
expansion_zones.pop_back();
|
||||
|
||||
expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps);
|
||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius);
|
||||
|
||||
expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps);
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, expansion_zones, closing_radius);
|
||||
|
||||
// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid });
|
||||
m_fill_surfaces.clear();
|
||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size());
|
||||
unsigned zones_expolygons_count = 0;
|
||||
for (const ExpansionZone& zone : expansion_zones)
|
||||
zones_expolygons_count += zone.expolygons.size();
|
||||
reserve_more(m_fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size());
|
||||
{
|
||||
Surface solid_templ(stInternalSolid, {});
|
||||
solid_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(shells), solid_templ);
|
||||
m_fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ);
|
||||
}
|
||||
{
|
||||
Surface sparse_templ(stInternal, {});
|
||||
sparse_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(sparse), sparse_templ);
|
||||
m_fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ);
|
||||
}
|
||||
m_fill_surfaces.append(std::move(bridges.surfaces));
|
||||
m_fill_surfaces.append(std::move(bottoms));
|
||||
|
224
src/libslic3r/LayerRegion.hpp
Normal file
224
src/libslic3r/LayerRegion.hpp
Normal file
@ -0,0 +1,224 @@
|
||||
#ifndef slic3r_LayerRegion_hpp_
|
||||
#define slic3r_LayerRegion_hpp_
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "libslic3r/Algorithm/RegionExpansion.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Layer;
|
||||
using LayerPtrs = std::vector<Layer*>;
|
||||
class PrintRegion;
|
||||
|
||||
// Range of indices, providing support for range based loops.
|
||||
template<typename T>
|
||||
class IndexRange
|
||||
{
|
||||
public:
|
||||
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
|
||||
IndexRange() = default;
|
||||
|
||||
// Just a bare minimum functionality iterator required by range-for loop.
|
||||
class Iterator {
|
||||
public:
|
||||
T operator*() const { return m_idx; }
|
||||
bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; }
|
||||
void operator++() { ++ m_idx; }
|
||||
private:
|
||||
friend class IndexRange<T>;
|
||||
Iterator(T idx) : m_idx(idx) {}
|
||||
T m_idx;
|
||||
};
|
||||
|
||||
Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); };
|
||||
Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); };
|
||||
|
||||
bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; }
|
||||
T size() const { assert(m_begin <= m_end); return m_end - m_begin; }
|
||||
|
||||
private:
|
||||
// Index of the first extrusion in LayerRegion.
|
||||
T m_begin { 0 };
|
||||
// Index of the last extrusion in LayerRegion.
|
||||
T m_end { 0 };
|
||||
};
|
||||
|
||||
template class IndexRange<uint32_t>;
|
||||
|
||||
using ExtrusionRange = IndexRange<uint32_t>;
|
||||
using ExPolygonRange = IndexRange<uint32_t>;
|
||||
|
||||
class LayerRegion
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] Layer* layer() { return m_layer; }
|
||||
[[nodiscard]] const Layer* layer() const { return m_layer; }
|
||||
[[nodiscard]] const PrintRegion& region() const { return *m_region; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
|
||||
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
[[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; }
|
||||
// and their bounding boxes
|
||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; }
|
||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||
// Not used for a plain single material print with no infill modifiers.
|
||||
[[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; }
|
||||
// and their bounding boxes
|
||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; }
|
||||
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
[[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; }
|
||||
|
||||
// collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
[[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; }
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
[[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; }
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; }
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
|
||||
|
||||
Flow flow(FlowRole role) const;
|
||||
Flow flow(FlowRole role, double layer_height) const;
|
||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
|
||||
void make_perimeters(
|
||||
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
||||
const SurfaceCollection &slices,
|
||||
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
||||
// newly created extrusions stored at this LayerRegion.
|
||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||
// All fill areas produced for all input slices above.
|
||||
ExPolygons &fill_expolygons,
|
||||
// Ranges of fill areas above per input slice.
|
||||
std::vector<ExPolygonRange> &fill_expolygons_ranges);
|
||||
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||
double infill_area_threshold() const;
|
||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||
void trim_surfaces(const Polygons &trimming_polygons);
|
||||
// Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer.
|
||||
// Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit.
|
||||
void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons);
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||
void export_region_slices_to_svg_debug(const char *name) const;
|
||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||
|
||||
// Is there any valid extrusion assigned to this LayerRegion?
|
||||
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
|
||||
|
||||
protected:
|
||||
friend class Layer;
|
||||
friend class PrintObject;
|
||||
|
||||
LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {}
|
||||
~LayerRegion() = default;
|
||||
|
||||
private:
|
||||
// Modifying m_slices
|
||||
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
||||
template<typename ThrowOnCancel>
|
||||
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
|
||||
|
||||
Layer *m_layer;
|
||||
const PrintRegion *m_region;
|
||||
|
||||
// Backed up slices before they are split into top/bottom/internal.
|
||||
// Only backed up for multi-region layers or layers with elephant foot compensation.
|
||||
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
|
||||
ExPolygons m_raw_slices;
|
||||
|
||||
//FIXME make m_slices public for unit tests
|
||||
public:
|
||||
// collection of surfaces generated by slicing the original geometry
|
||||
// divided by type top/bottom/internal
|
||||
SurfaceCollection m_slices;
|
||||
|
||||
private:
|
||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||
// and for re-starting of infills.
|
||||
ExPolygons m_fill_expolygons;
|
||||
// and their bounding boxes
|
||||
BoundingBoxes m_fill_expolygons_bboxes;
|
||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||
// Not used for a plain single material print with no infill modifiers.
|
||||
ExPolygons m_fill_expolygons_composite;
|
||||
// and their bounding boxes
|
||||
BoundingBoxes m_fill_expolygons_composite_bboxes;
|
||||
|
||||
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
|
||||
SurfaceCollection m_fill_surfaces;
|
||||
|
||||
// Collection of extrusion paths/loops filling gaps
|
||||
// These fills are generated by the perimeter generator.
|
||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||
ExtrusionEntityCollection m_thin_fills;
|
||||
|
||||
// collection of polylines representing the unsupported bridge edges
|
||||
Polylines m_unsupported_bridge_edges;
|
||||
|
||||
// ordered collection of extrusion paths/loops to build all perimeters
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_perimeters;
|
||||
|
||||
// ordered collection of extrusion paths to fill surfaces
|
||||
// (this collection contains only ExtrusionEntityCollection objects)
|
||||
ExtrusionEntityCollection m_fills;
|
||||
|
||||
// collection of expolygons representing the bridged areas (thus not
|
||||
// needing support material)
|
||||
// Polygons bridged;
|
||||
};
|
||||
|
||||
struct ExpansionZone {
|
||||
ExPolygons expolygons;
|
||||
Algorithm::RegionExpansionParameters parameters;
|
||||
bool expanded_into = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||
* detect bridges.
|
||||
* Trim "shells" by the expanded bridges.
|
||||
*/
|
||||
Surfaces expand_bridges_detect_orientations(
|
||||
Surfaces &surfaces,
|
||||
std::vector<ExpansionZone>& expansion_zones,
|
||||
const float closing_radius
|
||||
);
|
||||
|
||||
/**
|
||||
* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
||||
* Trim "shells" by the expanded bridges.
|
||||
*/
|
||||
Surfaces expand_merge_surfaces(
|
||||
Surfaces &surfaces,
|
||||
SurfaceType surface_type,
|
||||
std::vector<ExpansionZone>& expansion_zones,
|
||||
const float closing_radius,
|
||||
const double bridge_angle = -1
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#endif // slic3r_LayerRegion_hpp_
|
@ -433,7 +433,11 @@ bool Model::looks_like_multipart_object() const
|
||||
return false;
|
||||
|
||||
BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box();
|
||||
BoundingBoxf3 tbb_this = obj->instances[0]->transform_bounding_box(bb_this);
|
||||
|
||||
// FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that
|
||||
// case is performed only after this function is called. For now (shortly before the 2.7.2 release, let's
|
||||
// just do this non-invasive check. Reordering all the functions could break it much more.
|
||||
BoundingBoxf3 tbb_this = (! obj->instances.empty() ? obj->instances[0]->transform_bounding_box(bb_this) : bb_this);
|
||||
|
||||
if (!tbb.defined)
|
||||
tbb = tbb_this;
|
||||
|
@ -449,7 +449,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",
|
||||
@ -469,7 +469,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"elefant_foot_compensation", "xy_size_compensation", "resolution", "gcode_resolution", "arc_fitting",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
|
||||
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
|
||||
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_flow", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
|
||||
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
|
||||
"wall_distribution_count", "min_feature_size", "min_bead_width"
|
||||
};
|
||||
@ -477,8 +477,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",
|
||||
@ -509,8 +509,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"
|
||||
};
|
||||
@ -2240,6 +2240,14 @@ 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);
|
||||
|
@ -909,7 +909,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; }
|
||||
|
@ -37,8 +37,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::PRUSA_BUNDLE = "PrusaResearch";
|
||||
@ -1210,6 +1210,8 @@ 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));
|
||||
@ -1699,6 +1701,8 @@ 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);
|
||||
@ -1710,6 +1714,8 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
|
||||
return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PresetBundle::update_multi_material_filament_presets()
|
||||
{
|
||||
if (printers.get_edited_preset().printer_technology() != ptFFF)
|
||||
@ -1731,27 +1737,22 @@ 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;
|
||||
}
|
||||
|
@ -63,6 +63,30 @@ 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,
|
||||
|
@ -172,7 +172,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"use_relative_e_distances",
|
||||
"use_volumetric_e",
|
||||
"variable_layer_height",
|
||||
"wipe"
|
||||
"wipe",
|
||||
"wipe_tower_acceleration"
|
||||
};
|
||||
|
||||
static std::unordered_set<std::string> steps_ignore;
|
||||
@ -218,9 +219,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"
|
||||
@ -238,13 +242,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"
|
||||
|
@ -1096,6 +1096,23 @@ 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 "
|
||||
@ -1132,6 +1149,16 @@ 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.");
|
||||
@ -1549,6 +1576,15 @@ 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 "
|
||||
@ -2115,6 +2151,14 @@ 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. "
|
||||
@ -3264,13 +3308,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");
|
||||
def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the"
|
||||
@ -3281,6 +3318,11 @@ 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");
|
||||
@ -3336,6 +3378,16 @@ 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");
|
||||
@ -4354,7 +4406,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)
|
||||
@ -4486,6 +4539,26 @@ 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;
|
||||
|
@ -727,10 +727,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))
|
||||
@ -777,6 +780,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))
|
||||
@ -866,6 +870,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))
|
||||
@ -874,10 +879,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))
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@
|
||||
VALUE "ProductName", "@SLIC3R_APP_NAME@ G-code Viewer"
|
||||
VALUE "ProductVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "InternalName", "@SLIC3R_APP_NAME@ G-code Viewer"
|
||||
VALUE "LegalCopyright", "Copyright \251 2016-2023 Prusa Research, \251 2011-2018 Alessandro Ranellucci"
|
||||
VALUE "LegalCopyright", "Copyright \251 2016-2024 Prusa Research, \251 2011-2018 Alessandro Ranellucci"
|
||||
VALUE "OriginalFilename", "prusa-gcodeviewer.exe"
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@
|
||||
VALUE "ProductName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "ProductVersion", "@SLIC3R_BUILD_ID@"
|
||||
VALUE "InternalName", "@SLIC3R_APP_NAME@"
|
||||
VALUE "LegalCopyright", "Copyright \251 2016-2023 Prusa Research, \251 2011-2018 Alessandro Ranellucci"
|
||||
VALUE "LegalCopyright", "Copyright \251 2016-2024 Prusa Research, \251 2011-2018 Alessandro Ranellucci"
|
||||
VALUE "OriginalFilename", "prusa-slicer.exe"
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>@SLIC3R_APP_KEY@</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>@SLIC3R_APP_NAME@ Copyright (C) 2011-2019 Alessandro Ranellucci, (C) 2016-2023 Prusa Reseach</string>
|
||||
<string>@SLIC3R_APP_NAME@ Copyright (C) 2011-2019 Alessandro Ranellucci, (C) 2016-2024 Prusa Reseach</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>PrusaSlicer.icns</string>
|
||||
<key>CFBundleName</key>
|
||||
|
@ -287,7 +287,7 @@ AboutDialog::AboutDialog()
|
||||
"<html>"
|
||||
"<body bgcolor= %1% link= %2%>"
|
||||
"<font color=%3%>"
|
||||
"%4% © 2016-2023 Prusa Research. <br />"
|
||||
"%4% © 2016-2024 Prusa Research. <br />"
|
||||
"%5% © 2011-2018 Alessandro Ranellucci. <br />"
|
||||
"<a href=\"http://slic3r.org/\">Slic3r</a> %6% "
|
||||
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">%7%</a>."
|
||||
|
@ -261,7 +261,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
|
||||
for (auto el : { "perimeter_acceleration", "infill_acceleration", "top_solid_infill_acceleration",
|
||||
"solid_infill_acceleration", "external_perimeter_acceleration",
|
||||
"bridge_acceleration", "first_layer_acceleration" })
|
||||
"bridge_acceleration", "first_layer_acceleration", "wipe_tower_acceleration"})
|
||||
toggle_field(el, have_default_acceleration);
|
||||
|
||||
bool have_skirt = config->opt_int("skirts") > 0;
|
||||
@ -325,7 +325,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
|
||||
bool have_wipe_tower = config->opt_bool("wipe_tower");
|
||||
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle",
|
||||
"wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
|
||||
"wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
|
||||
toggle_field(el, have_wipe_tower);
|
||||
|
||||
bool have_non_zero_mmu_segmented_region_max_width = config->opt_float("mmu_segmented_region_max_width") > 0.;
|
||||
|
@ -1473,7 +1473,6 @@ PageDownloader::PageDownloader(ConfigWizard* parent)
|
||||
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
||||
append_spacer(VERTICAL_SPACING);
|
||||
|
||||
auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow built-in downloader"));
|
||||
// TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run.
|
||||
bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get_bool("downloader_url_registered") : true);
|
||||
@ -1518,11 +1517,11 @@ PageDownloader::PageDownloader(ConfigWizard* parent)
|
||||
));
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
append_text(wxString::Format(_L(
|
||||
"On Linux systems the process of registration also creates desktop integration files for this version of application."
|
||||
)));
|
||||
#endif
|
||||
#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
|
||||
box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->m_downloader->allow(event.IsChecked()); });
|
||||
|
||||
@ -1584,7 +1583,7 @@ bool DownloaderUtils::Worker::perform_register(const std::string& path)
|
||||
#elif __APPLE__
|
||||
// Apple registers for custom url in info.plist thus it has to be already registered since build.
|
||||
// The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method)
|
||||
#else
|
||||
#elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
// the performation should be called later during desktop integration
|
||||
perform_registration_linux = true;
|
||||
#endif
|
||||
@ -1602,7 +1601,7 @@ void DownloaderUtils::Worker::deregister()
|
||||
key_full = key_string;
|
||||
#elif __APPLE__
|
||||
// TODO
|
||||
#else
|
||||
#elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration";
|
||||
DesktopIntegrationDialog::undo_downloader_registration();
|
||||
perform_registration_linux = false;
|
||||
@ -2410,7 +2409,9 @@ void ConfigWizard::priv::load_pages()
|
||||
btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected || custom_printer_in_bundle);
|
||||
|
||||
index->add_page(page_update);
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
index->add_page(page_downloader);
|
||||
#endif
|
||||
index->add_page(page_reload_from_disk);
|
||||
#ifdef _WIN32
|
||||
index->add_page(page_files_association);
|
||||
@ -2777,12 +2778,12 @@ void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool i
|
||||
bool ConfigWizard::priv::on_bnt_finish()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
if (!page_downloader->on_finish_downloader()) {
|
||||
index->go_to(page_downloader);
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
/* If some printers were added/deleted, but related MaterialPage wasn't activated,
|
||||
* than last changes wouldn't be updated for filaments/materials.
|
||||
* SO, do that before check_and_install_missing_materials()
|
||||
@ -3064,14 +3065,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
if ((check_unsaved_preset_changes = install_bundles.size() > 0))
|
||||
header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
// Desktop integration on Linux
|
||||
BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << DownloaderUtils::Worker::perform_registration_linux;
|
||||
if (page_welcome->integrate_desktop())
|
||||
DesktopIntegrationDialog::perform_desktop_integration();
|
||||
if (DownloaderUtils::Worker::perform_registration_linux)
|
||||
DesktopIntegrationDialog::perform_downloader_desktop_integration();
|
||||
#endif
|
||||
#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
|
||||
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
|
||||
bool snapshot = true;
|
||||
@ -3405,7 +3406,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
|
||||
|
||||
p->add_page(p->page_update = new PageUpdate(this));
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
p->add_page(p->page_downloader = new PageDownloader(this));
|
||||
#endif
|
||||
p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
|
||||
#ifdef _WIN32
|
||||
p->add_page(p->page_files_association = new PageFilesAssociation(this));
|
||||
|
@ -170,19 +170,23 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent)
|
||||
sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e)
|
||||
{
|
||||
auto &project_config = wxGetApp().preset_bundle->project_config;
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
DynamicPrintConfig& project_config = preset_bundle->project_config;
|
||||
const bool use_custom_matrix = (project_config.option<ConfigOptionBool>("wiping_volumes_use_custom_matrix"))->value;
|
||||
const std::vector<double> &init_matrix = (project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values;
|
||||
const std::vector<double> &init_extruders = (project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values;
|
||||
|
||||
const std::vector<std::string> extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
||||
|
||||
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours);
|
||||
// Extract the relevant config options, even values from possibly modified presets.
|
||||
const double default_purge = static_cast<const ConfigOptionFloat*>(preset_bundle->printers.get_edited_preset().config.option("multimaterial_purging"))->value;
|
||||
std::vector<double> filament_purging_multipliers = preset_bundle->get_config_options_for_current_filaments<ConfigOptionPercents>("filament_purge_multiplier");
|
||||
|
||||
WipingDialog dlg(parent, cast<float>(init_matrix), extruder_colours, default_purge, filament_purging_multipliers, use_custom_matrix);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
std::vector<float> matrix = dlg.get_matrix();
|
||||
std::vector<float> extruders = dlg.get_extruders();
|
||||
(project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
|
||||
(project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
|
||||
(project_config.option<ConfigOptionBool>("wiping_volumes_use_custom_matrix"))->value = dlg.get_use_custom_matrix();
|
||||
// Update Project dirty state, update application title bar.
|
||||
Plater* plater = wxGetApp().plater();
|
||||
plater->update_project_dirty_from_presets();
|
||||
|
@ -2993,13 +2993,6 @@ void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
|
||||
|
||||
void GUI_App::MacOpenURL(const wxString& url)
|
||||
{
|
||||
if (app_config && !app_config->get_bool("downloader_url_registered"))
|
||||
{
|
||||
notification_manager()->push_notification(NotificationType::URLNotRegistered);
|
||||
BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string narrow_url = into_u8(url);
|
||||
if (boost::starts_with(narrow_url, "prusaslicer://open?file=")) {
|
||||
start_download(std::move(narrow_url));
|
||||
@ -3195,6 +3188,9 @@ void GUI_App::show_desktop_integration_dialog()
|
||||
|
||||
void GUI_App::show_downloader_registration_dialog()
|
||||
{
|
||||
#if defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
return;
|
||||
#endif
|
||||
InfoDialog msg(nullptr
|
||||
, format_wxstr(_L("Welcome to %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION)
|
||||
, format_wxstr(_L(
|
||||
@ -3206,10 +3202,10 @@ void GUI_App::show_downloader_registration_dialog()
|
||||
if (msg.ShowModal() == wxID_YES) {
|
||||
auto downloader_worker = new DownloaderUtils::Worker(nullptr);
|
||||
downloader_worker->perform_register(app_config->get("url_downloader_dest"));
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__)
|
||||
if (DownloaderUtils::Worker::perform_registration_linux)
|
||||
DesktopIntegrationDialog::perform_downloader_desktop_integration();
|
||||
#endif // __linux__
|
||||
#endif //(__linux__)
|
||||
} else {
|
||||
app_config->set("downloader_url_registered", "0");
|
||||
}
|
||||
@ -3601,6 +3597,19 @@ void GUI_App::start_download(std::string url)
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows register and deregister executable path to registry - cant get here when not registered
|
||||
// Apple registers via info.plist attached to exectable - might get here
|
||||
// Linux registers via desktop integration file - might get here only if such file was created outside Slicer.
|
||||
// Desktop integration is limited with SLIC3R_DESKTOP_INTEGRATION (cmake option).
|
||||
#if defined(__APPLE__) || (defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
if (app_config && app_config->get_bool("downloader_url_registered")) {
|
||||
notification_manager()->push_notification(NotificationType::URLNotRegistered);
|
||||
BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url;
|
||||
return;
|
||||
}
|
||||
#endif //defined(__APPLE__) || (defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
|
||||
//lets always init so if the download dest folder was changed, new dest is used
|
||||
boost::filesystem::path dest_folder(app_config->get("url_downloader_dest"));
|
||||
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
|
||||
|
@ -183,7 +183,7 @@ void ImGuiWrapper::set_language(const std::string &language)
|
||||
0,
|
||||
};
|
||||
m_font_cjk = false;
|
||||
if (lang == "cs" || lang == "pl" || lang == "hu") {
|
||||
if (lang == "cs" || lang == "pl" || lang == "hu" || lang == "sl") {
|
||||
ranges = ranges_latin2;
|
||||
} else if (lang == "ru" || lang == "uk" || lang == "be") {
|
||||
ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters
|
||||
|
@ -126,7 +126,7 @@ enum class NotificationType
|
||||
ExportOngoing,
|
||||
// Progressbar of download from prusaslicer:// url
|
||||
URLDownload,
|
||||
// MacOS specific - PS comes forward even when downloader is not allowed
|
||||
// MacOS and Linux flatpack (SLIC3R_DESKTOP_INTEGRATION = 0) specific - PS comes forward even when downloader is not allowed
|
||||
URLNotRegistered,
|
||||
// Config file was detected during startup, open wifi config dialog via hypertext
|
||||
WifiConfigFileDetected,
|
||||
|
@ -603,7 +603,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
|
||||
"brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "wipe_tower_extruder",
|
||||
"extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_notes", "printer_technology",
|
||||
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
|
||||
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
|
||||
|
@ -29,9 +29,13 @@
|
||||
#ifdef WIN32
|
||||
#include <wx/msw/registry.h>
|
||||
#endif // WIN32
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
#include "DesktopIntegrationDialog.hpp"
|
||||
#endif //__linux__
|
||||
#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
|
||||
#if defined(__linux__) && !defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
#include "NotificationManager.hpp"
|
||||
#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -131,10 +135,10 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
auto app_config = get_app_config();
|
||||
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
downloader->set_path_name(app_config->get("url_downloader_dest"));
|
||||
downloader->allow(!app_config->has("downloader_url_registered") || app_config->get_bool("downloader_url_registered"));
|
||||
|
||||
#endif
|
||||
for (const std::string& opt_key : {"suppress_hyperlinks", "downloader_url_registered"})
|
||||
m_optgroup_other->set_value(opt_key, app_config->get_bool(opt_key));
|
||||
|
||||
@ -642,15 +646,17 @@ void PreferencesDialog::build()
|
||||
//L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. "
|
||||
// "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."),
|
||||
app_config->get_bool("suppress_hyperlinks"));
|
||||
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
append_bool_option(m_optgroup_other, "downloader_url_registered",
|
||||
L("Allow downloads from Printables.com"),
|
||||
L("If enabled, PrusaSlicer will be allowed to download from Printables.com"),
|
||||
app_config->get_bool("downloader_url_registered"));
|
||||
#endif
|
||||
|
||||
activate_options_tab(m_optgroup_other);
|
||||
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
create_downloader_path_sizer();
|
||||
#endif
|
||||
create_settings_font_widget();
|
||||
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
@ -755,16 +761,18 @@ void PreferencesDialog::update_ctrls_alignment()
|
||||
|
||||
void PreferencesDialog::accept(wxEvent&)
|
||||
{
|
||||
#if !defined(__linux__) || (defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION))
|
||||
if(wxGetApp().is_editor()) {
|
||||
if (const auto it = m_values.find("downloader_url_registered"); it != m_values.end())
|
||||
downloader->allow(it->second == "1");
|
||||
if (!downloader->on_finish())
|
||||
return;
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__)
|
||||
if(DownloaderUtils::Worker::perform_registration_linux)
|
||||
DesktopIntegrationDialog::perform_downloader_desktop_integration();
|
||||
#endif // __linux__
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_pt_size", "suppress_round_corners" };
|
||||
|
||||
|
@ -115,6 +115,7 @@ void Chart::mouse_right_button_clicked(wxMouseEvent& event) {
|
||||
|
||||
|
||||
void Chart::mouse_clicked(wxMouseEvent& event) {
|
||||
m_uniform = (event.GetModifiers() == wxMOD_CONTROL);
|
||||
wxPoint point = event.GetPosition();
|
||||
int button_index = which_button_is_clicked(point);
|
||||
if ( button_index != -1) {
|
||||
@ -136,7 +137,15 @@ void Chart::mouse_moved(wxMouseEvent& event) {
|
||||
}
|
||||
int delta_x = pos.x - m_previous_mouse.x;
|
||||
int delta_y = pos.y - m_previous_mouse.y;
|
||||
m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height);
|
||||
|
||||
double new_y = m_dragged->get_pos().m_y - double(delta_y) / m_rect.GetHeight() * visible_area.m_height;
|
||||
|
||||
if (m_uniform)
|
||||
for (ButtonToDrag& b : m_buttons)
|
||||
b.move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width, new_y - b.get_pos().m_y);
|
||||
else
|
||||
m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width, new_y - m_dragged->get_pos().m_y);
|
||||
|
||||
m_previous_mouse = pos;
|
||||
recalculate_line();
|
||||
}
|
||||
@ -263,7 +272,7 @@ std::vector<float> Chart::get_ramming_speed(float sampling) const {
|
||||
std::vector<float> speeds_out;
|
||||
|
||||
const int number_of_samples = std::round( visible_area.m_width / sampling);
|
||||
if (number_of_samples>0) {
|
||||
if (number_of_samples>0 && !m_line_to_draw.empty()) {
|
||||
const int dx = (m_line_to_draw.size()-1) / number_of_samples;
|
||||
for (int j=0;j<number_of_samples;++j) {
|
||||
float left = screen_to_math(wxPoint(0,m_line_to_draw[j*dx])).m_y;
|
||||
|
@ -23,7 +23,7 @@ public:
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
m_rect = wxRect(wxPoint(legend_side,0),rect.GetSize()-wxSize(legend_side,legend_side));
|
||||
visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.);
|
||||
visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 60.);
|
||||
m_buttons.clear();
|
||||
if (initial_buttons.size()>0)
|
||||
for (const auto& pair : initial_buttons)
|
||||
@ -31,7 +31,7 @@ public:
|
||||
recalculate_line();
|
||||
}
|
||||
void set_xy_range(float x,float y) {
|
||||
x = int(x/0.5) * 0.5;
|
||||
x = int(x/0.25) * 0.25;
|
||||
if (x>=0) visible_area.SetRight(x);
|
||||
if (y>=0) visible_area.SetBottom(y);
|
||||
recalculate_line();
|
||||
@ -105,10 +105,7 @@ private:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
|
||||
void recalculate_line();
|
||||
void recalculate_volume();
|
||||
|
||||
|
||||
wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates)
|
||||
wxPoint m_previous_mouse;
|
||||
@ -118,6 +115,7 @@ private:
|
||||
ButtonToDrag* m_dragged = nullptr;
|
||||
float m_total_volume = 0.f;
|
||||
|
||||
bool m_uniform = false; // testing only
|
||||
};
|
||||
|
||||
|
||||
|
@ -1594,6 +1594,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("bridge_acceleration");
|
||||
optgroup->append_single_option_line("first_layer_acceleration");
|
||||
optgroup->append_single_option_line("first_layer_acceleration_over_raft");
|
||||
optgroup->append_single_option_line("wipe_tower_acceleration");
|
||||
optgroup->append_single_option_line("travel_acceleration");
|
||||
optgroup->append_single_option_line("default_acceleration");
|
||||
|
||||
@ -1628,6 +1629,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("wipe_tower_bridging");
|
||||
optgroup->append_single_option_line("wipe_tower_cone_angle");
|
||||
optgroup->append_single_option_line("wipe_tower_extra_spacing");
|
||||
optgroup->append_single_option_line("wipe_tower_extra_flow");
|
||||
optgroup->append_single_option_line("wipe_tower_no_sparse_layers");
|
||||
optgroup->append_single_option_line("single_extruder_multi_material_priming");
|
||||
|
||||
@ -2240,6 +2242,9 @@ void TabFilament::build()
|
||||
optgroup->append_single_option_line("filament_cooling_moves");
|
||||
optgroup->append_single_option_line("filament_cooling_initial_speed");
|
||||
optgroup->append_single_option_line("filament_cooling_final_speed");
|
||||
optgroup->append_single_option_line("filament_stamping_loading_speed");
|
||||
optgroup->append_single_option_line("filament_stamping_distance");
|
||||
optgroup->append_single_option_line("filament_purge_multiplier");
|
||||
|
||||
create_line_with_widget(optgroup.get(), "filament_ramming_parameters", "", [this](wxWindow* parent) {
|
||||
auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
|
||||
@ -3346,6 +3351,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
optgroup->append_single_option_line("cooling_tube_length");
|
||||
optgroup->append_single_option_line("parking_pos_retraction");
|
||||
optgroup->append_single_option_line("extra_loading_move");
|
||||
optgroup->append_single_option_line("multimaterial_purging");
|
||||
optgroup->append_single_option_line("high_current_on_filament_swap");
|
||||
if (from_initial_build)
|
||||
page->clear();
|
||||
|
@ -524,7 +524,7 @@ void DropDown::mouseReleased(wxMouseEvent& event)
|
||||
if (HasCapture())
|
||||
ReleaseMouse();
|
||||
if (hover_item >= 0) { // not moved
|
||||
#ifdef __WXOSX__
|
||||
#ifndef _WIN32
|
||||
// To avoid cases, when some dialog appears after item selection, but DropDown is still shown
|
||||
Hide();
|
||||
#endif
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "BitmapCache.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
|
||||
@ -71,14 +72,6 @@ RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters)
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE
|
||||
#else
|
||||
#define style wxSP_ARROW_KEYS
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/)
|
||||
{
|
||||
@ -109,11 +102,17 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters)
|
||||
#endif
|
||||
sizer_chart->Add(m_chart, 0, wxALL, 5);
|
||||
|
||||
m_widget_time = new ::SpinInputDouble(this,"", wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style, 0., 5., 3., 0.5);
|
||||
#ifdef _WIN32
|
||||
const long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE;
|
||||
#else
|
||||
const long style = wxSP_ARROW_KEYS;
|
||||
#endif
|
||||
|
||||
m_widget_time = new ::SpinInputDouble(this,"", wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style, 0., 5., 3., 0.25);
|
||||
m_widget_time->SetDigits(2);
|
||||
m_widget_volume = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,0,10000,0);
|
||||
m_widget_ramming_line_width_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,200,100);
|
||||
m_widget_ramming_step_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,200,100);
|
||||
m_widget_ramming_line_width_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,300,100);
|
||||
m_widget_ramming_step_multiplicator = new ::SpinInput(this,"",wxEmptyString,wxDefaultPosition,wxSize(ITEM_WIDTH(), -1),style,10,300,100);
|
||||
|
||||
#ifdef _WIN32
|
||||
update_ui(m_widget_time->GetText());
|
||||
@ -133,6 +132,15 @@ RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters)
|
||||
gsizer_param->Add(m_widget_ramming_line_width_multiplicator);
|
||||
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
gsizer_param->Add(m_widget_ramming_step_multiplicator);
|
||||
gsizer_param->AddSpacer(40);
|
||||
gsizer_param->AddSpacer(40);
|
||||
|
||||
std::string ctrl_str = shortkey_ctrl_prefix();
|
||||
if (! ctrl_str.empty() && ctrl_str.back() == '+')
|
||||
ctrl_str.pop_back();
|
||||
// TRN: The placeholder expands to Ctrl or Cmd (on macOS).
|
||||
gsizer_param->Add(new wxStaticText(this, wxID_ANY, format_wxstr(_L("For constant flow rate, hold %1% while dragging."), ctrl_str)), 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
|
||||
sizer_param->Add(gsizer_param, 0, wxTOP, scale(10));
|
||||
|
||||
@ -180,25 +188,53 @@ std::string RammingPanel::get_parameters()
|
||||
}
|
||||
|
||||
|
||||
// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode:
|
||||
WipingDialog::WipingDialog(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, const std::vector<std::string>& extruder_colours)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
|
||||
// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button.
|
||||
WipingDialog::WipingDialog(wxWindow* parent, const std::vector<float>& matrix, const std::vector<std::string>& extruder_colours,
|
||||
double printer_purging_volume, const std::vector<double>& filament_purging_multipliers, bool use_custom_matrix)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
update_ui(this);
|
||||
auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize);
|
||||
update_ui(widget_button);
|
||||
wxGetApp().SetWindowVariantForButton(widget_button);
|
||||
m_panel_wiping = new WipingPanel(this,matrix,extruders, extruder_colours, widget_button);
|
||||
m_widget_button = new wxButton(this,wxID_ANY,_L("Set values from configuration"), wxPoint(0, 0), wxDefaultSize);
|
||||
update_ui(m_widget_button);
|
||||
wxGetApp().SetWindowVariantForButton(m_widget_button);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_radio_button1 = new wxRadioButton(this, wxID_ANY, _L("Use values from configuration"));
|
||||
m_radio_button2 = new wxRadioButton(this, wxID_ANY, _L("Use custom project-specific settings"));
|
||||
auto stb1 = new wxStaticBox(this, wxID_ANY, wxEmptyString);
|
||||
auto stb2 = new wxStaticBox(this, wxID_ANY, wxEmptyString);
|
||||
|
||||
m_panel_wiping = new WipingPanel(this, matrix, extruder_colours, filament_purging_multipliers, printer_purging_volume, m_widget_button);
|
||||
|
||||
update_ui(m_radio_button1);
|
||||
update_ui(m_radio_button2);
|
||||
update_ui(stb1);
|
||||
update_ui(stb2);
|
||||
|
||||
auto heading_text = new wxStaticText(this, wxID_ANY, _L("The project uses single-extruder multimaterial printer with the wipe tower.\nThe volume of material used for purging can be configured here.") ,wxDefaultPosition, wxDefaultSize);
|
||||
m_info_text1 = new wxStaticText(this, wxID_ANY, _L("Options 'multimaterial_purging' and 'filament_purge_multiplier' will be used.") ,wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
// set min sizer width according to extruders count
|
||||
const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH());
|
||||
const auto sizer_width = (int)((std::sqrt(matrix.size()) + 2.8)*ITEM_WIDTH());
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->SetMinSize(wxSize(sizer_width, -1));
|
||||
|
||||
main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5);
|
||||
main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5);
|
||||
main_sizer->Add(heading_text, 0, wxALL, 10);
|
||||
|
||||
main_sizer->Add(m_radio_button1, 0, wxALL, 10);
|
||||
auto stb_sizer1 = new wxStaticBoxSizer(stb1, wxHORIZONTAL);
|
||||
stb_sizer1->Add(m_info_text1, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
|
||||
main_sizer->Add(stb_sizer1, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND | wxLEFT | wxRIGHT, 20);
|
||||
|
||||
auto t = new wxStaticText(this, wxID_ANY, _L("(all values in mm³)"), wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
main_sizer->Add(m_radio_button2, 0, wxALL, 10);
|
||||
auto stb_sizer2 = new wxStaticBoxSizer(stb2, wxVERTICAL);
|
||||
stb_sizer2->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5);
|
||||
stb_sizer2->Add(t, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5);
|
||||
stb_sizer2->Add(m_widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
main_sizer->Add(stb_sizer2, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 20);
|
||||
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
wxGetApp().SetWindowVariantForButton(buttons->GetAffirmativeButton());
|
||||
wxGetApp().SetWindowVariantForButton(buttons->GetCancelButton());
|
||||
@ -213,32 +249,49 @@ WipingDialog::WipingDialog(wxWindow* parent, const std::vector<float>& matrix, c
|
||||
|
||||
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked..
|
||||
m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values
|
||||
m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...()
|
||||
EndModal(wxID_OK);
|
||||
},wxID_OK);
|
||||
|
||||
this->Bind(wxEVT_RADIOBUTTON, [this](wxCommandEvent&) {
|
||||
enable_or_disable_panel();
|
||||
});
|
||||
|
||||
m_radio_button1->SetValue(! use_custom_matrix);
|
||||
m_radio_button2->SetValue(use_custom_matrix);
|
||||
enable_or_disable_panel();
|
||||
|
||||
this->Show();
|
||||
|
||||
}
|
||||
|
||||
// This function allows to "play" with sizers parameters (like align or border)
|
||||
void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/)
|
||||
// This function allows to "play" with sizrs parameters (like align or border)
|
||||
void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& table_title, int table_lshift/*=0*/)
|
||||
{
|
||||
wxSize text_size = GetTextExtent(info);
|
||||
auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
|
||||
info_str->Wrap(int(0.6*text_size.x));
|
||||
sizer->Add( info_str, 0, wxEXPAND);
|
||||
auto table_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift);
|
||||
table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50);
|
||||
table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10);
|
||||
table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 10);
|
||||
table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP | wxLEFT, 15);
|
||||
}
|
||||
|
||||
// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers)
|
||||
WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, const std::vector<std::string>& extruder_colours, wxButton* widget_button)
|
||||
|
||||
WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<std::string>& extruder_colours,
|
||||
const std::vector<double>& filament_purging_multipliers, double printer_purging_volume, wxButton* widget_button)
|
||||
: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/)
|
||||
{
|
||||
m_filament_purging_multipliers = filament_purging_multipliers;
|
||||
m_printer_purging_volume = printer_purging_volume;
|
||||
m_widget_button = widget_button; // pointer to the button in parent dialog
|
||||
m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); });
|
||||
m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){
|
||||
// Set the matrix to defaults.
|
||||
for (size_t i = 0; i < m_number_of_extruders; ++i) {
|
||||
for (size_t j = 0; j < m_number_of_extruders; ++j) {
|
||||
if (i != j) {
|
||||
double def_val = m_printer_purging_volume * m_filament_purging_multipliers[j] / 100.;
|
||||
edit_boxes[j][i]->SetValue(wxString("") << int(def_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_number_of_extruders = (int)(sqrt(matrix.size())+0.001);
|
||||
|
||||
@ -248,18 +301,12 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con
|
||||
m_colours.push_back(wxColor(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar()));
|
||||
}
|
||||
|
||||
// Create two switched panels with their own sizers
|
||||
m_sizer_simple = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer_advanced = new wxBoxSizer(wxVERTICAL);
|
||||
m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
m_page_simple->SetSizer(m_sizer_simple);
|
||||
m_page_advanced->SetSizer(m_sizer_advanced);
|
||||
|
||||
update_ui(m_page_simple);
|
||||
update_ui(m_page_advanced);
|
||||
|
||||
auto gridsizer_simple = new wxGridSizer(3, 5, 10);
|
||||
m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1);
|
||||
|
||||
// First create controls for advanced mode and assign them to m_page_advanced:
|
||||
@ -312,57 +359,15 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con
|
||||
}
|
||||
|
||||
// collect and format sizer
|
||||
format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced,
|
||||
_(L("Here you can adjust required purging volume (mm³) for any given pair of tools.")),
|
||||
_(L("Extruder changed to")));
|
||||
format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced, _(L("Extruder changed to")));
|
||||
|
||||
// Hide preview page before new page creating
|
||||
// It allows to do that from a beginning of the main panel
|
||||
m_page_advanced->Hide();
|
||||
|
||||
// Now the same for simple mode:
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
auto add_spin_ctrl = [this](std::vector<::SpinInput*>& vec, float initial)
|
||||
{
|
||||
::SpinInput* spin_ctrl = new ::SpinInput(m_page_simple, "", wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style | wxALIGN_RIGHT, 0, 300, (int)initial);
|
||||
update_ui(spin_ctrl);
|
||||
vec.push_back(spin_ctrl);
|
||||
};
|
||||
|
||||
for (unsigned int i=0;i<m_number_of_extruders;++i) {
|
||||
add_spin_ctrl(m_old, extruders[2 * i]);
|
||||
add_spin_ctrl(m_new, extruders[2 * i+1]);
|
||||
|
||||
auto hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxWindow* w = new wxWindow(m_page_simple, wxID_ANY, wxDefaultPosition, icon_size, wxBORDER_SIMPLE);
|
||||
w->SetCanFocus(false);
|
||||
w->SetBackgroundColour(m_colours[i]);
|
||||
hsizer->Add(w, wxALIGN_CENTER_VERTICAL);
|
||||
hsizer->AddSpacer(10);
|
||||
hsizer->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
gridsizer_simple->Add(hsizer, 1, wxEXPAND);
|
||||
gridsizer_simple->Add(m_old.back(),0);
|
||||
gridsizer_simple->Add(m_new.back(),0);
|
||||
}
|
||||
|
||||
// collect and format sizer
|
||||
format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple,
|
||||
_(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")),
|
||||
_(L("Volume to purge (mm³) when the filament is being")), 50);
|
||||
|
||||
m_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25);
|
||||
m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25);
|
||||
m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
m_sizer->SetSizeHints(this);
|
||||
SetSizer(m_sizer);
|
||||
|
||||
toggle_advanced(); // to show/hide what is appropriate
|
||||
|
||||
m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) {
|
||||
wxPaintDC dc(m_page_advanced);
|
||||
int y_pos = 0.5 * (edit_boxes[0][0]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetSize().y);
|
||||
@ -370,8 +375,24 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con
|
||||
int text_width = 0;
|
||||
int text_height = 0;
|
||||
dc.GetTextExtent(label,&text_width,&text_height);
|
||||
|
||||
int xpos = m_gridsizer_advanced->GetPosition().x;
|
||||
dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90);
|
||||
if (!m_page_advanced->IsEnabled()) {
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(
|
||||
#if defined (__linux__) && defined (__WXGTK2__)
|
||||
wxSYS_COLOUR_BTNTEXT
|
||||
#else
|
||||
wxSYS_COLOUR_GRAYTEXT
|
||||
#endif
|
||||
));
|
||||
dc.DrawRotatedText(label, xpos - text_height, y_pos + text_width / 2.f, 90);
|
||||
#ifdef _WIN32
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT));
|
||||
dc.DrawRotatedText(label, xpos - text_height-1, y_pos + text_width / 2.f+1, 90);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
dc.DrawRotatedText(label, xpos - text_height, y_pos + text_width / 2.f, 90);
|
||||
});
|
||||
}
|
||||
|
||||
@ -380,8 +401,6 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con
|
||||
|
||||
// Reads values from the (advanced) wiping matrix:
|
||||
std::vector<float> WipingPanel::read_matrix_values() {
|
||||
if (!m_advanced)
|
||||
fill_in_matrix();
|
||||
std::vector<float> output;
|
||||
for (unsigned int i=0;i<m_number_of_extruders;++i) {
|
||||
for (unsigned int j=0;j<m_number_of_extruders;++j) {
|
||||
@ -393,60 +412,12 @@ std::vector<float> WipingPanel::read_matrix_values() {
|
||||
return output;
|
||||
}
|
||||
|
||||
// Reads values from simple mode to save them for next time:
|
||||
std::vector<float> WipingPanel::read_extruders_values() {
|
||||
std::vector<float> output;
|
||||
for (unsigned int i=0;i<m_number_of_extruders;++i) {
|
||||
output.push_back(m_old[i]->GetValue());
|
||||
output.push_back(m_new[i]->GetValue());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// This updates the "advanced" matrix based on values from "simple" mode
|
||||
void WipingPanel::fill_in_matrix() {
|
||||
for (unsigned i=0;i<m_number_of_extruders;++i) {
|
||||
for (unsigned j=0;j<m_number_of_extruders;++j) {
|
||||
if (i==j) continue;
|
||||
edit_boxes[j][i]->SetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Function to check if simple and advanced settings are matching
|
||||
bool WipingPanel::advanced_matches_simple() {
|
||||
for (unsigned i=0;i<m_number_of_extruders;++i) {
|
||||
for (unsigned j=0;j<m_number_of_extruders;++j) {
|
||||
if (i==j) continue;
|
||||
if (edit_boxes[j][i]->GetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Switches the dialog from simple to advanced mode and vice versa
|
||||
void WipingPanel::toggle_advanced(bool user_action) {
|
||||
if (m_advanced && !advanced_matches_simple() && user_action) {
|
||||
if (MessageDialog(this, _L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"),
|
||||
_L("Warning"),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES)
|
||||
return;
|
||||
}
|
||||
if (user_action)
|
||||
m_advanced = !m_advanced; // user demands a change -> toggle
|
||||
else
|
||||
m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate
|
||||
|
||||
(m_advanced ? m_page_advanced : m_page_simple)->Show();
|
||||
(!m_advanced ? m_page_advanced : m_page_simple)->Hide();
|
||||
|
||||
m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings")));
|
||||
if (m_advanced)
|
||||
if (user_action) fill_in_matrix(); // otherwise keep values loaded from config
|
||||
|
||||
m_sizer->Layout();
|
||||
Refresh();
|
||||
|
||||
void WipingDialog::enable_or_disable_panel()
|
||||
{
|
||||
bool enable = m_radio_button2->GetValue();
|
||||
m_info_text1->Enable(! enable);
|
||||
m_widget_button->Enable(enable);
|
||||
m_panel_wiping->Enable(enable);
|
||||
m_panel_wiping->Refresh();
|
||||
}
|
@ -51,29 +51,22 @@ private:
|
||||
|
||||
class WipingPanel : public wxPanel {
|
||||
public:
|
||||
WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, const std::vector<std::string>& extruder_colours, wxButton* widget_button);
|
||||
WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<std::string>& extruder_colours,
|
||||
const std::vector<double>& filament_purging_multipliers, double printer_purging_volume, wxButton* widget_button);
|
||||
std::vector<float> read_matrix_values();
|
||||
std::vector<float> read_extruders_values();
|
||||
void toggle_advanced(bool user_action = false);
|
||||
void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0);
|
||||
void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& table_title, int table_lshift=0);
|
||||
|
||||
private:
|
||||
void fill_in_matrix();
|
||||
bool advanced_matches_simple();
|
||||
|
||||
std::vector<::SpinInput*> m_old;
|
||||
std::vector<::SpinInput*> m_new;
|
||||
std::vector<std::vector<wxTextCtrl*>> edit_boxes;
|
||||
std::vector<wxColour> m_colours;
|
||||
unsigned int m_number_of_extruders = 0;
|
||||
bool m_advanced = false;
|
||||
wxPanel* m_page_simple = nullptr;
|
||||
wxPanel* m_page_advanced = nullptr;
|
||||
wxBoxSizer* m_sizer = nullptr;
|
||||
wxBoxSizer* m_sizer_simple = nullptr;
|
||||
wxBoxSizer* m_sizer_advanced = nullptr;
|
||||
wxGridSizer* m_gridsizer_advanced = nullptr;
|
||||
wxButton* m_widget_button = nullptr;
|
||||
double m_printer_purging_volume;
|
||||
std::vector<double> m_filament_purging_multipliers; // In percents !
|
||||
};
|
||||
|
||||
|
||||
@ -82,15 +75,21 @@ private:
|
||||
|
||||
class WipingDialog : public wxDialog {
|
||||
public:
|
||||
WipingDialog(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, const std::vector<std::string>& extruder_colours);
|
||||
WipingDialog(wxWindow* parent, const std::vector<float>& matrix, const std::vector<std::string>& extruder_colours,
|
||||
double printer_purging_volume, const std::vector<double>& filament_purging_multipliers, bool use_custom_matrix);
|
||||
std::vector<float> get_matrix() const { return m_output_matrix; }
|
||||
std::vector<float> get_extruders() const { return m_output_extruders; }
|
||||
bool get_use_custom_matrix() const { return m_radio_button2->GetValue(); }
|
||||
|
||||
|
||||
private:
|
||||
void enable_or_disable_panel();
|
||||
|
||||
WipingPanel* m_panel_wiping = nullptr;
|
||||
std::vector<float> m_output_matrix;
|
||||
std::vector<float> m_output_extruders;
|
||||
wxRadioButton* m_radio_button1 = nullptr;
|
||||
wxRadioButton* m_radio_button2 = nullptr;
|
||||
wxButton* m_widget_button = nullptr;
|
||||
wxStaticText* m_info_text1 = nullptr;
|
||||
};
|
||||
|
||||
#endif // _WIPE_TOWER_DIALOG_H_
|
@ -16,6 +16,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_gcode_travels.cpp
|
||||
test_gcodefindreplace.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_cancel_object.cpp
|
||||
test_layers.cpp
|
||||
test_model.cpp
|
||||
test_multi.cpp
|
||||
|
200
tests/fff_print/test_cancel_object.cpp
Normal file
200
tests/fff_print/test_cancel_object.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Test;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
std::string remove_object(const std::string &gcode, const int id) {
|
||||
std::string result{gcode};
|
||||
std::string start_token{"M486 S" + std::to_string(id) + "\n"};
|
||||
std::string end_token{"M486 S-1\n"};
|
||||
|
||||
std::size_t start{result.find(start_token)};
|
||||
|
||||
while (start != std::string::npos) {
|
||||
std::size_t end_token_start{result.find(end_token, start)};
|
||||
std::size_t end{end_token_start + end_token.size()};
|
||||
result.replace(start, end - start, "");
|
||||
start = result.find(start_token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_CASE("Remove object sanity check", "[CancelObject]") {
|
||||
// clang-format off
|
||||
const std::string gcode{
|
||||
"the\n"
|
||||
"M486 S2\n"
|
||||
"to delete\n"
|
||||
"M486 S-1\n"
|
||||
"kept\n"
|
||||
"M486 S2\n"
|
||||
"to also delete\n"
|
||||
"M486 S-1\n"
|
||||
"lines\n"
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
const std::string result{remove_object(gcode, 2)};
|
||||
|
||||
// clang-format off
|
||||
CHECK(result == std::string{
|
||||
"the\n"
|
||||
"kept\n"
|
||||
"lines\n"
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void check_retraction(const std::string &gcode, double offset = 0.0) {
|
||||
GCodeReader parser;
|
||||
std::map<int, double> retracted;
|
||||
unsigned count{0};
|
||||
std::set<int> there_is_unretract;
|
||||
int extruder_id{0};
|
||||
|
||||
parser.parse_buffer(
|
||||
gcode,
|
||||
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
INFO("Line number: " + std::to_string(++count));
|
||||
INFO("Extruder id: " + std::to_string(extruder_id));
|
||||
if (!line.raw().empty() && line.raw().front() == 'T') {
|
||||
extruder_id = std::stoi(std::string{line.raw().back()});
|
||||
}
|
||||
if (line.dist_XY(self) < std::numeric_limits<double>::epsilon()) {
|
||||
if (line.has_e() && line.e() < 0) {
|
||||
retracted[extruder_id] += line.e();
|
||||
}
|
||||
if (line.has_e() && line.e() > 0) {
|
||||
INFO("Line: " + line.raw());
|
||||
if (there_is_unretract.count(extruder_id) == 0) {
|
||||
there_is_unretract.insert(extruder_id);
|
||||
REQUIRE(retracted[extruder_id] + offset + line.e() == Approx(0.0));
|
||||
} else {
|
||||
REQUIRE(retracted[extruder_id] + line.e() == Approx(0.0));
|
||||
}
|
||||
retracted[extruder_id] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void add_object(
|
||||
Model &model, const std::string &name, const int extruder, const Vec3d &offset = Vec3d::Zero()
|
||||
) {
|
||||
std::string extruder_id{std::to_string(extruder)};
|
||||
ModelObject *object = model.add_object();
|
||||
object->name = name;
|
||||
ModelVolume *volume = object->add_volume(Test::mesh(Test::TestMesh::cube_20x20x20));
|
||||
volume->set_material_id("material" + extruder_id);
|
||||
volume->translate(offset);
|
||||
DynamicPrintConfig config;
|
||||
config.set_deserialize_strict({
|
||||
{"extruder", extruder_id},
|
||||
});
|
||||
volume->config.assign_config(config);
|
||||
object->add_instance();
|
||||
object->ensure_on_bed();
|
||||
}
|
||||
|
||||
class CancelObjectFixture
|
||||
{
|
||||
public:
|
||||
CancelObjectFixture() {
|
||||
config.set_deserialize_strict({
|
||||
{"gcode_flavor", "marlin2"},
|
||||
{"gcode_label_objects", "firmware"},
|
||||
{"gcode_comments", "1"},
|
||||
{"use_relative_e_distances", "1"},
|
||||
{"wipe", "0"},
|
||||
{"skirts", "0"},
|
||||
});
|
||||
|
||||
add_object(two_cubes, "no_offset_cube", 0);
|
||||
add_object(two_cubes, "offset_cube", 0, {30.0, 0.0, 0.0});
|
||||
|
||||
add_object(multimaterial_cubes, "no_offset_cube", 1);
|
||||
add_object(multimaterial_cubes, "offset_cube", 2, {30.0, 0.0, 0.0});
|
||||
|
||||
retract_length = config.option<ConfigOptionFloats>("retract_length")->get_at(0);
|
||||
retract_length_toolchange = config.option<ConfigOptionFloats>("retract_length_toolchange")
|
||||
->get_at(0);
|
||||
}
|
||||
|
||||
DynamicPrintConfig config{Slic3r::DynamicPrintConfig::full_print_config()};
|
||||
|
||||
Model two_cubes;
|
||||
Model multimaterial_cubes;
|
||||
|
||||
double retract_length{};
|
||||
double retract_length_toolchange{};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(CancelObjectFixture, "Single extruder", "[CancelObject]") {
|
||||
Print print;
|
||||
print.apply(two_cubes, config);
|
||||
print.validate();
|
||||
const std::string gcode{Test::gcode(print)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"single_extruder_two.gcode"};
|
||||
output << gcode;
|
||||
}
|
||||
|
||||
SECTION("One remaining") {
|
||||
const std::string removed_object_gcode{remove_object(gcode, 0)};
|
||||
REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos);
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"single_extruder_one.gcode"};
|
||||
output << removed_object_gcode;
|
||||
}
|
||||
|
||||
check_retraction(removed_object_gcode);
|
||||
}
|
||||
|
||||
SECTION("All cancelled") {
|
||||
const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)};
|
||||
|
||||
// First retraction is not compensated - set offset.
|
||||
check_retraction(removed_all_gcode, retract_length);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") {
|
||||
config.set_deserialize_strict({{"complete_objects", 1}});
|
||||
|
||||
Print print;
|
||||
print.apply(two_cubes, config);
|
||||
print.validate();
|
||||
const std::string gcode{Test::gcode(print)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"sequential_print_two.gcode"};
|
||||
output << gcode;
|
||||
}
|
||||
|
||||
SECTION("One remaining") {
|
||||
const std::string removed_object_gcode{remove_object(gcode, 0)};
|
||||
REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos);
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"sequential_print_one.gcode"};
|
||||
output << removed_object_gcode;
|
||||
}
|
||||
|
||||
check_retraction(removed_object_gcode);
|
||||
}
|
||||
|
||||
SECTION("All cancelled") {
|
||||
const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)};
|
||||
|
||||
// First retraction is not compensated - set offset.
|
||||
check_retraction(removed_all_gcode, retract_length);
|
||||
}
|
||||
}
|
@ -145,11 +145,17 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
|
||||
std::string gcode = Test::gcode(print);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream gcode_file{"sequential_print.gcode"};
|
||||
gcode_file << gcode;
|
||||
}
|
||||
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
INFO("Unexpected E argument");
|
||||
CHECK(!line.has_e());
|
||||
|
||||
if (line.has_z()) {
|
||||
if (line.has_z() && std::abs(line.dist_Z(self)) > 0) {
|
||||
z_moves.emplace_back(line.z());
|
||||
}
|
||||
if (line.has_x() || line.has_y()) {
|
||||
|
@ -42,6 +42,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_anyptr.cpp
|
||||
test_jump_point_search.cpp
|
||||
test_support_spots_generator.cpp
|
||||
test_layer_region.cpp
|
||||
../data/prusaparts.cpp
|
||||
../data/prusaparts.hpp
|
||||
test_static_map.cpp
|
||||
|
148
tests/libslic3r/test_layer_region.cpp
Normal file
148
tests/libslic3r/test_layer_region.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/LayerRegion.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
constexpr bool export_svgs = false;
|
||||
|
||||
ExPolygon rectangle(const Point& origin, const int width, const int height) {
|
||||
return {
|
||||
origin,
|
||||
origin + Point{width, 0},
|
||||
origin + Point{width, height},
|
||||
origin + Point{0, height},
|
||||
};
|
||||
}
|
||||
|
||||
struct LayerRegionFixture {
|
||||
Surfaces surfaces{
|
||||
Surface{
|
||||
stBottomBridge,
|
||||
rectangle({scaled(-1.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||
},
|
||||
Surface{
|
||||
stBottomBridge,
|
||||
rectangle({scaled(0.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||
},
|
||||
Surface{
|
||||
stBottomBridge,
|
||||
rectangle({scaled(-3.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||
}
|
||||
};
|
||||
|
||||
ExPolygons shells{{
|
||||
rectangle({scaled(-1.0), scaled(1.0)}, scaled(3.0), scaled(1.0))
|
||||
}};
|
||||
ExPolygons sparse {{
|
||||
rectangle({scaled(-2.0), scaled(-1.0)}, scaled(1.0), scaled(3.0))
|
||||
}};
|
||||
|
||||
const float scaled_spacing{scaled(0.3)};
|
||||
|
||||
static constexpr const float expansion_step = scaled<float>(0.1);
|
||||
static constexpr const size_t max_nr_expansion_steps = 5;
|
||||
const float closing_radius = 0.55f * 0.65f * 1.05f * scaled_spacing;
|
||||
const int shells_expansion_depth = scaled(0.6);
|
||||
const RegionExpansionParameters expansion_params_into_solid_infill = RegionExpansionParameters::build(
|
||||
shells_expansion_depth,
|
||||
expansion_step,
|
||||
max_nr_expansion_steps
|
||||
);
|
||||
const int sparse_expansion_depth = scaled(0.3);
|
||||
const RegionExpansionParameters expansion_params_into_sparse_infill = RegionExpansionParameters::build(
|
||||
sparse_expansion_depth,
|
||||
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,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(LayerRegionFixture, "test the surface expansion", "[LayerRegion]") {
|
||||
const double custom_angle{1.234f};
|
||||
|
||||
const Surfaces result{expand_merge_surfaces(
|
||||
surfaces, stBottomBridge,
|
||||
expansion_zones,
|
||||
closing_radius,
|
||||
custom_angle
|
||||
)};
|
||||
|
||||
if constexpr (export_svgs) {
|
||||
SVG svg("general_expansion.svg", BoundingBox{
|
||||
Point{scaled(-3.0), scaled(-1.0)},
|
||||
Point{scaled(2.0), scaled(2.0)}
|
||||
});
|
||||
|
||||
svg.draw(surfaces, "blue");
|
||||
svg.draw(expansion_zones[0].expolygons, "green");
|
||||
svg.draw(expansion_zones[1].expolygons, "red");
|
||||
svg.draw_outline(result, "black", "", scale_(0.01));
|
||||
}
|
||||
|
||||
REQUIRE(result.size() == 2);
|
||||
CHECK(result.at(0).bridge_angle == Approx(custom_angle));
|
||||
CHECK(result.at(1).bridge_angle == Approx(custom_angle));
|
||||
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||
|
||||
// These lines in the polygons should correspond to the expansion depth.
|
||||
CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth);
|
||||
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(LayerRegionFixture, "test the bridge expansion with the bridge angle detection", "[LayerRegion]") {
|
||||
Surfaces result{expand_bridges_detect_orientations(
|
||||
surfaces,
|
||||
expansion_zones,
|
||||
closing_radius
|
||||
)};
|
||||
|
||||
if constexpr (export_svgs) {
|
||||
SVG svg("bridge_expansion.svg", BoundingBox{
|
||||
Point{scaled(-3.0), scaled(-1.0)},
|
||||
Point{scaled(2.0), scaled(2.0)}
|
||||
});
|
||||
|
||||
svg.draw(surfaces, "blue");
|
||||
svg.draw(expansion_zones[0].expolygons, "green");
|
||||
svg.draw(expansion_zones[1].expolygons, "red");
|
||||
svg.draw_outline(result, "black", "", scale_(0.01));
|
||||
}
|
||||
|
||||
REQUIRE(result.size() == 2);
|
||||
CHECK(result.at(0).bridge_angle == Approx(1.5707963268));
|
||||
CHECK(std::fmod(result.at(1).bridge_angle, M_PI) == Approx(0.0));
|
||||
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||
|
||||
// These lines in the polygons should correspond to the expansion depth.
|
||||
CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth);
|
||||
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user