Merge branch 'updating' of https://github.com/prusa3d/Slic3r into updating

This commit is contained in:
YuSanka 2018-04-27 15:40:25 +02:00
commit 9fb8ee9377
17 changed files with 892 additions and 455 deletions

52
doc/updating/Updatig.md Normal file
View File

@ -0,0 +1,52 @@
# Slic3r PE 1.40 configuration update
Slic3r PE 1.40.0 comes with a major re-work of the way configuration presets work.
There are three new features:
+ A two-tier system of presets being divided into _System_ and _User_ groups
+ Configuration snapshots
+ Configuration updating from the internet
## System and User presets
- _System preset_: These are the presets that come with Slic3r PE installation. They come from a vendor configuration bundle (not individual files like before). They are **read-only** a user cannot modify them, but may instead create a derived User preset based on a System preset
- _User preset_: These are regular presets stored in files just like before. Additionally, they may be derived (inherited) from one of the System presets
A derived User preset keeps track of wich settings are inherited from the parent System preset and which are modified by the user. When a system preset is updated (either via installation of a new Slic3r or automatically from the internet), in a User preset the settings that are modified by the user will stay that way, while the ones that are inherited reflect the updated System preset.
This system ensures that we don't overwrite user's settings when there is an update to the built in presets.
Slic3r GUI now displays accurately which settings are inherited and which are modified.
A setting derived from a System preset is represeted by green label and a locked lock icon:
![a system setting](setting_sys.png)
A settings modified in a User preset has an open lock icon:
![a user setting](setting_user.png)
Clickign the open lock icon restored the system setting.
Additionaly, any setting that is modified but not yet saved onto disk is represented by orange label and a back-arrow:
![a modified setting](setting_mod.png)
Clicking the back-arrow restores the value that was previously saved in this Preset.
## Configuration snapshots
Configuration snapshots can now be taken via the _Configuration_ menu.
A snapshot contains complete configuration from the point when the snapshot was taken.
Users may move back and forth between snapshots at will using a dialog:
![snapshots dialog](snapshots_dialog.png)
# Updating from the internet
Slic3r PE 1.40.0 checks for updates of the built-in System presets and downloads them.
The first-time configuration assistant will ask you if you want to enable this feature - it is **not** mandatory.
Updates are checked for and downloaded in the background. If there's is an update, Slic3r will prompt about it
next time it is launched, never during normal program operation. An update may be either accepted or refused.
Before any update is applied a configuration snapshot (as described above) is taken.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -161,8 +161,12 @@ sub thread_cleanup {
*Slic3r::Print::SupportMaterial2::DESTROY = sub {};
*Slic3r::TriangleMesh::DESTROY = sub {};
*Slic3r::GUI::AppConfig::DESTROY = sub {};
*Slic3r::GUI::GCodePreviewData::DESTROY = sub {};
*Slic3r::GUI::PresetBundle::DESTROY = sub {};
*Slic3r::GUI::Tab::DESTROY = sub {};
*Slic3r::GUI::PresetHints::DESTROY = sub {};
*Slic3r::GUI::TabIface::DESTROY = sub {};
*Slic3r::OctoPrint::DESTROY = sub {};
*Slic3r::PresetUpdater::DESTROY = sub {};
return undef; # this prevents a "Scalars leaked" warning
}

View File

@ -389,7 +389,7 @@ sub mouse_event {
$self->_mouse_dragging($e->Dragging);
if ($e->Entering && &Wx::wxMSW) {
if ($e->Entering && (&Wx::wxMSW || $^O eq 'linux')) {
# wxMSW needs focus in order to catch mouse wheel events
$self->SetFocus;
$self->_drag_start_xy(undef);

View File

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 15;
plan tests => 14;
BEGIN {
use FindBin;
@ -79,6 +79,7 @@ $config->set('disable_fan_first_layers', [ 0 ]);
"G1 X50 F2500\n" .
"G1 F3000;_EXTRUDE_SET_SPEED\n" .
"G1 X100 E1\n" .
";_EXTRUDE_END\n" .
"G1 E4 F400",
# Print time of $gcode.
my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60);
@ -203,8 +204,8 @@ $config->set('disable_fan_first_layers', [ 0 ]);
ok $all_below, 'slowdown_below_layer_time is honored';
# check that all layers have at least one unaltered external perimeter speed
my $external = all { $_ > 0 } values %layer_external;
ok $external, 'slowdown_below_layer_time does not alter external perimeters';
# my $external = all { $_ > 0 } values %layer_external;
# ok $external, 'slowdown_below_layer_time does not alter external perimeters';
}
__END__

View File

@ -23,6 +23,7 @@ const std::string CONTENT_TYPES_FILE = "[Content_Types].xml";
const std::string RELATIONSHIPS_FILE = "_rels/.rels";
const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
const char* MODEL_TAG = "model";
const char* RESOURCES_TAG = "resources";
@ -315,6 +316,7 @@ namespace Slic3r {
typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
XML_Parser m_xml_parser;
Model* m_model;
@ -326,6 +328,7 @@ namespace Slic3r {
IdToGeometryMap m_geometries;
CurrentConfig m_curr_config;
IdToMetadataMap m_objects_metadata;
IdToLayerHeightsProfileMap m_layer_heights_profiles;
public:
_3MF_Importer();
@ -339,7 +342,8 @@ namespace Slic3r {
bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
bool _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
// handlers to parse the .model file
@ -437,6 +441,7 @@ namespace Slic3r {
m_curr_config.object_id = -1;
m_curr_config.volume_id = -1;
m_objects_metadata.clear();
m_layer_heights_profiles.clear();
clear_errors();
return _load_model_from_file(filename, model, bundle);
@ -489,15 +494,15 @@ namespace Slic3r {
return false;
}
}
else if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE))
{
// extract slic3r lazer heights profile file
_extract_layer_heights_profile_config_from_archive(archive, stat);
}
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
{
// extract slic3r print config file
if (!_extract_print_config_from_archive(archive, stat, bundle, filename))
{
mz_zip_reader_end(&archive);
add_error("Archive does not contain a valid print config");
return false;
}
_extract_print_config_from_archive(archive, stat, bundle, filename);
}
else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE))
{
@ -526,6 +531,13 @@ namespace Slic3r {
return false;
}
IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.first);
if (obj_layer_heights_profile != m_layer_heights_profiles.end())
{
object.second->layer_height_profile = obj_layer_heights_profile->second;
object.second->layer_height_profile_valid = true;
}
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
if (obj_metadata != m_objects_metadata.end())
{
@ -609,23 +621,90 @@ namespace Slic3r {
return true;
}
bool _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename)
void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename)
{
if (stat.m_uncomp_size > 0)
{
std::vector<char> buffer((size_t)stat.m_uncomp_size + 1, 0);
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
add_error("Error while reading config data to buffer");
return false;
return;
}
buffer.back() = '\0';
bundle.load_config_string(buffer.data(), archive_filename.c_str());
}
}
return true;
void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size > 0)
{
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
add_error("Error while reading layer heights profile data to buffer");
return;
}
if (buffer.back() == '\n')
buffer.pop_back();
std::vector<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
for (const std::string& object : objects)
{
std::vector<std::string> object_data;
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
if (object_data.size() != 2)
{
add_error("Error while reading object data");
continue;
}
std::vector<std::string> object_data_id;
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
if (object_data_id.size() != 2)
{
add_error("Error while reading object id");
continue;
}
int object_id = std::atoi(object_data_id[1].c_str());
if (object_id == 0)
{
add_error("Found invalid object id");
continue;
}
IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id);
if (object_item != m_layer_heights_profiles.end())
{
add_error("Found duplicated layer heights profile");
continue;
}
std::vector<std::string> object_data_profile;
boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off);
if ((object_data_profile.size() <= 4) || (object_data_profile.size() % 2 != 0))
{
add_error("Found invalid layer heights profile");
continue;
}
std::vector<coordf_t> profile;
profile.reserve(object_data_profile.size());
for (const std::string& value : object_data_profile)
{
profile.push_back((coordf_t)std::atof(value.c_str()));
}
m_layer_heights_profiles.insert(IdToLayerHeightsProfileMap::value_type(object_id, profile));
}
}
}
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
@ -1429,6 +1508,7 @@ namespace Slic3r {
bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets);
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model);
};
@ -1477,6 +1557,14 @@ namespace Slic3r {
return false;
}
// adds layer height profile file
if (!_add_layer_height_profile_file_to_archive(archive, model))
{
mz_zip_writer_end(&archive);
boost::filesystem::remove(filename);
return false;
}
// adds slic3r print config file
if (export_print_config)
{
@ -1736,6 +1824,44 @@ namespace Slic3r {
return true;
}
bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
{
std::string out = "";
char buffer[1024];
unsigned int count = 0;
for (const ModelObject* object : model.objects)
{
++count;
std::vector<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
if ((layer_height_profile.size() >= 4) && ((layer_height_profile.size() % 2) == 0))
{
sprintf(buffer, "object_id=%d|", count);
out += buffer;
// Store the layer height profile as a single semicolon separated list.
for (size_t i = 0; i < layer_height_profile.size(); ++i)
{
sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]);
out += buffer;
}
out += "\n";
}
}
if (!out.empty())
{
if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
add_error("Unable to add layer heights profile file to archive");
return false;
}
}
return true;
}
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print)
{
char buffer[1024];
@ -1744,11 +1870,14 @@ namespace Slic3r {
GCode::append_full_config(print, out);
if (!out.empty())
{
if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
add_error("Unable to add print config file to archive");
return false;
}
}
return true;
}
@ -1832,10 +1961,7 @@ namespace Slic3r {
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *bundle);
if (!res)
importer.log_errors();
return res;
}

View File

@ -30,74 +30,8 @@ void CoolingBuffer::reset()
m_current_pos[4] = float(m_gcodegen.config().travel_speed.value);
}
#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
struct CoolingLine
{
const FullPrintConfig &config = m_gcodegen.config();
const std::vector<Extruder> &extruders = m_gcodegen.writer().extruders();
const size_t num_extruders = extruders.size();
// Calculate the required per extruder time stretches.
struct Adjustment {
Adjustment(unsigned int extruder_id = 0) : extruder_id(extruder_id) {}
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
float elapsed_time_total() {
float time_total = 0.f;
for (const Line &line : lines)
time_total += line.time;
return time_total;
}
// Calculate the maximum time when slowing down.
float maximum_time(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const Line &line : lines)
if (line.adjustable(slowdown_external_perimeters)) {
if (line.time_max == FLT_MAX)
return FLT_MAX;
else
time_total += line.time_max;
} else
time_total += line.time;
return time_total;
}
// Calculate the non-adjustable part of the total time.
float non_adjustable_time(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const Line &line : lines)
if (! line.adjustable(slowdown_external_perimeters))
time_total += line.time;
return time_total;
}
float slow_down_maximum(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (Line &line : lines) {
if (line.adjustable(slowdown_external_perimeters)) {
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
line.slowdown = true;
line.time = line.time_max;
}
time_total += line.time;
}
return time_total;
}
float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
assert(factor >= 1.f);
float time_total = 0.f;
for (Line &line : lines) {
if (line.adjustable(slowdown_external_perimeters)) {
line.slowdown = true;
line.time = std::min(line.time_max, line.time * factor);
}
time_total += line.time;
}
return time_total;
}
bool operator<(const Adjustment &rhs) const { return this->extruder_id < rhs.extruder_id; }
struct Line
{
enum Type {
TYPE_SET_TOOL = 1 << 0,
TYPE_EXTRUDE_END = 1 << 1,
@ -114,9 +48,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
TYPE_G92 = 1 << 11,
};
Line(unsigned int type, size_t line_start, size_t line_end) :
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
type(type), line_start(line_start), line_end(line_end),
length(0.f), time(0.f), time_max(0.f), slowdown(false) {}
length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
bool adjustable(bool slowdown_external_perimeters) const {
return (this->type & TYPE_ADJUSTABLE) &&
@ -124,6 +58,10 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
this->time < this->time_max;
}
bool adjustable() const {
return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max;
}
size_t type;
// Start of this line at the G-code snippet.
size_t line_start;
@ -131,53 +69,217 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
size_t line_end;
// XY Euclidian length of this segment.
float length;
// Current feedrate, possibly adjusted.
float feedrate;
// Current duration of this segment.
float time;
// Maximum duration of this segment.
float time_max;
// If marked with the "slowdown" flag, the line has been slowed down.
bool slowdown;
};
};
// Calculate the required per extruder time stretches.
struct PerExtruderAdjustments
{
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
float elapsed_time_total() {
float time_total = 0.f;
for (const CoolingLine &line : lines)
time_total += line.time;
return time_total;
}
// Calculate the total elapsed time when slowing down
// to the minimum extrusion feed rate defined for the current material.
float maximum_time_after_slowdown(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const CoolingLine &line : lines)
if (line.adjustable(slowdown_external_perimeters)) {
if (line.time_max == FLT_MAX)
return FLT_MAX;
else
time_total += line.time_max;
} else
time_total += line.time;
return time_total;
}
// Calculate the adjustable part of the total time.
float adjustable_time(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const CoolingLine &line : lines)
if (line.adjustable(slowdown_external_perimeters))
time_total += line.time;
return time_total;
}
// Calculate the non-adjustable part of the total time.
float non_adjustable_time(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const CoolingLine &line : lines)
if (! line.adjustable(slowdown_external_perimeters))
time_total += line.time;
return time_total;
}
// Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
// Used by both proportional and non-proportional slow down.
float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (CoolingLine &line : lines) {
if (line.adjustable(slowdown_external_perimeters)) {
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
line.slowdown = true;
line.time = line.time_max;
line.feedrate = line.length / line.time;
}
time_total += line.time;
}
return time_total;
}
// Slow down each adjustable G-code line proportionally by a factor.
// Used by the proportional slow down.
float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
assert(factor >= 1.f);
float time_total = 0.f;
for (CoolingLine &line : lines) {
if (line.adjustable(slowdown_external_perimeters)) {
line.slowdown = true;
line.time = std::min(line.time_max, line.time * factor);
line.feedrate = line.length / line.time;
}
time_total += line.time;
}
return time_total;
}
// Sort the lines, adjustable first, higher feedrate first.
// Used by non-proportional slow down.
void sort_lines_by_decreasing_feedrate() {
std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
bool adj1 = l1.adjustable();
bool adj2 = l2.adjustable();
return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
});
for (n_lines_adjustable = 0;
n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
++ n_lines_adjustable);
time_non_adjustable = 0.f;
for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
time_non_adjustable += lines[i].time;
}
// Calculate the maximum time stretch when slowing down to min_feedrate.
// Slowdown to min_feedrate shall be allowed for this extruder's material.
// Used by non-proportional slow down.
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) {
float time_stretch = 0.f;
assert(this->min_print_speed < min_feedrate + EPSILON);
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
const CoolingLine &line = lines[i];
if (line.feedrate > min_feedrate)
time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
}
return time_stretch;
}
// Slow down all adjustable lines down to min_feedrate.
// Slowdown to min_feedrate shall be allowed for this extruder's material.
// Used by non-proportional slow down.
void slow_down_to_feedrate(float min_feedrate) {
assert(this->min_print_speed < min_feedrate + EPSILON);
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
CoolingLine &line = lines[i];
if (line.feedrate > min_feedrate) {
line.time *= std::max(1.f, line.feedrate / min_feedrate);
line.feedrate = min_feedrate;
line.slowdown = true;
}
}
}
// Extruder, for which the G-code will be adjusted.
unsigned int extruder_id;
unsigned int extruder_id = 0;
// Is the cooling slow down logic enabled for this extruder's material?
bool cooling_slow_down_enabled = false;
// Slow down the print down to min_print_speed if the total layer time is below slowdown_below_layer_time.
float slowdown_below_layer_time = 0.f;
// Minimum print speed allowed for this extruder.
float min_print_speed = 0.f;
// Parsed lines.
std::vector<Line> lines;
};
std::vector<Adjustment> adjustments(num_extruders, Adjustment());
for (size_t i = 0; i < num_extruders; ++ i)
adjustments[i].extruder_id = extruders[i].id();
std::vector<CoolingLine> lines;
// The following two values are set by sort_lines_by_decreasing_feedrate():
// Number of adjustable lines, at the start of lines.
size_t n_lines_adjustable = 0;
// Non-adjustable time of lines starting with n_lines_adjustable.
float time_non_adjustable = 0;
// Current total time for this extruder.
float time_total = 0;
// Maximum time for this extruder, when the maximum slow down is applied.
float time_maximum = 0;
// Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
size_t idx_line_begin = 0;
size_t idx_line_end = 0;
};
std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
{
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos);
float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
return this->apply_layer_cooldown(gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
}
// Parse the layer G-code for the moves, which could be adjusted.
// Return the list of parsed lines, bucketed by an extruder.
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const
{
const FullPrintConfig &config = m_gcodegen.config();
const std::vector<Extruder> &extruders = m_gcodegen.writer().extruders();
unsigned int num_extruders = 0;
for (const Extruder &ex : extruders)
num_extruders = std::max(ex.id() + 1, num_extruders);
std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
std::vector<size_t> map_extruder_to_per_extruder_adjustment(num_extruders, 0);
for (size_t i = 0; i < extruders.size(); ++ i) {
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
unsigned int extruder_id = extruders[i].id();
adj.extruder_id = extruder_id;
adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
adj.min_print_speed = config.min_print_speed.get_at(extruder_id);
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
}
const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
// Parse the layer G-code for the moves, which could be adjusted.
{
float min_print_speed = float(EXTRUDER_CONFIG(min_print_speed));
auto adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder));
unsigned int initial_extruder = m_current_extruder;
unsigned int current_extruder = m_current_extruder;
PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
const char *line_start = gcode.c_str();
const char *line_end = line_start;
const char extrusion_axis = config.get_extrusion_axis()[0];
// Index of an existing Adjustment::Line of the current adjustment, which holds the feedrate setting command
// Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
// for a sequence of extrusion moves.
size_t active_speed_modifier = size_t(-1);
for (; *line_start != 0; line_start = line_end) {
for (; *line_start != 0; line_start = line_end)
{
while (*line_end != '\n' && *line_end != 0)
++ line_end;
// sline will not contain the trailing '\n'.
std::string sline(line_start, line_end);
// Adjustment::Line will contain the trailing '\n'.
// CoolingLine will contain the trailing '\n'.
if (*line_end == '\n')
++ line_end;
Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
if (boost::starts_with(sline, "G0 "))
line.type = Adjustment::Line::TYPE_G0;
line.type = CoolingLine::TYPE_G0;
else if (boost::starts_with(sline, "G1 "))
line.type = Adjustment::Line::TYPE_G1;
line.type = CoolingLine::TYPE_G1;
else if (boost::starts_with(sline, "G92 "))
line.type = Adjustment::Line::TYPE_G92;
line.type = CoolingLine::TYPE_G92;
if (line.type) {
// G0, G1 or G92
// Parse the G-code line.
std::vector<float> new_pos(m_current_pos);
std::vector<float> new_pos(current_pos);
const char *c = sline.data() + 3;
for (;;) {
// Skip whitespaces.
@ -192,9 +294,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
if (axis == 4) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
if ((line.type & Adjustment::Line::TYPE_G92) == 0)
if ((line.type & CoolingLine::TYPE_G92) == 0)
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
line.type |= Adjustment::Line::TYPE_HAS_F;
line.type |= CoolingLine::TYPE_HAS_F;
}
}
// Skip this word.
@ -203,21 +305,21 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
bool wipe = boost::contains(sline, ";_WIPE");
if (external_perimeter)
line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER;
line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
if (wipe)
line.type |= Adjustment::Line::TYPE_WIPE;
line.type |= CoolingLine::TYPE_WIPE;
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
line.type |= Adjustment::Line::TYPE_ADJUSTABLE;
line.type |= CoolingLine::TYPE_ADJUSTABLE;
active_speed_modifier = adjustment->lines.size();
}
if ((line.type & Adjustment::Line::TYPE_G92) == 0) {
if ((line.type & CoolingLine::TYPE_G92) == 0) {
// G0 or G1. Calculate the duration.
if (config.use_relative_e_distances.value)
// Reset extruder accumulator.
m_current_pos[3] = 0.f;
current_pos[3] = 0.f;
float dif[4];
for (size_t i = 0; i < 4; ++ i)
dif[i] = new_pos[i] - m_current_pos[i];
dif[i] = new_pos[i] - current_pos[i];
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
float dxyz2 = dxy2 + dif[2] * dif[2];
if (dxyz2 > 0.f) {
@ -227,15 +329,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
// Movement in the extruder axis.
line.length = std::abs(dif[3]);
}
line.feedrate = new_pos[4];
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
if (line.length > 0)
line.time = line.length / new_pos[4]; // current F
line.time = line.length / line.feedrate;
line.time_max = line.time;
if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed);
if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) {
if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
assert((line.type & Adjustment::Line::TYPE_HAS_F) == 0);
Adjustment::Line &sm = adjustment->lines[active_speed_modifier];
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
CoolingLine &sm = adjustment->lines[active_speed_modifier];
assert(sm.feedrate > 0.f);
sm.length += line.length;
sm.time += line.time;
if (sm.time_max != FLT_MAX) {
@ -248,26 +353,25 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
line.type = 0;
}
}
m_current_pos = std::move(new_pos);
current_pos = std::move(new_pos);
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
line.type = Adjustment::Line::TYPE_EXTRUDE_END;
line.type = CoolingLine::TYPE_EXTRUDE_END;
active_speed_modifier = size_t(-1);
} else if (boost::starts_with(sline, toolchange_prefix)) {
// Switch the tool.
line.type = Adjustment::Line::TYPE_SET_TOOL;
line.type = CoolingLine::TYPE_SET_TOOL;
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
min_print_speed = float(EXTRUDER_CONFIG(min_print_speed));
adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder));
if (new_extruder != current_extruder) {
current_extruder = new_extruder;
adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
}
} else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START;
line.type = CoolingLine::TYPE_BRIDGE_FAN_START;
} else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END;
line.type = CoolingLine::TYPE_BRIDGE_FAN_END;
} else if (boost::starts_with(sline, "G4 ")) {
// Parse the wait time.
line.type = Adjustment::Line::TYPE_G4;
line.type = CoolingLine::TYPE_G4;
size_t pos_S = sline.find('S', 3);
size_t pos_P = sline.find('P', 3);
line.time = line.time_max = float(
@ -277,112 +381,228 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
if (line.type != 0)
adjustment->lines.emplace_back(std::move(line));
}
m_current_extruder = initial_extruder;
}
// Sort the extruders by the increasing slowdown_below_layer_time.
std::vector<size_t> by_slowdown_layer_time;
by_slowdown_layer_time.reserve(num_extruders);
return per_extruder_adjustments;
}
// Slow down an extruder range proportionally down to slowdown_below_layer_time.
// Return the total time for the complete layer.
static inline float extruder_range_slow_down_proportional(
std::vector<PerExtruderAdjustments*>::iterator it_begin,
std::vector<PerExtruderAdjustments*>::iterator it_end,
// Elapsed time for the extruders already processed.
float elapsed_time_total0,
// Initial total elapsed time before slow down.
float elapsed_time_before_slowdown,
// Target time for the complete layer (all extruders applied).
float slowdown_below_layer_time)
{
// Total layer time after the slow down has been applied.
float total_after_slowdown = elapsed_time_before_slowdown;
// Now decide, whether the external perimeters shall be slowed down as well.
float max_time_nep = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
max_time_nep += (*it)->maximum_time_after_slowdown(false);
if (max_time_nep > slowdown_below_layer_time) {
// It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
// Slow down the non-external perimeters proportionally.
float non_adjustable_time = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
non_adjustable_time += (*it)->non_adjustable_time(false);
// The following step is a linear programming task due to the minimum movement speeds of the print moves.
// Run maximum 5 iterations until a good enough approximation is reached.
for (size_t iter = 0; iter < 5; ++ iter) {
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
assert(factor > 1.f);
total_after_slowdown = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
total_after_slowdown += (*it)->slow_down_proportional(factor, false);
if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
break;
}
} else {
// Slow down everything. First slow down the non-external perimeters to maximum.
for (auto it = it_begin; it != it_end; ++ it)
(*it)->slowdown_to_minimum_feedrate(false);
// Slow down the external perimeters proportionally.
float non_adjustable_time = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
non_adjustable_time += (*it)->non_adjustable_time(true);
for (size_t iter = 0; iter < 5; ++ iter) {
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
assert(factor > 1.f);
total_after_slowdown = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
total_after_slowdown += (*it)->slow_down_proportional(factor, true);
if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
break;
}
}
return total_after_slowdown;
}
// Slow down an extruder range to slowdown_below_layer_time.
// Return the total time for the complete layer.
static inline void extruder_range_slow_down_non_proportional(
std::vector<PerExtruderAdjustments*>::iterator it_begin,
std::vector<PerExtruderAdjustments*>::iterator it_end,
float time_stretch)
{
// Slow down. Try to equalize the feedrates.
std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
// Find the next highest adjustable feedrate among the extruders.
float feedrate = 0;
for (PerExtruderAdjustments *adj : by_min_print_speed) {
adj->idx_line_begin = 0;
adj->idx_line_end = 0;
assert(adj->idx_line_begin < adj->n_lines_adjustable);
if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
feedrate = adj->lines[adj->idx_line_begin].feedrate;
}
assert(feedrate > 0.f);
// Sort by min_print_speed, maximum speed first.
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; });
// Slow down, fast moves first.
for (;;) {
// For each extruder, find the span of lines with a feedrate close to feedrate.
for (PerExtruderAdjustments *adj : by_min_print_speed) {
for (adj->idx_line_end = adj->idx_line_begin;
adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
++ adj->idx_line_end) ;
}
// Find the next highest adjustable feedrate among the extruders.
float feedrate_next = 0.f;
for (PerExtruderAdjustments *adj : by_min_print_speed)
if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
feedrate_next = adj->lines[adj->idx_line_end].feedrate;
// Slow down, limited by max(feedrate_next, min_print_speed).
for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
// Slow down at most by time_stretch.
if ((*adj)->min_print_speed == 0.f) {
// All the adjustable speeds are now lowered to the same speed,
// and the minimum speed is set to zero.
float time_adjustable = 0.f;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
time_adjustable += (*it)->adjustable_time(true);
float rate = (time_adjustable + time_stretch) / time_adjustable;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
(*it)->slow_down_proportional(rate, true);
return;
} else {
float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
bool done = false;
float time_stretch_max = 0.f;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
if (time_stretch_max >= time_stretch) {
feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
done = true;
} else
time_stretch -= time_stretch_max;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
(*it)->slow_down_to_feedrate(feedrate_limit);
if (done)
return;
}
// Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
auto next = adj;
for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);
adj = next;
}
if (feedrate_next == 0.f)
// There are no other extrusions available for slow down.
break;
for (PerExtruderAdjustments *adj : by_min_print_speed) {
adj->idx_line_begin = adj->idx_line_end;
feedrate = feedrate_next;
}
}
}
// Calculate slow down for all the extruders.
float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
{
// Sort the extruders by an increasing slowdown_below_layer_time.
// The layers with a lower slowdown_below_layer_time are slowed down
// together with all the other layers with slowdown_below_layer_time above.
std::vector<PerExtruderAdjustments*> by_slowdown_time;
by_slowdown_time.reserve(per_extruder_adjustments.size());
// Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
// Collect total print time of non-adjustable extruders.
float elapsed_time_total_non_adjustable = 0.f;
for (size_t i = 0; i < num_extruders; ++ i) {
if (config.cooling.get_at(extruders[i].id()))
by_slowdown_layer_time.emplace_back(i);
else
elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total();
float elapsed_time_total0 = 0.f;
for (PerExtruderAdjustments &adj : per_extruder_adjustments) {
// Curren total time for this extruder.
adj.time_total = adj.elapsed_time_total();
// Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed.
adj.time_maximum = adj.maximum_time_after_slowdown(true);
if (adj.cooling_slow_down_enabled) {
by_slowdown_time.emplace_back(&adj);
if (! m_cooling_logic_proportional)
// sorts the lines, also sets adj.time_non_adjustable
adj.sort_lines_by_decreasing_feedrate();
} else
elapsed_time_total0 += adj.elapsed_time_total();
}
std::sort(by_slowdown_layer_time.begin(), by_slowdown_layer_time.end(),
[&config, &extruders](const size_t idx1, const size_t idx2){
return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) <
config.slowdown_below_layer_time.get_at(extruders[idx2].id());
});
std::sort(by_slowdown_time.begin(), by_slowdown_time.end(),
[](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2)
{ return adj1->slowdown_below_layer_time < adj2->slowdown_below_layer_time; });
// Elapsed time after adjustment.
float elapsed_time_total = 0.f;
{
// Elapsed time for the already adjusted extruders.
float elapsed_time_total0 = elapsed_time_total_non_adjustable;
for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) {
// Idx in adjustments.
size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time];
// Macro to sum or adjust all sections starting with i_by_slowdown_layer_time.
#define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \
ACCUMULATOR = elapsed_time_total0;\
for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \
ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION
for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) {
PerExtruderAdjustments &adj = *(*cur_begin);
// Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
float total;
FORALL_UNPROCESSED(total, elapsed_time_total());
float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(adjustments[idx].extruder_id)) * 1.001f;
float total = elapsed_time_total0;
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
total += (*it)->time_total;
float slowdown_below_layer_time = adj.slowdown_below_layer_time * 1.001f;
if (total > slowdown_below_layer_time) {
// The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
} else {
// Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
// Sum maximum slow down time as if everything was slowed down including the external perimeters.
float max_time;
FORALL_UNPROCESSED(max_time, maximum_time(true));
float max_time = elapsed_time_total0;
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
max_time += (*it)->time_maximum;
if (max_time > slowdown_below_layer_time) {
// By slowing every possible movement, the layer time could be reached. Now decide
// whether the external perimeters shall be slowed down as well.
float max_time_nep;
FORALL_UNPROCESSED(max_time_nep, maximum_time(false));
if (max_time_nep > slowdown_below_layer_time) {
// It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
// Slow down the non-external perimeters proportionally.
float non_adjustable_time;
FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(false));
// The following step is a linear programming task due to the minimum movement speeds of the print moves.
// Run maximum 5 iterations until a good enough approximation is reached.
for (size_t iter = 0; iter < 5; ++ iter) {
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time);
assert(factor > 1.f);
FORALL_UNPROCESSED(total, slow_down_proportional(factor, false));
if (total > 0.95f * slowdown_below_layer_time)
break;
}
} else {
// Slow down everything. First slow down the non-external perimeters to maximum.
FORALL_UNPROCESSED(total, slow_down_maximum(false));
// Slow down the external perimeters proportionally.
float non_adjustable_time;
FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true));
for (size_t iter = 0; iter < 5; ++ iter) {
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time);
assert(factor > 1.f);
FORALL_UNPROCESSED(total, slow_down_proportional(factor, true));
if (total > 0.95f * slowdown_below_layer_time)
break;
}
}
if (m_cooling_logic_proportional)
extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slowdown_below_layer_time);
else
extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slowdown_below_layer_time - total);
} else {
// Slow down to maximum possible.
FORALL_UNPROCESSED(total, slow_down_maximum(true));
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
(*it)->slowdown_to_minimum_feedrate(true);
}
}
#undef FORALL_UNPROCESSED
// Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time.
if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size())
// Optimization for single extruder prints.
elapsed_time_total0 = total;
else
elapsed_time_total0 += adjustments[idx].elapsed_time_total();
}
elapsed_time_total = elapsed_time_total0;
elapsed_time_total0 += adj.elapsed_time_total();
}
// Transform the G-code.
// First sort the adjustment lines by their position in the source G-code.
std::vector<const Adjustment::Line*> lines;
return elapsed_time_total0;
}
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
std::string CoolingBuffer::apply_layer_cooldown(
// Source G-code for the current layer.
const std::string &gcode,
// ID of the current layer, used to disable fan for the first n layers.
size_t layer_id,
// Total time of this layer after slow down, used to control the fan.
float layer_time,
// Per extruder list of G-code lines and their cool down attributes.
std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
{
// First sort the adjustment lines by of multiple extruders by their position in the source G-code.
std::vector<const CoolingLine*> lines;
{
size_t n_lines = 0;
for (const Adjustment &adj : adjustments)
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
n_lines += adj.lines.size();
lines.reserve(n_lines);
for (const Adjustment &adj : adjustments)
for (const Adjustment::Line &line : adj.lines)
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
for (const CoolingLine &line : adj.lines)
lines.emplace_back(&line);
std::sort(lines.begin(), lines.end(), [](const Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } );
std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
}
// Second generate the adjusted G-code.
std::string new_gcode;
@ -390,8 +610,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
int fan_speed = -1;
bool bridge_fan_control = false;
int bridge_fan_speed = 0;
auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
const FullPrintConfig &config = m_gcodegen.config();
#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) {
@ -399,17 +620,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time));
float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time));
if (EXTRUDER_CONFIG(cooling)) {
if (elapsed_time_total < slowdown_below_layer_time) {
if (layer_time < slowdown_below_layer_time) {
// Layer time very short. Enable the fan to a full throttle.
fan_speed_new = max_fan_speed;
} else if (elapsed_time_total < fan_below_layer_time) {
} else if (layer_time < fan_below_layer_time) {
// Layer time quite short. Enable the fan proportionally according to the current layer time.
assert(elapsed_time_total >= slowdown_below_layer_time);
double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
assert(layer_time >= slowdown_below_layer_time);
double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
}
}
bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
#undef EXTRUDER_CONFIG
bridge_fan_control = bridge_fan_speed > fan_speed_new;
} else {
bridge_fan_control = false;
@ -421,31 +643,32 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
new_gcode += m_gcodegen.writer().set_fan(fan_speed);
}
};
change_extruder_set_fan();
const char *pos = gcode.c_str();
int current_feedrate = 0;
for (const Adjustment::Line *line : lines) {
const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
change_extruder_set_fan();
for (const CoolingLine *line : lines) {
const char *line_start = gcode.c_str() + line->line_start;
const char *line_end = gcode.c_str() + line->line_end;
if (line_start > pos)
new_gcode.append(pos, line_start - pos);
if (line->type & Adjustment::Line::TYPE_SET_TOOL) {
if (line->type & CoolingLine::TYPE_SET_TOOL) {
unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size());
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
change_extruder_set_fan();
}
new_gcode.append(line_start, line_end - line_start);
} else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) {
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
if (bridge_fan_control)
new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true);
} else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_END) {
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) {
if (bridge_fan_control)
new_gcode += m_gcodegen.writer().set_fan(fan_speed, true);
} else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) {
} else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
// Just remove this comment.
} else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE | Adjustment::Line::TYPE_HAS_F)) {
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
// Find the start of a comment, or roll to the end of line.
const char *end = line_start;
for (; end < line_end && *end != ';'; ++ end);
@ -456,14 +679,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
assert(fpos != nullptr);
if (line->slowdown) {
modify = true;
new_feedrate = int(floor(60. * (line->length / line->time) + 0.5));
new_feedrate = int(floor(60. * line->feedrate + 0.5));
} else {
new_feedrate = atoi(fpos);
if (new_feedrate != current_feedrate) {
// Append the line without the comment.
new_gcode.append(line_start, end - line_start);
current_feedrate = new_feedrate;
} else if ((line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) || line->length == 0.) {
} else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) {
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
end = line_end;
} else {
@ -497,13 +720,13 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
}
// Process the rest of the line.
if (end < line_end) {
if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) {
if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
std::string comment(end, line_end);
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER)
if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
if (line->type & Adjustment::Line::TYPE_WIPE)
if (line->type & CoolingLine::TYPE_WIPE)
boost::replace_all(comment, ";_WIPE", "");
new_gcode += comment;
} else {

View File

@ -9,13 +9,17 @@ namespace Slic3r {
class GCode;
class Layer;
class PerExtruderAdjustments;
/*
A standalone G-code filter, to control cooling of the print.
The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
and the print is modified to stretch over a minimum layer time.
*/
// A standalone G-code filter, to control cooling of the print.
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
// and the print is modified to stretch over a minimum layer time.
//
// The simple it sounds, the actual implementation is significantly more complex.
// Namely, for a multi-extruder print, each material may require a different cooling logic.
// For example, some materials may not like to print too slowly, while with some materials
// we may slow down significantly.
//
class CoolingBuffer {
public:
CoolingBuffer(GCode &gcodegen);
@ -25,7 +29,12 @@ public:
GCode* gcodegen() { return &m_gcodegen; }
private:
CoolingBuffer& operator=(const CoolingBuffer&);
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
GCode& m_gcodegen;
std::string m_gcode;
@ -34,6 +43,9 @@ private:
std::vector<char> m_axis;
std::vector<float> m_current_pos;
unsigned int m_current_extruder;
// Old logic: proportional.
bool m_cooling_logic_proportional = false;
};
}

View File

@ -99,17 +99,31 @@ void GCodePreviewData::Range::set_from(const Range& other)
float GCodePreviewData::Range::step_size() const
{
return (max - min) / (float)Colors_Count;
return (max - min) / (float)(Colors_Count - 1);
}
const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at_max() const
GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const
{
return colors[Colors_Count - 1];
}
if (empty())
return Color::Dummy;
const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at(float value) const
{
return empty() ? get_color_at_max() : colors[clamp((unsigned int)0, Colors_Count - 1, (unsigned int)((value - min) / step_size()))];
float global_t = (value - min) / step_size();
unsigned int low = (unsigned int)global_t;
unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1);
Color color_low = colors[low];
Color color_high = colors[high];
float local_t = global_t - (float)low;
// interpolate in RGB space
Color ret;
for (unsigned int i = 0; i < 4; ++i)
{
ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t);
}
return ret;
}
GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color)
@ -266,22 +280,22 @@ const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(Extrus
return extrusion.role_colors[role];
}
const GCodePreviewData::Color& GCodePreviewData::get_height_color(float height) const
GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const
{
return ranges.height.get_color_at(height);
}
const GCodePreviewData::Color& GCodePreviewData::get_width_color(float width) const
GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const
{
return ranges.width.get_color_at(width);
}
const GCodePreviewData::Color& GCodePreviewData::get_feedrate_color(float feedrate) const
GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const
{
return ranges.feedrate.get_color_at(feedrate);
}
const GCodePreviewData::Color& GCodePreviewData::get_volumetric_rate_color(float rate) const
GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const
{
return ranges.volumetric_rate.get_color_at(rate);
}
@ -373,7 +387,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
for (int i = Range::Colors_Count - 1; i >= 0; --i)
{
char buf[1024];
sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step));
sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step));
list.emplace_back(buf, range.colors[i]);
}
}
@ -408,7 +422,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
}
case Extrusion::Feedrate:
{
Helper::FillListFromRange(items, ranges.feedrate, 0, 1.0f);
Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f);
break;
}
case Extrusion::VolumetricRate:

View File

@ -41,8 +41,7 @@ public:
void set_from(const Range& other);
float step_size() const;
const Color& get_color_at(float value) const;
const Color& get_color_at_max() const;
Color get_color_at(float value) const;
};
struct Ranges
@ -189,10 +188,10 @@ public:
bool empty() const;
const Color& get_extrusion_role_color(ExtrusionRole role) const;
const Color& get_height_color(float height) const;
const Color& get_width_color(float width) const;
const Color& get_feedrate_color(float feedrate) const;
const Color& get_volumetric_rate_color(float rate) const;
Color get_height_color(float height) const;
Color get_width_color(float width) const;
Color get_feedrate_color(float feedrate) const;
Color get_volumetric_rate_color(float rate) const;
void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);
void set_extrusion_paths_colors(const std::vector<std::string>& colors);

View File

@ -53,6 +53,7 @@
#include "ConfigWizard.hpp"
#include "Preferences.hpp"
#include "PresetBundle.hpp"
#include "UpdateDialogs.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "../Config/Snapshot.hpp"
@ -480,24 +481,8 @@ bool config_wizard_startup(bool app_config_exists)
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
const auto msg = _(L("Configuration update"));
const auto ext_msg = wxString::Format(
_(L(
"Slic3r PE now uses an updated configuration structure.\n\n"
"So called 'System presets' have been introduced, which hold the built-in default settings for various "
"printers. These System presets cannot be modified, instead, users now may create their"
"own presets inheriting settings from one of the System presets.\n"
"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n"
"Please proceed with the %s that follows to set up the new presets "
"and to choose whether to enable automatic preset updates."
)),
ConfigWizard::name()
);
wxMessageDialog dlg(NULL, msg, _(L("Configuration update")), wxOK|wxCENTRE);
dlg.SetExtendedMessage(ext_msg);
const auto res = dlg.ShowModal();
MsgDataLegacy dlg;
dlg.ShowModal();
config_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;

View File

@ -1004,29 +1004,6 @@ void TabPrint::update()
on_value_change("fill_density", fill_density);
}
auto first_layer_height = m_config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value;
auto layer_height = m_config->opt_float("layer_height");
if (m_config->opt_bool("wipe_tower") &&
(first_layer_height != 0.2 || layer_height < 0.15 || layer_height > 0.35)) {
wxString msg_text = _(L("The Wipe Tower currently supports only:\n"
"- first layer height 0.2mm\n"
"- layer height from 0.15mm to 0.35mm\n"
"\nShall I adjust those settings in order to enable the Wipe Tower?"));
auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO);
DynamicPrintConfig new_conf = *m_config;
if (dialog->ShowModal() == wxID_YES) {
const auto &val = *m_config->option<ConfigOptionFloatOrPercent>("first_layer_height");
auto percent = val.percent;
new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, percent));
if (m_config->opt_float("layer_height") < 0.15) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.15));
if (m_config->opt_float("layer_height") > 0.35) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.35));
}
else
new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false));
load_config(new_conf);
}
if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") &&
m_config->opt_float("support_material_contact_distance") > 0. &&
(m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) {

View File

@ -12,6 +12,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
@ -201,5 +202,36 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
MsgDataIncompatible::~MsgDataIncompatible() {}
// MsgDataLegacy
MsgDataLegacy::MsgDataLegacy() :
MsgDialog(_(L("Configuration update")), _(L("Configuration update")))
{
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(
_(L(
"Slic3r PE now uses an updated configuration structure.\n\n"
"So called 'System presets' have been introduced, which hold the built-in default settings for various "
"printers. These System presets cannot be modified, instead, users now may create their "
"own presets inheriting settings from one of the System presets.\n"
"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n"
"Please proceed with the %s that follows to set up the new presets "
"and to choose whether to enable automatic preset updates."
)),
ConfigWizard::name()
));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
// TODO: Add link to wiki?
Fit();
}
MsgDataLegacy::~MsgDataLegacy() {}
}
}

View File

@ -85,6 +85,18 @@ public:
~MsgDataIncompatible();
};
// Informs about a legacy data directory - an update from Slic3r PE < 1.40
class MsgDataLegacy : public MsgDialog
{
public:
MsgDataLegacy();
MsgDataLegacy(MsgDataLegacy &&) = delete;
MsgDataLegacy(const MsgDataLegacy &) = delete;
MsgDataLegacy &operator=(MsgDataLegacy &&) = delete;
MsgDataLegacy &operator=(const MsgDataLegacy &) = delete;
~MsgDataLegacy();
};
}
}