Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/libslic3r/GCode.cpp')
-rw-r--r--src/libslic3r/GCode.cpp2432
1 files changed, 995 insertions, 1437 deletions
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 19116b336..cc9dfce30 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -1,13 +1,16 @@
#include "libslic3r.h"
#include "I18N.hpp"
#include "GCode.hpp"
+#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
#include "EdgeGrid.hpp"
#include "Geometry.hpp"
#include "GCode/PrintExtents.hpp"
#include "GCode/WipeTower.hpp"
#include "ShortestPath.hpp"
+#include "Print.hpp"
#include "Utils.hpp"
+#include "ClipperUtils.hpp"
#include "libslic3r.h"
#include <algorithm>
@@ -20,9 +23,7 @@
#include <boost/foreach.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
-#if ENABLE_THUMBNAIL_GENERATOR
#include <boost/beast/core/detail/base64.hpp>
-#endif // ENABLE_THUMBNAIL_GENERATOR
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/cstdio.hpp>
@@ -49,311 +50,234 @@ using namespace std::literals::string_view_literals;
namespace Slic3r {
-//! macro used to mark string used at localization,
-//! return same string
+ //! macro used to mark string used at localization,
+ //! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
// Only add a newline in case the current G-code does not end with a newline.
-static inline void check_add_eol(std::string &gcode)
-{
- if (! gcode.empty() && gcode.back() != '\n')
- gcode += '\n';
-}
+ static inline void check_add_eol(std::string& gcode)
+ {
+ if (!gcode.empty() && gcode.back() != '\n')
+ gcode += '\n';
+ }
-// Return true if tch_prefix is found in custom_gcode
-static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
-{
- bool ok = false;
- size_t from_pos = 0;
- size_t pos = 0;
- while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
- if (pos+1 == custom_gcode.size())
- break;
- from_pos = pos+1;
- // only whitespace is allowed before the command
- while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
- if (! std::isspace(custom_gcode[pos]))
- goto NEXT;
- }
- {
- // we should also check that the extruder changes to what was expected
- std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
- unsigned num = 0;
- if (ss >> num)
- ok = (num == next_extruder);
+ // Return true if tch_prefix is found in custom_gcode
+ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
+ {
+ bool ok = false;
+ size_t from_pos = 0;
+ size_t pos = 0;
+ while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
+ if (pos + 1 == custom_gcode.size())
+ break;
+ from_pos = pos + 1;
+ // only whitespace is allowed before the command
+ while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
+ if (!std::isspace(custom_gcode[pos]))
+ goto NEXT;
+ }
+ {
+ // we should also check that the extruder changes to what was expected
+ std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
+ unsigned num = 0;
+ if (ss >> num)
+ ok = (num == next_extruder);
+ }
+ NEXT:;
}
-NEXT: ;
+ return ok;
}
- return ok;
-}
-void AvoidCrossingPerimeters::init_external_mp(const Print &print)
-{
- m_external_mp = Slic3r::make_unique<MotionPlanner>(union_ex(this->collect_contours_all_layers(print.objects())));
-}
+ std::string OozePrevention::pre_toolchange(GCode& gcodegen)
+ {
+ std::string gcode;
-// Plan a travel move while minimizing the number of perimeter crossings.
-// point is in unscaled coordinates, in the coordinate system of the current active object
-// (set by gcodegen.set_origin()).
-Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point)
-{
- // 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 = this->use_external_mp || this->use_external_mp_once;
- Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
- Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
- shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
- if (use_external)
- result.translate(- scaled_origin);
- return result;
-}
+ // move to the nearest standby point
+ if (!this->standby_points.empty()) {
+ // get current position in print coordinates
+ Vec3d writer_pos = gcodegen.writer().get_position();
+ Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
-// Collect outer contours of all objects over all layers.
-// Discard objects only containing thin walls (offset would fail on an empty polygon).
-// Used by avoid crossing perimeters feature.
-Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
-{
- Polygons islands;
- for (const PrintObject *object : objects) {
- // Reducing all the object slices into the Z projection in a logarithimc fashion.
- // First reduce to half the number of layers.
- std::vector<Polygons> polygons_per_layer((object->layers().size() + 1) / 2);
- tbb::parallel_for(tbb::blocked_range<size_t>(0, object->layers().size() / 2),
- [&object, &polygons_per_layer](const tbb::blocked_range<size_t> &range) {
- for (size_t i = range.begin(); i < range.end(); ++ i) {
- const Layer* layer1 = object->layers()[i * 2];
- const Layer* layer2 = object->layers()[i * 2 + 1];
- Polygons polys;
- polys.reserve(layer1->lslices.size() + layer2->lslices.size());
- for (const ExPolygon &expoly : layer1->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- for (const ExPolygon &expoly : layer2->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- polygons_per_layer[i] = union_(polys);
- }
- });
- if (object->layers().size() & 1) {
- const Layer *layer = object->layers().back();
- Polygons polys;
- polys.reserve(layer->lslices.size());
- for (const ExPolygon &expoly : layer->lslices)
- //FIXME no holes?
- polys.emplace_back(expoly.contour);
- polygons_per_layer.back() = union_(polys);
- }
- // Now reduce down to a single layer.
- size_t cnt = polygons_per_layer.size();
- while (cnt > 1) {
- tbb::parallel_for(tbb::blocked_range<size_t>(0, cnt / 2),
- [&polygons_per_layer](const tbb::blocked_range<size_t> &range) {
- for (size_t i = range.begin(); i < range.end(); ++ i) {
- Polygons polys;
- polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
- polygons_append(polys, polygons_per_layer[i * 2]);
- polygons_append(polys, polygons_per_layer[i * 2 + 1]);
- polygons_per_layer[i * 2] = union_(polys);
- }
- });
- for (size_t i = 0; i < cnt / 2; ++ i)
- polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
- if (cnt & 1)
- polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
- cnt = (cnt + 1) / 2;
- }
- // And collect copies of the objects.
- for (const PrintInstance &instance : object->instances()) {
- // All the layers were reduced to the 1st item of polygons_per_layer.
- size_t i = islands.size();
- polygons_append(islands, polygons_per_layer.front());
- for (; i < islands.size(); ++ i)
- islands[i].translate(instance.shift);
+ // find standby point
+ Point standby_point;
+ pos.nearest_point(this->standby_points, &standby_point);
+
+ /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
+ triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
+ of the destination point must not be transformed by origin nor current extruder offset. */
+ gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
+ "move to standby position");
}
- }
- return islands;
-}
-std::string OozePrevention::pre_toolchange(GCode &gcodegen)
-{
- std::string gcode;
-
- // move to the nearest standby point
- if (!this->standby_points.empty()) {
- // get current position in print coordinates
- Vec3d writer_pos = gcodegen.writer().get_position();
- Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
-
- // find standby point
- Point standby_point;
- pos.nearest_point(this->standby_points, &standby_point);
-
- /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
- triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
- of the destination point must not be transformed by origin nor current extruder offset. */
- gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
- "move to standby position");
- }
-
- if (gcodegen.config().standby_temperature_delta.value != 0) {
- // we assume that heating is always slower than cooling, so no need to block
- gcode += gcodegen.writer().set_temperature
+ if (gcodegen.config().standby_temperature_delta.value != 0) {
+ // we assume that heating is always slower than cooling, so no need to block
+ gcode += gcodegen.writer().set_temperature
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
+ }
+
+ return gcode;
}
-
- return gcode;
-}
-std::string OozePrevention::post_toolchange(GCode &gcodegen)
-{
- return (gcodegen.config().standby_temperature_delta.value != 0) ?
- gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
- std::string();
-}
+ std::string OozePrevention::post_toolchange(GCode& gcodegen)
+ {
+ return (gcodegen.config().standby_temperature_delta.value != 0) ?
+ gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
+ std::string();
+ }
-int
-OozePrevention::_get_temp(GCode &gcodegen)
-{
- return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
- ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
- : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
-}
+ int
+ OozePrevention::_get_temp(GCode& gcodegen)
+ {
+ return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
+ ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
+ : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
+ }
-std::string Wipe::wipe(GCode &gcodegen, bool toolchange)
-{
- std::string gcode;
-
- /* Reduce feedrate a bit; travel speed is often too high to move on existing material.
- Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
- double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
-
- // get the retraction length
- double length = toolchange
- ? gcodegen.writer().extruder()->retract_length_toolchange()
- : gcodegen.writer().extruder()->retract_length();
- // Shorten the retraction length by the amount already retracted before wipe.
- length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
-
- if (length > 0) {
- /* Calculate how long we need to travel in order to consume the required
- amount of retraction. In other words, how far do we move in XY at wipe_speed
- for the time needed to consume retract_length at retract_speed? */
- double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
-
- /* Take the stored wipe path and replace first point with the current actual position
- (they might be different, for example, in case of loop clipping). */
- Polyline wipe_path;
- wipe_path.append(gcodegen.last_pos());
- wipe_path.append(
- this->path.points.begin() + 1,
- this->path.points.end()
- );
-
- wipe_path.clip_end(wipe_path.length() - wipe_dist);
-
- // subdivide the retraction in segments
- if (! wipe_path.empty()) {
- for (const Line &line : wipe_path.lines()) {
- double segment_length = line.length();
- /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
- due to rounding (TODO: test and/or better math for this) */
- double dE = length * (segment_length / wipe_dist) * 0.95;
- //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
- // Is it here for the cooling markers? Or should it be outside of the cycle?
- gcode += gcodegen.writer().set_speed(wipe_speed*60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
- gcode += gcodegen.writer().extrude_to_xy(
- gcodegen.point_to_gcode(line.b),
- -dE,
- "wipe and retract"
- );
+ std::string Wipe::wipe(GCode& gcodegen, bool toolchange)
+ {
+ std::string gcode;
+
+ /* Reduce feedrate a bit; travel speed is often too high to move on existing material.
+ Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
+ double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
+
+ // get the retraction length
+ double length = toolchange
+ ? gcodegen.writer().extruder()->retract_length_toolchange()
+ : gcodegen.writer().extruder()->retract_length();
+ // Shorten the retraction length by the amount already retracted before wipe.
+ length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
+
+ if (length > 0) {
+ /* Calculate how long we need to travel in order to consume the required
+ amount of retraction. In other words, how far do we move in XY at wipe_speed
+ for the time needed to consume retract_length at retract_speed? */
+ double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
+
+ /* Take the stored wipe path and replace first point with the current actual position
+ (they might be different, for example, in case of loop clipping). */
+ Polyline wipe_path;
+ wipe_path.append(gcodegen.last_pos());
+ wipe_path.append(
+ this->path.points.begin() + 1,
+ this->path.points.end()
+ );
+
+ wipe_path.clip_end(wipe_path.length() - wipe_dist);
+
+ // subdivide the retraction in segments
+ if (!wipe_path.empty()) {
+ // add tag for processor
+ gcode += ";" + GCodeProcessor::Wipe_Start_Tag + "\n";
+ for (const Line& line : wipe_path.lines()) {
+ double segment_length = line.length();
+ /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
+ due to rounding (TODO: test and/or better math for this) */
+ double dE = length * (segment_length / wipe_dist) * 0.95;
+ //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
+ // Is it here for the cooling markers? Or should it be outside of the cycle?
+ gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
+ gcode += gcodegen.writer().extrude_to_xy(
+ gcodegen.point_to_gcode(line.b),
+ -dE,
+ "wipe and retract"
+ );
+ }
+ // add tag for processor
+ gcode += ";" + GCodeProcessor::Wipe_End_Tag + "\n";
+ gcodegen.set_last_pos(wipe_path.points.back());
}
- gcodegen.set_last_pos(wipe_path.points.back());
+
+ // prevent wiping again on same path
+ this->reset_path();
}
-
- // prevent wiping again on same path
- this->reset_path();
+
+ return gcode;
}
-
- return gcode;
-}
-static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt)
-{
- return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
-}
+ static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt)
+ {
+ return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
+ }
-std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z) const
-{
- if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
- throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
+ std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
+ {
+ if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
+ throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
- std::string gcode;
+ std::string gcode;
- // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
- // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
- float alpha = m_wipe_tower_rotation/180.f * float(M_PI);
- Vec2f start_pos = tcr.start_pos;
- Vec2f end_pos = tcr.end_pos;
- if (!tcr.priming) {
- start_pos = Eigen::Rotation2Df(alpha) * start_pos;
- start_pos += m_wipe_tower_pos;
- end_pos = Eigen::Rotation2Df(alpha) * end_pos;
- end_pos += m_wipe_tower_pos;
- }
-
- Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
- float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
-
- std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
-
- if (!tcr.priming) {
- // Move over the wipe tower.
- // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
- gcode += gcodegen.retract(true);
- gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
- gcode += gcodegen.travel_to(
- wipe_tower_point_to_object_point(gcodegen, start_pos),
- erMixed,
- "Travel to a Wipe Tower");
- gcode += gcodegen.unretract();
- }
-
- double current_z = gcodegen.writer().get_position().z();
- if (z == -1.) // in case no specific z was provided, print at current_z pos
- z = current_z;
- if (! is_approx(z, current_z)) {
- gcode += gcodegen.writer().retract();
- gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
- gcode += gcodegen.writer().unretract();
- }
-
-
- // Process the end filament gcode.
- std::string end_filament_gcode_str;
- if (gcodegen.writer().extruder() != nullptr) {
- // Process the custom end_filament_gcode in case of single_extruder_multi_material.
- unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
- const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
- if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) {
- end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
- check_add_eol(end_filament_gcode_str);
+ // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
+ // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
+ float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
+
+ auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
+ Vec2f out = Eigen::Rotation2Df(alpha) * pt;
+ out += m_wipe_tower_pos;
+ return out;
+ };
+
+ Vec2f start_pos = tcr.start_pos;
+ Vec2f end_pos = tcr.end_pos;
+ if (! tcr.priming) {
+ start_pos = transform_wt_pt(start_pos);
+ end_pos = transform_wt_pt(end_pos);
+ }
+
+ Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
+ float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
+
+ std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
+
+ if (! tcr.priming) {
+ // Move over the wipe tower.
+ // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
+ gcode += gcodegen.retract(true);
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
+ gcode += gcodegen.travel_to(
+ wipe_tower_point_to_object_point(gcodegen, start_pos),
+ erMixed,
+ "Travel to a Wipe Tower");
+ gcode += gcodegen.unretract();
+ }
+
+ double current_z = gcodegen.writer().get_position().z();
+ if (z == -1.) // in case no specific z was provided, print at current_z pos
+ z = current_z;
+ if (! is_approx(z, current_z)) {
+ gcode += gcodegen.writer().retract();
+ gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
+ gcode += gcodegen.writer().unretract();
+ }
+
+
+ // Process the end filament gcode.
+ std::string end_filament_gcode_str;
+ if (gcodegen.writer().extruder() != nullptr) {
+ // Process the custom end_filament_gcode in case of single_extruder_multi_material.
+ unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
+ const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
+ if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) {
+ end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
+ check_add_eol(end_filament_gcode_str);
+ }
}
- }
- // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
- // Otherwise, leave control to the user completely.
- std::string toolchange_gcode_str;
- if (true /*gcodegen.writer().extruder() != nullptr*/) {
+ // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
+ // Otherwise, leave control to the user completely.
+ std::string toolchange_gcode_str;
const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
- if (!toolchange_gcode.empty()) {
+// m_max_layer_z = std::max(m_max_layer_z, tcr.print_z);
+ if (! toolchange_gcode.empty()) {
DynamicConfig config;
int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
- config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
- config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
- config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
+ config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
+ config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
+ config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
+// config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
check_add_eol(toolchange_gcode_str);
}
@@ -361,299 +285,274 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
std::string toolchange_command;
if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
- if (! custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
+ if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
toolchange_gcode_str += toolchange_command;
else {
// We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
}
- }
- gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
+ gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
- // Process the start filament gcode.
- std::string start_filament_gcode_str;
- const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
- if (! start_filament_gcode.empty()) {
- // Process the start_filament_gcode for the active filament only.
- DynamicConfig config;
- config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
- start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
- check_add_eol(start_filament_gcode_str);
- }
+ // Process the start filament gcode.
+ std::string start_filament_gcode_str;
+ const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
+ if (!start_filament_gcode.empty()) {
+ // Process the start_filament_gcode for the active filament only.
+ DynamicConfig config;
+ config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
+ start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
+ check_add_eol(start_filament_gcode_str);
+ }
- // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
- DynamicConfig config;
- config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
- config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
- config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
- std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
- unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
- gcode += tcr_gcode;
- check_add_eol(toolchange_gcode_str);
+ // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
+ DynamicConfig config;
+ config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
+ config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
+ config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
+ std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
+ unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
+ gcode += tcr_gcode;
+ check_add_eol(toolchange_gcode_str);
- // A phony move to the end position at the wipe tower.
- gcodegen.writer().travel_to_xy(end_pos.cast<double>());
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
- if (! is_approx(z, current_z)) {
- gcode += gcodegen.writer().retract();
- gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
- gcode += gcodegen.writer().unretract();
- }
+ // A phony move to the end position at the wipe tower.
+ gcodegen.writer().travel_to_xy(end_pos.cast<double>());
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
+ if (!is_approx(z, current_z)) {
+ gcode += gcodegen.writer().retract();
+ gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
+ gcode += gcodegen.writer().unretract();
+ }
- else {
- // Prepare a future wipe.
- gcodegen.m_wipe.path.points.clear();
- if (new_extruder_id >= 0) {
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
- end_pos.y())));
+ else {
+ // Prepare a future wipe.
+ gcodegen.m_wipe.reset_path();
+ for (const Vec2f& wipe_pt : tcr.wipe_path)
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)));
}
- }
- // Let the planner know we are traveling between objects.
- gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
- return gcode;
-}
+ // Let the planner know we are traveling between objects.
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
+ return gcode;
+ }
-// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
-// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
-std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
-{
- Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
-
- std::istringstream gcode_str(tcr.gcode);
- std::string gcode_out;
- std::string line;
- Vec2f pos = tcr.start_pos;
- Vec2f transformed_pos = pos;
- Vec2f old_pos(-1000.1f, -1000.1f);
- std::string never_skip_tag = WipeTower::never_skip_tag();
-
- while (gcode_str) {
- std::getline(gcode_str, line); // we read the gcode line by line
-
- // All G1 commands should be translated and rotated. X and Y coords are
- // only pushed to the output when they differ from last time.
- // WT generator can override this by appending the never_skip_tag
- if (line.find("G1 ") == 0) {
- bool never_skip = false;
- auto it = line.find(never_skip_tag);
- if (it != std::string::npos) {
- // remove the tag and remember we saw it
- never_skip = true;
- line.erase(it, it+never_skip_tag.size());
- }
- std::ostringstream line_out;
- std::istringstream line_str(line);
- line_str >> std::noskipws; // don't skip whitespace
- char ch = 0;
- while (line_str >> ch) {
- if (ch == 'X' || ch =='Y')
- line_str >> (ch == 'X' ? pos.x() : pos.y());
- else
- line_out << ch;
- }
+ // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
+ // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
+ std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
+ {
+ Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
+
+ std::istringstream gcode_str(tcr.gcode);
+ std::string gcode_out;
+ std::string line;
+ Vec2f pos = tcr.start_pos;
+ Vec2f transformed_pos = pos;
+ Vec2f old_pos(-1000.1f, -1000.1f);
+
+ while (gcode_str) {
+ std::getline(gcode_str, line); // we read the gcode line by line
+
+ // All G1 commands should be translated and rotated. X and Y coords are
+ // only pushed to the output when they differ from last time.
+ // WT generator can override this by appending the never_skip_tag
+ if (line.find("G1 ") == 0) {
+ bool never_skip = false;
+ auto it = line.find(WipeTower::never_skip_tag());
+ if (it != std::string::npos) {
+ // remove the tag and remember we saw it
+ never_skip = true;
+ line.erase(it, it + WipeTower::never_skip_tag().size());
+ }
+ std::ostringstream line_out;
+ std::istringstream line_str(line);
+ line_str >> std::noskipws; // don't skip whitespace
+ char ch = 0;
+ while (line_str >> ch) {
+ if (ch == 'X' || ch == 'Y')
+ line_str >> (ch == 'X' ? pos.x() : pos.y());
+ else
+ line_out << ch;
+ }
- transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
-
- if (transformed_pos != old_pos || never_skip) {
- line = line_out.str();
- std::ostringstream oss;
- oss << std::fixed << std::setprecision(3) << "G1 ";
- if (transformed_pos.x() != old_pos.x() || never_skip)
- oss << " X" << transformed_pos.x() - extruder_offset.x();
- if (transformed_pos.y() != old_pos.y() || never_skip)
- oss << " Y" << transformed_pos.y() - extruder_offset.y();
- oss << " ";
- line.replace(line.find("G1 "), 3, oss.str());
- old_pos = transformed_pos;
+ transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
+
+ if (transformed_pos != old_pos || never_skip) {
+ line = line_out.str();
+ std::ostringstream oss;
+ oss << std::fixed << std::setprecision(3) << "G1 ";
+ if (transformed_pos.x() != old_pos.x() || never_skip)
+ oss << " X" << transformed_pos.x() - extruder_offset.x();
+ if (transformed_pos.y() != old_pos.y() || never_skip)
+ oss << " Y" << transformed_pos.y() - extruder_offset.y();
+ oss << " ";
+ line.replace(line.find("G1 "), 3, oss.str());
+ old_pos = transformed_pos;
+ }
}
- }
- gcode_out += line + "\n";
+ gcode_out += line + "\n";
- // If this was a toolchange command, we should change current extruder offset
- if (line == "[toolchange_gcode]") {
- extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
+ // If this was a toolchange command, we should change current extruder offset
+ if (line == "[toolchange_gcode]") {
+ extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
- // If the extruder offset changed, add an extra move so everything is continuous
- if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
- std::ostringstream oss;
- oss << std::fixed << std::setprecision(3)
- << "G1 X" << transformed_pos.x() - extruder_offset.x()
- << " Y" << transformed_pos.y() - extruder_offset.y()
- << "\n";
- gcode_out += oss.str();
+ // If the extruder offset changed, add an extra move so everything is continuous
+ if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
+ std::ostringstream oss;
+ oss << std::fixed << std::setprecision(3)
+ << "G1 X" << transformed_pos.x() - extruder_offset.x()
+ << " Y" << transformed_pos.y() - extruder_offset.y()
+ << "\n";
+ gcode_out += oss.str();
+ }
}
}
+ return gcode_out;
}
- return gcode_out;
-}
-
-
-std::string WipeTowerIntegration::prime(GCode &gcodegen)
-{
- assert(m_layer_idx == 0);
- std::string gcode;
-
-
- // Disable linear advance for the wipe tower operations.
- //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
- for (const WipeTower::ToolChangeResult& tcr : m_priming) {
- if (!tcr.extrusions.empty())
- gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
-
-
- // Let the tool change be executed by the wipe tower class.
- // Inform the G-code writer about the changes done behind its back.
- //gcode += tcr.gcode;
- // Let the m_writer know the current extruder_id, but ignore the generated G-code.
- // unsigned int current_extruder_id = tcr.extrusions.back().tool;
- // gcodegen.writer().toolchange(current_extruder_id);
- // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
+ std::string WipeTowerIntegration::prime(GCode& gcodegen)
+ {
+ std::string gcode;
+ for (const WipeTower::ToolChangeResult& tcr : m_priming) {
+ if (! tcr.extrusions.empty())
+ gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
+ }
+ return gcode;
}
- // A phony move to the end position at the wipe tower.
- /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y));
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Prepare a future wipe.
- gcodegen.m_wipe.path.points.clear();
- // Start the wipe at the current position.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos));
- // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
- gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
- WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left,
- m_priming.back().end_pos.y)));*/
-
- return gcode;
-}
+ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer)
+ {
+ std::string gcode;
+ assert(m_layer_idx >= 0);
+ if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
+ if (m_layer_idx < (int)m_tool_changes.size()) {
+ if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
+ throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
+
+
+ // Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
+ // resulting in a wipe tower with sparse layers.
+ double wipe_tower_z = -1;
+ bool ignore_sparse = false;
+ if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
+ wipe_tower_z = m_last_wipe_tower_print_z;
+ ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
+ if (m_tool_change_idx == 0 && !ignore_sparse)
+ wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
+ }
-std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
-{
- std::string gcode;
- assert(m_layer_idx >= 0);
- if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
- if (m_layer_idx < (int)m_tool_changes.size()) {
- if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
- throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer.");
-
-
- // Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
- // resulting in a wipe tower with sparse layers.
- double wipe_tower_z = -1;
- bool ignore_sparse = false;
- if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
- wipe_tower_z = m_last_wipe_tower_print_z;
- ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
- if (m_tool_change_idx == 0 && ! ignore_sparse)
- wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
+ if (!ignore_sparse) {
+ gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
+ m_last_wipe_tower_print_z = wipe_tower_z;
+ }
}
+ m_brim_done = true;
+ }
+ return gcode;
+ }
- if (! ignore_sparse) {
- gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
- m_last_wipe_tower_print_z = wipe_tower_z;
- }
- }
- m_brim_done = true;
+ // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
+ std::string WipeTowerIntegration::finalize(GCode& gcodegen)
+ {
+ std::string gcode;
+ if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON)
+ gcode += gcodegen.change_layer(m_final_purge.print_z);
+ gcode += append_tcr(gcodegen, m_final_purge, -1);
+ return gcode;
}
- return gcode;
-}
-// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
-std::string WipeTowerIntegration::finalize(GCode &gcodegen)
-{
- std::string gcode;
- if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON)
- gcode += gcodegen.change_layer(m_final_purge.print_z);
- gcode += append_tcr(gcodegen, m_final_purge, -1);
- return gcode;
-}
+ const std::vector<std::string> ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" };
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
// Collect pairs of object_layer + support_layer sorted by print_z.
// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
-std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject &object)
+std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject& object)
{
std::vector<GCode::LayerToPrint> layers_to_print;
layers_to_print.reserve(object.layers().size() + object.support_layers().size());
- // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
- // This is the same logic as in support generator.
- //FIXME should we use the printing extruders instead?
- double gap_over_supports = object.config().support_material_contact_distance;
- // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
- assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
+ // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
+ // This is the same logic as in support generator.
+ //FIXME should we use the printing extruders instead?
+ double gap_over_supports = object.config().support_material_contact_distance;
+ // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
+ assert(!object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
if (gap_over_supports != 0.) {
gap_over_supports = std::max(0., gap_over_supports);
- // Not a soluble support,
- double support_layer_height_min = 1000000.;
- for (auto lh : object.print()->config().min_layer_height.values)
- support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
- gap_over_supports += support_layer_height_min;
+ // Not a soluble support,
+ double support_layer_height_min = 1000000.;
+ for (auto lh : object.print()->config().min_layer_height.values)
+ support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
+ gap_over_supports += support_layer_height_min;
}
// Pair the object layers with the support layers by z.
- size_t idx_object_layer = 0;
+ size_t idx_object_layer = 0;
size_t idx_support_layer = 0;
const LayerToPrint* last_extrusion_layer = nullptr;
while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
LayerToPrint layer_to_print;
- layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr;
- layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr;
+ layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
+ layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
if (layer_to_print.object_layer && layer_to_print.support_layer) {
if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) {
layer_to_print.support_layer = nullptr;
- -- idx_support_layer;
- } else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
+ --idx_support_layer;
+ }
+ else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
layer_to_print.object_layer = nullptr;
- -- idx_object_layer;
+ --idx_object_layer;
}
}
layers_to_print.emplace_back(layer_to_print);
+ bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
+ || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
+
+ // Check that there are extrusions on the very first layer.
+ if (layers_to_print.size() == 1u) {
+ if (!has_extrusions)
+ throw Slic3r::SlicingError(_(L("There is an object with no extrusions on the first layer.")));
+ }
+
// In case there are extrusions on this layer, check there is a layer to lay it on.
if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
- // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
- || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
+ // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
+ || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer)
- ? gap_over_supports
- : 0.;
+ ? gap_over_supports
+ : 0.;
double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.)
- + layer_to_print.layer()->height
- + support_contact_z;
+ + layer_to_print.layer()->height
+ + support_contact_z;
// Negative support_contact_z is not taken into account, it can result in false positives in cases
// where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
- // Only check this layer in case it has some extrusions.
- bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
- || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
-
- if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON)
- throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" +
+ if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
+ const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
+ _(L("Empty layers detected, the output would not be printable.")) + "\n\n" +
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
- "usually caused by negligibly small extrusions or by a faulty model. Try to repair "
- "the model or change its orientation on the bed.")));
+ "usually caused by negligibly small extrusions or by a faulty model. Try to repair "
+ "the model or change its orientation on the bed.")));
+ }
+
// Remember last layer with extrusions.
- last_extrusion_layer = &layers_to_print.back();
+ if (has_extrusions)
+ last_extrusion_layer = &layers_to_print.back();
}
}
return layers_to_print;
}
-// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
+// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
// will be printed for all objects at once.
// Return a list of <print_z, per object LayerToPrint> items.
-std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print &print)
+std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print& print)
{
struct OrderingItem {
coordf_t print_z;
@@ -668,30 +567,31 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
OrderingItem ordering_item;
ordering_item.object_idx = i;
ordering.reserve(ordering.size() + per_object[i].size());
- const LayerToPrint &front = per_object[i].front();
- for (const LayerToPrint &ltp : per_object[i]) {
- ordering_item.print_z = ltp.print_z();
+ const LayerToPrint& front = per_object[i].front();
+ for (const LayerToPrint& ltp : per_object[i]) {
+ ordering_item.print_z = ltp.print_z();
ordering_item.layer_idx = &ltp - &front;
ordering.emplace_back(ordering_item);
}
}
- std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
+ std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
+
// Merge numerically very close Z values.
for (size_t i = 0; i < ordering.size();) {
// Find the last layer with roughly the same print_z.
size_t j = i + 1;
coordf_t zmax = ordering[i].print_z + EPSILON;
- for (; j < ordering.size() && ordering[j].print_z <= zmax; ++ j) ;
+ for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
// Merge into layers_to_print.
std::pair<coordf_t, std::vector<LayerToPrint>> merged;
// Assign an average print_z to the set of layers with nearly equal print_z.
- merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z);
+ merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
merged.second.assign(print.objects().size(), LayerToPrint());
for (; i < j; ++i) {
- const OrderingItem &oi = ordering[i];
+ const OrderingItem& oi = ordering[i];
assert(merged.second[oi.object_idx].layer() == nullptr);
merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
}
@@ -701,11 +601,18 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
return layers_to_print;
}
-#if ENABLE_THUMBNAIL_GENERATOR
-void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
-#else
-void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data)
-#endif // ENABLE_THUMBNAIL_GENERATOR
+// free functions called by GCode::do_export()
+namespace DoExport {
+ static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
+ {
+ const GCodeProcessor::Result& result = processor.get_result();
+ print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time);
+ print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
+ get_time_dhms(result.time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A";
+ }
+} // namespace DoExport
+
+void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb)
{
PROFILE_CLEAR();
@@ -725,22 +632,16 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
if (file == nullptr)
- throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
-
- m_enable_analyzer = preview_data != nullptr;
+ throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
try {
m_placeholder_parser_failed_templates.clear();
-#if ENABLE_THUMBNAIL_GENERATOR
this->_do_export(*print, file, thumbnail_cb);
-#else
- this->_do_export(*print, file);
-#endif // ENABLE_THUMBNAIL_GENERATOR
fflush(file);
if (ferror(file)) {
fclose(file);
boost::nowide::remove(path_tmp.c_str());
- throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
+ throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
}
} catch (std::exception & /* ex */) {
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
@@ -753,46 +654,33 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
if (! m_placeholder_parser_failed_templates.empty()) {
// G-code export proceeded, but some of the PlaceholderParser substitutions failed.
+ //FIXME localize!
std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n";
- for (const std::string &name : m_placeholder_parser_failed_templates)
- msg += std::string("\t") + name + "\n";
+ for (const auto &name_and_error : m_placeholder_parser_failed_templates)
+ msg += name_and_error.first + "\n" + name_and_error.second + "\n";
msg += "\nPlease inspect the file ";
msg += path_tmp + " for error messages enclosed between\n";
msg += " !!!!! Failed to process the custom G-code template ...\n";
msg += "and\n";
msg += " !!!!! End of an error report for the custom G-code template ...\n";
- throw std::runtime_error(msg);
+ msg += "for all macro processing errors.";
+ throw Slic3r::PlaceholderParserError(msg);
}
- GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data();
- GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data();
-
- bool remaining_times_enabled = print->config().remaining_times.value;
-
- BOOST_LOG_TRIVIAL(debug) << "Time estimator post processing" << log_memory_info();
- GCodeTimeEstimator::post_process(path_tmp, 60.0f, remaining_times_enabled ? &normal_data : nullptr, (remaining_times_enabled && m_silent_time_estimator_enabled) ? &silent_data : nullptr);
-
- if (remaining_times_enabled)
- {
- m_normal_time_estimator.reset();
- if (m_silent_time_estimator_enabled)
- m_silent_time_estimator.reset();
- }
-
- // starts analyzer calculations
- if (m_enable_analyzer) {
- BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data" << log_memory_info();
- m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); });
- m_analyzer.reset();
- }
+ BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info();
+ m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); });
+ DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
+ if (result != nullptr)
+ *result = std::move(m_processor.extract_result());
+ BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info();
if (rename_file(path_tmp, path))
- throw std::runtime_error(
+ throw Slic3r::RuntimeError(
std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
"Is " + path_tmp + " locked?" + '\n');
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
- print->set_done(psGCodeExport);
+ print->set_done(psGCodeExport);
// Write the profiler measurements to file
PROFILE_UPDATE();
@@ -801,104 +689,13 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
// free functions called by GCode::_do_export()
namespace DoExport {
- static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled)
- {
- // resets time estimators
- normal_time_estimator.reset();
- normal_time_estimator.set_dialect(config.gcode_flavor);
- normal_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
- silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode;
-
- // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
- // and let the user to enter the G-code limits into the start G-code.
- // If the following block is enabled for other firmwares than the Marlin, then the function
- // this->print_machine_envelope(file, print);
- // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
- if (config.gcode_flavor.value == gcfMarlin) {
- normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]);
- normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]);
- normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]);
- normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]);
- normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]);
- normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]);
- normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]);
- normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]);
- normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]);
- normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]);
- normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]);
- normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]);
- normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]);
- normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]);
- normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]);
- normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]);
-
- if (silent_time_estimator_enabled)
- {
- silent_time_estimator.reset();
- silent_time_estimator.set_dialect(config.gcode_flavor);
- silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]);
- /* "Stealth mode" values can be just a copy of "normal mode" values
- * (when they aren't input for a printer preset).
- * Thus, use back value from values, instead of second one, which could be absent
- */
- silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back());
- silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back());
- silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back());
- silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back());
- silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back());
- silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back());
- silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back());
- silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back());
- silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back());
- silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back());
- silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back());
- silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back());
- silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back());
- silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back());
- silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back());
- silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back());
- if (config.single_extruder_multi_material) {
- // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
- // are considered to be active for the single extruder multi-material printers only.
- silent_time_estimator.set_filament_load_times(config.filament_load_time.values);
- silent_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
- }
- }
- }
- // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
- if (config.single_extruder_multi_material) {
- // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
- // are considered to be active for the single extruder multi-material printers only.
- normal_time_estimator.set_filament_load_times(config.filament_load_time.values);
- normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values);
- }
- }
-
- static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer)
- {
- // resets analyzer
- analyzer.reset();
-
- // send extruder offset data to analyzer
- GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets;
- unsigned int num_extruders = static_cast<unsigned int>(config.nozzle_diameter.values.size());
- for (unsigned int extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
- {
- Vec2d offset = config.extruder_offset.get_at(extruder_id);
- if (!offset.isApprox(Vec2d::Zero()))
- extruder_offsets[extruder_id] = offset;
- }
- analyzer.set_extruder_offsets(extruder_offsets);
-
- // tell analyzer about the extrusion axis
- analyzer.set_extrusion_axis(config.get_extrusion_axis()[0]);
-
- // send extruders count to analyzer to allow it to detect invalid extruder idxs
- analyzer.set_extruders_count(num_extruders);
-
- // tell analyzer about the gcode flavor
- analyzer.set_gcode_flavor(config.gcode_flavor);
- }
+ static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
+ {
+ silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode;
+ processor.reset();
+ processor.apply_config(config);
+ processor.enable_stealth_time_estimator(silent_time_estimator_enabled);
+ }
static double autospeed_volumetric_limit(const Print &print)
{
@@ -917,8 +714,21 @@ namespace DoExport {
if (region->config().get_abs_value("infill_speed") == 0 ||
region->config().get_abs_value("solid_infill_speed") == 0 ||
region->config().get_abs_value("top_solid_infill_speed") == 0 ||
- region->config().get_abs_value("bridge_speed") == 0)
- mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
+ region->config().get_abs_value("bridge_speed") == 0)
+ {
+ // Minimal volumetric flow should not be calculated over ironing extrusions.
+ // Use following lambda instead of the built-it method.
+ // https://github.com/prusa3d/PrusaSlicer/issues/5082
+ auto min_mm3_per_mm_no_ironing = [](const ExtrusionEntityCollection& eec) -> double {
+ double min = std::numeric_limits<double>::max();
+ for (const ExtrusionEntity* ee : eec.entities)
+ if (ee->role() != erIroning)
+ min = std::min(min, ee->min_mm3_per_mm());
+ return min;
+ };
+
+ mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills));
+ }
}
}
if (object->config().get_abs_value("support_material_speed") == 0 ||
@@ -943,7 +753,8 @@ namespace DoExport {
return volumetric_speed;
}
- static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
+
+ static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
{
// Calculate wiping points if needed
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
@@ -975,7 +786,6 @@ namespace DoExport {
}
}
- #if ENABLE_THUMBNAIL_GENERATOR
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
{
@@ -1019,27 +829,18 @@ namespace DoExport {
}
}
}
- #endif // ENABLE_THUMBNAIL_GENERATOR
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
- static std::string update_print_stats_and_format_filament_stats(
- const GCodeTimeEstimator &normal_time_estimator,
- const GCodeTimeEstimator &silent_time_estimator,
- const bool silent_time_estimator_enabled,
- const bool has_wipe_tower,
+ static std::string update_print_stats_and_format_filament_stats(
+ const bool has_wipe_tower,
const WipeTowerData &wipe_tower_data,
const std::vector<Extruder> &extruders,
PrintStatistics &print_statistics)
- {
+ {
std::string filament_stats_string_out;
print_statistics.clear();
- print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/();
- print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A";
- print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true);
- if (silent_time_estimator_enabled)
- print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true);
- print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
+ print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
@@ -1064,14 +865,14 @@ namespace DoExport {
++ dst.second;
};
print_statistics.filament_stats.insert(std::pair<size_t, float>{extruder.id(), (float)used_filament});
- append(out_filament_used_mm, "%.1lf", used_filament);
- append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001);
+ append(out_filament_used_mm, "%.2lf", used_filament);
+ append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001);
if (filament_weight > 0.) {
print_statistics.total_weight = print_statistics.total_weight + filament_weight;
- append(out_filament_used_g, "%.1lf", filament_weight);
+ append(out_filament_used_g, "%.2lf", filament_weight);
if (filament_cost > 0.) {
print_statistics.total_cost = print_statistics.total_cost + filament_cost;
- append(out_filament_cost, "%.1lf", filament_cost);
+ append(out_filament_cost, "%.2lf", filament_cost);
}
}
print_statistics.total_used_filament += used_filament;
@@ -1081,26 +882,26 @@ namespace DoExport {
}
filament_stats_string_out += out_filament_used_mm.first;
filament_stats_string_out += "\n" + out_filament_used_cm3.first;
- if (out_filament_used_g.second)
+ if (out_filament_used_g.second)
filament_stats_string_out += "\n" + out_filament_used_g.first;
- if (out_filament_cost.second)
+ if (out_filament_cost.second)
filament_stats_string_out += "\n" + out_filament_cost.first;
- }
- return filament_stats_string_out;
- }
+ }
+ return filament_stats_string_out;
+ }
}
// Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints.
static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
{
std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end());
- std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
- std::vector<const PrintInstance*> instances;
- instances.reserve(objects.size());
- for (const PrintObject *object : objects)
- for (size_t i = 0; i < object->instances().size(); ++ i)
- instances.emplace_back(&object->instances()[i]);
- return instances;
+ std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
+ std::vector<const PrintInstance*> instances;
+ instances.reserve(objects.size());
+ for (const PrintObject *object : objects)
+ for (size_t i = 0; i < object->instances().size(); ++ i)
+ instances.emplace_back(&object->instances()[i]);
+ return instances;
}
// Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model().
@@ -1125,23 +926,26 @@ std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Pri
return instances;
}
-#if ENABLE_THUMBNAIL_GENERATOR
void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb)
-#else
-void GCode::_do_export(Print& print, FILE* file)
-#endif // ENABLE_THUMBNAIL_GENERATOR
{
PROFILE_FUNC();
- DoExport::init_time_estimators(print.config(),
- // modifies the following:
- m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled);
- DoExport::init_gcode_analyzer(print.config(), m_analyzer);
+ // modifies m_silent_time_estimator_enabled
+ DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
// resets analyzer's tracking data
- m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
- m_last_width = GCodeAnalyzer::Default_Width;
- m_last_height = GCodeAnalyzer::Default_Height;
+ m_last_height = 0.f;
+ m_last_layer_z = 0.f;
+ m_max_layer_z = 0.f;
+#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+ m_last_width = 0.f;
+#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ m_last_mm3_per_mm = 0.;
+#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+ m_last_width = 0.f;
+#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
@@ -1194,8 +998,8 @@ void GCode::_do_export(Print& print, FILE* file)
// Write information on the generator.
_write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
- DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
- [this, file](const char* sz) { this->_write(file, sz); },
+ DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
+ [this, file](const char* sz) { this->_write(file, sz); },
[&print]() { print.throw_if_canceled(); });
// Write notes (content of the Print Settings tab -> Notes)
@@ -1230,14 +1034,10 @@ void GCode::_do_export(Print& print, FILE* file)
_write_format(file, "\n");
}
print.throw_if_canceled();
-
+
// adds tags for time estimators
if (print.config().remaining_times.value)
- {
- _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag);
- if (m_silent_time_estimator_enabled)
- _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
- }
+ _writeln(file, GCodeProcessor::First_Line_M73_Placeholder_Tag);
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
@@ -1265,12 +1065,12 @@ void GCode::_do_export(Print& print, FILE* file)
}
// We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
// Use the extruder IDs collected from Regions.
- this->set_extruders(print.extruders());
+ this->set_extruders(print.extruders());
} else {
- // Find tool ordering for all the objects at once, and the initial extruder ID.
+ // Find tool ordering for all the objects at once, and the initial extruder ID.
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
- tool_ordering = print.tool_ordering();
- tool_ordering.assign_custom_gcodes(print);
+ tool_ordering = print.tool_ordering();
+ tool_ordering.assign_custom_gcodes(print);
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ?
// The priming towers will be skipped.
@@ -1279,7 +1079,7 @@ void GCode::_do_export(Print& print, FILE* file)
tool_ordering.first_extruder();
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
// Therefore initialize the printing extruders from there.
- this->set_extruders(tool_ordering.all_extruders());
+ this->set_extruders(tool_ordering.all_extruders());
// Order object instances using a nearest neighbor search.
print_object_instances_ordering = chain_print_object_instances(print);
}
@@ -1314,16 +1114,36 @@ void GCode::_do_export(Print& print, FILE* file)
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
-
+ {
+ BoundingBoxf bbox(print.config().bed_shape.values);
+ m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
+ m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
+ m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
+ }
+ {
+ // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
+ // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
+ // It does NOT encompass user extrusions generated by custom G-code,
+ // therefore it does NOT encompass the initial purge line.
+ // It does NOT encompass MMU/MMU2 starting (wipe) areas.
+ auto pts = std::make_unique<ConfigOptionPoints>();
+ pts->values.reserve(print.first_layer_convex_hull().size());
+ for (const Point &pt : print.first_layer_convex_hull().points)
+ pts->values.emplace_back(unscale(pt));
+ BoundingBoxf bbox(pts->values);
+ m_placeholder_parser.set("first_layer_print_convex_hull", pts.release());
+ m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
+ m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
+ m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
+ }
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
// Set extruder(s) temperature before and after start G-code.
this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
- if (m_enable_analyzer)
- // adds tag for analyzer
- _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+ // adds tag for processor
+ _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
// Write the custom start G-code
_writeln(file, start_gcode);
@@ -1343,17 +1163,13 @@ void GCode::_do_export(Print& print, FILE* file)
// Set other general things.
_write(file, this->preamble());
- // Initialize a motion planner for object-to-object travel moves.
- m_avoid_crossing_perimeters.reset();
- if (print.config().avoid_crossing_perimeters.value) {
- m_avoid_crossing_perimeters.init_external_mp(print);
- print.throw_if_canceled();
- }
-
// Calculate wiping points if needed
DoExport::init_ooze_prevention(print, m_ooze_prevention);
print.throw_if_canceled();
-
+
+ // Collect custom seam data from all objects.
+ m_seam_placer.init(print);
+
if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
// Set initial extruder only after custom start G-code.
// Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
@@ -1382,12 +1198,12 @@ void GCode::_do_export(Print& print, FILE* file)
// 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 = true;
+ m_avoid_crossing_perimeters.use_external_mp_once();
_write(file, this->retract());
_write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
m_enable_cooling_markers = true;
// Disable motion planner when traveling to first object point.
- m_avoid_crossing_perimeters.disable_once = true;
+ m_avoid_crossing_perimeters.disable_once();
// Ff we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. This happens before the Z move
// is triggered, so machine has more time to reach such temperatures.
@@ -1428,7 +1244,7 @@ void GCode::_do_export(Print& print, FILE* file)
m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
_write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
if (print.config().single_extruder_multi_material_priming) {
- _write(file, m_wipe_tower->prime(*this));
+ _write(file, m_wipe_tower->prime(*this));
// Verify, whether the print overaps the priming extrusions.
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
@@ -1437,18 +1253,30 @@ void GCode::_do_export(Print& print, FILE* file)
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
bbox_prime.offset(0.5f);
- // Beep for 500ms, tone 800Hz. Yet better, play some Morse.
- _write(file, this->retract());
- _write(file, "M300 S800 P500\n");
- if (bbox_prime.overlap(bbox_print)) {
- // Wait for the user to remove the priming extrusions, otherwise they would
- // get covered by the print.
- _write(file, "M1 Remove priming towers and click button.\n");
- }
- else {
- // Just wait for a bit to let the user check, that the priming succeeded.
- //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
- _write(file, "M1 S10\n");
+ bool overlap = bbox_prime.overlap(bbox_print);
+
+ if (print.config().gcode_flavor == gcfMarlin) {
+ _write(file, this->retract());
+ _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz.
+ if (overlap) {
+ // Wait for the user to remove the priming extrusions.
+ _write(file, "M1 Remove priming towers and click button.\n");
+ } else {
+ // Just wait for a bit to let the user check, that the priming succeeded.
+ //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
+ _write(file, "M1 S10\n");
+ }
+ } else {
+ // This is not Marlin, M1 command is probably not supported.
+ // (See https://github.com/prusa3d/PrusaSlicer/issues/5441.)
+ if (overlap) {
+ print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
+ _(L("Your print is very close to the priming regions. "
+ "Make sure there is no collision.")));
+ } else {
+ // Just continue printing, no action necessary.
+ }
+
}
}
print.throw_if_canceled();
@@ -1474,15 +1302,15 @@ void GCode::_do_export(Print& print, FILE* file)
_write(file, this->retract());
_write(file, m_writer.set_fan(false));
- if (m_enable_analyzer)
- // adds tag for analyzer
- _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+ // adds tag for processor
+ _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
// Process filament-specific gcode in extruder order.
{
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value));
+ config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
if (print.config().single_extruder_multi_material) {
// Process the end_filament_gcode for the active filament only.
int extruder_id = m_writer.extruder()->id();
@@ -1490,7 +1318,7 @@ void GCode::_do_export(Print& print, FILE* file)
_writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config));
} else {
for (const std::string &end_gcode : print.config().end_filament_gcode.values) {
- int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front());
+ int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front());
config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id));
_writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config));
}
@@ -1502,40 +1330,28 @@ void GCode::_do_export(Print& print, FILE* file)
// adds tags for time estimators
if (print.config().remaining_times.value)
- {
- _writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag);
- if (m_silent_time_estimator_enabled)
- _writeln(file, GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag);
- }
+ _writeln(file, GCodeProcessor::Last_Line_M73_Placeholder_Tag);
print.throw_if_canceled();
- // calculates estimated printing time
- m_normal_time_estimator.calculate_time(false);
- if (m_silent_time_estimator_enabled)
- m_silent_time_estimator.calculate_time(false);
-
// Get filament stats.
_write(file, DoExport::update_print_stats_and_format_filament_stats(
// Const inputs
- m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled,
- has_wipe_tower, print.wipe_tower_data(),
+ has_wipe_tower, print.wipe_tower_data(),
m_writer.extruders(),
// Modifies
print.m_print_statistics));
_write(file, "\n");
- _write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
- _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
+ _write_format(file, "; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight);
+ _write_format(file, "; total filament cost = %.2lf\n", print.m_print_statistics.total_cost);
if (print.m_print_statistics.total_toolchanges > 0)
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
- _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
- if (m_silent_time_estimator_enabled)
- _write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
+ _writeln(file, GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag);
// Append full config.
_write(file, "\n");
{
- std::string full_config = "";
+ std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
_write(file, full_config);
@@ -1546,21 +1362,25 @@ void GCode::_do_export(Print& print, FILE* file)
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
{
try {
- return m_placeholder_parser.process(templ, current_extruder_id, config_override);
+ return m_placeholder_parser.process(templ, current_extruder_id, config_override, &m_placeholder_parser_context);
} catch (std::runtime_error &err) {
// Collect the names of failed template substitutions for error reporting.
- m_placeholder_parser_failed_templates.insert(name);
+ auto it = m_placeholder_parser_failed_templates.find(name);
+ if (it == m_placeholder_parser_failed_templates.end())
+ // Only if there was no error reported for this template, store the first error message into the map to be reported.
+ // We don't want to collect error message for each and every occurence of a single custom G-code section.
+ m_placeholder_parser_failed_templates.insert(it, std::make_pair(name, std::string(err.what())));
// Insert the macro error message into the G-code.
return
- std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
- err.what() +
+ std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
+ err.what() +
"!!!!! End of an error report for the custom G-code template " + name + "\n\n";
}
}
-// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
+// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code.
// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
-static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out)
+static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out)
{
temp_out = -1;
if (gcode.empty())
@@ -1571,20 +1391,26 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
while (*ptr != 0) {
// Skip whitespaces.
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
- if (*ptr == 'M') {
- // Line starts with 'M'. It is a machine command.
+ if (*ptr == 'M' || // Line starts with 'M'. It is a machine command.
+ (*ptr == 'G' && include_g10)) { // Only check for G10 if requested
+ bool is_gcode = *ptr == 'G';
++ ptr;
- // Parse the M code value.
+ // Parse the M or G code value.
char *endptr = nullptr;
- int mcode = int(strtol(ptr, &endptr, 10));
- if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
- // M104/M109 or M140/M190 found.
- ptr = endptr;
- // Let the caller know that the custom G-code sets the temperature.
- temp_set_by_gcode = true;
+ int mgcode = int(strtol(ptr, &endptr, 10));
+ if (endptr != nullptr && endptr != ptr &&
+ is_gcode ?
+ // G10 found
+ mgcode == 10 :
+ // M104/M109 or M140/M190 found.
+ (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) {
+ ptr = endptr;
+ if (! is_gcode)
+ // Let the caller know that the custom M-code sets the temperature.
+ temp_set_by_gcode = true;
// Now try to parse the temperature value.
- // While not at the end of the line:
- while (strchr(";\r\n\0", *ptr) == nullptr) {
+ // While not at the end of the line:
+ while (strchr(";\r\n\0", *ptr) == nullptr) {
// Skip whitespaces.
for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
if (*ptr == 'S') {
@@ -1593,22 +1419,26 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
// Parse an int.
endptr = nullptr;
long temp_parsed = strtol(ptr, &endptr, 10);
- if (endptr > ptr) {
- ptr = endptr;
- temp_out = temp_parsed;
- }
+ if (endptr > ptr) {
+ ptr = endptr;
+ temp_out = temp_parsed;
+ // Let the caller know that the custom G-code sets the temperature
+ // Only do this after successfully parsing temperature since G10
+ // can be used for other reasons
+ temp_set_by_gcode = true;
+ }
} else {
// Skip this word.
- for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
+ for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
}
}
}
}
// Skip the rest of the line.
for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr);
- // Skip the end of line indicators.
+ // Skip the end of line indicators.
for (; *ptr == '\r' || *ptr == '\n'; ++ ptr);
- }
+ }
return temp_set_by_gcode;
}
@@ -1616,7 +1446,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
void GCode::print_machine_envelope(FILE *file, Print &print)
{
- if (print.config().gcode_flavor.value == gcfMarlin) {
+ if (print.config().gcode_flavor.value == gcfMarlin && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) {
fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
int(print.config().machine_max_acceleration_x.values.front() + 0.5),
int(print.config().machine_max_acceleration_y.values.front() + 0.5),
@@ -1652,7 +1482,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id);
// Is the bed temperature set by the provided custom G-code?
int temp_by_gcode = -1;
- bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
+ bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode);
if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
temp = temp_by_gcode;
// Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
@@ -1666,11 +1496,13 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
// M104 - Set Extruder Temperature
// M109 - Set Extruder Temperature and Wait
+// RepRapFirmware: G10 Sxx
void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
{
// Is the bed temperature set by the provided custom G-code?
- int temp_by_gcode = -1;
- if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
+ int temp_by_gcode = -1;
+ bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware;
+ if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) {
// Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
@@ -1697,9 +1529,9 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c
}
inline GCode::ObjectByExtruder& object_by_extruder(
- std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
- unsigned int extruder_id,
- size_t object_idx,
+ std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
+ unsigned int extruder_id,
+ size_t object_idx,
size_t num_objects)
{
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = by_extruder[extruder_id];
@@ -1709,9 +1541,9 @@ inline GCode::ObjectByExtruder& object_by_extruder(
}
inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
- std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
- unsigned int extruder_id,
- size_t object_idx,
+ std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
+ unsigned int extruder_id,
+ size_t object_idx,
size_t num_objects,
size_t num_islands)
{
@@ -1722,142 +1554,144 @@ inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
}
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
- std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
- const std::vector<LayerToPrint> &layers,
- // Ordering must be defined for normal (non-sequential print).
- const std::vector<const PrintInstance*> *ordering,
- // For sequential print, the instance of the object to be printing has to be defined.
- const size_t single_object_instance_idx)
+ std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
+ const std::vector<LayerToPrint> &layers,
+ // Ordering must be defined for normal (non-sequential print).
+ const std::vector<const PrintInstance*> *ordering,
+ // For sequential print, the instance of the object to be printing has to be defined.
+ const size_t single_object_instance_idx)
{
std::vector<InstanceToPrint> out;
if (ordering == nullptr) {
- // Sequential print, single object is being printed.
- for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
- const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
- const PrintObject *print_object = layers[layer_id].object();
- if (print_object)
- out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
- }
+ // Sequential print, single object is being printed.
+ for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
+ const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
+ const PrintObject *print_object = layers[layer_id].object();
+ if (print_object)
+ out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
+ }
} else {
- // Create mapping from PrintObject* to ObjectByExtruder*.
- std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
- sorted.reserve(objects_by_extruder.size());
- for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
- const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
- const PrintObject *print_object = layers[layer_id].object();
- if (print_object)
- sorted.emplace_back(print_object, &object_by_extruder);
- }
- std::sort(sorted.begin(), sorted.end());
-
- if (! sorted.empty()) {
- const Print &print = *sorted.front().first->print();
- out.reserve(sorted.size());
- for (const PrintInstance *instance : *ordering) {
- const PrintObject &print_object = *instance->print_object;
- std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
- auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
- if (it != sorted.end() && it->first == &print_object)
- // ObjectByExtruder for this PrintObject was found.
- out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
- }
- }
- }
- return out;
+ // Create mapping from PrintObject* to ObjectByExtruder*.
+ std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
+ sorted.reserve(objects_by_extruder.size());
+ for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
+ const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
+ const PrintObject *print_object = layers[layer_id].object();
+ if (print_object)
+ sorted.emplace_back(print_object, &object_by_extruder);
+ }
+ std::sort(sorted.begin(), sorted.end());
+
+ if (! sorted.empty()) {
+ out.reserve(sorted.size());
+ for (const PrintInstance *instance : *ordering) {
+ const PrintObject &print_object = *instance->print_object;
+ std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
+ auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
+ if (it != sorted.end() && it->first == &print_object)
+ // ObjectByExtruder for this PrintObject was found.
+ out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data());
+ }
+ }
+ }
+ return out;
}
namespace ProcessLayer
{
static std::string emit_custom_gcode_per_print_z(
- const CustomGCode::Item *custom_gcode,
+ const CustomGCode::Item *custom_gcode,
// ID of the first extruder printing this layer.
unsigned int first_extruder_id,
- bool single_extruder_printer)
- {
+ const PrintConfig &config)
+ {
std::string gcode;
-
+ bool single_extruder_printer = config.nozzle_diameter.size() == 1;
+
if (custom_gcode != nullptr) {
- // Extruder switches are processed by LayerTools, they should be filtered out.
- assert(custom_gcode->gcode != ToolChangeCode);
-
- const std::string &custom_code = custom_gcode->gcode;
- bool color_change = custom_code == ColorChangeCode;
- bool tool_change = custom_code == ToolChangeCode;
- // Tool Change is applied as Color Change for a single extruder printer only.
- assert(! tool_change || single_extruder_printer);
-
- std::string pause_print_msg;
- int m600_extruder_before_layer = -1;
- if (color_change && custom_gcode->extruder > 0)
- m600_extruder_before_layer = custom_gcode->extruder - 1;
- else if (custom_code == PausePrintCode)
- pause_print_msg = custom_gcode->color;
-
- // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
- if (color_change || tool_change)
- {
+ // Extruder switches are processed by LayerTools, they should be filtered out.
+ assert(custom_gcode->type != CustomGCode::ToolChange);
+
+ CustomGCode::Type gcode_type = custom_gcode->type;
+ bool color_change = gcode_type == CustomGCode::ColorChange;
+ bool tool_change = gcode_type == CustomGCode::ToolChange;
+ // Tool Change is applied as Color Change for a single extruder printer only.
+ assert(! tool_change || single_extruder_printer);
+
+ std::string pause_print_msg;
+ int m600_extruder_before_layer = -1;
+ if (color_change && custom_gcode->extruder > 0)
+ m600_extruder_before_layer = custom_gcode->extruder - 1;
+ else if (gcode_type == CustomGCode::PausePrint)
+ pause_print_msg = custom_gcode->extra;
+
+ // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
+ if (color_change || tool_change)
+ {
+ assert(m600_extruder_before_layer >= 0);
// Color Change or Tool Change as Color Change.
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
- // add tag for time estimator
- gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
-
- if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != m600_extruder_before_layer
- // && !MMU1
- ) {
- //! FIXME_in_fw show message during print pause
- gcode += "M601\n"; // pause print
- gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
- }
+ // add tag for processor
+ gcode += ";" + GCodeProcessor::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n";
+
+ if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer
+ // && !MMU1
+ ) {
+ //! FIXME_in_fw show message during print pause
+ gcode += config.pause_print_gcode;// pause print
+ gcode += "\n";
+ gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
+ }
else {
- gcode += ColorChangeCode;
+ gcode += config.color_change_gcode;//ColorChangeCode;
gcode += "\n";
}
}
else
{
- if (custom_code == PausePrintCode) // Pause print
+ if (gcode_type == CustomGCode::PausePrint) // Pause print
{
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n";
- //! FIXME_in_fw show message during print pause
+ // add tag for processor
+ gcode += ";" + GCodeProcessor::Pause_Print_Tag + "\n";
+ //! FIXME_in_fw show message during print pause
if (!pause_print_msg.empty())
gcode += "M117 " + pause_print_msg + "\n";
- // add tag for time estimator
- gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
- }
- else // custom Gcode
+ gcode += config.pause_print_gcode;
+ }
+ else
{
- // add tag for analyzer
- gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n";
- // add tag for time estimator
- //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
- }
- gcode += custom_code + "\n";
- }
- }
+ // add tag for processor
+ gcode += ";" + GCodeProcessor::Custom_Code_Tag + "\n";
+ if (gcode_type == CustomGCode::Template) // Template Cistom Gcode
+ gcode += config.template_custom_gcode;
+ else // custom Gcode
+ gcode += custom_gcode->extra;
- return gcode;
- }
+ }
+ gcode += "\n";
+ }
+ }
+
+ return gcode;
+ }
} // namespace ProcessLayer
namespace Skirt {
- static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map<unsigned int, std::pair<size_t, size_t>> &skirt_loops_per_extruder_out)
- {
+ static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map<unsigned int, std::pair<size_t, size_t>> &skirt_loops_per_extruder_out)
+ {
// Prime all extruders printing over the 1st layer over the skirt lines.
size_t n_loops = print.skirt().entities.size();
size_t n_tools = layer_tools.extruders.size();
size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools;
for (size_t i = 0; i < n_loops; i += lines_per_extruder)
skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair<size_t, size_t>(i, std::min(i + lines_per_extruder, n_loops));
- }
+ }
static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_1st_layer(
const Print &print,
- const std::vector<GCode::LayerToPrint> & /*layers */,
- const LayerTools &layer_tools,
+ const std::vector<GCode::LayerToPrint> & /*layers */,
+ const LayerTools &layer_tools,
// Heights (print_z) at which the skirt has already been extruded.
std::vector<coordf_t> &skirt_done)
{
@@ -1865,7 +1699,7 @@ namespace Skirt {
// not at the print_z of the interlaced support material layers.
std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty()) {
- skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
+ skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
skirt_done.emplace_back(layer_tools.print_z);
}
return skirt_loops_per_extruder_out;
@@ -1873,11 +1707,11 @@ namespace Skirt {
static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_other_layers(
const Print &print,
- const std::vector<GCode::LayerToPrint> &layers,
- const LayerTools &layer_tools,
- // First non-empty support layer.
- const SupportLayer *support_layer,
- // Heights (print_z) at which the skirt has already been extruded.
+ const std::vector<GCode::LayerToPrint> &layers,
+ const LayerTools &layer_tools,
+ // First non-empty support layer.
+ const SupportLayer *support_layer,
+ // Heights (print_z) at which the skirt has already been extruded.
std::vector<coordf_t> &skirt_done)
{
// Extrude skirt at the print_z of the raft layers and normal object layers
@@ -1888,14 +1722,16 @@ namespace Skirt {
//FIXME infinite or high skirt does not make sense for sequential print!
(skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) &&
// This print_z has not been extruded yet (sequential print)
- skirt_done.back() < layer_tools.print_z - EPSILON &&
+ // FIXME: The skirt_done should not be empty at this point. The check is a workaround
+ // of https://github.com/prusa3d/PrusaSlicer/issues/5652, but it deserves a real fix.
+ (! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON) &&
// and this layer is an object layer, or it is a raft layer.
(layer_tools.has_object || support_layer->id() < (size_t)support_layer->object()->config().raft_layers.value)) {
#if 0
// Prime just the first printing extruder. This is original Slic3r's implementation.
skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair<size_t, size_t>(0, print.config().skirts.value);
#else
- // Prime all extruders planned for this layer, see
+ // Prime all extruders planned for this layer, see
// https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619
skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
#endif
@@ -1907,7 +1743,7 @@ namespace Skirt {
} // namespace Skirt
-// In sequential mode, process_layer is called once per each object and its copy,
+// In sequential mode, process_layer is called once per each object and its copy,
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
@@ -1919,14 +1755,13 @@ void GCode::process_layer(
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
const LayerTools &layer_tools,
- // Pairs of PrintObject index and its instance index.
- const std::vector<const PrintInstance*> *ordering,
+ // Pairs of PrintObject index and its instance index.
+ const std::vector<const PrintInstance*> *ordering,
// If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object.
const size_t single_object_instance_idx)
{
assert(! layers.empty());
-// assert(! layer_tools.extruders.empty());
// Either printing all copies of all objects, or just a single copy of a single object.
assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
@@ -1953,6 +1788,7 @@ void GCode::process_layer(
// Check whether it is possible to apply the spiral vase logic for this layer.
// Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
+ m_enable_loop_clipping = true;
if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) {
bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt());
if (enable) {
@@ -1964,24 +1800,40 @@ void GCode::process_layer(
break;
}
}
- m_spiral_vase->enable = enable;
+ m_spiral_vase->enable(enable);
+ // If we're going to apply spiralvase to this layer, disable loop clipping.
+ m_enable_loop_clipping = !enable;
}
- // If we're going to apply spiralvase to this layer, disable loop clipping
- m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase->enable;
-
+
std::string gcode;
+ // add tag for processor
+ gcode += ";" + GCodeProcessor::Layer_Change_Tag + "\n";
+ // export layer z
+ char buf[64];
+ sprintf(buf, ";Z:%g\n", print_z);
+ gcode += buf;
+ // export layer height
+ float height = first_layer ? static_cast<float>(print_z) : static_cast<float>(print_z) - m_last_layer_z;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), height);
+ gcode += buf;
+ // update caches
+ m_last_layer_z = static_cast<float>(print_z);
+ m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z);
+ m_last_height = height;
+
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
if (! print.config().before_layer_gcode.value.empty()) {
DynamicConfig config;
- config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
- config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
+ config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
+ config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
+ config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
gcode += this->placeholder_parser_process("before_layer_gcode",
print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
}
gcode += this->change_layer(print_z); // this will increase m_layer_index
- m_layer = &layer;
+ m_layer = &layer;
if (! print.config().layer_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
@@ -1989,6 +1841,7 @@ void GCode::process_layer(
gcode += this->placeholder_parser_process("layer_gcode",
print.config().layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
+ config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
}
if (! first_layer && ! m_second_layer_things_done) {
@@ -2012,7 +1865,7 @@ void GCode::process_layer(
if (single_object_instance_idx == size_t(-1)) {
// Normal (non-sequential) print.
- gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config().nozzle_diameter.size() == 1);
+ gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config());
}
// Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers.
@@ -2073,8 +1926,8 @@ void GCode::process_layer(
}
if (layer_to_print.object_layer != nullptr) {
const Layer &layer = *layer_to_print.object_layer;
- // We now define a strategy for building perimeters and fills. The separation
- // between regions doesn't matter in terms of printing order, as we follow
+ // We now define a strategy for building perimeters and fills. The separation
+ // between regions doesn't matter in terms of printing order, as we follow
// another logic instead:
// - we group all extrusions by extruder so that we minimize toolchanges
// - we start from the last used extruder
@@ -2089,13 +1942,13 @@ void GCode::process_layer(
std::vector<size_t> slices_test_order;
slices_test_order.reserve(n_slices);
for (size_t i = 0; i < n_slices; ++ i)
- slices_test_order.emplace_back(i);
+ slices_test_order.emplace_back(i);
std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) {
- const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
- const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
- return s1.x() * s1.y() < s2.x() * s2.y();
+ const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
+ const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
+ return s1.x() * s1.y() < s2.x() * s2.y();
});
- auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
+ auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
const BoundingBox &bbox = layer_surface_bboxes[i];
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
@@ -2126,27 +1979,27 @@ void GCode::process_layer(
// Let's recover vector of extruder overrides:
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
if (! layer_tools.has_extruder(correct_extruder_id)) {
- // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
+ // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
correct_extruder_id = layer_tools.extruders.back();
}
printing_extruders.clear();
if (is_anything_overridden) {
- entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
- if (entity_overrides == nullptr) {
- printing_extruders.emplace_back(correct_extruder_id);
- } else {
- printing_extruders.reserve(entity_overrides->size());
- for (int extruder : *entity_overrides)
- printing_extruders.emplace_back(extruder >= 0 ?
- // at least one copy is overridden to use this extruder
- extruder :
- // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
- static_cast<unsigned int>(- extruder - 1));
- Slic3r::sort_remove_duplicates(printing_extruders);
- }
- } else
- printing_extruders.emplace_back(correct_extruder_id);
+ entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size());
+ if (entity_overrides == nullptr) {
+ printing_extruders.emplace_back(correct_extruder_id);
+ } else {
+ printing_extruders.reserve(entity_overrides->size());
+ for (int extruder : *entity_overrides)
+ printing_extruders.emplace_back(extruder >= 0 ?
+ // at least one copy is overridden to use this extruder
+ extruder :
+ // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
+ static_cast<unsigned int>(- extruder - 1));
+ Slic3r::sort_remove_duplicates(printing_extruders);
+ }
+ } else
+ printing_extruders.emplace_back(correct_extruder_id);
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
for (unsigned int extruder : printing_extruders)
@@ -2157,10 +2010,10 @@ void GCode::process_layer(
&layer_to_print - layers.data(),
layers.size(), n_slices+1);
for (size_t i = 0; i <= n_slices; ++ i) {
- bool last = i == n_slices;
- size_t island_idx = last ? n_slices : slices_test_order[i];
+ bool last = i == n_slices;
+ size_t island_idx = last ? n_slices : slices_test_order[i];
if (// extrusions->first_point does not fit inside any slice
- last ||
+ last ||
// extrusions->first_point fits inside ith slice
point_inside_surface(island_idx, extrusions->first_point())) {
if (islands[island_idx].by_region.empty())
@@ -2185,13 +2038,13 @@ void GCode::process_layer(
this->set_extruder(extruder_id, print_z);
// let analyzer tag generator aware of a role type change
- if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower)
- m_last_analyzer_extrusion_role = erWipeTower;
+ if (layer_tools.has_wipe_tower && m_wipe_tower)
+ m_last_processor_extrusion_role = erWipeTower;
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
const std::pair<size_t, size_t> loops = loops_it->second;
this->set_origin(0., 0.);
- m_avoid_crossing_perimeters.use_external_mp = true;
+ m_avoid_crossing_perimeters.use_external_mp();
Flow layer_skirt_flow(print.skirt_flow());
layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
@@ -2205,23 +2058,23 @@ void GCode::process_layer(
//FIXME using the support_material_speed of the 1st object printed.
gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
}
- m_avoid_crossing_perimeters.use_external_mp = false;
+ m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
if (first_layer && loops.first == 0)
- m_avoid_crossing_perimeters.disable_once = true;
+ m_avoid_crossing_perimeters.disable_once();
}
// Extrude brim with the extruder of the 1st region.
if (! m_brim_done) {
this->set_origin(0., 0.);
- m_avoid_crossing_perimeters.use_external_mp = true;
+ m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity *ee : print.brim().entities) {
gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value);
}
m_brim_done = true;
- m_avoid_crossing_perimeters.use_external_mp = false;
+ m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point.
- m_avoid_crossing_perimeters.disable_once = true;
+ m_avoid_crossing_perimeters.disable_once();
}
@@ -2229,10 +2082,10 @@ void GCode::process_layer(
if (objects_by_extruder_it == by_extruder.end())
continue;
- std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
+ std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
// 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):
- std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
+ std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
if (is_anything_overridden && print_wipe_extrusions == 0)
gcode+="; PURGING FINISHED\n";
@@ -2241,15 +2094,14 @@ void GCode::process_layer(
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layers[instance_to_print.layer_id].layer();
if (m_config.avoid_crossing_perimeters)
- m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true));
-
+ m_avoid_crossing_perimeters.init_layer(*m_layer);
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move.
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy)
- m_avoid_crossing_perimeters.use_external_mp_once = true;
+ m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
@@ -2259,25 +2111,29 @@ void GCode::process_layer(
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
m_layer = layers[instance_to_print.layer_id].layer();
}
+ //FIXME order islands?
+ // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
- //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
+ //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way.
if (print.config().infill_first) {
- gcode += this->extrude_infill(print, by_region_specific);
+ gcode += this->extrude_infill(print, by_region_specific, false);
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
} else {
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
- gcode += this->extrude_infill(print,by_region_specific);
+ gcode += this->extrude_infill(print,by_region_specific, false);
}
+ // ironing
+ gcode += this->extrude_infill(print,by_region_specific, true);
}
if (this->config().gcode_label_objects)
- gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
+ gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
}
}
}
// Apply spiral vase post-processing if this layer contains suitable geometry
- // (we must feed all the G-code into the post-processor, including the first
+ // (we must feed all the G-code into the post-processor, including the first
// bottom non-spiral layers otherwise it will mess with positions)
// we apply spiral vase at this stage because it requires a full layer.
// Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
@@ -2288,12 +2144,6 @@ void GCode::process_layer(
if (m_cooling_buffer)
gcode = m_cooling_buffer->process_layer(gcode, layer.id());
- // add tag for analyzer
- if (gcode.find(GCodeAnalyzer::Pause_Print_Tag) != gcode.npos)
- gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
- else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos)
- gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n";
-
#ifdef HAS_PRESSURE_EQUALIZER
// Apply pressure equalization if enabled;
// printf("G-code before filter:\n%s\n", gcode.c_str());
@@ -2301,13 +2151,9 @@ void GCode::process_layer(
gcode = m_pressure_equalizer->process(gcode.c_str(), false);
// printf("G-code after filter:\n%s\n", out.c_str());
#endif /* HAS_PRESSURE_EQUALIZER */
-
+
_write(file, gcode);
- BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
- ", time estimator memory: " <<
- format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) <<
- ", analyzer memory: " <<
- format_memsize_MB(m_analyzer.memory_used()) <<
+ BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
log_memory_info();
}
@@ -2319,19 +2165,20 @@ void GCode::apply_print_config(const PrintConfig &print_config)
void GCode::append_full_config(const Print &print, std::string &str)
{
- const DynamicPrintConfig &cfg = print.full_print_config();
+ const DynamicPrintConfig &cfg = print.full_print_config();
// Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
- static constexpr auto banned_keys = {
- "compatible_printers"sv,
- "compatible_prints"sv,
- "print_host"sv,
- "printhost_apikey"sv,
- "printhost_cafile"sv
- };
+ static constexpr auto banned_keys = {
+ "compatible_printers"sv,
+ "compatible_prints"sv,
+ //FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
+ "print_host"sv,
+ "printhost_apikey"sv,
+ "printhost_cafile"sv
+ };
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
- auto is_banned = [](const std::string &key) {
- return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
- };
+ auto is_banned = [](const std::string &key) {
+ return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
+ };
for (const std::string &key : cfg.keys())
if (! is_banned(key) && ! cfg.option(key)->is_nil())
str += "; " + key + " = " + cfg.opt_serialize(key) + "\n";
@@ -2340,7 +2187,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
{
m_writer.set_extruders(extruder_ids);
-
+
// enable wipe path generation if any extruder has wipe enabled
m_wipe.enable = false;
for (auto id : extruder_ids)
@@ -2351,7 +2198,7 @@ void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
}
void GCode::set_origin(const Vec2d &pointf)
-{
+{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
const Point translate(
scale_(m_origin(0) - pointf(0)),
@@ -2365,13 +2212,13 @@ void GCode::set_origin(const Vec2d &pointf)
std::string GCode::preamble()
{
std::string gcode = m_writer.preamble();
-
+
/* Perform a *silent* move to z_offset: we need this to initialize the Z
position of our writer object so that any initial lift taking place
before the first layer change will raise the extruder from the correct
initial Z instead of 0. */
m_writer.travel_to_z(m_config.z_offset.value);
-
+
return gcode;
}
@@ -2391,178 +2238,14 @@ std::string GCode::change_layer(coordf_t print_z)
comment << "move to next layer (" << m_layer_index << ")";
gcode += m_writer.travel_to_z(z, comment.str());
}
-
+
// forget last wiping path as wiping after raising Z is pointless
m_wipe.reset_path();
-
- return gcode;
-}
-
-// Return a value in <0, 1> of a cubic B-spline kernel centered around zero.
-// The B-spline is re-scaled so it has value 1 at zero.
-static inline float bspline_kernel(float x)
-{
- x = std::abs(x);
- if (x < 1.f) {
- return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
- }
- else if (x < 2.f) {
- x -= 1.f;
- float x2 = x * x;
- float x3 = x2 * x;
- return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
- }
- else
- return 0;
-}
-
-static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
-{
- // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
- // Solved by sympy package:
-/*
-from sympy import *
-(x,a,b,c,d,r,z)=symbols('x a b c d r z')
-p = a + b*x + c*x*x + d*x*x*x
-p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
-from sympy.plotting import plot
-plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
-*/
- if (overlap_distance < - nozzle_r) {
- // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
- return 0.f;
- } else {
- float x = overlap_distance / nozzle_r;
- float x2 = x * x;
- float x3 = x2 * x;
- return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
- }
-}
-
-static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
-{
- assert(polygon.points.size() >= 2);
- if (polygon.points.size() <= 1)
- if (polygon.points.size() == 1)
- return polygon.points.begin();
-
- Point pt_min;
- double d_min = std::numeric_limits<double>::max();
- size_t i_min = size_t(-1);
- for (size_t i = 0; i < polygon.points.size(); ++ i) {
- size_t j = i + 1;
- if (j == polygon.points.size())
- j = 0;
- const Point &p1 = polygon.points[i];
- const Point &p2 = polygon.points[j];
- const Slic3r::Point v_seg = p2 - p1;
- const Slic3r::Point v_pt = pt - p1;
- const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
- int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
- if (t_pt < 0) {
- // Closest to p1.
- double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
- if (dabs < d_min) {
- d_min = dabs;
- i_min = i;
- pt_min = p1;
- }
- }
- else if (t_pt > l2_seg) {
- // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step.
- continue;
- } else {
- // Closest to the segment.
- assert(t_pt >= 0 && t_pt <= l2_seg);
- int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
- double d = double(d_seg) / sqrt(double(l2_seg));
- double dabs = std::abs(d);
- if (dabs < d_min) {
- d_min = dabs;
- i_min = i;
- // Evaluate the foot point.
- pt_min = p1;
- double linv = double(d_seg) / double(l2_seg);
- pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5));
- pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
- assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
- }
- }
- }
-
- assert(i_min != size_t(-1));
- if ((pt_min - polygon.points[i_min]).cast<double>().norm() > eps) {
- // Insert a new point on the segment i_min, i_min+1.
- return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min);
- }
- return polygon.points.begin() + i_min;
+ return gcode;
}
-std::vector<float> polygon_parameter_by_length(const Polygon &polygon)
-{
- // Parametrize the polygon by its length.
- std::vector<float> lengths(polygon.points.size()+1, 0.);
- for (size_t i = 1; i < polygon.points.size(); ++ i)
- lengths[i] = lengths[i-1] + (polygon.points[i] - polygon.points[i-1]).cast<float>().norm();
- lengths.back() = lengths[lengths.size()-2] + (polygon.points.front() - polygon.points.back()).cast<float>().norm();
- return lengths;
-}
-std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths, float min_arm_length)
-{
- assert(polygon.points.size() + 1 == lengths.size());
- if (min_arm_length > 0.25f * lengths.back())
- min_arm_length = 0.25f * lengths.back();
-
- // Find the initial prev / next point span.
- size_t idx_prev = polygon.points.size();
- size_t idx_curr = 0;
- size_t idx_next = 1;
- while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length)
- -- idx_prev;
- while (idx_next < idx_prev && lengths[idx_next] < min_arm_length)
- ++ idx_next;
-
- std::vector<float> angles(polygon.points.size(), 0.f);
- for (; idx_curr < polygon.points.size(); ++ idx_curr) {
- // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
- if (idx_prev >= idx_curr) {
- while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length)
- ++ idx_prev;
- if (idx_prev == polygon.points.size())
- idx_prev = 0;
- }
- while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length)
- ++ idx_prev;
- // Move idx_prev one step back.
- if (idx_prev == 0)
- idx_prev = polygon.points.size() - 1;
- else
- -- idx_prev;
- // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
- if (idx_curr <= idx_next) {
- while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length)
- ++ idx_next;
- if (idx_next == polygon.points.size())
- idx_next = 0;
- }
- while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length)
- ++ idx_next;
- // Calculate angle between idx_prev, idx_curr, idx_next.
- const Point &p0 = polygon.points[idx_prev];
- const Point &p1 = polygon.points[idx_curr];
- const Point &p2 = polygon.points[idx_next];
- const Point v1 = p1 - p0;
- const Point v2 = p2 - p1;
- int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
- int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
- float angle = float(atan2(double(cross), double(dot)));
- angles[idx_curr] = angle;
- }
-
- return angles;
-}
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
{
@@ -2589,195 +2272,49 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
#endif
}
}
-
+
// extrude all loops ccw
bool was_clockwise = loop.make_counter_clockwise();
-
+
SeamPosition seam_position = m_config.seam_position;
- if (loop.loop_role() == elrSkirt)
+ if (loop.loop_role() == elrSkirt)
seam_position = spNearest;
-
+
// find the point of the loop that is closest to the current extruder position
// or randomize if requested
Point last_pos = this->last_pos();
if (m_config.spiral_vase) {
loop.split_at(last_pos, false);
- } else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) {
- Polygon polygon = loop.polygon();
- const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
- const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
-
- // Retrieve the last start position for this object.
- float last_pos_weight = 1.f;
-
- if (seam_position == spAligned) {
- // Seam is aligned to the seam at the preceding layer.
- if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) {
- last_pos = m_seam_position[m_layer->object()];
- last_pos_weight = 1.f;
- }
- }
- else if (seam_position == spRear) {
- // Object is centered around (0,0) in its current coordinate system.
- last_pos.x() = 0;
- last_pos.y() += coord_t(3. * m_layer->object()->bounding_box().radius());
- last_pos_weight = 5.f;
- }
-
- // Insert a projection of last_pos into the polygon.
- size_t last_pos_proj_idx;
- {
- Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
- last_pos_proj_idx = it - polygon.points.begin();
- }
-
- // Parametrize the polygon by its length.
- std::vector<float> lengths = polygon_parameter_by_length(polygon);
-
- // For each polygon point, store a penalty.
- // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
- std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
- // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
- const float penaltyConvexVertex = 1.f;
- const float penaltyFlatSurface = 5.f;
- const float penaltyOverhangHalf = 10.f;
- // Penalty for visible seams.
- for (size_t i = 0; i < polygon.points.size(); ++ i) {
- float ccwAngle = penalties[i];
- if (was_clockwise)
- ccwAngle = - ccwAngle;
- float penalty = 0;
-// if (ccwAngle <- float(PI/3.))
- if (ccwAngle <- float(0.6 * PI))
- // Sharp reflex vertex. We love that, it hides the seam perfectly.
- penalty = 0.f;
-// else if (ccwAngle > float(PI/3.))
- else if (ccwAngle > float(0.6 * PI))
- // Seams on sharp convex vertices are more visible than on reflex vertices.
- penalty = penaltyConvexVertex;
- else if (ccwAngle < 0.f) {
- // Interpolate penalty between maximum and zero.
- penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
- } else {
- assert(ccwAngle >= 0.f);
- // Interpolate penalty between maximum and the penalty for a convex vertex.
- penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
- }
- // Give a negative penalty for points close to the last point or the prefered seam location.
- //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]);
- float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
- std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
- std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
- float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
- penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
- penalties[i] = std::max(0.f, penalty);
- }
-
- // Penalty for overhangs.
- if (lower_layer_edge_grid && (*lower_layer_edge_grid)) {
- // Use the edge grid distance field structure over the lower layer to calculate overhangs.
- coord_t nozzle_r = coord_t(floor(scale_(0.5 * nozzle_dmr) + 0.5));
- coord_t search_r = coord_t(floor(scale_(0.8 * nozzle_dmr) + 0.5));
- for (size_t i = 0; i < polygon.points.size(); ++ i) {
- const Point &p = polygon.points[i];
- coordf_t dist;
- // Signed distance is positive outside the object, negative inside the object.
- // The point is considered at an overhang, if it is more than nozzle radius
- // outside of the lower layer contour.
- #ifdef NDEBUG // to suppress unused variable warning in release mode
- (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
- #else
- bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
- #endif
- // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
- // then the signed distnace shall always be known.
- assert(found);
- penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
- }
- }
-
- // Find a point with a minimum penalty.
- size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
-
- // if (seam_position == spAligned)
- // For all (aligned, nearest, rear) seams:
- {
- // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
- // In that case use last_pos_proj_idx instead.
- float penalty_aligned = penalties[last_pos_proj_idx];
- float penalty_min = penalties[idx_min];
- float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
- float penalty_max = std::max(penalty_min, penalty_aligned);
- float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
- // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
- if (penalty_diff_rel < 0.05) {
- // Penalty of the aligned point is very close to the minimum penalty.
- // Align the seams as accurately as possible.
- idx_min = last_pos_proj_idx;
- }
- m_seam_position[m_layer->object()] = polygon.points[idx_min];
- }
-
- // Export the contour into a SVG file.
- #if 0
- {
- static int iRun = 0;
- SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
- if (m_layer->lower_layer != NULL)
- svg.draw(m_layer->lower_layer->slices);
- for (size_t i = 0; i < loop.paths.size(); ++ i)
- svg.draw(loop.paths[i].as_polyline(), "red");
- Polylines polylines;
- for (size_t i = 0; i < loop.paths.size(); ++ i)
- polylines.push_back(loop.paths[i].as_polyline());
- Slic3r::Polygons polygons;
- coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
- coord_t delta = scale_(0.5*nozzle_dmr);
- Slic3r::offset(polylines, &polygons, delta);
-// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
- svg.draw(last_pos, "green", 3);
- svg.draw(polygon.points[idx_min], "yellow", 3);
- svg.Close();
- }
- #endif
-
+ } else {
+ const EdgeGrid::Grid* edge_grid_ptr = (lower_layer_edge_grid && *lower_layer_edge_grid)
+ ? lower_layer_edge_grid->get()
+ : nullptr;
+ Point seam = m_seam_placer.get_seam(*m_layer, seam_position, loop,
+ last_pos, EXTRUDER_CONFIG(nozzle_diameter),
+ (m_layer == NULL ? nullptr : m_layer->object()),
+ was_clockwise, edge_grid_ptr);
// Split the loop at the point with a minium penalty.
- if (!loop.split_at_vertex(polygon.points[idx_min]))
+ if (!loop.split_at_vertex(seam))
// The point is not in the original loop. Insert it.
- loop.split_at(polygon.points[idx_min], true);
-
- } else if (seam_position == spRandom) {
- if (loop.loop_role() == elrContourInternalPerimeter) {
- // This loop does not contain any other loop. Set a random position.
- // The other loops will get a seam close to the random point chosen
- // on the inner most contour.
- //FIXME This works correctly for inner contours first only.
- //FIXME Better parametrize the loop by its length.
- Polygon polygon = loop.polygon();
- Point centroid = polygon.centroid();
- last_pos = Point(polygon.bounding_box().max(0), centroid(1));
- last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid);
- }
- // Find the closest point, avoid overhangs.
- loop.split_at(last_pos, true);
+ loop.split_at(seam, true);
}
-
+
// clip the path to avoid the extruder to get exactly on the first point of the loop;
// if polyline was shorter than the clipping distance we'd get a null polyline, so
// we discard it in that case
- double clip_length = m_enable_loop_clipping ?
- scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
+ double clip_length = m_enable_loop_clipping ?
+ scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
0;
// get paths
ExtrusionPaths paths;
loop.clip_end(clip_length, &paths);
if (paths.empty()) return "";
-
+
// apply the small perimeter speed
if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
-
+
// extrude along the path
std::string gcode;
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
@@ -2786,31 +2323,31 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
path->simplify(SCALED_RESOLUTION);
gcode += this->_extrude(*path, description, speed);
}
-
+
// reset acceleration
gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5));
-
+
if (m_wipe.enable)
m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path
-
+
// make a little move inwards before leaving loop
- if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
+ if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) {
// detect angle between last and first segment
// the side depends on the original winding order of the polygon (left for contours, right for holes)
- //FIXME improve the algorithm in case the loop is tiny.
- //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
+ //FIXME improve the algorithm in case the loop is tiny.
+ //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
Point a = paths.front().polyline.points[1]; // second point
Point b = *(paths.back().polyline.points.end()-3); // second to last point
if (was_clockwise) {
// swap points
Point c = a; a = b; b = c;
}
-
+
double angle = paths.front().first_point().ccw_angle(a, b) / 3;
-
+
// turn left if contour, turn right if hole
if (was_clockwise) angle *= -1;
-
+
// create the destination point along the first segment and rotate it
// we make sure we don't exceed the segment length because we don't know
// the rotation of the second segment so we might cross the object boundary
@@ -2826,7 +2363,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// generate the travel move
gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel");
}
-
+
return gcode;
}
@@ -2858,7 +2395,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
else
- throw std::invalid_argument("Invalid argument supplied to extrude()");
+ throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()");
return "";
}
@@ -2890,21 +2427,29 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
}
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
-std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region)
+std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, bool ironing)
{
- std::string gcode;
+ std::string gcode;
+ ExtrusionEntitiesPtr extrusions;
+ const char* extrusion_name = ironing ? "ironing" : "infill";
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.infills.empty()) {
- m_config.apply(print.regions()[&region - &by_region.front()]->config());
- ExtrusionEntitiesPtr extrusions { region.infills };
- chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
- for (const ExtrusionEntity *fill : extrusions) {
- auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
- if (eec) {
- for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
- gcode += this->extrude_entity(*ee, "infill");
- } else
- gcode += this->extrude_entity(*fill, "infill");
+ extrusions.clear();
+ extrusions.reserve(region.infills.size());
+ for (ExtrusionEntity *ee : region.infills)
+ if ((ee->role() == erIroning) == ironing)
+ extrusions.emplace_back(ee);
+ if (! extrusions.empty()) {
+ m_config.apply(print.regions()[&region - &by_region.front()]->config());
+ chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
+ for (const ExtrusionEntity *fill : extrusions) {
+ auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
+ if (eec) {
+ for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
+ gcode += this->extrude_entity(*ee, extrusion_name);
+ } else
+ gcode += this->extrude_entity(*fill, extrusion_name);
+ }
}
}
return gcode;
@@ -2940,15 +2485,9 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
void GCode::_write(FILE* file, const char *what)
{
if (what != nullptr) {
- // apply analyzer, if enabled
- const char* gcode = m_enable_analyzer ? m_analyzer.process_gcode(what).c_str() : what;
-
+ const char* gcode = what;
// writes string to file
fwrite(gcode, 1, ::strlen(gcode), file);
- // updates time estimator and gcode lines vector
- m_normal_time_estimator.add_gcode_block(gcode);
- if (m_silent_time_estimator_enabled)
- m_silent_time_estimator.add_gcode_block(gcode);
}
}
@@ -2993,10 +2532,10 @@ void GCode::_write_format(FILE* file, const char* format, ...)
std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed)
{
std::string gcode;
-
+
if (is_bridge(path.role()))
description += " (bridge)";
-
+
// go to first point of extrusion path
if (!m_last_pos_defined || m_last_pos != path.first_point()) {
gcode += this->travel_to(
@@ -3005,10 +2544,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
"move to first " + description + " point"
);
}
-
+
// compensate retraction
gcode += this->unretract();
-
+
// adjust acceleration
{
double acceleration;
@@ -3025,11 +2564,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5));
}
-
+
// calculate extrusion length per distance unit
double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm;
if (m_writer.extrusion_axis().empty()) e_per_mm = 0;
-
+
// set speed
if (speed == -1) {
if (path.role() == erPerimeter) {
@@ -3044,10 +2583,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
speed = m_config.get_abs_value("solid_infill_speed");
} else if (path.role() == erTopSolidInfill) {
speed = m_config.get_abs_value("top_solid_infill_speed");
+ } else if (path.role() == erIroning) {
+ speed = m_config.get_abs_value("ironing_speed");
} else if (path.role() == erGapFill) {
speed = m_config.get_abs_value("gap_fill_speed");
} else {
- throw std::invalid_argument("Invalid speed");
+ throw Slic3r::InvalidArgument("Invalid speed");
}
}
if (this->on_first_layer())
@@ -3069,7 +2610,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
);
}
double F = speed * 60; // convert mm/sec to mm/min
-
+
// extrude arc or line
if (m_enable_extrusion_role_markers)
{
@@ -3085,41 +2626,46 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
}
- // adds analyzer tags and updates analyzer's tracking data
- if (m_enable_analyzer)
- {
- // PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width
- // so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines
- bool last_was_wipe_tower = (m_last_analyzer_extrusion_role == erWipeTower);
- char buf[64];
+ // adds processor tags and updates processor tracking data
+ // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height
+ // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines
+ bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower);
+ char buf[64];
- if (path.role() != m_last_analyzer_extrusion_role)
- {
- m_last_analyzer_extrusion_role = path.role();
- sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role));
- gcode += buf;
- }
+ if (path.role() != m_last_processor_extrusion_role) {
+ m_last_processor_extrusion_role = path.role();
+ sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str());
+ gcode += buf;
+ }
- if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm))
- {
- m_last_mm3_per_mm = path.mm3_per_mm;
- sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
- gcode += buf;
- }
+#if ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+ if (last_was_wipe_tower || m_last_width != path.width) {
+ m_last_width = path.width;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width);
+ gcode += buf;
+ }
+#endif // ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
- if (last_was_wipe_tower || (m_last_width != path.width))
- {
- m_last_width = path.width;
- sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width);
- gcode += buf;
- }
+#if ENABLE_GCODE_VIEWER_DATA_CHECKING
+ if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) {
+ m_last_mm3_per_mm = path.mm3_per_mm;
+ sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
+ gcode += buf;
+ }
- if (last_was_wipe_tower || (m_last_height != path.height))
- {
- m_last_height = path.height;
- sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height);
- gcode += buf;
- }
+#if !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+ if (last_was_wipe_tower || m_last_width != path.width) {
+ m_last_width = path.width;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width);
+ gcode += buf;
+ }
+#endif // !ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE
+#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
+
+ if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) {
+ m_last_height = path.height;
+ sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), m_last_height);
+ gcode += buf;
}
std::string comment;
@@ -3148,54 +2694,62 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
if (m_enable_cooling_markers)
gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";
-
+
this->set_last_pos(path.last_point());
return gcode;
}
// This method accepts &point in print coordinates.
std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment)
-{
+{
/* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by
this->origin in order to get G-code coordinates. */
- Polyline travel;
- travel.append(this->last_pos());
- travel.append(point);
-
+ Polyline travel { this->last_pos(), point };
+
// check whether a straight travel move would need retraction
- bool needs_retraction = this->needs_retraction(travel, role);
-
+ bool needs_retraction = this->needs_retraction(travel, role);
+ // check whether wipe could be disabled without causing visible stringing
+ bool could_be_wipe_disabled = false;
+
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space
if (needs_retraction
&& m_config.avoid_crossing_perimeters
- && ! m_avoid_crossing_perimeters.disable_once) {
- travel = m_avoid_crossing_perimeters.travel_to(*this, point);
-
+ && ! m_avoid_crossing_perimeters.disabled_once()) {
+ travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled);
// check again whether the new travel path still needs a retraction
needs_retraction = this->needs_retraction(travel, role);
//if (needs_retraction && m_layer_index > 1) exit(0);
}
-
+
// Re-allow avoid_crossing_perimeters for the next travel moves
- m_avoid_crossing_perimeters.disable_once = false;
- m_avoid_crossing_perimeters.use_external_mp_once = false;
-
+ m_avoid_crossing_perimeters.reset_once_modifiers();
+
// generate G-code for the travel move
std::string gcode;
- if (needs_retraction)
+ if (needs_retraction) {
+ if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
+ m_wipe.reset_path();
+
+ Point last_post_before_retract = this->last_pos();
gcode += this->retract();
- else
+ // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
+ // Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction()
+ if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) {
+ Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract);
+ append(retract_travel.points, travel.points);
+ travel = std::move(retract_travel);
+ }
+ } else
// Reset the wipe path when traveling, so one would not wipe along an old path.
m_wipe.reset_path();
-
+
// use G1 because we rely on paths being straight (G0 may make round paths)
- Lines lines = travel.lines();
- if (! lines.empty()) {
- for (const Line &line : lines)
- gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment);
- this->set_last_pos(lines.back().b);
+ if (travel.size() >= 2) {
+ for (size_t i = 1; i < travel.size(); ++ i)
+ gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
+ this->set_last_pos(travel.points.back());
}
return gcode;
}
@@ -3206,7 +2760,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
// skip retraction if the move is shorter than the configured threshold
return false;
}
-
+
if (role == erSupportMaterial) {
const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
//FIXME support_layer->support_islands.contains should use some search structure!
@@ -3223,7 +2777,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
// internal infill is enabled (so that stringing is entirely not visible).
//FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
return false;
-
+
// retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return true;
}
@@ -3231,26 +2785,26 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
std::string GCode::retract(bool toolchange)
{
std::string gcode;
-
+
if (m_writer.extruder() == nullptr)
return gcode;
-
+
// wipe (if it's enabled for this extruder and we have a stored wipe path)
if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) {
gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true);
gcode += m_wipe.wipe(*this, toolchange);
}
-
+
/* The parent class will decide whether we need to perform an actual retraction
- (the extruder might be already retracted fully or partially). We call these
+ (the extruder might be already retracted fully or partially). We call these
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();
-
+
gcode += m_writer.reset_e();
if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction)
gcode += m_writer.lift();
-
+
return gcode;
}
@@ -3258,11 +2812,11 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
{
if (!m_writer.need_toolchange(extruder_id))
return "";
-
+
// if we are running a single-extruder setup, just set the extruder and return nothing
if (!m_writer.multiple_extruders) {
m_placeholder_parser.set("current_extruder", extruder_id);
-
+
std::string gcode;
// Append the filament start G-code.
const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
@@ -3274,13 +2828,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
gcode += m_writer.toolchange(extruder_id);
return gcode;
}
-
+
// prepend retraction on the current extruder
std::string gcode = this->retract(true);
// Always reset the extrusion path, even if the tool change retract is set to zero.
m_wipe.reset_path();
-
+
if (m_writer.extruder() != nullptr) {
// Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
// so it should not be injected twice.
@@ -3292,7 +2846,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
}
}
-
+
// If ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature.
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
@@ -3308,6 +2862,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id));
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
+ config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
toolchange_gcode_parsed = placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config);
gcode += toolchange_gcode_parsed;
check_add_eol(gcode);
@@ -3341,7 +2896,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
// Set the new extruder to the operating temperature.
if (m_ooze_prevention.enable)
gcode += m_ooze_prevention.post_toolchange(*this);
-
+
return gcode;
}
@@ -3368,17 +2923,17 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
{
bool has_overrides = false;
for (const auto& reg : by_region)
- if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
- has_overrides = true;
- break;
- }
+ if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) {
+ has_overrides = true;
+ break;
+ }
- // Data is cleared, but the memory is not.
+ // Data is cleared, but the memory is not.
by_region_per_copy_cache.clear();
if (! has_overrides)
- // Simple case. No need to copy the regions.
- return wiping_entities ? by_region_per_copy_cache : this->by_region;
+ // Simple case. No need to copy the regions.
+ return wiping_entities ? by_region_per_copy_cache : this->by_region;
// Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions.
// Some of the extrusions of some object instances are printed later - those are the clean print extrusions.
@@ -3397,25 +2952,25 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru
// Now the most important thing - which extrusion should we print.
// See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
if (wiping_entities) {
- // Apply overrides for this region.
- for (unsigned int i = 0; i < overrides.size(); ++ i) {
- const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
- // This copy (aka object instance) should be printed with this extruder, which overrides the default one.
- if (this_override != nullptr && (*this_override)[copy] == int(extruder))
- target_eec.emplace_back(entities[i]);
- }
- } else {
- // Apply normal extrusions (non-overrides) for this region.
- unsigned int i = 0;
- for (; i < overrides.size(); ++ i) {
- const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
- // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
- if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
- target_eec.emplace_back(entities[i]);
- }
- for (; i < entities.size(); ++ i)
+ // Apply overrides for this region.
+ for (unsigned int i = 0; i < overrides.size(); ++ i) {
+ const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
+ // This copy (aka object instance) should be printed with this extruder, which overrides the default one.
+ if (this_override != nullptr && (*this_override)[copy] == int(extruder))
+ target_eec.emplace_back(entities[i]);
+ }
+ } else {
+ // Apply normal extrusions (non-overrides) for this region.
+ unsigned int i = 0;
+ for (; i < overrides.size(); ++ i) {
+ const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i];
+ // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one.
+ if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1)
+ target_eec.emplace_back(entities[i]);
+ }
+ for (; i < entities.size(); ++ i)
target_eec.emplace_back(entities[i]);
- }
+ }
}
}
return by_region_per_copy_cache;
@@ -3435,27 +2990,30 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr
perimeters_or_infills_overrides = &perimeters_overrides;
break;
case INFILL:
- perimeters_or_infills = &infills;
- perimeters_or_infills_overrides = &infills_overrides;
+ perimeters_or_infills = &infills;
+ perimeters_or_infills_overrides = &infills_overrides;
break;
default:
- throw std::invalid_argument("Unknown parameter!");
+ throw Slic3r::InvalidArgument("Unknown parameter!");
}
// First we append the entities, there are eec->entities.size() of them:
size_t old_size = perimeters_or_infills->size();
- size_t new_size = old_size + eec->entities.size();
+ size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1);
perimeters_or_infills->reserve(new_size);
- for (auto* ee : eec->entities)
- perimeters_or_infills->emplace_back(ee);
+ if (eec->can_reverse()) {
+ for (auto* ee : eec->entities)
+ perimeters_or_infills->emplace_back(ee);
+ } else
+ perimeters_or_infills->emplace_back(const_cast<ExtrusionEntityCollection*>(eec));
if (copies_extruder != nullptr) {
- // Don't reallocate overrides if not needed.
- // Missing overrides are implicitely considered non-overridden.
+ // Don't reallocate overrides if not needed.
+ // Missing overrides are implicitely considered non-overridden.
perimeters_or_infills_overrides->reserve(new_size);
perimeters_or_infills_overrides->resize(old_size, nullptr);
perimeters_or_infills_overrides->resize(new_size, copies_extruder);
- }
+ }
}
} // namespace Slic3r