Exemplo n.º 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{
Exemplo n.º 2
0
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;
}
Exemplo n.º 3
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;
}