diff options
author | supermerill <merill@fr.fr> | 2018-09-05 18:27:30 +0300 |
---|---|---|
committer | supermerill <merill@fr.fr> | 2018-09-05 18:27:30 +0300 |
commit | 75e79189a2e18faebb23203e64856f7926055841 (patch) | |
tree | e4b2c4b0e69c38b4e1c9555c35b212c18af22640 /xs/src/libslic3r | |
parent | 107de68b03fd00abfaea570cb107f9559c708091 (diff) | |
parent | 82838cc640950a2505e77b0b5205f74cd86dfdf1 (diff) |
Merge remote-tracking branch 'remotes/prusa/master' into master_slic3rPE_PR
cleaning some tabs & wrong indentation at the same time.
Diffstat (limited to 'xs/src/libslic3r')
27 files changed, 1455 insertions, 666 deletions
diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index dd3500eba..945bb1f86 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -603,6 +603,8 @@ namespace Slic3r { if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr)) return false; + + object.second->center_around_origin(); } // fixes the min z of the model if negative @@ -1491,6 +1493,7 @@ namespace Slic3r { stl_get_size(&stl); volume->mesh.repair(); + volume->calculate_convex_hull(); // apply volume's name and config data for (const Metadata& metadata : volume_data.metadata) diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 600aa6cd9..886bbae97 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -406,6 +406,7 @@ void AMFParserContext::endElement(const char * /* name */) } stl_get_size(&stl); m_volume->mesh.repair(); + m_volume->calculate_convex_hull(); m_volume_facets.clear(); m_volume = nullptr; break; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 5ae97f8f7..62563ef25 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); @@ -377,10 +440,9 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ } fclose(file); - if (print->config.gcode_flavor.value == gcfMarlin) + if (print->config.remaining_times.value) { m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); - if (m_silent_time_estimator_enabled) m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); } @@ -462,8 +524,21 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config.machine_max_jerk_y.values[1]); m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config.machine_max_jerk_z.values[1]); m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config.machine_max_jerk_e.values[1]); + if (print.config.single_extruder_multi_material) { + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + m_silent_time_estimator.set_filament_load_times(print.config.filament_load_time.values); + m_silent_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); + } } } + // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. + if (print.config.single_extruder_multi_material) { + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + m_normal_time_estimator.set_filament_load_times(print.config.filament_load_time.values); + m_normal_time_estimator.set_filament_unload_times(print.config.filament_unload_time.values); + } // resets analyzer m_analyzer.reset(); @@ -513,7 +588,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // get the minimum cross-section used in the print std::vector<double> mm3_per_mm; for (auto object : printable_objects) { - for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { + for (size_t region_id = 0; region_id < print.regions.size(); ++region_id) { auto region = print.regions[region_id]; for (auto layer : object->layers) { auto layerm = layer->regions[region_id]; @@ -602,21 +677,24 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) size_t initial_print_object_id = 0; bool has_wipe_tower = false; if (print.config.complete_objects.value) { - // Find the 1st printing object, find its tool ordering and the initial extruder ID. + // Find the 1st printing object, find its tool ordering and the initial extruder ID. for (; initial_print_object_id < printable_objects.size(); ++initial_print_object_id) { tool_ordering = ToolOrdering(*printable_objects[initial_print_object_id], initial_extruder_id); - if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) - break; - } - } - else { - // Find tool ordering for all the objects at once, and the initial extruder ID. + if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) + break; + } + } 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() ? + 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 +722,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 +803,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 +885,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 +1087,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(), @@ -1331,10 +1416,10 @@ void GCode::process_layer( const ExtrusionEntitiesPtr& source_entities = entity_type=="infills" ? layerm->fills.entities : layerm->perimeters.entities; for (const ExtrusionEntity *ee : source_entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); + // fill represents infill extrusions of a single island. + const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee); if (fill->entities.empty()) // This shouldn't happen but first_point() would fail. - continue; + continue; // This extrusion is part of certain Region, which tells us which extruder should be used for it: int correct_extruder_id = Print::get_extruder(*fill, region); entity_type=="infills" ? std::max<int>(0, (is_solid_infill(fill->entities.front()->role()) ? region.config.solid_infill_extruder : region.config.infill_extruder) - 1) : @@ -1353,10 +1438,10 @@ void GCode::process_layer( //by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) { std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder( - by_extruder, - extruder, - &layer_to_print - layers.data(), - layers.size(), n_slices+1); + by_extruder, + extruder, + &layer_to_print - layers.data(), + layers.size(), n_slices+1); for (size_t i = 0; i <= n_slices; ++i) { if (// fill->first_point does not fit inside any slice i == n_slices || @@ -1408,7 +1493,7 @@ void GCode::process_layer( for (ExtrusionPath &path : loop.paths) { path.height = (float)layer.height; path.mm3_per_mm = mm3_per_mm; - } + } gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); } m_avoid_crossing_perimeters.use_external_mp = false; @@ -1458,7 +1543,7 @@ void GCode::process_layer( copies.push_back(print_object->_shifted_copies[single_object_idx]); //FIXME: Sort the copies by the closest point starting with the current print position. // be careful to set a good copy_idx (used to identify the copy). - + unsigned int copy_id = 0; for (const Point © : copies) { if (this->config().gcode_comments){ @@ -1528,7 +1613,7 @@ void GCode::append_full_config(const Print& print, std::string& str) for (const std::string &key : cfg->keys()) if (key != "compatible_printers") str += "; " + key + " = " + cfg->serialize(key) + "\n"; - } + } const DynamicConfig &full_config = print.placeholder_parser.config(); for (const char *key : { "print_settings_id", "filament_settings_id", "printer_settings_id", @@ -2141,8 +2226,8 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false); gcode += extrude_entity(chained, "infill"); } - } - return gcode; + } + return gcode; } std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) @@ -2598,7 +2683,7 @@ const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtru for (unsigned int i=0;i<entities.size();++i) if (overrides[i]->at(copy) == this_extruder_mark) // this copy should be printed with this extruder target_eec.append((*entities[i])); -} + } } return by_region_per_copy_cache; } diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index d1c3c8d56..b343b53de 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..42c06252b 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: @@ -793,11 +793,16 @@ void WipeTowerPrusaMM::toolchange_Unload( float turning_point = (!m_left_to_right ? xl : xr ); float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming writer.suppress_preview() - .load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed + .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s + .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) + + /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) - .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate + .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ .resume_preview(); if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait. @@ -874,10 +879,15 @@ void WipeTowerPrusaMM::toolchange_Load( writer.append("; CP TOOLCHANGE LOAD\n") .suppress_preview() - .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration + /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + + .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) + .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate .resume_preview(); @@ -950,7 +960,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 +979,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 +987,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 +1051,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 +1173,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 +1196,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..305dbc40a 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -65,9 +65,9 @@ public: // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, - float unloading_speed, float delay, int cooling_moves, float cooling_initial_speed, - float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) + void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, + float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, + float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) { //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); @@ -76,7 +76,9 @@ public: m_filpar[idx].temperature = temp; m_filpar[idx].first_layer_temperature = first_layer_temp; m_filpar[idx].loading_speed = loading_speed; + m_filpar[idx].loading_speed_start = loading_speed_start; m_filpar[idx].unloading_speed = unloading_speed; + m_filpar[idx].unloading_speed_start = unloading_speed_start; m_filpar[idx].delay = delay; m_filpar[idx].cooling_moves = cooling_moves; m_filpar[idx].cooling_initial_speed = cooling_initial_speed; @@ -102,6 +104,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 +193,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. @@ -213,7 +218,9 @@ private: int temperature = 0; int first_layer_temperature = 0; float loading_speed = 0.f; + float loading_speed_start = 0.f; float unloading_speed = 0.f; + float unloading_speed_start = 0.f; float delay = 0.f ; int cooling_moves = 0; float cooling_initial_speed = 0.f; diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp index 965b7ef8e..79b6ed970 100644 --- a/xs/src/libslic3r/GCodeReader.cpp +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -114,6 +114,28 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback) this->parse_line(line, callback); } +bool GCodeReader::GCodeLine::has(char axis) const +{ + const char *c = m_raw.c_str(); + // Skip the whitespaces. + c = skip_whitespaces(c); + // Skip the command. + c = skip_word(c); + // Up to the end of line or comment. + while (! is_end_of_gcode_line(*c)) { + // Skip whitespaces. + c = skip_whitespaces(c); + if (is_end_of_gcode_line(*c)) + break; + // Check the name of the axis. + if (*c == axis) + return true; + // Skip the rest of the word. + c = skip_word(c); + } + return false; +} + bool GCodeReader::GCodeLine::has_value(char axis, float &value) const { const char *c = m_raw.c_str(); diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp index 102cbd27a..84ed89a7c 100644 --- a/xs/src/libslic3r/GCodeReader.hpp +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -27,6 +27,7 @@ public: bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; } float value(Axis axis) const { return m_axis[axis]; } + bool has(char axis) const; bool has_value(char axis, float &value) const; float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 5543b5cc9..c4ffb572a 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -469,6 +469,40 @@ namespace Slic3r { return _state.minimum_travel_feedrate; } + void GCodeTimeEstimator::set_filament_load_times(const std::vector<double> &filament_load_times) + { + _state.filament_load_times.clear(); + for (double t : filament_load_times) + _state.filament_load_times.push_back(t); + } + + void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times) + { + _state.filament_unload_times.clear(); + for (double t : filament_unload_times) + _state.filament_unload_times.push_back(t); + } + + float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) + { + return + (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + 0 : + (_state.filament_load_times.size() <= id_extruder) ? + _state.filament_load_times.front() : + _state.filament_load_times[id_extruder]; + } + + float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) + { + return + (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + 0 : + (_state.filament_unload_times.size() <= id_extruder) ? + _state.filament_unload_times.front() : + _state.filament_unload_times[id_extruder]; + } + void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) { _state.extrude_factor_override_percentage = percentage; @@ -535,6 +569,23 @@ namespace Slic3r { _state.g1_line_id = 0; } + void GCodeTimeEstimator::set_extruder_id(unsigned int id) + { + _state.extruder_id = id; + } + + unsigned int GCodeTimeEstimator::get_extruder_id() const + { + return _state.extruder_id; + } + + void GCodeTimeEstimator::reset_extruder_id() + { + // Set the initial extruder ID to unknown. For the multi-material setup it means + // that all the filaments are parked in the MMU and no filament is loaded yet. + _state.extruder_id = _state.extruder_id_unloaded; + } + void GCodeTimeEstimator::add_additional_time(float timeSec) { PROFILE_FUNC(); @@ -575,6 +626,9 @@ namespace Slic3r { set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]); set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); } + + _state.filament_load_times.clear(); + _state.filament_unload_times.clear(); } void GCodeTimeEstimator::reset() @@ -613,6 +667,7 @@ namespace Slic3r { set_additional_time(0.0f); + reset_extruder_id(); reset_g1_line_id(); _g1_line_ids.clear(); @@ -666,6 +721,8 @@ namespace Slic3r { } _last_st_synchronized_block_id = _blocks.size() - 1; + // The additional time has been consumed (added to the total time), reset it to zero. + set_additional_time(0.); } void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) @@ -778,10 +835,20 @@ namespace Slic3r { _processM566(line); break; } + case 702: // MK3 MMU2: Process the final filament unload. + { + _processM702(line); + break; + } } break; } + case 'T': // Select Tools + { + _processT(line); + break; + } } } } @@ -1164,11 +1231,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) @@ -1223,6 +1304,37 @@ namespace Slic3r { set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } + void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + if (line.has('C')) { + // MK3 MMU2 specific M code: + // M702 C is expected to be sent by the custom end G-code when finalizing a print. + // The MK3 unit shall unload and park the active filament into the MMU2 unit. + add_additional_time(get_filament_unload_time(get_extruder_id())); + reset_extruder_id(); + _simulate_st_synchronize(); + } + } + + void GCodeTimeEstimator::_processT(const GCodeReader::GCodeLine& line) + { + std::string cmd = line.cmd(); + if (cmd.length() > 1) + { + unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); + if (get_extruder_id() != id) + { + // Specific to the MK3 MMU2: The initial extruder ID is set to -1 indicating + // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. + add_additional_time(get_filament_unload_time(get_extruder_id())); + set_extruder_id(id); + add_additional_time(get_filament_load_time(get_extruder_id())); + _simulate_st_synchronize(); + } + } + } + void GCodeTimeEstimator::_simulate_st_synchronize() { PROFILE_FUNC(); diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index 2dfefda0b..1fa74e304 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -79,7 +79,15 @@ namespace Slic3r { float minimum_feedrate; // mm/s float minimum_travel_feedrate; // mm/s float extrude_factor_override_percentage; + // Additional load / unload times for a filament exchange sequence. + std::vector<float> filament_load_times; + std::vector<float> filament_unload_times; unsigned int g1_line_id; + // extruder_id is currently used to correctly calculate filament load / unload times + // into the total print time. This is currently only really used by the MK3 MMU2: + // Extruder id (-1) means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. + static const unsigned int extruder_id_unloaded = (unsigned int)-1; + unsigned int extruder_id; }; public: @@ -281,6 +289,11 @@ namespace Slic3r { void set_minimum_travel_feedrate(float feedrate_mm_sec); float get_minimum_travel_feedrate() const; + void set_filament_load_times(const std::vector<double> &filament_load_times); + void set_filament_unload_times(const std::vector<double> &filament_unload_times); + float get_filament_load_time(unsigned int id_extruder); + float get_filament_unload_time(unsigned int id_extruder); + void set_extrude_factor_override_percentage(float percentage); float get_extrude_factor_override_percentage() const; @@ -300,6 +313,10 @@ namespace Slic3r { void increment_g1_line_id(); void reset_g1_line_id(); + void set_extruder_id(unsigned int id); + unsigned int get_extruder_id() const; + void reset_extruder_id(); + void add_additional_time(float timeSec); void set_additional_time(float timeSec); float get_additional_time() const; @@ -383,6 +400,12 @@ namespace Slic3r { // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); + // Unload the current filament into the MK3 MMU2 unit at the end of print. + void _processM702(const GCodeReader::GCodeLine& line); + + // Processes T line (Select Tool) + void _processT(const GCodeReader::GCodeLine& line); + // Simulates firmware st_synchronize() call void _simulate_st_synchronize(); diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index a94bb62ff..ed18f66db 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -195,47 +195,62 @@ using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { -static bool -sort_points (Point a, Point b) -{ - return (a.x < b.x) || (a.x == b.x && a.y < b.y); -} +struct SortPoints { + template <class T> + bool operator()(const T& a, const T& b) const { + return (b.x > a.x) || (a.x == b.x && b.y > a.y); + } +}; -/* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */ -Polygon -convex_hull(Points points) +// This implementation is based on Andrew's monotone chain 2D convex hull algorithm +template<class T> +static T raw_convex_hull(T& points) { assert(points.size() >= 3); // sort input points - std::sort(points.begin(), points.end(), sort_points); + std::sort(points.begin(), points.end(), SortPoints()); int n = points.size(), k = 0; - Polygon hull; + T hull; if (n >= 3) { - hull.points.resize(2*n); + hull.resize(2*n); // Build lower hull for (int i = 0; i < n; i++) { - while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; - hull.points[k++] = points[i]; + while (k >= 2 && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--; + hull[k++] = points[i]; } // Build upper hull for (int i = n-2, t = k+1; i >= 0; i--) { - while (k >= t && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; - hull.points[k++] = points[i]; + while (k >= t && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--; + hull[k++] = points[i]; } - hull.points.resize(k); + hull.resize(k); - assert( hull.points.front().coincides_with(hull.points.back()) ); - hull.points.pop_back(); + assert( hull.front().coincides_with(hull.back()) ); + hull.pop_back(); } return hull; } +Pointf3s +convex_hull(Pointf3s points) +{ + return raw_convex_hull(points); +} + +Polygon +convex_hull(Points points) +{ + Polygon hull; + hull.points = raw_convex_hull(points); + return hull; +} + Polygon convex_hull(const Polygons &polygons) { @@ -243,7 +258,7 @@ convex_hull(const Polygons &polygons) for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { pp.insert(pp.end(), p->points.begin(), p->points.end()); } - return convex_hull(pp); + return convex_hull(std::move(pp)); } /* accepts an arrayref of points and returns a list of indices diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index c2c3dc8b7..956ef82aa 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -108,8 +108,10 @@ inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, co return true; } +Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); + void chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near); void chained_path(const Points &points, std::vector<Points::size_type> &retval); template<class T> void chained_path_items(Points &points, T &items, T &retval); diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 2f73cad20..09b515c2f 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> @@ -22,6 +17,11 @@ #include "SVG.hpp" #include <Eigen/Dense> +static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + namespace Slic3r { unsigned int Model::s_auto_extruder_id = 1; @@ -240,14 +240,6 @@ BoundingBoxf3 Model::bounding_box() const return bb; } -BoundingBoxf3 Model::transformed_bounding_box() const -{ - BoundingBoxf3 bb; - for (const ModelObject* obj : this->objects) - bb.merge(obj->tight_bounding_box(false)); - return bb; -} - void Model::center_instances_around_point(const Pointf &point) { // BoundingBoxf3 bb = this->bounding_box(); @@ -304,369 +296,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. @@ -961,54 +620,6 @@ const BoundingBoxf3& ModelObject::bounding_box() const return m_bounding_box; } -BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const -{ - BoundingBoxf3 bb; - - for (const ModelVolume* vol : this->volumes) - { - if (include_modifiers || !vol->modifier) - { - for (const ModelInstance* inst : this->instances) - { - double c = cos(inst->rotation); - double s = sin(inst->rotation); - - for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f) - { - const stl_facet& facet = vol->mesh.stl.facet_start[f]; - - for (int i = 0; i < 3; ++i) - { - // original point - const stl_vertex& v = facet.vertex[i]; - Pointf3 p((double)v.x, (double)v.y, (double)v.z); - - // scale - p.x *= inst->scaling_factor; - p.y *= inst->scaling_factor; - p.z *= inst->scaling_factor; - - // rotate Z - double x = p.x; - double y = p.y; - p.x = c * x - s * y; - p.y = s * x + c * y; - - // translate - p.x += inst->offset.x; - p.y += inst->offset.y; - - bb.merge(p); - } - } - } - } - } - - return bb; -} - // A mesh containing all transformed instances of this object. TriangleMesh ModelObject::mesh() const { @@ -1093,37 +704,36 @@ void ModelObject::center_around_origin() void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelVolume *v : this->volumes) + { v->mesh.translate(float(x), float(y), float(z)); - if (m_bounding_box_valid) + v->m_convex_hull.translate(float(x), float(y), float(z)); + } + + if (m_bounding_box_valid) m_bounding_box.translate(x, y, z); } void ModelObject::scale(const Pointf3 &versor) { for (ModelVolume *v : this->volumes) + { v->mesh.scale(versor); + v->m_convex_hull.scale(versor); + } // reset origin translation since it doesn't make sense anymore this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } -void ModelObject::rotate(float angle, const Axis &axis) +void ModelObject::rotate(float angle, const Pointf3& axis) { - float min_z = FLT_MAX; for (ModelVolume *v : this->volumes) { v->mesh.rotate(angle, axis); - min_z = std::min(min_z, v->mesh.stl.stats.min.z); + v->m_convex_hull.rotate(angle, axis); } - 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); - } - } + center_around_origin(); this->origin_translation = Pointf3(0, 0, 0); this->invalidate_bounding_box(); @@ -1137,6 +747,7 @@ void ModelObject::transform(const float* matrix3x4) for (ModelVolume* v : volumes) { v->mesh.transform(matrix3x4); + v->m_convex_hull.transform(matrix3x4); } origin_translation = Pointf3(0.0, 0.0, 0.0); @@ -1146,8 +757,12 @@ void ModelObject::transform(const float* matrix3x4) void ModelObject::mirror(const Axis &axis) { for (ModelVolume *v : this->volumes) + { v->mesh.mirror(axis); - this->origin_translation = Pointf3(0,0,0); + v->m_convex_hull.mirror(axis); + } + + this->origin_translation = Pointf3(0, 0, 0); this->invalidate_bounding_box(); } @@ -1251,45 +866,20 @@ void ModelObject::split(ModelObjectPtrs* new_objects) void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) { - for (ModelVolume* vol : this->volumes) + for (const ModelVolume* vol : this->volumes) { if (!vol->modifier) { for (ModelInstance* inst : this->instances) { - BoundingBoxf3 bb; - - double c = cos(inst->rotation); - double s = sin(inst->rotation); - - for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f) - { - const stl_facet& facet = vol->mesh.stl.facet_start[f]; - - for (int i = 0; i < 3; ++i) - { - // original point - const stl_vertex& v = facet.vertex[i]; - Pointf3 p((double)v.x, (double)v.y, (double)v.z); - - // scale - p.x *= inst->scaling_factor; - p.y *= inst->scaling_factor; - p.z *= inst->scaling_factor; + std::vector<float> world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX)); + Eigen::Transform<float, 3, Eigen::Affine> m = Eigen::Transform<float, 3, Eigen::Affine>::Identity(); + m.translate(Eigen::Vector3f((float)inst->offset.x, (float)inst->offset.y, 0.0f)); + m.rotate(Eigen::AngleAxisf(inst->rotation, Eigen::Vector3f::UnitZ())); + m.scale(inst->scaling_factor); + ::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float)); - // rotate Z - double x = p.x; - double y = p.y; - p.x = c * x - s * y; - p.y = s * x + c * y; - - // translate - p.x += inst->offset.x; - p.y += inst->offset.y; - - bb.merge(p); - } - } + BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(world_mat); if (print_volume.contains(bb)) inst->print_volume_state = ModelInstance::PVS_Inside; @@ -1372,6 +962,16 @@ ModelMaterial* ModelVolume::assign_unique_material() return model->add_material(this->_material_id); } +void ModelVolume::calculate_convex_hull() +{ + m_convex_hull = mesh.convex_hull_3d(); +} + +const TriangleMesh& ModelVolume::get_convex_hull() const +{ + return m_convex_hull; +} + // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index f5e97fb6a..dadd515de 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -105,9 +105,6 @@ public: // This bounding box is being cached. const BoundingBoxf3& bounding_box() const; void invalidate_bounding_box() { m_bounding_box_valid = false; } - // Returns a snug bounding box of the transformed instances. - // This bounding box is not being cached. - BoundingBoxf3 tight_bounding_box(bool include_modifiers) const; // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; @@ -123,7 +120,7 @@ public: void translate(const Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); } void translate(coordf_t x, coordf_t y, coordf_t z); void scale(const Pointf3 &versor); - void rotate(float angle, const Axis &axis); + void rotate(float angle, const Pointf3& axis); void transform(const float* matrix3x4); void mirror(const Axis &axis); size_t materials_count() const; @@ -157,6 +154,10 @@ private: class ModelVolume { friend class ModelObject; + + // The convex hull of this model's mesh. + TriangleMesh m_convex_hull; + public: std::string name; // The triangular model. @@ -180,19 +181,32 @@ public: ModelMaterial* assign_unique_material(); + void calculate_convex_hull(); + const TriangleMesh& get_convex_hull() const; + private: // Parent object owning this ModelVolume. ModelObject* object; t_model_material_id _material_id; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) {} - ModelVolume(ModelObject *object, TriangleMesh &&mesh) : mesh(std::move(mesh)), modifier(false), object(object) {} - ModelVolume(ModelObject *object, const ModelVolume &other) : - name(other.name), mesh(other.mesh), config(other.config), modifier(other.modifier), object(object) - { this->material_id(other.material_id()); } - ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) + { + if (mesh.stl.stats.number_of_facets > 1) + calculate_convex_hull(); + } + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {} + ModelVolume(ModelObject *object, const ModelVolume &other) : + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object) + { + this->material_id(other.material_id()); + } + ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) - { this->material_id(other.material_id()); } + { + this->material_id(other.material_id()); + if (mesh.stl.stats.number_of_facets > 1) + calculate_convex_hull(); + } }; // A single instance of a ModelObject. @@ -285,13 +299,10 @@ public: bool add_default_instances(); // Returns approximate axis aligned bounding box of this model BoundingBoxf3 bounding_box() const; - // Returns tight axis aligned bounding box of this model - BoundingBoxf3 transformed_bounding_box() const; 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/Point.cpp b/xs/src/libslic3r/Point.cpp index 2abcd26af..349b00bb6 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -263,6 +263,12 @@ operator<<(std::ostream &stm, const Pointf &pointf) return stm << pointf.x << "," << pointf.y; } +double +Pointf::ccw(const Pointf &p1, const Pointf &p2) const +{ + return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x); +} + std::string Pointf::wkt() const { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 87104674f..347966c03 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -221,6 +221,7 @@ public: static Pointf new_unscale(const Point &p) { return Pointf(unscale(p.x), unscale(p.y)); }; + double ccw(const Pointf &p1, const Pointf &p2) const; std::string wkt() const; std::string dump_perl() const; void scale(double factor); diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 029da33b6..e77bf5859 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -129,7 +129,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option "gcode_comments", "gcode_flavor", "infill_acceleration", - "infill_first", "layer_gcode", "min_fan_speed", "max_fan_speed", @@ -156,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", @@ -167,17 +167,16 @@ 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; std::vector<PrintObjectStep> osteps; bool invalidated = false; - // 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); - for (const t_config_option_key &opt_key : opt_keys) { if (steps_ignore.find(opt_key) != steps_ignore.end()) { // These options only affect G-code export or they are just notes without influence on the generated G-code, @@ -202,21 +201,22 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option || opt_key == "filament_soluble" || opt_key == "first_layer_temperature" || opt_key == "filament_loading_speed" + || opt_key == "filament_loading_speed_start" || opt_key == "filament_unloading_speed" + || opt_key == "filament_unloading_speed_start" || 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" || opt_key == "gcode_flavor" + || opt_key == "infill_first" || opt_key == "single_extruder_multi_material" || 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" @@ -1052,6 +1052,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: @@ -1124,7 +1126,9 @@ void Print::_make_wipe_tower() this->config.temperature.get_at(i), this->config.first_layer_temperature.get_at(i), this->config.filament_loading_speed.get_at(i), + this->config.filament_loading_speed_start.get_at(i), this->config.filament_unloading_speed.get_at(i), + this->config.filament_unloading_speed_start.get_at(i), this->config.filament_toolchange_delay.get_at(i), this->config.filament_cooling_moves.get_at(i), this->config.filament_cooling_initial_speed.get_at(i), @@ -1145,12 +1149,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); + + // add back the minimal amount toforce on the wipe tower: + volume_to_wipe += config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); - 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); + // 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; } } @@ -1163,7 +1174,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) { @@ -1184,10 +1196,6 @@ void Print::_make_wipe_tower() wipe_tower.tool_change((unsigned int)-1, false)); } - - - - std::string Print::output_filename() { this->placeholder_parser.update_timestamp(); @@ -1226,7 +1234,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) { @@ -1234,5 +1241,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 5edf6e0b7..bfc058a78 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -276,6 +276,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. @@ -329,6 +330,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 a2c25f2cc..479fa6cd6 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -549,6 +549,14 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 28. }; + def = this->add("filament_loading_speed_start", coFloats); + def->label = L("Loading speed at the start"); + def->tooltip = L("Speed used at the very beginning of loading phase. "); + def->sidetext = L("mm/s"); + def->cli = "filament-loading-speed-start=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 3. }; + def = this->add("filament_unloading_speed", coFloats); def->label = L("Unloading speed"); def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect " @@ -558,6 +566,14 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 90. }; + def = this->add("filament_unloading_speed_start", coFloats); + def->label = L("Unloading speed at the start"); + def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming. "); + def->sidetext = L("mm/s"); + def->cli = "filament-unloading-speed-start=f@"; + def->min = 0; + def->default_value = new ConfigOptionFloats { 100. }; + def = this->add("filament_toolchange_delay", coFloats); def->label = L("Delay after unloading"); def->tooltip = L("Time to wait after the filament is unloaded. " @@ -580,19 +596,38 @@ 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 tool change, the exact position of the newly loaded filament inside " + "the nozzle may not be known, and the filament pressure is likely not yet stable. " + "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime " + "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably."); + def->cli = "filament-minimal-purge-on-wipe-tower=f@"; + def->sidetext = L("mm³"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 15.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 }; + def = this->add("filament_load_time", coFloats); + def->label = L("Filament load time"); + def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); + def->cli = "filament-load-time=i@"; + def->sidetext = L("s"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 0.0f }; + def = this->add("filament_ramming_parameters", coStrings); def->label = L("Ramming parameters"); def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters "); @@ -600,6 +635,14 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }; + def = this->add("filament_unload_time", coFloats); + def->label = L("Filament unload time"); + def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to unload a filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); + def->cli = "filament-unload-time=i@"; + def->sidetext = L("s"); + def->min = 0; + def->default_value = new ConfigOptionFloats { 0.0f }; + def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper " @@ -621,10 +664,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("filament_type", coStrings); def->label = L("Filament type"); - def->tooltip = L("If you want to process the output G-code through custom scripts, just list their " - "absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed " - "the absolute path to the G-code file as the first argument, and they can access " - "the Slic3r config settings by reading environment variables."); + def->tooltip = L("The filament material type for use in custom G-codes."); def->cli = "filament_type=s@"; def->gui_type = "f_enum_open"; def->gui_flags = "show_value"; @@ -976,8 +1016,16 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(0.3); + def = this->add("remaining_times", coBool); + def->label = L("Supports remaining times"); + def->tooltip = L("Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute" + " intervals into the G-code to let the firmware show accurate remaining time." + " As of now only the Prusa i3 MK3 firmware recognizes M73." + " Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode."); + def->default_value = new ConfigOptionBool(false); + def = this->add("silent_mode", coBool); - def->label = L("Support silent mode"); + def->label = L("Supports silent mode"); def->tooltip = L("Set silent mode for the G-code flavor"); def->default_value = new ConfigOptionBool(true); @@ -1189,25 +1237,37 @@ PrintConfigDef::PrintConfigDef() def->cli = "nozzle-diameter=f@"; def->default_value = new ConfigOptionFloats { 0.5 }; - def = this->add("octoprint_apikey", coString); - def->label = L("API Key"); - def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain " - "the API Key required for authentication."); - def->cli = "octoprint-apikey=s"; + def = this->add("host_type", coEnum); + def->label = L("Host Type"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " + "the kind of the host."); + def->cli = "host-type=s"; + def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values(); + def->enum_values.push_back("octoprint"); + def->enum_values.push_back("duet"); + def->enum_labels.push_back("OctoPrint"); + def->enum_labels.push_back("Duet"); + def->default_value = new ConfigOptionEnum<PrintHostType>(htOctoPrint); + + def = this->add("printhost_apikey", coString); + def->label = L("API Key / Password"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " + "the API Key or the password required for authentication."); + def->cli = "printhost-apikey=s"; def->default_value = new ConfigOptionString(""); - def = this->add("octoprint_cafile", coString); + def = this->add("printhost_cafile", coString); def->label = "HTTPS CA file"; def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " "If left blank, the default OS CA certificate repository is used."; - def->cli = "octoprint-cafile=s"; + def->cli = "printhost-cafile=s"; def->default_value = new ConfigOptionString(""); - def = this->add("octoprint_host", coString); + def = this->add("print_host", coString); def->label = L("Hostname, IP or URL"); - def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain " - "the hostname, IP address or URL of the OctoPrint instance."); - def->cli = "octoprint-host=s"; + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " + "the hostname, IP address or URL of the printer host instance."); + def->cli = "print-host=s"; def->default_value = new ConfigOptionString(""); def = this->add("only_retract_when_crossing_perimeters", coBool); @@ -1729,6 +1789,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"); @@ -2099,8 +2165,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_into_infill", coBool); def->category = L("Extruders"); - def->label = L("Purging into infill"); - def->tooltip = L("Wiping after toolchange will be preferentially done inside infills. " + def->label = L("Wipe into this object's infill"); + def->tooltip = L("Purging after toolchange will done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " " due to additional travel moves."); def->cli = "wipe-into-infill!"; @@ -2108,8 +2174,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_into_objects", coBool); def->category = L("Extruders"); - def->label = L("Purging into objects"); - def->tooltip = L("Objects will be used to wipe the nozzle after a toolchange to save material " + def->label = L("Wipe into this object"); + def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " "Colours of the objects will be mixed as a result."); def->cli = "wipe-into-objects!"; @@ -2185,10 +2251,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va std::ostringstream oss; oss << "0x0," << p.value.x << "x0," << p.value.x << "x" << p.value.y << ",0x" << p.value.y; value = oss.str(); -// Maybe one day we will rename octoprint_host to print_host as it has been done in the upstream Slic3r. -// Commenting this out fixes github issue #869 for now. -// } else if (opt_key == "octoprint_host" && !value.empty()) { -// opt_key = "print_host"; } else if ((opt_key == "perimeter_acceleration" && value == "25") || (opt_key == "infill_acceleration" && value == "50")) { /* For historical reasons, the world's full of configs having these very low values; @@ -2199,6 +2261,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "support_material_pattern" && value == "pillars") { // Slic3r PE does not support the pillars. They never worked well. value = "rectilinear"; + } else if (opt_key == "octoprint_host") { + opt_key = "print_host"; + } else if (opt_key == "octoprint_cafile") { + opt_key = "printhost_cafile"; + } else if (opt_key == "octoprint_apikey") { + opt_key = "printhost_apikey"; } // Ignore the following obsolete configuration keys: @@ -2208,9 +2276,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", "seal_position", "vibration_limit", "bed_size", - // Maybe one day we will rename octoprint_host to print_host as it has been done in the upstream Slic3r. - // Commenting this out fixes github issue #869 for now. - // "octoprint_host", "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" }; @@ -2220,7 +2285,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } if (! print_config_def.has(opt_key)) { - //printf("Unknown option %s\n", opt_key.c_str()); opt_key = ""; return; } diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 9949e61d5..837c4f682 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -27,6 +27,10 @@ enum GCodeFlavor { gcfSmoothie, gcfNoExtrusion, }; +enum PrintHostType { + htOctoPrint, htDuet, +}; + enum InfillPattern { ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSmooth, ipSmoothHilbert, ipSmoothTriple, @@ -62,6 +66,15 @@ template<> inline t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get_enum_ return keys_map; } +template<> inline t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["octoprint"] = htOctoPrint; + keys_map["duet"] = htDuet; + } + return keys_map; +} + template<> inline t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { @@ -557,10 +570,15 @@ public: ConfigOptionFloats filament_cost; ConfigOptionFloats filament_max_volumetric_speed; ConfigOptionFloats filament_loading_speed; + ConfigOptionFloats filament_loading_speed_start; + ConfigOptionFloats filament_load_time; ConfigOptionFloats filament_unloading_speed; + ConfigOptionFloats filament_unloading_speed_start; ConfigOptionFloats filament_toolchange_delay; + 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; @@ -582,6 +600,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; @@ -591,6 +610,7 @@ public: ConfigOptionFloat cooling_tube_retraction; ConfigOptionFloat cooling_tube_length; ConfigOptionFloat parking_pos_retraction; + ConfigOptionBool remaining_times; ConfigOptionBool silent_mode; ConfigOptionFloat extra_loading_move; @@ -618,10 +638,15 @@ protected: OPT_PTR(filament_cost); OPT_PTR(filament_max_volumetric_speed); OPT_PTR(filament_loading_speed); + OPT_PTR(filament_loading_speed_start); + OPT_PTR(filament_load_time); OPT_PTR(filament_unloading_speed); + OPT_PTR(filament_unloading_speed_start); + OPT_PTR(filament_unload_time); 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); @@ -641,6 +666,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); @@ -652,6 +678,7 @@ protected: OPT_PTR(cooling_tube_retraction); OPT_PTR(cooling_tube_length); OPT_PTR(parking_pos_retraction); + OPT_PTR(remaining_times); OPT_PTR(silent_mode); OPT_PTR(extra_loading_move); } @@ -808,18 +835,20 @@ class HostConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(HostConfig) public: - ConfigOptionString octoprint_host; - ConfigOptionString octoprint_apikey; - ConfigOptionString octoprint_cafile; + ConfigOptionEnum<PrintHostType> host_type; + ConfigOptionString print_host; + ConfigOptionString printhost_apikey; + ConfigOptionString printhost_cafile; ConfigOptionString serial_port; ConfigOptionInt serial_speed; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { - OPT_PTR(octoprint_host); - OPT_PTR(octoprint_apikey); - OPT_PTR(octoprint_cafile); + OPT_PTR(host_type); + OPT_PTR(print_host); + OPT_PTR(printhost_apikey); + OPT_PTR(printhost_cafile); OPT_PTR(serial_port); OPT_PTR(serial_speed); } diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index c7c8aa62d..91baa3392 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; } diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 45e4b6f5d..4c45680b6 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1,6 +1,9 @@ #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include "qhull/src/libqhullcpp/Qhull.h" +#include "qhull/src/libqhullcpp/QhullFacetList.h" +#include "qhull/src/libqhullcpp/QhullVertexSet.h" #include <cmath> #include <deque> #include <queue> @@ -10,11 +13,14 @@ #include <utility> #include <algorithm> #include <math.h> +#include <type_traits> #include <boost/log/trivial.hpp> #include <tbb/parallel_for.h> +#include <Eigen/Dense> + #if 0 #define DEBUG #define _DEBUG @@ -318,6 +324,17 @@ void TriangleMesh::translate(float x, float y, float z) stl_invalidate_shared_vertices(&this->stl); } +void TriangleMesh::rotate(float angle, Pointf3 axis) +{ + if (angle == 0.f) + return; + + axis = normalize(axis); + Eigen::Transform<float, 3, Eigen::Affine> m = Eigen::Transform<float, 3, Eigen::Affine>::Identity(); + m.rotate(Eigen::AngleAxisf(angle, Eigen::Vector3f(axis.x, axis.y, axis.z))); + stl_transform(&stl, (float*)m.data()); +} + void TriangleMesh::rotate(float angle, const Axis &axis) { if (angle == 0.f) @@ -597,6 +614,140 @@ TriangleMesh::bounding_box() const return bb; } +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const std::vector<float>& matrix) const +{ + bool has_shared = (stl.v_shared != nullptr); + if (!has_shared) + stl_generate_shared_vertices(&stl); + + unsigned int vertices_count = (stl.stats.shared_vertices > 0) ? (unsigned int)stl.stats.shared_vertices : 3 * (unsigned int)stl.stats.number_of_facets; + + if (vertices_count == 0) + return BoundingBoxf3(); + + Eigen::MatrixXf src_vertices(3, vertices_count); + + if (stl.stats.shared_vertices > 0) + { + stl_vertex* vertex_ptr = stl.v_shared; + for (int i = 0; i < stl.stats.shared_vertices; ++i) + { + src_vertices(0, i) = vertex_ptr->x; + src_vertices(1, i) = vertex_ptr->y; + src_vertices(2, i) = vertex_ptr->z; + vertex_ptr += 1; + } + } + else + { + stl_facet* facet_ptr = stl.facet_start; + unsigned int v_id = 0; + while (facet_ptr < stl.facet_start + stl.stats.number_of_facets) + { + for (int i = 0; i < 3; ++i) + { + src_vertices(0, v_id) = facet_ptr->vertex[i].x; + src_vertices(1, v_id) = facet_ptr->vertex[i].y; + src_vertices(2, v_id) = facet_ptr->vertex[i].z; + } + facet_ptr += 1; + ++v_id; + } + } + + if (!has_shared && (stl.stats.shared_vertices > 0)) + stl_invalidate_shared_vertices(&stl); + + Eigen::Transform<float, 3, Eigen::Affine> m; + ::memcpy((void*)m.data(), (const void*)matrix.data(), 16 * sizeof(float)); + + Eigen::MatrixXf dst_vertices(3, vertices_count); + dst_vertices = m * src_vertices.colwise().homogeneous(); + + float min_x = dst_vertices(0, 0); + float max_x = dst_vertices(0, 0); + float min_y = dst_vertices(1, 0); + float max_y = dst_vertices(1, 0); + float min_z = dst_vertices(2, 0); + float max_z = dst_vertices(2, 0); + + for (int i = 1; i < vertices_count; ++i) + { + min_x = std::min(min_x, dst_vertices(0, i)); + max_x = std::max(max_x, dst_vertices(0, i)); + min_y = std::min(min_y, dst_vertices(1, i)); + max_y = std::max(max_y, dst_vertices(1, i)); + min_z = std::min(min_z, dst_vertices(2, i)); + max_z = std::max(max_z, dst_vertices(2, i)); + } + + return BoundingBoxf3(Pointf3((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Pointf3((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z)); +} + +TriangleMesh TriangleMesh::convex_hull_3d() const +{ + // Helper struct for qhull: + struct PointForQHull{ + PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {} + realT x, y, z; + }; + std::vector<PointForQHull> src_vertices; + + // We will now fill the vector with input points for computation: + stl_facet* facet_ptr = stl.facet_start; + while (facet_ptr < stl.facet_start + stl.stats.number_of_facets) + { + for (int i = 0; i < 3; ++i) + { + const stl_vertex& v = facet_ptr->vertex[i]; + src_vertices.emplace_back(v.x, v.y, v.z); + } + + facet_ptr += 1; + } + + // The qhull call: + orgQhull::Qhull qhull; + qhull.disableOutputStream(); // we want qhull to be quiet + try + { + qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt"); + } + catch (...) + { + std::cout << "Unable to create convex hull" << std::endl; + return TriangleMesh(); + } + + // Let's collect results: + Pointf3s det_vertices; + std::vector<Point3> facets; + auto facet_list = qhull.facetList().toStdVector(); + for (const orgQhull::QhullFacet& facet : facet_list) + { // iterate through facets + orgQhull::QhullVertexSet vertices = facet.vertices(); + for (int i = 0; i < 3; ++i) + { // iterate through facet's vertices + + orgQhull::QhullPoint p = vertices[i].point(); + const float* coords = p.coordinates(); + det_vertices.emplace_back(coords[0], coords[1], coords[2]); + } + unsigned int size = (unsigned int)det_vertices.size(); + facets.emplace_back(size - 3, size - 2, size - 1); + } + + TriangleMesh output_mesh(det_vertices, facets); + output_mesh.repair(); + output_mesh.require_shared_vertices(); + return output_mesh; +} + +const float* TriangleMesh::first_vertex() const +{ + return stl.facet_start ? &stl.facet_start->vertex[0].x : nullptr; +} + void TriangleMesh::require_shared_vertices() { diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index c700784a5..72e541afc 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -40,6 +40,7 @@ public: void scale(const Pointf3 &versor); void translate(float x, float y, float z); void rotate(float angle, const Axis &axis); + void rotate(float angle, Pointf3 axis); void rotate_x(float angle); void rotate_y(float angle); void rotate_z(float angle); @@ -53,8 +54,13 @@ public: TriangleMeshPtrs split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; + const float* first_vertex() const; Polygon convex_hull(); BoundingBoxf3 bounding_box() const; + // Returns the bbox of this TriangleMesh transformed by the given matrix + BoundingBoxf3 transformed_bounding_box(const std::vector<float>& matrix) const; + // Returns the convex hull of this TriangleMesh + TriangleMesh convex_hull_3d() const; void reset_repair_stats(); bool needed_repair() const; size_t facets_count() const; @@ -66,7 +72,7 @@ public: // Count disconnected triangle patches. size_t number_of_patches() const; - stl_file stl; + mutable stl_file stl; bool repaired; private: diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 6678a78b0..fc55d0f80 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include <boost/thread.hpp> #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.41.0-alpha3" +#define SLIC3R_VERSION "1.41.0" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; |