std::string GCode::extrude(ExtrusionLoop loop, std::string description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); if (this->config.spiral_vase) { loop.split_at(last_pos); } else if (this->config.seam_position == spNearest || this->config.seam_position == spAligned) { Polygon polygon = loop.polygon(); // simplify polygon in order to skip false positives in concave/convex detection // (loop is always ccw as polygon.simplify() only works on ccw polygons) Polygons simplified = polygon.simplify(scale_(EXTRUDER_CONFIG(nozzle_diameter))/2); // restore original winding order so that concave and convex detection always happens // on the right/outer side of the polygon if (was_clockwise) { for (Polygons::iterator p = simplified.begin(); p != simplified.end(); ++p) p->reverse(); } // concave vertices have priority Points candidates; for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { Points concave = p->concave_points(PI*4/3); candidates.insert(candidates.end(), concave.begin(), concave.end()); } // if no concave points were found, look for convex vertices if (candidates.empty()) { for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { Points convex = p->convex_points(PI*2/3); candidates.insert(candidates.end(), convex.begin(), convex.end()); } } // retrieve the last start position for this object if (this->layer != NULL && this->_seam_position.count(this->layer->object()) > 0) { last_pos = this->_seam_position[this->layer->object()]; } Point point; if (this->config.seam_position == spNearest) { if (candidates.empty()) candidates = polygon.points; last_pos.nearest_point(candidates, &point); // On 32-bit Linux, Clipper will change some point coordinates by 1 unit // while performing simplify_polygons(), thus split_at_vertex() won't // find them anymore. if (!loop.split_at_vertex(point)) loop.split_at(point); } else if (!candidates.empty()) { Points non_overhang; for (Points::const_iterator p = candidates.begin(); p != candidates.end(); ++p) { if (!loop.has_overhang_point(*p)) non_overhang.push_back(*p); } if (!non_overhang.empty()) candidates = non_overhang; last_pos.nearest_point(candidates, &point); if (!loop.split_at_vertex(point)) loop.split_at(point); // see note above } else { if (this->config.seam_position == spAlwaysHideSeam){ if (loop.role == elrContourInternalPerimeter) { Polygon polygon = loop.polygon(); Point centroid = polygon.centroid(); point = Point(polygon.bounding_box().max.x, centroid.y); point.rotate(rand() % 2*PI, centroid); } } } else{
std::string GCode::extrude(ExtrusionLoop loop, std::string description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); SeamPosition seam_position = this->config.seam_position; if (loop.role == elrSkirt) seam_position = spNearest; // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); if (this->config.spiral_vase) { loop.split_at(last_pos); } else if (seam_position == spNearest || seam_position == spAligned) { const Polygon polygon = loop.polygon(); // simplify polygon in order to skip false positives in concave/convex detection // (loop is always ccw as polygon.simplify() only works on ccw polygons) Polygons simplified = polygon.simplify(scale_(EXTRUDER_CONFIG(nozzle_diameter))/2); // restore original winding order so that concave and convex detection always happens // on the right/outer side of the polygon if (was_clockwise) { for (Polygons::iterator p = simplified.begin(); p != simplified.end(); ++p) p->reverse(); } // concave vertices have priority Points candidates; for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { Points concave = p->concave_points(PI*4/3); candidates.insert(candidates.end(), concave.begin(), concave.end()); } // if no concave points were found, look for convex vertices if (candidates.empty()) { for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { Points convex = p->convex_points(PI*2/3); candidates.insert(candidates.end(), convex.begin(), convex.end()); } } // retrieve the last start position for this object if (this->layer != NULL && this->_seam_position.count(this->layer->object()) > 0) { last_pos = this->_seam_position[this->layer->object()]; } Point point; if (seam_position == spNearest) { if (candidates.empty()) candidates = polygon.points; last_pos.nearest_point(candidates, &point); // On 32-bit Linux, Clipper will change some point coordinates by 1 unit // while performing simplify_polygons(), thus split_at_vertex() won't // find them anymore. if (!loop.split_at_vertex(point)) loop.split_at(point); } else if (!candidates.empty()) { Points non_overhang; for (Points::const_iterator p = candidates.begin(); p != candidates.end(); ++p) { if (!loop.has_overhang_point(*p)) non_overhang.push_back(*p); } if (!non_overhang.empty()) candidates = non_overhang; last_pos.nearest_point(candidates, &point); if (!loop.split_at_vertex(point)) loop.split_at(point); // see note above } else { point = last_pos.projection_onto(polygon); loop.split_at(point); } if (this->layer != NULL) this->_seam_position[this->layer->object()] = point; } else if (seam_position == spRandom) { if (loop.role == elrContourInternalPerimeter) { Polygon polygon = loop.polygon(); Point centroid = polygon.centroid(); last_pos = Point(polygon.bounding_box().max.x, centroid.y); last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid); } loop.split_at(last_pos); } // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case double clip_length = this->enable_loop_clipping ? scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : 0; // get paths ExtrusionPaths paths; loop.clip_end(clip_length, &paths); if (paths.empty()) return ""; // apply the small perimeter speed if (paths.front().is_perimeter() && loop.length() <= SMALL_PERIMETER_LENGTH) { if (speed == -1) speed = this->config.get_abs_value("small_perimeter_speed"); } // extrude along the path std::string gcode; for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) gcode += this->_extrude(*path, description, speed); // reset acceleration gcode += this->writer.set_acceleration(this->config.default_acceleration.value); if (this->wipe.enable) this->wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path // make a little move inwards before leaving loop if (paths.back().role == erExternalPerimeter && this->layer != NULL && this->config.perimeters > 1) { // detect angle between last and first segment // the side depends on the original winding order of the polygon (left for contours, right for holes) Point a = paths.front().polyline.points[1]; // second point Point b = *(paths.back().polyline.points.end()-3); // second to last point if (was_clockwise) { // swap points Point c = a; a = b; b = c; } double angle = paths.front().first_point().ccw_angle(a, b) / 3; // turn left if contour, turn right if hole if (was_clockwise) angle *= -1; // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know // the rotation of the second segment so we might cross the object boundary Line first_segment( paths.front().polyline.points[0], paths.front().polyline.points[1] ); double distance = std::min( scale_(EXTRUDER_CONFIG(nozzle_diameter)), first_segment.length() ); Point point = first_segment.point_at(distance); point.rotate(angle, first_segment.a); // generate the travel move gcode += this->writer.travel_to_xy(this->point_to_gcode(point), "move inwards before travel"); } return gcode; }
bool BridgeDetector::detect_angle() { if (this->_edges.empty() || this->_anchors.empty()) return false; /* Outset the bridge expolygon by half the amount we used for detecting anchors; we'll use this one to clip our test lines and be sure that their endpoints are inside the anchors and not on their contours leading to false negatives. */ Polygons clip_area; offset(this->expolygon, &clip_area, +this->extrusion_width/2); /* we'll now try several directions using a rudimentary visibility check: bridge in several directions and then sum the length of lines having both endpoints within anchors */ // we test angles according to configured resolution std::vector<double> angles; for (int i = 0; i <= PI/this->resolution; ++i) angles.push_back(i * this->resolution); // we also test angles of each bridge contour { Polygons pp = this->expolygon; for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { Lines lines = p->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) angles.push_back(line->direction()); } } /* we also test angles of each open supporting edge (this finds the optimal angle for C-shaped supports) */ for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) { if (edge->first_point().coincides_with(edge->last_point())) continue; angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); } // remove duplicates double min_resolution = PI/180.0; // 1 degree std::sort(angles.begin(), angles.end()); for (size_t i = 1; i < angles.size(); ++i) { if (xd::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { angles.erase(angles.begin() + i); --i; } } /* compare first value with last one and remove the greatest one (PI) in case they are parallel (PI, 0) */ if (xd::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) angles.pop_back(); BridgeDirectionComparator bdcomp(this->extrusion_width); double line_increment = this->extrusion_width; bool have_coverage = false; for (std::vector<double>::const_iterator angle = angles.begin(); angle != angles.end(); ++angle) { Polygons my_clip_area = clip_area; ExPolygons my_anchors = this->_anchors; // rotate everything - the center point doesn't matter for (Polygons::iterator it = my_clip_area.begin(); it != my_clip_area.end(); ++it) it->rotate(-*angle, Point(0,0)); for (ExPolygons::iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) it->rotate(-*angle, Point(0,0)); // generate lines in this direction BoundingBox bb; for (ExPolygons::const_iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) bb.merge((Points)*it); Lines lines; for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) lines.push_back(Line(Point(bb.min.x, y), Point(bb.max.x, y))); Lines clipped_lines; intersection(lines, my_clip_area, &clipped_lines); // remove any line not having both endpoints within anchors for (size_t i = 0; i < clipped_lines.size(); ++i) { Line &line = clipped_lines[i]; if (!xd::Geometry::contains(my_anchors, line.a) || !xd::Geometry::contains(my_anchors, line.b)) { clipped_lines.erase(clipped_lines.begin() + i); --i; } } std::vector<double> lengths; double total_length = 0; for (Lines::const_iterator line = clipped_lines.begin(); line != clipped_lines.end(); ++line) { double len = line->length(); lengths.push_back(len); total_length += len; } if (total_length) have_coverage = true; // sum length of bridged lines bdcomp.dir_coverage[*angle] = total_length; /* The following produces more correct results in some cases and more broken in others. TODO: investigate, as it looks more reliable than line clipping. */ // $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; // max length of bridged lines bdcomp.dir_avg_length[*angle] = !lengths.empty() ? *std::max_element(lengths.begin(), lengths.end()) : 0; } // if no direction produced coverage, then there's no bridge direction if (!have_coverage) return false; // sort directions by score std::sort(angles.begin(), angles.end(), bdcomp); this->angle = angles.front(); if (this->angle >= PI) this->angle -= PI; // #ifdef SLIC3R_DEBUG // printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); // #endif return true; }
inline void polygons_rotate(Polygons &polys, double angle) { for (Polygons::iterator p = polys.begin(); p != polys.end(); ++p) p->rotate(angle); }
void BridgeDetector::coverage(double angle, Polygons* coverage) const { // Clone our expolygon and rotate it so that we work with vertical lines. ExPolygon expolygon = this->expolygon; expolygon.rotate(PI/2.0 - angle, Point(0,0)); /* Outset the bridge expolygon by half the amount we used for detecting anchors; we'll use this one to generate our trapezoids and be sure that their vertices are inside the anchors and not on their contours leading to false negatives. */ ExPolygons grown; offset(expolygon, &grown, this->extrusion_width/2.0); // Compute trapezoids according to a vertical orientation Polygons trapezoids; for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) it->get_trapezoids2(&trapezoids, PI/2.0); // get anchors, convert them to Polygons and rotate them too Polygons anchors; for (ExPolygons::const_iterator anchor = this->_anchors.begin(); anchor != this->_anchors.end(); ++anchor) { Polygons pp = *anchor; for (Polygons::iterator p = pp.begin(); p != pp.end(); ++p) p->rotate(PI/2.0 - angle, Point(0,0)); anchors.insert(anchors.end(), pp.begin(), pp.end()); } Polygons covered; for (Polygons::const_iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { Lines lines = trapezoid->lines(); Lines supported; intersection(lines, anchors, &supported); // not nice, we need a more robust non-numeric check for (size_t i = 0; i < supported.size(); ++i) { if (supported[i].length() < this->extrusion_width) { supported.erase(supported.begin() + i); i--; } } if (supported.size() >= 2) covered.push_back(*trapezoid); } // merge trapezoids and rotate them back Polygons _coverage; union_(covered, &_coverage); for (Polygons::iterator p = _coverage.begin(); p != _coverage.end(); ++p) p->rotate(-(PI/2.0 - angle), Point(0,0)); // intersect trapezoids with actual bridge area to remove extra margins // and append it to result intersection(_coverage, this->expolygon, coverage); /* if (0) { my @lines = map @{$_->lines}, @$trapezoids; $_->rotate(-(PI/2 - $angle), [0,0]) for @lines; require "Slic3r/SVG.pm"; Slic3r::SVG::output( "coverage_" . rad2deg($angle) . ".svg", expolygons => [$self->expolygon], green_expolygons => $self->_anchors, red_expolygons => $coverage, lines => \@lines, ); } */ }