diff options
author | bubnikv <bubnikv@gmail.com> | 2018-08-04 00:04:44 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2018-08-04 00:04:44 +0300 |
commit | ac2b20b54b03cc67dd776681f7405ed4a93924ab (patch) | |
tree | cdac5ef4170ae392977331768c9c9d661038c231 /xs/src/libslic3r | |
parent | fa6a72ab2d0f7f3096e714c0f696a9cc816ae770 (diff) | |
parent | 73ad49b9aedb4f568621d73d30c0ace815cfe667 (diff) |
Merge branch 'master' into time_estimate
Diffstat (limited to 'xs/src/libslic3r')
-rw-r--r-- | xs/src/libslic3r/GCode.cpp | 143 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode.hpp | 12 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/PrintExtents.cpp | 10 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTower.hpp | 29 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 66 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 3 | ||||
-rw-r--r-- | xs/src/libslic3r/GCodeTimeEstimator.cpp | 22 | ||||
-rw-r--r-- | xs/src/libslic3r/Model.cpp | 400 | ||||
-rw-r--r-- | xs/src/libslic3r/Model.hpp | 3 | ||||
-rw-r--r-- | xs/src/libslic3r/ModelArrange.hpp | 597 | ||||
-rw-r--r-- | xs/src/libslic3r/Print.cpp | 41 | ||||
-rw-r--r-- | xs/src/libslic3r/Print.hpp | 4 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintConfig.cpp | 22 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintConfig.hpp | 4 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintObject.cpp | 4 |
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 ®ion) { @@ -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; } |