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

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbubnikv <bubnikv@gmail.com>2018-08-04 00:04:44 +0300
committerbubnikv <bubnikv@gmail.com>2018-08-04 00:04:44 +0300
commitac2b20b54b03cc67dd776681f7405ed4a93924ab (patch)
treecdac5ef4170ae392977331768c9c9d661038c231 /xs/src/libslic3r
parentfa6a72ab2d0f7f3096e714c0f696a9cc816ae770 (diff)
parent73ad49b9aedb4f568621d73d30c0ace815cfe667 (diff)
Merge branch 'master' into time_estimate
Diffstat (limited to 'xs/src/libslic3r')
-rw-r--r--xs/src/libslic3r/GCode.cpp143
-rw-r--r--xs/src/libslic3r/GCode.hpp12
-rw-r--r--xs/src/libslic3r/GCode/PrintExtents.cpp10
-rw-r--r--xs/src/libslic3r/GCode/WipeTower.hpp29
-rw-r--r--xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp66
-rw-r--r--xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp3
-rw-r--r--xs/src/libslic3r/GCodeTimeEstimator.cpp22
-rw-r--r--xs/src/libslic3r/Model.cpp400
-rw-r--r--xs/src/libslic3r/Model.hpp3
-rw-r--r--xs/src/libslic3r/ModelArrange.hpp597
-rw-r--r--xs/src/libslic3r/Print.cpp41
-rw-r--r--xs/src/libslic3r/Print.hpp4
-rw-r--r--xs/src/libslic3r/PrintConfig.cpp22
-rw-r--r--xs/src/libslic3r/PrintConfig.hpp4
-rw-r--r--xs/src/libslic3r/PrintObject.cpp4
15 files changed, 896 insertions, 464 deletions
diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
index 94634f4e4..1c1eeee6f 100644
--- a/xs/src/libslic3r/GCode.cpp
+++ b/xs/src/libslic3r/GCode.cpp
@@ -167,6 +167,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
{
std::string gcode;
+ // Toolchangeresult.gcode assumes the wipe tower corner is at the origin
+ // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
+ float alpha = m_wipe_tower_rotation/180.f * M_PI;
+ WipeTower::xy start_pos = tcr.start_pos;
+ WipeTower::xy end_pos = tcr.end_pos;
+ start_pos.rotate(alpha);
+ start_pos.translate(m_wipe_tower_pos);
+ end_pos.rotate(alpha);
+ end_pos.translate(m_wipe_tower_pos);
+ std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha);
+
+
// Disable linear advance for the wipe tower operations.
gcode += "M900 K0\n";
// Move over the wipe tower.
@@ -174,14 +186,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
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, tcr.start_pos),
+ wipe_tower_point_to_object_point(gcodegen, start_pos),
erMixed,
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
// 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;
+ gcode += tcr_rotated_gcode;
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
gcodegen.writer().toolchange(new_extruder_id);
@@ -195,18 +207,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
check_add_eol(gcode);
}
// A phony move to the end position at the wipe tower.
- gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
- gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
+ gcodegen.writer().travel_to_xy(Pointf(end_pos.x, end_pos.y));
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
// 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, tcr.end_pos));
+ 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,
- WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left,
- tcr.end_pos.y)));
+ WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left,
+ end_pos.y)));
}
// Let the planner know we are traveling between objects.
@@ -214,6 +226,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
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::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const
+{
+ std::istringstream gcode_str(gcode_original);
+ std::string gcode_out;
+ std::string line;
+ WipeTower::xy pos = start_pos;
+ WipeTower::xy transformed_pos;
+ WipeTower::xy old_pos(-1000.1f, -1000.1f);
+
+ while (gcode_str) {
+ std::getline(gcode_str, line); // we read the gcode line by line
+ if (line.find("G1 ") == 0) {
+ 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')
+ line_str >> pos.x;
+ else
+ if (ch == 'Y')
+ line_str >> pos.y;
+ else
+ line_out << ch;
+ }
+
+ transformed_pos = pos;
+ transformed_pos.rotate(angle);
+ transformed_pos.translate(translation);
+
+ if (transformed_pos != old_pos) {
+ line = line_out.str();
+ char buf[2048] = "G1";
+ if (transformed_pos.x != old_pos.x)
+ sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x);
+ if (transformed_pos.y != old_pos.y)
+ sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y);
+
+ line.replace(line.find("G1 "), 3, buf);
+ old_pos = transformed_pos;
+ }
+ }
+ gcode_out += line + "\n";
+ }
+ return gcode_out;
+}
+
+
+
std::string WipeTowerIntegration::prime(GCode &gcodegen)
{
assert(m_layer_idx == 0);
@@ -608,15 +671,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
break;
}
- }
- else {
+ } else {
// 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.m_tool_ordering.empty() ?
ToolOrdering(print, initial_extruder_id) :
print.m_tool_ordering;
- initial_extruder_id = tool_ordering.first_extruder();
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.
+ tool_ordering.all_extruders().back() :
+ // Don't skip the priming towers.
+ tool_ordering.first_extruder();
}
if (initial_extruder_id == (unsigned int)-1) {
// Nothing to print!
@@ -644,6 +710,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
m_placeholder_parser.set("current_object_idx", 0);
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
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);
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.
@@ -724,8 +791,11 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
}
}
- // Set initial extruder only after custom start G-code.
- _write(file, this->set_extruder(initial_extruder_id));
+ 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.
+ _write(file, this->set_extruder(initial_extruder_id));
+ }
// Do all objects for each layer.
if (print.config.complete_objects.value) {
@@ -803,27 +873,29 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
_write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
- _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;
- for (const PrintObject *print_object : printable_objects)
- bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
- 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");
+ if (print.config.single_extruder_multi_material_priming) {
+ _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;
+ for (const PrintObject *print_object : printable_objects)
+ bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
+ 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");
+ }
}
}
// Extrude the layers.
@@ -1003,9 +1075,10 @@ void GCode::print_machine_envelope(FILE *file, Print &print)
int(print.config.machine_max_feedrate_y.values.front() + 0.5),
int(print.config.machine_max_feedrate_z.values.front() + 0.5),
int(print.config.machine_max_feedrate_e.values.front() + 0.5));
- fprintf(file, "M204 S%d T%d ; sets acceleration (S) and retract acceleration (T), mm/sec^2\n",
+ fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5),
- int(print.config.machine_max_acceleration_retracting.values.front() + 0.5));
+ int(print.config.machine_max_acceleration_retracting.values.front() + 0.5),
+ int(print.config.machine_max_acceleration_extruding.values.front() + 0.5));
fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
print.config.machine_max_jerk_x.values.front(),
print.config.machine_max_jerk_y.values.front(),
diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp
index 8b40385e6..4953c39fe 100644
--- a/xs/src/libslic3r/GCode.hpp
+++ b/xs/src/libslic3r/GCode.hpp
@@ -83,8 +83,10 @@ public:
const WipeTower::ToolChangeResult &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
- m_left(float(print_config.wipe_tower_x.value)),
- m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)),
+ m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
+ m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
+ m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
+ m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_priming(priming),
m_tool_changes(tool_changes),
m_final_purge(final_purge),
@@ -101,9 +103,14 @@ private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
+ // Postprocesses gcode: rotates and moves all G1 extrusions and returns result
+ std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const;
+
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
+ const WipeTower::xy m_wipe_tower_pos;
+ const float m_wipe_tower_rotation;
// Reference to cached values at the Printer class.
const WipeTower::ToolChangeResult &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
@@ -112,6 +119,7 @@ private:
int m_layer_idx;
int m_tool_change_idx;
bool m_brim_done;
+ bool i_have_brim = false;
};
class GCode {
diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp
index 3c3f0f8d5..37b79f343 100644
--- a/xs/src/libslic3r/GCode/PrintExtents.cpp
+++ b/xs/src/libslic3r/GCode/PrintExtents.cpp
@@ -134,6 +134,11 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
// The projection does not contain the priming regions.
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
{
+ // Wipe tower extrusions are saved as if the tower was at the origin with no rotation
+ // We need to get position and angle of the wipe tower to transform them to actual position.
+ Pointf wipe_tower_pos(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value);
+ float wipe_tower_angle = print.config.wipe_tower_rotation_angle.value;
+
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.m_wipe_tower_tool_changes) {
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
@@ -144,6 +149,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
if (e.width > 0) {
Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y);
Pointf p2(e.pos.x, e.pos.y);
+ p1.rotate(wipe_tower_angle);
+ p1.translate(wipe_tower_pos);
+ p2.rotate(wipe_tower_angle);
+ p2.translate(wipe_tower_pos);
+
bbox.merge(p1);
coordf_t radius = 0.5 * e.width;
bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius);
diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp
index 36cebeb84..9bf350328 100644
--- a/xs/src/libslic3r/GCode/WipeTower.hpp
+++ b/xs/src/libslic3r/GCode/WipeTower.hpp
@@ -25,18 +25,30 @@ public:
bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
- // Rotate the point around given point about given angle (in degrees)
- // shifts the result so that point of rotation is in the middle of the tower
- xy rotate(const xy& origin, float width, float depth, float angle) const {
+ // Rotate the point around center of the wipe tower about given angle (in degrees)
+ xy rotate(float width, float depth, float angle) const {
xy out(0,0);
float temp_x = x - width / 2.f;
float temp_y = y - depth / 2.f;
angle *= M_PI/180.;
- out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle);
- out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle);
- return out + origin;
+ out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f;
+ out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f;
+
+ return out;
}
-
+
+ // Rotate the point around origin about given angle in degrees
+ void rotate(float angle) {
+ float temp_x = x * cos(angle) - y * sin(angle);
+ y = x * sin(angle) + y * cos(angle);
+ x = temp_x;
+ }
+
+ void translate(const xy& vect) {
+ x += vect.x;
+ y += vect.y;
+ }
+
float x;
float y;
};
@@ -104,6 +116,9 @@ public:
// This is useful not only for the print time estimation, but also for the control of layer cooling.
float elapsed_time;
+ // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
+ bool priming;
+
// Sum the total length of the extrusion.
float total_extrusion_length_in_plane() {
float e_length = 0.f;
diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
index f466fc4f6..3d0dba07a 100644
--- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
+++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
@@ -5,7 +5,7 @@ TODO LIST
1. cooling moves - DONE
2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE
-3. priming extrusions (last wipe must clear the color)
+3. priming extrusions (last wipe must clear the color) - DONE
4. Peter's wipe tower - layer's are not exactly square
5. Peter's wipe tower - variable width for higher levels
6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer)
@@ -17,7 +17,6 @@ TODO LIST
#include <assert.h>
#include <math.h>
-#include <fstream>
#include <iostream>
#include <vector>
#include <numeric>
@@ -68,8 +67,11 @@ public:
return *this;
}
- Writer& set_initial_position(const WipeTower::xy &pos) {
- m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg);
+ Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
+ m_wipe_tower_width = width;
+ m_wipe_tower_depth = depth;
+ m_internal_angle = internal_angle;
+ m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle);
m_current_pos = pos;
return *this;
}
@@ -81,9 +83,6 @@ public:
Writer& set_extrusion_flow(float flow)
{ m_extrusion_flow = flow; return *this; }
-
- Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle)
- { m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); }
Writer& set_y_shift(float shift) {
m_current_pos.y -= shift-m_y_shift;
@@ -110,7 +109,7 @@ public:
float y() const { return m_current_pos.y; }
const WipeTower::xy& pos() const { return m_current_pos; }
const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
- const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); }
+ const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
float elapsed_time() const { return m_elapsed_time; }
// Extrude with an explicitely provided amount of extrusion.
@@ -125,9 +124,9 @@ public:
double len = sqrt(dx*dx+dy*dy);
- // For rotated wipe tower, transform position to printer coordinates
- WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are
- WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go
+ // Now do the "internal rotation" with respect to the wipe tower center
+ WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are
+ WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go
if (! m_preview_suppressed && e > 0.f && len > 0.) {
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
@@ -147,6 +146,7 @@ public:
if (std::abs(rot.y - rotated_current_pos.y) > EPSILON)
m_gcode += set_format_Y(rot.y);
+
if (e != 0.f)
m_gcode += set_format_E(e);
@@ -397,9 +397,8 @@ private:
std::string m_gcode;
std::vector<WipeTower::Extrusion> m_extrusions;
float m_elapsed_time;
- float m_angle_deg = 0.f;
+ float m_internal_angle = 0.f;
float m_y_shift = 0.f;
- WipeTower::xy m_wipe_tower_pos;
float m_wipe_tower_width = 0.f;
float m_wipe_tower_depth = 0.f;
float m_last_fan_speed = 0.f;
@@ -539,6 +538,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
m_print_brim = true;
ToolChangeResult result;
+ result.priming = true;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@@ -575,7 +575,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
}
box_coordinates cleaning_box(
- m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
+ xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
m_wipe_tower_width - m_perimeter_width,
(tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width
: m_wipe_tower_depth-m_perimeter_width));
@@ -584,7 +584,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
- .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
.set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
.append(";--------------------\n"
"; CP TOOLCHANGE START\n")
@@ -594,7 +593,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
.speed_override(100);
xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed);
- writer.set_initial_position(initial_position);
+ writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
// Increase the extruder driver current to allow fast ramming.
writer.set_extruder_trimpot(750);
@@ -616,11 +615,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
if (last_change_in_layer) {// draw perimeter line
writer.set_y_shift(m_y_shift);
if (m_peters_wipe_tower)
- writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
+ writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
else {
- writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
+ writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle
- writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y());
+ writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y());
}
}
}
@@ -634,6 +633,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
"\n\n");
ToolChangeResult result;
+ result.priming = false;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@@ -647,7 +647,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset)
{
const box_coordinates wipeTower_box(
- m_wipe_tower_pos,
+ WipeTower::xy(0.f, 0.f),
m_wipe_tower_width,
m_wipe_tower_depth);
@@ -655,12 +655,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
writer.set_extrusion_flow(m_extrusion_flow * 1.1f)
.set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop.
.set_initial_tool(m_current_tool)
- .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
.append(";-------------------------------------\n"
"; CP WIPE TOWER FIRST LAYER BRIM START\n");
xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0);
- writer.set_initial_position(initial_position);
+ writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower.
1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400);
@@ -685,6 +684,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
m_print_brim = false; // Mark the brim as extruded
ToolChangeResult result;
+ result.priming = false;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@@ -724,7 +724,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
// this is y of the center of previous sparse infill border
- float sparse_beginning_y = m_wipe_tower_pos.y;
+ float sparse_beginning_y = 0.f;
if (m_current_shape == SHAPE_REVERSED)
sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth())
- ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ;
@@ -742,7 +742,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange
if (tch.old_tool == m_current_tool) {
sum_of_depths += tch.ramming_depth;
- float ramming_end_y = m_wipe_tower_pos.y + sum_of_depths;
+ float ramming_end_y = sum_of_depths;
ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
// debugging:
@@ -950,7 +950,7 @@ void WipeTowerPrusaMM::toolchange_Wipe(
if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) {
m_left_to_right = !m_left_to_right;
writer.travel(writer.x(), writer.y() - dy)
- .travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y());
+ .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
}
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
@@ -969,7 +969,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
- .set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
.set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f))
.append(";--------------------\n"
"; CP EMPTY GRID START\n")
@@ -978,14 +977,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
// Slow down on the 1st layer.
float speed_factor = m_is_first_layer ? 0.5f : 1.f;
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
- box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
+ box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
- if (m_left_to_right) // so there is never a diagonal travel
- writer.set_initial_position(fill_box.ru);
- else
- writer.set_initial_position(fill_box.lu);
+ writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
+ m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
box_coordinates box = fill_box;
for (int i=0;i<2;++i) {
@@ -1044,6 +1041,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
ToolChangeResult result;
+ result.priming = false;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@@ -1165,9 +1163,9 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
{
set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z);
if (m_peters_wipe_tower)
- m_wipe_tower_rotation_angle += 90.f;
+ m_internal_rotation += 90.f;
else
- m_wipe_tower_rotation_angle += 180.f;
+ m_internal_rotation += 180.f;
if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
@@ -1188,7 +1186,7 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
last_toolchange.gcode += buf;
}
last_toolchange.gcode += finish_layer_toolchange.gcode;
- last_toolchange.extrusions.insert(last_toolchange.extrusions.end(),finish_layer_toolchange.extrusions.begin(),finish_layer_toolchange.extrusions.end());
+ last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end());
last_toolchange.end_pos = finish_layer_toolchange.end_pos;
}
else
diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
index 1ae4616d8..e1529bcf4 100644
--- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
+++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
@@ -102,6 +102,8 @@ public:
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
+ float get_depth() const { return m_wipe_tower_depth; }
+
// Switch to a next layer.
@@ -189,6 +191,7 @@ private:
float m_wipe_tower_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
+ float m_internal_rotation = 0.f;
float m_y_shift = 0.f; // y shift passed to writer
float m_z_pos = 0.f; // Current Z position.
float m_layer_height = 0.f; // Current layer height.
diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp
index 909cdc26c..877695f39 100644
--- a/xs/src/libslic3r/GCodeTimeEstimator.cpp
+++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp
@@ -1185,11 +1185,25 @@ namespace Slic3r {
{
PROFILE_FUNC();
float value;
- if (line.has_value('S', value))
+ if (line.has_value('S', value)) {
+ // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware,
+ // and it is also generated by Slic3r to control acceleration per extrusion type
+ // (there is a separate acceleration settings in Slicer for perimeter, first layer etc).
set_acceleration(value);
-
- if (line.has_value('T', value))
- set_retract_acceleration(value);
+ if (line.has_value('T', value))
+ set_retract_acceleration(value);
+ } else {
+ // New acceleration format, compatible with the upstream Marlin.
+ if (line.has_value('P', value))
+ set_acceleration(value);
+ if (line.has_value('R', value))
+ set_retract_acceleration(value);
+ if (line.has_value('T', value)) {
+ // Interpret the T value as the travel acceleration in the new Marlin format.
+ //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value.
+ // set_travel_acceleration(value);
+ }
+ }
}
void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line)
diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
index d6f1f05c9..bceeea258 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -7,11 +7,6 @@
#include "Format/STL.hpp"
#include "Format/3mf.hpp"
-#include <numeric>
-#include <libnest2d.h>
-#include <ClipperUtils.hpp>
-#include "slic3r/GUI/GUI.hpp"
-
#include <float.h>
#include <boost/algorithm/string/predicate.hpp>
@@ -304,369 +299,36 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
return result;
}
-namespace arr {
-
-using namespace libnest2d;
-
-std::string toString(const Model& model, bool holes = true) {
- std::stringstream ss;
-
- ss << "{\n";
-
- for(auto objptr : model.objects) {
- if(!objptr) continue;
-
- auto rmesh = objptr->raw_mesh();
-
- for(auto objinst : objptr->instances) {
- if(!objinst) continue;
-
- Slic3r::TriangleMesh tmpmesh = rmesh;
- tmpmesh.scale(objinst->scaling_factor);
- objinst->transform_mesh(&tmpmesh);
- ExPolygons expolys = tmpmesh.horizontal_projection();
- for(auto& expoly_complex : expolys) {
-
- auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
- if(tmp.empty()) continue;
- auto expoly = tmp.front();
- expoly.contour.make_clockwise();
- for(auto& h : expoly.holes) h.make_counter_clockwise();
-
- ss << "\t{\n";
- ss << "\t\t{\n";
-
- for(auto v : expoly.contour.points) ss << "\t\t\t{"
- << v.x << ", "
- << v.y << "},\n";
- {
- auto v = expoly.contour.points.front();
- ss << "\t\t\t{" << v.x << ", " << v.y << "},\n";
- }
- ss << "\t\t},\n";
-
- // Holes:
- ss << "\t\t{\n";
- if(holes) for(auto h : expoly.holes) {
- ss << "\t\t\t{\n";
- for(auto v : h.points) ss << "\t\t\t\t{"
- << v.x << ", "
- << v.y << "},\n";
- {
- auto v = h.points.front();
- ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\n";
- }
- ss << "\t\t\t},\n";
- }
- ss << "\t\t},\n";
-
- ss << "\t},\n";
- }
- }
- }
-
- ss << "}\n";
-
- return ss.str();
-}
-
-void toSVG(SVG& svg, const Model& model) {
- for(auto objptr : model.objects) {
- if(!objptr) continue;
-
- auto rmesh = objptr->raw_mesh();
-
- for(auto objinst : objptr->instances) {
- if(!objinst) continue;
-
- Slic3r::TriangleMesh tmpmesh = rmesh;
- tmpmesh.scale(objinst->scaling_factor);
- objinst->transform_mesh(&tmpmesh);
- ExPolygons expolys = tmpmesh.horizontal_projection();
- svg.draw(expolys);
- }
- }
-}
-
-// A container which stores a pointer to the 3D object and its projected
-// 2D shape from top view.
-using ShapeData2D =
- std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
-
-ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
- ShapeData2D ret;
-
- auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
- [](size_t s, ModelObject* o){
- return s + o->instances.size();
- });
-
- ret.reserve(s);
-
- for(auto objptr : model.objects) {
- if(objptr) {
-
- auto rmesh = objptr->raw_mesh();
-
- for(auto objinst : objptr->instances) {
- if(objinst) {
- Slic3r::TriangleMesh tmpmesh = rmesh;
- ClipperLib::PolygonImpl pn;
-
- tmpmesh.scale(objinst->scaling_factor);
-
- // TODO export the exact 2D projection
- auto p = tmpmesh.convex_hull();
-
- p.make_clockwise();
- p.append(p.first_point());
- pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
-
- // Efficient conversion to item.
- Item item(std::move(pn));
-
- // Invalid geometries would throw exceptions when arranging
- if(item.vertexCount() > 3) {
- item.rotation(objinst->rotation);
- item.translation( {
- ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR),
- ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR)
- });
- ret.emplace_back(objinst, item);
- }
- }
- }
- }
- }
-
- return ret;
-}
-
-/**
- * \brief Arranges the model objects on the screen.
- *
- * The arrangement considers multiple bins (aka. print beds) for placing all
- * the items provided in the model argument. If the items don't fit on one
- * print bed, the remaining will be placed onto newly created print beds.
- * The first_bin_only parameter, if set to true, disables this behaviour and
- * makes sure that only one print bed is filled and the remaining items will be
- * untouched. When set to false, the items which could not fit onto the
- * print bed will be placed next to the print bed so the user should see a
- * pile of items on the print bed and some other piles outside the print
- * area that can be dragged later onto the print bed as a group.
- *
- * \param model The model object with the 3D content.
- * \param dist The minimum distance which is allowed for any pair of items
- * on the print bed in any direction.
- * \param bb The bounding box of the print bed. It corresponds to the 'bin'
- * for bin packing.
- * \param first_bin_only This parameter controls whether to place the
- * remaining items which do not fit onto the print area next to the print
- * bed or leave them untouched (let the user arrange them by hand or remove
- * them).
- */
-bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
- bool first_bin_only,
- std::function<void(unsigned)> progressind)
-{
- using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
-
- bool ret = true;
-
- // Create the arranger config
- auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
-
- // Get the 2D projected shapes with their 3D model instance pointers
- auto shapemap = arr::projectModelFromTop(model);
-
- bool hasbin = bb != nullptr && bb->defined;
- double area_max = 0;
-
- // Copy the references for the shapes only as the arranger expects a
- // sequence of objects convertible to Item or ClipperPolygon
- std::vector<std::reference_wrapper<Item>> shapes;
- shapes.reserve(shapemap.size());
- std::for_each(shapemap.begin(), shapemap.end(),
- [&shapes, min_obj_distance, &area_max, hasbin]
- (ShapeData2D::value_type& it)
- {
- shapes.push_back(std::ref(it.second));
- });
-
- Box bin;
-
- if(hasbin) {
- // Scale up the bounding box to clipper scale.
- BoundingBoxf bbb = *bb;
- bbb.scale(1.0/SCALING_FACTOR);
-
- bin = Box({
- static_cast<libnest2d::Coord>(bbb.min.x),
- static_cast<libnest2d::Coord>(bbb.min.y)
- },
- {
- static_cast<libnest2d::Coord>(bbb.max.x),
- static_cast<libnest2d::Coord>(bbb.max.y)
- });
- }
-
- // Will use the DJD selection heuristic with the BottomLeft placement
- // strategy
- using Arranger = Arranger<NfpPlacer, FirstFitSelection>;
- using PConf = Arranger::PlacementConfig;
- using SConf = Arranger::SelectionConfig;
-
- PConf pcfg; // Placement configuration
- SConf scfg; // Selection configuration
-
- // Align the arranged pile into the center of the bin
- pcfg.alignment = PConf::Alignment::CENTER;
-
- // Start placing the items from the center of the print bed
- pcfg.starting_point = PConf::Alignment::CENTER;
-
- // TODO cannot use rotations until multiple objects of same geometry can
- // handle different rotations
- // arranger.useMinimumBoundigBoxRotation();
- pcfg.rotations = { 0.0 };
-
- // Magic: we will specify what is the goal of arrangement... In this case
- // we override the default object function to make the larger items go into
- // the center of the pile and smaller items orbit it so the resulting pile
- // has a circle-like shape. This is good for the print bed's heat profile.
- // We alse sacrafice a bit of pack efficiency for this to work. As a side
- // effect, the arrange procedure is a lot faster (we do not need to
- // calculate the convex hulls)
- pcfg.object_function = [bin, hasbin](
- NfpPlacer::Pile pile, // The currently arranged pile
- double /*area*/, // Sum area of items (not needed)
- double norm, // A norming factor for physical dimensions
- double penality) // Min penality in case of bad arrangement
- {
- auto bb = ShapeLike::boundingBox(pile);
-
- // We get the current item that's being evaluated.
- auto& sh = pile.back();
-
- // We retrieve the reference point of this item
- auto rv = Nfp::referenceVertex(sh);
-
- // We get the distance of the reference point from the center of the
- // heat bed
- auto c = bin.center();
- auto d = PointLike::distance(rv, c);
-
- // The score will be the normalized distance which will be minimized,
- // effectively creating a circle shaped pile of items
- double score = double(d)/norm;
-
- // If it does not fit into the print bed we will beat it
- // with a large penality. If we would not do this, there would be only
- // one big pile that doesn't care whether it fits onto the print bed.
- if(hasbin && !NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
-
- return score;
- };
-
- // Create the arranger object
- Arranger arranger(bin, min_obj_distance, pcfg, scfg);
-
- // Set the progress indicator for the arranger.
- arranger.progressIndicator(progressind);
-
- // Arrange and return the items with their respective indices within the
- // input sequence.
- auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end());
-
- auto applyResult = [&shapemap](ArrangeResult::value_type& group,
- Coord batch_offset)
- {
- for(auto& r : group) {
- auto idx = r.first; // get the original item index
- Item& item = r.second; // get the item itself
-
- // Get the model instance from the shapemap using the index
- ModelInstance *inst_ptr = shapemap[idx].first;
-
- // Get the tranformation data from the item object and scale it
- // appropriately
- auto off = item.translation();
- Radians rot = item.rotation();
- Pointf foff(off.X*SCALING_FACTOR + batch_offset,
- off.Y*SCALING_FACTOR);
-
- // write the tranformation data into the model instance
- inst_ptr->rotation = rot;
- inst_ptr->offset = foff;
- }
- };
-
- if(first_bin_only) {
- applyResult(result.front(), 0);
- } else {
-
- const auto STRIDE_PADDING = 1.2;
-
- Coord stride = static_cast<Coord>(STRIDE_PADDING*
- bin.width()*SCALING_FACTOR);
- Coord batch_offset = 0;
-
- for(auto& group : result) {
- applyResult(group, batch_offset);
-
- // Only the first pack group can be placed onto the print bed. The
- // other objects which could not fit will be placed next to the
- // print bed
- batch_offset += stride;
- }
- }
-
- for(auto objptr : model.objects) objptr->invalidate_bounding_box();
-
- return ret && result.size() == 1;
-}
-}
-
/* arrange objects preserving their instance count
but altering their instance positions */
-bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb,
- std::function<void(unsigned)> progressind)
-{
- bool ret = false;
- if(bb != nullptr && bb->defined) {
- // Despite the new arrange is able to run without a specified bin,
- // the perl testsuit still fails for this case. For now the safest
- // thing to do is to use the new arrange only when a proper bin is
- // specified.
- ret = arr::arrange(*this, dist, bb, false, progressind);
- } else {
- // get the (transformed) size of each instance so that we take
- // into account their different transformations when packing
- Pointfs instance_sizes;
- Pointfs instance_centers;
- for (const ModelObject *o : this->objects)
- for (size_t i = 0; i < o->instances.size(); ++ i) {
- // an accurate snug bounding box around the transformed mesh.
- BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
- instance_sizes.push_back(bbox.size());
- instance_centers.push_back(bbox.center());
- }
+bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
+{
+ // get the (transformed) size of each instance so that we take
+ // into account their different transformations when packing
+ Pointfs instance_sizes;
+ Pointfs instance_centers;
+ for (const ModelObject *o : this->objects)
+ for (size_t i = 0; i < o->instances.size(); ++ i) {
+ // an accurate snug bounding box around the transformed mesh.
+ BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
+ instance_sizes.push_back(bbox.size());
+ instance_centers.push_back(bbox.center());
+ }
- Pointfs positions;
- if (! _arrange(instance_sizes, dist, bb, positions))
- return false;
+ Pointfs positions;
+ if (! _arrange(instance_sizes, dist, bb, positions))
+ return false;
- size_t idx = 0;
- for (ModelObject *o : this->objects) {
- for (ModelInstance *i : o->instances) {
- i->offset = positions[idx] - instance_centers[idx];
- ++ idx;
- }
- o->invalidate_bounding_box();
+ size_t idx = 0;
+ for (ModelObject *o : this->objects) {
+ for (ModelInstance *i : o->instances) {
+ i->offset = positions[idx] - instance_centers[idx];
+ ++ idx;
}
+ o->invalidate_bounding_box();
}
- return ret;
+ return true;
}
// Duplicate the entire model preserving instance relative positions.
@@ -1109,9 +771,23 @@ void ModelObject::scale(const Pointf3 &versor)
void ModelObject::rotate(float angle, const Axis &axis)
{
+ float min_z = FLT_MAX;
for (ModelVolume *v : this->volumes)
+ {
v->mesh.rotate(angle, axis);
- this->origin_translation = Pointf3(0,0,0);
+ min_z = std::min(min_z, v->mesh.stl.stats.min.z);
+ }
+
+ if (min_z != 0.0f)
+ {
+ // translate the object so that its minimum z lays on the bed
+ for (ModelVolume *v : this->volumes)
+ {
+ v->mesh.translate(0.0f, 0.0f, -min_z);
+ }
+ }
+
+ this->origin_translation = Pointf3(0, 0, 0);
this->invalidate_bounding_box();
}
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index f5e97fb6a..4c650f0de 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -290,8 +290,7 @@ public:
void center_instances_around_point(const Pointf &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
- bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL,
- std::function<void(unsigned)> progressind = [](unsigned){});
+ bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
// Croaks if the duplicated objects do not fit the print bed.
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp
new file mode 100644
index 000000000..f2d399ac6
--- /dev/null
+++ b/xs/src/libslic3r/ModelArrange.hpp
@@ -0,0 +1,597 @@
+#ifndef MODELARRANGE_HPP
+#define MODELARRANGE_HPP
+
+#include "Model.hpp"
+#include "SVG.hpp"
+#include <libnest2d.h>
+
+#include <numeric>
+#include <ClipperUtils.hpp>
+
+#include <boost/geometry/index/rtree.hpp>
+
+namespace Slic3r {
+namespace arr {
+
+using namespace libnest2d;
+
+std::string toString(const Model& model, bool holes = true) {
+ std::stringstream ss;
+
+ ss << "{\n";
+
+ for(auto objptr : model.objects) {
+ if(!objptr) continue;
+
+ auto rmesh = objptr->raw_mesh();
+
+ for(auto objinst : objptr->instances) {
+ if(!objinst) continue;
+
+ Slic3r::TriangleMesh tmpmesh = rmesh;
+ tmpmesh.scale(objinst->scaling_factor);
+ objinst->transform_mesh(&tmpmesh);
+ ExPolygons expolys = tmpmesh.horizontal_projection();
+ for(auto& expoly_complex : expolys) {
+
+ auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
+ if(tmp.empty()) continue;
+ auto expoly = tmp.front();
+ expoly.contour.make_clockwise();
+ for(auto& h : expoly.holes) h.make_counter_clockwise();
+
+ ss << "\t{\n";
+ ss << "\t\t{\n";
+
+ for(auto v : expoly.contour.points) ss << "\t\t\t{"
+ << v.x << ", "
+ << v.y << "},\n";
+ {
+ auto v = expoly.contour.points.front();
+ ss << "\t\t\t{" << v.x << ", " << v.y << "},\n";
+ }
+ ss << "\t\t},\n";
+
+ // Holes:
+ ss << "\t\t{\n";
+ if(holes) for(auto h : expoly.holes) {
+ ss << "\t\t\t{\n";
+ for(auto v : h.points) ss << "\t\t\t\t{"
+ << v.x << ", "
+ << v.y << "},\n";
+ {
+ auto v = h.points.front();
+ ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\n";
+ }
+ ss << "\t\t\t},\n";
+ }
+ ss << "\t\t},\n";
+
+ ss << "\t},\n";
+ }
+ }
+ }
+
+ ss << "}\n";
+
+ return ss.str();
+}
+
+void toSVG(SVG& svg, const Model& model) {
+ for(auto objptr : model.objects) {
+ if(!objptr) continue;
+
+ auto rmesh = objptr->raw_mesh();
+
+ for(auto objinst : objptr->instances) {
+ if(!objinst) continue;
+
+ Slic3r::TriangleMesh tmpmesh = rmesh;
+ tmpmesh.scale(objinst->scaling_factor);
+ objinst->transform_mesh(&tmpmesh);
+ ExPolygons expolys = tmpmesh.horizontal_projection();
+ svg.draw(expolys);
+ }
+ }
+}
+
+namespace bgi = boost::geometry::index;
+
+using SpatElement = std::pair<Box, unsigned>;
+using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
+
+std::tuple<double /*score*/, Box /*farthest point from bin center*/>
+objfunc(const PointImpl& bincenter,
+ double /*bin_area*/,
+ ShapeLike::Shapes<PolygonImpl>& pile, // The currently arranged pile
+ double /*pile_area*/,
+ const Item &item,
+ double norm, // A norming factor for physical dimensions
+ std::vector<double>& areacache, // pile item areas will be cached
+ // a spatial index to quickly get neighbors of the candidate item
+ SpatIndex& spatindex
+ )
+{
+ using pl = PointLike;
+ using sl = ShapeLike;
+
+ static const double BIG_ITEM_TRESHOLD = 0.2;
+ static const double ROUNDNESS_RATIO = 0.5;
+ static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
+
+ // We will treat big items (compared to the print bed) differently
+ auto normarea = [norm](double area) { return std::sqrt(area)/norm; };
+
+ // If a new bin has been created:
+ if(pile.size() < areacache.size()) {
+ areacache.clear();
+ spatindex.clear();
+ }
+
+ // We must fill the caches:
+ int idx = 0;
+ for(auto& p : pile) {
+ if(idx == areacache.size()) {
+ areacache.emplace_back(sl::area(p));
+ if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD)
+ spatindex.insert({sl::boundingBox(p), idx});
+ }
+
+ idx++;
+ }
+
+ // Candidate item bounding box
+ auto ibb = item.boundingBox();
+
+ // Calculate the full bounding box of the pile with the candidate item
+ pile.emplace_back(item.transformedShape());
+ auto fullbb = ShapeLike::boundingBox(pile);
+ pile.pop_back();
+
+ // The bounding box of the big items (they will accumulate in the center
+ // of the pile
+ Box bigbb;
+ if(spatindex.empty()) bigbb = fullbb;
+ else {
+ auto boostbb = spatindex.bounds();
+ boost::geometry::convert(boostbb, bigbb);
+ }
+
+ // The size indicator of the candidate item. This is not the area,
+ // but almost...
+ double item_normarea = normarea(item.area());
+
+ // Will hold the resulting score
+ double score = 0;
+
+ if(item_normarea > BIG_ITEM_TRESHOLD) {
+ // This branch is for the bigger items..
+ // Here we will use the closest point of the item bounding box to
+ // the already arranged pile. So not the bb center nor the a choosen
+ // corner but whichever is the closest to the center. This will
+ // prevent some unwanted strange arrangements.
+
+ auto minc = ibb.minCorner(); // bottom left corner
+ auto maxc = ibb.maxCorner(); // top right corner
+
+ // top left and bottom right corners
+ auto top_left = PointImpl{getX(minc), getY(maxc)};
+ auto bottom_right = PointImpl{getX(maxc), getY(minc)};
+
+ // Now the distance of the gravity center will be calculated to the
+ // five anchor points and the smallest will be chosen.
+ std::array<double, 5> dists;
+ auto cc = fullbb.center(); // The gravity center
+ dists[0] = pl::distance(minc, cc);
+ dists[1] = pl::distance(maxc, cc);
+ dists[2] = pl::distance(ibb.center(), cc);
+ dists[3] = pl::distance(top_left, cc);
+ dists[4] = pl::distance(bottom_right, cc);
+
+ // The smalles distance from the arranged pile center:
+ auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
+
+ // Density is the pack density: how big is the arranged pile
+ auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
+
+ // Prepare a variable for the alignment score.
+ // This will indicate: how well is the candidate item aligned with
+ // its neighbors. We will check the aligment with all neighbors and
+ // return the score for the best alignment. So it is enough for the
+ // candidate to be aligned with only one item.
+ auto alignment_score = std::numeric_limits<double>::max();
+
+ auto& trsh = item.transformedShape();
+
+ auto querybb = item.boundingBox();
+
+ // Query the spatial index for the neigbours
+ std::vector<SpatElement> result;
+ spatindex.query(bgi::intersects(querybb), std::back_inserter(result));
+
+ for(auto& e : result) { // now get the score for the best alignment
+ auto idx = e.second;
+ auto& p = pile[idx];
+ auto parea = areacache[idx];
+ auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{p, trsh});
+ auto bbarea = bb.area();
+ auto ascore = 1.0 - (item.area() + parea)/bbarea;
+
+ if(ascore < alignment_score) alignment_score = ascore;
+ }
+
+ // The final mix of the score is the balance between the distance
+ // from the full pile center, the pack density and the
+ // alignment with the neigbours
+ auto C = 0.33;
+ score = C * dist + C * density + C * alignment_score;
+
+ } else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) {
+ // If there are no big items, only small, we should consider the
+ // density here as well to not get silly results
+ auto bindist = pl::distance(ibb.center(), bincenter) / norm;
+ auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
+ score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density;
+ } else {
+ // Here there are the small items that should be placed around the
+ // already processed bigger items.
+ // No need to play around with the anchor points, the center will be
+ // just fine for small items
+ score = pl::distance(ibb.center(), bigbb.center()) / norm;
+ }
+
+ return std::make_tuple(score, fullbb);
+}
+
+template<class PConf>
+void fillConfig(PConf& pcfg) {
+
+ // Align the arranged pile into the center of the bin
+ pcfg.alignment = PConf::Alignment::CENTER;
+
+ // Start placing the items from the center of the print bed
+ pcfg.starting_point = PConf::Alignment::CENTER;
+
+ // TODO cannot use rotations until multiple objects of same geometry can
+ // handle different rotations
+ // arranger.useMinimumBoundigBoxRotation();
+ pcfg.rotations = { 0.0 };
+
+ // The accuracy of optimization.
+ // Goes from 0.0 to 1.0 and scales performance as well
+ pcfg.accuracy = 0.6f;
+}
+
+template<class TBin>
+class AutoArranger {};
+
+template<class TBin>
+class _ArrBase {
+protected:
+ using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>;
+ using Selector = FirstFitSelection;
+ using Packer = Arranger<Placer, Selector>;
+ using PConfig = typename Packer::PlacementConfig;
+ using Distance = TCoord<PointImpl>;
+ using Pile = ShapeLike::Shapes<PolygonImpl>;
+
+ Packer pck_;
+ PConfig pconf_; // Placement configuration
+ double bin_area_;
+ std::vector<double> areacache_;
+ SpatIndex rtree_;
+public:
+
+ _ArrBase(const TBin& bin, Distance dist,
+ std::function<void(unsigned)> progressind):
+ pck_(bin, dist), bin_area_(ShapeLike::area<PolygonImpl>(bin))
+ {
+ fillConfig(pconf_);
+ pck_.progressIndicator(progressind);
+ }
+
+ template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
+ areacache_.clear();
+ return pck_.arrangeIndexed(std::forward<Args>(args)...);
+ }
+};
+
+template<>
+class AutoArranger<Box>: public _ArrBase<Box> {
+public:
+
+ AutoArranger(const Box& bin, Distance dist,
+ std::function<void(unsigned)> progressind):
+ _ArrBase<Box>(bin, dist, progressind)
+ {
+ pconf_.object_function = [this, bin] (
+ Pile& pile,
+ const Item &item,
+ double pile_area,
+ double norm,
+ double /*penality*/) {
+
+ auto result = objfunc(bin.center(), bin_area_, pile,
+ pile_area, item, norm, areacache_, rtree_);
+ double score = std::get<0>(result);
+ auto& fullbb = std::get<1>(result);
+
+ auto wdiff = fullbb.width() - bin.width();
+ auto hdiff = fullbb.height() - bin.height();
+ if(wdiff > 0) score += std::pow(wdiff, 2) / norm;
+ if(hdiff > 0) score += std::pow(hdiff, 2) / norm;
+
+ return score;
+ };
+
+ pck_.configure(pconf_);
+ }
+};
+
+template<>
+class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
+public:
+ AutoArranger(const PolygonImpl& bin, Distance dist,
+ std::function<void(unsigned)> progressind):
+ _ArrBase<PolygonImpl>(bin, dist, progressind)
+ {
+ pconf_.object_function = [this, &bin] (
+ Pile& pile,
+ const Item &item,
+ double pile_area,
+ double norm,
+ double /*penality*/) {
+
+ auto binbb = ShapeLike::boundingBox(bin);
+ auto result = objfunc(binbb.center(), bin_area_, pile,
+ pile_area, item, norm, areacache_, rtree_);
+ double score = std::get<0>(result);
+
+ pile.emplace_back(item.transformedShape());
+ auto chull = ShapeLike::convexHull(pile);
+ pile.pop_back();
+
+ // If it does not fit into the print bed we will beat it with a
+ // large penality. If we would not do this, there would be only one
+ // big pile that doesn't care whether it fits onto the print bed.
+ if(!Placer::wouldFit(chull, bin)) score += norm;
+
+ return score;
+ };
+
+ pck_.configure(pconf_);
+ }
+};
+
+template<> // Specialization with no bin
+class AutoArranger<bool>: public _ArrBase<Box> {
+public:
+
+ AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
+ _ArrBase<Box>(Box(0, 0), dist, progressind)
+ {
+ this->pconf_.object_function = [this] (
+ Pile& pile,
+ const Item &item,
+ double pile_area,
+ double norm,
+ double /*penality*/) {
+
+ auto result = objfunc({0, 0}, 0, pile, pile_area,
+ item, norm, areacache_, rtree_);
+ return std::get<0>(result);
+ };
+
+ this->pck_.configure(pconf_);
+ }
+};
+
+// A container which stores a pointer to the 3D object and its projected
+// 2D shape from top view.
+using ShapeData2D =
+ std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
+
+ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
+ ShapeData2D ret;
+
+ auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
+ [](size_t s, ModelObject* o){
+ return s + o->instances.size();
+ });
+
+ ret.reserve(s);
+
+ for(auto objptr : model.objects) {
+ if(objptr) {
+
+ auto rmesh = objptr->raw_mesh();
+
+ for(auto objinst : objptr->instances) {
+ if(objinst) {
+ Slic3r::TriangleMesh tmpmesh = rmesh;
+ ClipperLib::PolygonImpl pn;
+
+ tmpmesh.scale(objinst->scaling_factor);
+
+ // TODO export the exact 2D projection
+ auto p = tmpmesh.convex_hull();
+
+ p.make_clockwise();
+ p.append(p.first_point());
+ pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
+
+ // Efficient conversion to item.
+ Item item(std::move(pn));
+
+ // Invalid geometries would throw exceptions when arranging
+ if(item.vertexCount() > 3) {
+ item.rotation(objinst->rotation);
+ item.translation( {
+ ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR),
+ ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR)
+ });
+ ret.emplace_back(objinst, item);
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+enum BedShapeHint {
+ BOX,
+ CIRCLE,
+ IRREGULAR,
+ WHO_KNOWS
+};
+
+BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) {
+ // Determine the bed shape by hand
+ return BOX;
+}
+
+void applyResult(
+ IndexedPackGroup::value_type& group,
+ Coord batch_offset,
+ ShapeData2D& shapemap)
+{
+ for(auto& r : group) {
+ auto idx = r.first; // get the original item index
+ Item& item = r.second; // get the item itself
+
+ // Get the model instance from the shapemap using the index
+ ModelInstance *inst_ptr = shapemap[idx].first;
+
+ // Get the tranformation data from the item object and scale it
+ // appropriately
+ auto off = item.translation();
+ Radians rot = item.rotation();
+ Pointf foff(off.X*SCALING_FACTOR + batch_offset,
+ off.Y*SCALING_FACTOR);
+
+ // write the tranformation data into the model instance
+ inst_ptr->rotation = rot;
+ inst_ptr->offset = foff;
+ }
+}
+
+
+/**
+ * \brief Arranges the model objects on the screen.
+ *
+ * The arrangement considers multiple bins (aka. print beds) for placing all
+ * the items provided in the model argument. If the items don't fit on one
+ * print bed, the remaining will be placed onto newly created print beds.
+ * The first_bin_only parameter, if set to true, disables this behaviour and
+ * makes sure that only one print bed is filled and the remaining items will be
+ * untouched. When set to false, the items which could not fit onto the
+ * print bed will be placed next to the print bed so the user should see a
+ * pile of items on the print bed and some other piles outside the print
+ * area that can be dragged later onto the print bed as a group.
+ *
+ * \param model The model object with the 3D content.
+ * \param dist The minimum distance which is allowed for any pair of items
+ * on the print bed in any direction.
+ * \param bb The bounding box of the print bed. It corresponds to the 'bin'
+ * for bin packing.
+ * \param first_bin_only This parameter controls whether to place the
+ * remaining items which do not fit onto the print area next to the print
+ * bed or leave them untouched (let the user arrange them by hand or remove
+ * them).
+ */
+bool arrange(Model &model, coordf_t min_obj_distance,
+ const Slic3r::Polyline& bed,
+ BedShapeHint bedhint,
+ bool first_bin_only,
+ std::function<void(unsigned)> progressind)
+{
+ using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
+
+ bool ret = true;
+
+ // Get the 2D projected shapes with their 3D model instance pointers
+ auto shapemap = arr::projectModelFromTop(model);
+
+ // Copy the references for the shapes only as the arranger expects a
+ // sequence of objects convertible to Item or ClipperPolygon
+ std::vector<std::reference_wrapper<Item>> shapes;
+ shapes.reserve(shapemap.size());
+ std::for_each(shapemap.begin(), shapemap.end(),
+ [&shapes] (ShapeData2D::value_type& it)
+ {
+ shapes.push_back(std::ref(it.second));
+ });
+
+ IndexedPackGroup result;
+ BoundingBox bbb(bed.points);
+
+ auto binbb = Box({
+ static_cast<libnest2d::Coord>(bbb.min.x),
+ static_cast<libnest2d::Coord>(bbb.min.y)
+ },
+ {
+ static_cast<libnest2d::Coord>(bbb.max.x),
+ static_cast<libnest2d::Coord>(bbb.max.y)
+ });
+
+ switch(bedhint) {
+ case BOX: {
+
+ // Create the arranger for the box shaped bed
+ AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
+
+ // Arrange and return the items with their respective indices within the
+ // input sequence.
+ result = arrange(shapes.begin(), shapes.end());
+ break;
+ }
+ case CIRCLE:
+ break;
+ case IRREGULAR:
+ case WHO_KNOWS: {
+ using P = libnest2d::PolygonImpl;
+
+ auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
+ P irrbed = ShapeLike::create<PolygonImpl>(std::move(ctour));
+
+// std::cout << ShapeLike::toString(irrbed) << std::endl;
+
+ AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
+
+ // Arrange and return the items with their respective indices within the
+ // input sequence.
+ result = arrange(shapes.begin(), shapes.end());
+ break;
+ }
+ };
+
+ if(first_bin_only) {
+ applyResult(result.front(), 0, shapemap);
+ } else {
+
+ const auto STRIDE_PADDING = 1.2;
+
+ Coord stride = static_cast<Coord>(STRIDE_PADDING*
+ binbb.width()*SCALING_FACTOR);
+ Coord batch_offset = 0;
+
+ for(auto& group : result) {
+ applyResult(group, batch_offset, shapemap);
+
+ // Only the first pack group can be placed onto the print bed. The
+ // other objects which could not fit will be placed next to the
+ // print bed
+ batch_offset += stride;
+ }
+ }
+
+ for(auto objptr : model.objects) objptr->invalidate_bounding_box();
+
+ return ret && result.size() == 1;
+}
+
+}
+}
+#endif // MODELARRANGE_HPP
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index e08ae1fc4..ab5d13950 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -155,6 +155,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"retract_restart_extra",
"retract_restart_extra_toolchange",
"retract_speed",
+ "single_extruder_multi_material_priming",
"slowdown_below_layer_time",
"standby_temperature_delta",
"start_gcode",
@@ -166,7 +167,10 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"use_relative_e_distances",
"use_volumetric_e",
"variable_layer_height",
- "wipe"
+ "wipe",
+ "wipe_tower_x",
+ "wipe_tower_y",
+ "wipe_tower_rotation_angle"
};
std::vector<PrintStep> steps;
@@ -175,7 +179,12 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
// Always invalidate the wipe tower. This is probably necessary because of the wipe_into_infill / wipe_into_objects
// features - nearly anything can influence what should (and could) be wiped into.
- steps.emplace_back(psWipeTower);
+ // Only these three parameters don't invalidate the wipe tower (they only affect the gcode export):
+ for (const t_config_option_key &opt_key : opt_keys)
+ if (opt_key != "wipe_tower_x" && opt_key != "wipe_tower_y" && opt_key != "wipe_tower_rotation_angle") {
+ steps.emplace_back(psWipeTower);
+ break;
+ }
for (const t_config_option_key &opt_key : opt_keys) {
if (steps_ignore.find(opt_key) != steps_ignore.end()) {
@@ -204,6 +213,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "filament_unloading_speed"
|| opt_key == "filament_toolchange_delay"
|| opt_key == "filament_cooling_moves"
+ || opt_key == "filament_minimal_purge_on_wipe_tower"
|| opt_key == "filament_cooling_initial_speed"
|| opt_key == "filament_cooling_final_speed"
|| opt_key == "filament_ramming_parameters"
@@ -212,10 +222,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "spiral_vase"
|| opt_key == "temperature"
|| opt_key == "wipe_tower"
- || opt_key == "wipe_tower_x"
- || opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_width"
- || opt_key == "wipe_tower_rotation_angle"
|| opt_key == "wipe_tower_bridging"
|| opt_key == "wiping_volumes_matrix"
|| opt_key == "parking_pos_retraction"
@@ -1051,6 +1058,8 @@ void Print::_make_wipe_tower()
if (! this->has_wipe_tower())
return;
+ m_wipe_tower_depth = 0.f;
+
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
std::vector<float> wiping_matrix((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end());
// Extract purging volumes for each extruder pair:
@@ -1144,12 +1153,19 @@ void Print::_make_wipe_tower()
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
for (const auto extruder_id : layer_tools.extruders) {
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
- float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
+ float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
+ // Not all of that can be used for infill purging:
+ volume_to_wipe -= config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
// try to assign some infills/objects for the wiping:
- volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, wipe_volumes[current_extruder_id][extruder_id]);
+ volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
- wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_tool_ordering.all_extruders().back(), volume_to_wipe);
+ // add back the minimal amount toforce on the wipe tower:
+ volume_to_wipe += config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
+
+ // request a toolchange at the wipe tower with at least volume_to_wipe purging amount
+ wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
+ first_layer && extruder_id == m_tool_ordering.all_extruders().back(), volume_to_wipe);
current_extruder_id = extruder_id;
}
}
@@ -1162,7 +1178,8 @@ void Print::_make_wipe_tower()
// Generate the wipe tower layers.
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
wipe_tower.generate(m_wipe_tower_tool_changes);
-
+ m_wipe_tower_depth = wipe_tower.get_depth();
+
// Unload the current filament over the purge tower.
coordf_t layer_height = this->objects.front()->config.layer_height.value;
if (m_tool_ordering.back().wipe_tower_partitions > 0) {
@@ -1183,10 +1200,6 @@ void Print::_make_wipe_tower()
wipe_tower.tool_change((unsigned int)-1, false));
}
-
-
-
-
std::string Print::output_filename()
{
this->placeholder_parser.update_timestamp();
@@ -1225,7 +1238,6 @@ void Print::set_status(int percent, const std::string &message)
printf("Print::status %d => %s\n", percent, message.c_str());
}
-
// Returns extruder this eec should be printed with, according to PrintRegion config
int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region)
{
@@ -1233,5 +1245,4 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion
std::max<int>(region.config.perimeter_extruder.value - 1, 0);
}
-
}
diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
index bcd61ea02..e3430ad0e 100644
--- a/xs/src/libslic3r/Print.hpp
+++ b/xs/src/libslic3r/Print.hpp
@@ -273,6 +273,7 @@ public:
void add_model_object(ModelObject* model_object, int idx = -1);
bool apply_config(DynamicPrintConfig config);
+ float get_wipe_tower_depth() const { return m_wipe_tower_depth; }
bool has_infinite_skirt() const;
bool has_skirt() const;
// Returns an empty string if valid, otherwise returns an error message.
@@ -326,6 +327,9 @@ private:
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
+ // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
+ float m_wipe_tower_depth = 0.f;
+
// Has the calculation been canceled?
tbb::atomic<bool> m_canceled;
};
diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
index 01d0a7380..1035951eb 100644
--- a/xs/src/libslic3r/PrintConfig.cpp
+++ b/xs/src/libslic3r/PrintConfig.cpp
@@ -504,15 +504,27 @@ PrintConfigDef::PrintConfigDef()
def = this->add("filament_cooling_initial_speed", coFloats);
def->label = L("Speed of the first cooling move");
def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. ");
- def->cli = "filament-cooling-initial-speed=i@";
+ def->cli = "filament-cooling-initial-speed=f@";
def->sidetext = L("mm/s");
def->min = 0;
def->default_value = new ConfigOptionFloats { 2.2f };
+ def = this->add("filament_minimal_purge_on_wipe_tower", coFloats);
+ def->label = L("Minimal purge on wipe tower");
+ def->tooltip = L("After a toolchange, certain amount of filament is used for purging. This "
+ "can end up on the wipe tower, infill or sacrificial object. If there was "
+ "enough infill etc. available, this could result in bad quality at the beginning "
+ "of purging. This is a minimum that must be wiped on the wipe tower before "
+ "Slic3r considers moving elsewhere. ");
+ def->cli = "filament-minimal-purge-on-wipe-tower=f@";
+ def->sidetext = L("mm³");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 5.f };
+
def = this->add("filament_cooling_final_speed", coFloats);
def->label = L("Speed of the last cooling move");
def->tooltip = L("Cooling moves are gradually accelerating towards this speed. ");
- def->cli = "filament-cooling-final-speed=i@";
+ def->cli = "filament-cooling-final-speed=f@";
def->sidetext = L("mm/s");
def->min = 0;
def->default_value = new ConfigOptionFloats { 3.4f };
@@ -1639,6 +1651,12 @@ PrintConfigDef::PrintConfigDef()
def->cli = "single-extruder-multi-material!";
def->default_value = new ConfigOptionBool(false);
+ def = this->add("single_extruder_multi_material_priming", coBool);
+ def->label = L("Prime all printing extruders");
+ def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print.");
+ def->cli = "single-extruder-multi-material-priming!";
+ def->default_value = new ConfigOptionBool(true);
+
def = this->add("support_material", coBool);
def->label = L("Generate support material");
def->category = L("Support material");
diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
index 0a5f70f78..602f1f0aa 100644
--- a/xs/src/libslic3r/PrintConfig.hpp
+++ b/xs/src/libslic3r/PrintConfig.hpp
@@ -534,6 +534,7 @@ public:
ConfigOptionFloats filament_unload_time;
ConfigOptionInts filament_cooling_moves;
ConfigOptionFloats filament_cooling_initial_speed;
+ ConfigOptionFloats filament_minimal_purge_on_wipe_tower;
ConfigOptionFloats filament_cooling_final_speed;
ConfigOptionStrings filament_ramming_parameters;
ConfigOptionBool gcode_comments;
@@ -555,6 +556,7 @@ public:
ConfigOptionString start_gcode;
ConfigOptionStrings start_filament_gcode;
ConfigOptionBool single_extruder_multi_material;
+ ConfigOptionBool single_extruder_multi_material_priming;
ConfigOptionString toolchange_gcode;
ConfigOptionFloat travel_speed;
ConfigOptionBool use_firmware_retraction;
@@ -597,6 +599,7 @@ protected:
OPT_PTR(filament_toolchange_delay);
OPT_PTR(filament_cooling_moves);
OPT_PTR(filament_cooling_initial_speed);
+ OPT_PTR(filament_minimal_purge_on_wipe_tower);
OPT_PTR(filament_cooling_final_speed);
OPT_PTR(filament_ramming_parameters);
OPT_PTR(gcode_comments);
@@ -616,6 +619,7 @@ protected:
OPT_PTR(retract_restart_extra_toolchange);
OPT_PTR(retract_speed);
OPT_PTR(single_extruder_multi_material);
+ OPT_PTR(single_extruder_multi_material_priming);
OPT_PTR(start_gcode);
OPT_PTR(start_filament_gcode);
OPT_PTR(toolchange_gcode);
diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
index 47495dad8..7150ead59 100644
--- a/xs/src/libslic3r/PrintObject.cpp
+++ b/xs/src/libslic3r/PrintObject.cpp
@@ -75,6 +75,7 @@ bool PrintObject::delete_last_copy()
bool PrintObject::set_copies(const Points &points)
{
+ bool copies_num_changed = this->_copies.size() != points.size();
this->_copies = points;
// order copies with a nearest neighbor search and translate them by _copies_shift
@@ -93,7 +94,8 @@ bool PrintObject::set_copies(const Points &points)
bool invalidated = this->_print->invalidate_step(psSkirt);
invalidated |= this->_print->invalidate_step(psBrim);
- invalidated |= this->_print->invalidate_step(psWipeTower);
+ if (copies_num_changed)
+ invalidated |= this->_print->invalidate_step(psWipeTower);
return invalidated;
}