Exemple #1
0
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{
Exemple #2
0
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;
}
Exemple #4
0
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,
        );
    }
    */
}