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{
ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, Polylines &thin_walls) const { // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object ExtrusionEntityCollection coll; for (PerimeterGeneratorLoops::const_iterator loop = loops.begin(); loop != loops.end(); ++loop) { bool is_external = loop->is_external(); ExtrusionRole role; ExtrusionLoopRole loop_role; role = is_external ? erExternalPerimeter : erPerimeter; if (loop->is_internal_contour()) { // Note that we set loop role to ContourInternalPerimeter // also when loop is both internal and external (i.e. // there's only one contour loop). loop_role = elrContourInternalPerimeter; } else { loop_role = elrDefault; } // detect overhanging/bridging perimeters ExtrusionPaths paths; if (this->config->overhangs && this->layer_id > 0 && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { // get non-overhang paths by intersecting this loop with the grown lower slices { Polylines polylines; intersection((Polygons)loop->polygon, this->_lower_slices_p, &polylines); for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { ExtrusionPath path(role); path.polyline = *polyline; path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; path.height = this->layer_height; paths.push_back(path); } } // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter { Polylines polylines; diff((Polygons)loop->polygon, this->_lower_slices_p, &polylines); for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { ExtrusionPath path(erOverhangPerimeter); path.polyline = *polyline; path.mm3_per_mm = this->_mm3_per_mm_overhang; path.width = this->overhang_flow.width; path.height = this->overhang_flow.height; paths.push_back(path); } } // reapply the nearest point search for starting point // We allow polyline reversal because Clipper may have randomly // reversed polylines during clipping. paths = ExtrusionEntityCollection(paths).chained_path(); } else { ExtrusionPath path(role); path.polyline = loop->polygon.split_at_first_point(); path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; path.height = this->layer_height; paths.push_back(path); } coll.append(ExtrusionLoop(paths, loop_role)); } // append thin walls to the nearest-neighbor search (only for first iteration) for (Polylines::const_iterator polyline = thin_walls.begin(); polyline != thin_walls.end(); ++polyline) { ExtrusionPath path(erExternalPerimeter); path.polyline = *polyline; path.mm3_per_mm = this->_mm3_per_mm; path.width = this->perimeter_flow.width; path.height = this->layer_height; coll.append(path); } thin_walls.clear(); // sort entities into a new collection using a nearest-neighbor search, // preserving the original indices which are useful for detecting thin walls ExtrusionEntityCollection sorted_coll; coll.chained_path(&sorted_coll, false, &sorted_coll.orig_indices); // traverse children and build the final collection ExtrusionEntityCollection entities; for (std::vector<size_t>::const_iterator idx = sorted_coll.orig_indices.begin(); idx != sorted_coll.orig_indices.end(); ++idx) { if (*idx >= loops.size()) { // this is a thin wall // let's get it from the sorted collection as it might have been reversed size_t i = idx - sorted_coll.orig_indices.begin(); entities.append(*sorted_coll.entities[i]); } else { const PerimeterGeneratorLoop &loop = loops[*idx]; ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[*idx]); ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls); if (loop.is_contour) { eloop.make_counter_clockwise(); entities.append(children.entities); entities.append(eloop); } else { eloop.make_clockwise(); entities.append(eloop); entities.append(children.entities); } } } return entities; }
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; }