コード例 #1
0
ファイル: ClipperUtils.cpp プロジェクト: prusa3d/Slic3r
//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper 
// conversions and unnecessary Clipper calls.
ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1,
    const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
    Polygons polys;
    for (const ExPolygon &expoly : expolygons)
        append(polys, 
               offset(offset_ex(expoly, delta1, joinType, miterLimit), 
                      delta2, joinType, miterLimit));
    return union_ex(polys);
}
コード例 #2
0
ファイル: ClipperUtils.cpp プロジェクト: jaysuk/Slic3r
void
offset(const Slic3r::Surface &surface, Slic3r::Surfaces &retval, const float delta,
    double scale, ClipperLib::JoinType joinType, double miterLimit)
{
    // perform offset
    Slic3r::ExPolygons expp;
    offset_ex(surface.expolygon, expp, delta, scale, joinType, miterLimit);
    
    // clone the input surface for each expolygon we got
    retval.clear();
    retval.reserve(expp.size());
    for (ExPolygons::iterator it = expp.begin(); it != expp.end(); ++it) {
        Surface s = surface;  // clone
        s.expolygon = *it;
        retval.push_back(s);
    }
}
コード例 #3
0
ファイル: ClipperUtils.cpp プロジェクト: alpha6/Slic3r
Surfaces
offset(const Surface &surface, const float delta,
    double scale, ClipperLib::JoinType joinType, double miterLimit)
{
    // perform offset
    ExPolygons expp = offset_ex(surface.expolygon, delta, scale, joinType, miterLimit);
    
    // clone the input surface for each expolygon we got
    Surfaces retval;
    retval.reserve(expp.size());
    for (ExPolygons::iterator it = expp.begin(); it != expp.end(); ++it) {
        Surface s = surface;  // clone
        s.expolygon = *it;
        retval.push_back(s);
    }
    return retval;
}
コード例 #4
0
ファイル: MotionPlanner.cpp プロジェクト: Salandora/Slic3r
void
MotionPlanner::initialize()
{
    if (this->initialized) return;
    if (this->islands.empty()) return;  // prevent initialization of empty BoundingBox
    
    // loop through islands in order to create inner expolygons and collect their contours
    Polygons outer_holes;
    for (std::vector<MotionPlannerEnv>::iterator island = this->islands.begin(); island != this->islands.end(); ++island) {
        // generate the internal env boundaries by shrinking the island
        // we'll use these inner rings for motion planning (endpoints of the Voronoi-based
        // graph, visibility check) in order to avoid moving too close to the boundaries
        island->env = offset_ex(island->island, -MP_INNER_MARGIN);
        
        // island contours are holes of our external environment
        outer_holes.push_back(island->island.contour);
    }
    
    // generate outer contour as bounding box of everything
    BoundingBox bb;
    for (Polygons::const_iterator contour = outer_holes.begin(); contour != outer_holes.end(); ++contour)
        bb.merge(contour->bounding_box());
    
    // grow outer contour
    Polygons contour = offset(bb.polygon(), +MP_OUTER_MARGIN*2);
    assert(contour.size() == 1);
    
    // make expolygon for outer environment
    ExPolygons outer = diff_ex(contour, outer_holes);
    assert(outer.size() == 1);
    this->outer.island = outer.front();
    
    this->outer.env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN)));
    
    this->graphs.resize(this->islands.size() + 1, NULL);
    this->initialized = true;
}
コード例 #5
0
ファイル: LayerRegion.cpp プロジェクト: jeffkyjin/Slic3r
void
LayerRegion::process_external_surfaces(const Layer* lower_layer)
{
    const Surfaces &surfaces = this->fill_surfaces.surfaces;
    const double margin = scale_(EXTERNAL_INFILL_MARGIN);
    
    SurfaceCollection bottom;
    for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
        if (!surface->is_bottom()) continue;
        
        ExPolygons grown = offset_ex(surface->expolygon, +margin);
        
        /*  detect bridge direction before merging grown surfaces otherwise adjacent bridges
            would get merged into a single one while they need different directions
            also, supply the original expolygon instead of the grown one, because in case
            of very thin (but still working) anchors, the grown expolygon would go beyond them */
        double angle = -1;
        if (lower_layer != NULL) {
            BridgeDetector bd(
                surface->expolygon,
                lower_layer->slices,
                this->flow(frInfill, this->layer()->height, true).scaled_width()
            );
            
            #ifdef SLIC3R_DEBUG
            printf("Processing bridge at layer %zu:\n", this->layer()->id());
            #endif
            
            if (bd.detect_angle()) {
                angle = bd.angle;
            
                if (this->layer()->object()->config.support_material) {
                    Polygons coverage = bd.coverage();
                    this->bridged.insert(this->bridged.end(), coverage.begin(), coverage.end());
                    this->unsupported_bridge_edges.append(bd.unsupported_edges()); 
                }
            }
        }
        
        for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) {
            Surface s       = *surface;
            s.expolygon     = *it;
            s.bridge_angle  = angle;
            bottom.surfaces.push_back(s);
        }
    }
    
    SurfaceCollection top;
    for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
        if (surface->surface_type != stTop) continue;
        
        // give priority to bottom surfaces
        ExPolygons grown = diff_ex(
            offset(surface->expolygon, +margin),
            (Polygons)bottom
        );
        for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) {
            Surface s   = *surface;
            s.expolygon = *it;
            top.surfaces.push_back(s);
        }
    }
    
    /*  if we're slicing with no infill, we can't extend external surfaces
        over non-existent infill */
    SurfaceCollection fill_boundaries;
    if (this->region()->config.fill_density.value > 0) {
        fill_boundaries = SurfaceCollection(surfaces);
    } else {
        for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) {
            if (it->surface_type != stInternal)
                fill_boundaries.surfaces.push_back(*it);
        }
    }
    
    // intersect the grown surfaces with the actual fill boundaries
    SurfaceCollection new_surfaces;
    {
        // merge top and bottom in a single collection
        SurfaceCollection tb = top;
        tb.append(bottom);
        
        // group surfaces
        std::vector<SurfacesConstPtr> groups;
        tb.group(&groups);
        
        for (std::vector<SurfacesConstPtr>::const_iterator g = groups.begin(); g != groups.end(); ++g) {
            Polygons subject;
            for (SurfacesConstPtr::const_iterator s = g->begin(); s != g->end(); ++s)
                append_to(subject, (Polygons)**s);
            
            ExPolygons expp = intersection_ex(
                subject,
                (Polygons)fill_boundaries,
                true // to ensure adjacent expolygons are unified
            );
            
            for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
                Surface s = *g->front();
                s.expolygon = *ex;
                new_surfaces.surfaces.push_back(s);
            }
        }
    }
    
    /* subtract the new top surfaces from the other non-top surfaces and re-add them */
    {
        SurfaceCollection other;
        for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) {
            if (s->surface_type != stTop && !s->is_bottom())
                other.surfaces.push_back(*s);
        }
        
        // group surfaces
        std::vector<SurfacesConstPtr> groups;
        other.group(&groups);
        
        for (std::vector<SurfacesConstPtr>::const_iterator g = groups.begin(); g != groups.end(); ++g) {
            Polygons subject;
            for (SurfacesConstPtr::const_iterator s = g->begin(); s != g->end(); ++s)
                append_to(subject, (Polygons)**s);
            
            ExPolygons expp = diff_ex(
                subject,
                (Polygons)new_surfaces
            );
            
            for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
                Surface s = *g->front();
                s.expolygon = *ex;
                new_surfaces.surfaces.push_back(s);
            }
        }
    }
    
    this->fill_surfaces = new_surfaces;
}
コード例 #6
0
ファイル: FillGyroid.cpp プロジェクト: Sebastianv650/Slic3r
void FillGyroid::_fill_surface_single(
    const FillParams                &params, 
    unsigned int                     thickness_layers,
    const std::pair<float, Point>   &direction, 
    ExPolygon                       &expolygon, 
    Polylines                       &polylines_out)
{
    // no rotation is supported for this infill pattern
    BoundingBox bb = expolygon.contour.bounding_box();
    coord_t     distance = coord_t(scale_(this->spacing) / (params.density*this->scaling));

    // align bounding box to a multiple of our grid module
    bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
    
    // generate pattern
    Polylines   polylines = make_gyroid_waves(
        scale_(this->z),
        params.density*this->scaling,
        this->spacing,
        ceil(bb.size().x / distance) + 1.,
        ceil(bb.size().y / distance) + 1.);
    
    // move pattern in place
    for (Polyline &polyline : polylines)
        polyline.translate(bb.min.x, bb.min.y);

    // clip pattern to boundaries
    polylines = intersection_pl(polylines, (Polygons)expolygon);

    // connect lines
    if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
        ExPolygon expolygon_off;
        {
            ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON);
            if (! expolygons_off.empty()) {
                // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
                assert(expolygons_off.size() == 1);
                std::swap(expolygon_off, expolygons_off.front());
            }
        }
        Polylines chained = PolylineCollection::chained_path_from(
            std::move(polylines), 
            PolylineCollection::leftmost_point(polylines), false); // reverse allowed
        bool first = true;
        for (Polyline &polyline : chained) {
            if (! first) {
                // Try to connect the lines.
                Points &pts_end = polylines_out.back().points;
                const Point &first_point = polyline.points.front();
                const Point &last_point = pts_end.back();
                // TODO: we should also check that both points are on a fill_boundary to avoid 
                // connecting paths on the boundaries of internal regions
                // TODO: avoid crossing current infill path
                if (first_point.distance_to(last_point) <= 5 * distance && 
                    expolygon_off.contains(Line(last_point, first_point))) {
                    // Append the polyline.
                    pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
                    continue;
                }
            }
            // The lines cannot be connected.
            polylines_out.emplace_back(std::move(polyline));
            first = false;
        }
    }
}
コード例 #7
0
ファイル: ClipperUtils.cpp プロジェクト: alpha6/Slic3r
ExPolygons
offset_ex(const ExPolygons &expolygons, const float delta,
    double scale, ClipperLib::JoinType joinType, double miterLimit)
{
    return offset_ex(to_polygons(expolygons), delta, scale, joinType, miterLimit);
}
コード例 #8
0
ファイル: MotionPlanner.cpp プロジェクト: Salandora/Slic3r
Polyline
MotionPlanner::shortest_path(const Point &from, const Point &to)
{
    // if we have an empty configuration space, return a straight move
    if (this->islands.empty())
        return Line(from, to);
    
    // Are both points in the same island?
    int island_idx = -1;
    for (std::vector<MotionPlannerEnv>::const_iterator island = this->islands.begin(); island != this->islands.end(); ++island) {
        if (island->island.contains(from) && island->island.contains(to)) {
            // since both points are in the same island, is a direct move possible?
            // if so, we avoid generating the visibility environment
            if (island->island.contains(Line(from, to)))
                return Line(from, to);
            
            island_idx = island - this->islands.begin();
            break;
        }
    }
    
    // lazy generation of configuration space
    this->initialize();
    
    // get environment
    MotionPlannerEnv env = this->get_env(island_idx);
    if (env.env.expolygons.empty()) {
        // if this environment is empty (probably because it's too small), perform straight move
        // and avoid running the algorithms on empty dataset
        return Line(from, to);
    }
    
    // Now check whether points are inside the environment.
    Point inner_from = from;
    Point inner_to   = to;
    
    if (island_idx == -1) {
        // TODO: instead of using the nearest_env_point() logic, we should
        // create a temporary graph where we connect 'from' and 'to' to the
        // nodes which don't require more than one crossing, and let Dijkstra
        // figure out the entire path - this should also replace the call to
        // find_node() below
        if (!env.island.contains(inner_from)) {
            // Find the closest inner point to start from.
            inner_from = env.nearest_env_point(from, to);
        }
        if (!env.island.contains(inner_to)) {
            // Find the closest inner point to start from.
            inner_to = env.nearest_env_point(to, inner_from);
        }
    }
    
    // perform actual path search
    MotionPlannerGraph* graph = this->init_graph(island_idx);
    Polyline polyline = graph->shortest_path(graph->find_node(inner_from), graph->find_node(inner_to));
    
    polyline.points.insert(polyline.points.begin(), from);
    polyline.points.push_back(to);
    
    {
        // grow our environment slightly in order for simplify_by_visibility()
        // to work best by considering moves on boundaries valid as well
        ExPolygonCollection grown_env(offset_ex((Polygons)env.env, +SCALED_EPSILON));
        
        if (island_idx == -1) {
            /*  If 'from' or 'to' are not inside our env, they were connected using the 
                nearest_env_point() search which maybe produce ugly paths since it does not
                include the endpoint in the Dijkstra search; the simplify_by_visibility() 
                call below will not work in many cases where the endpoint is not contained in
                grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN,
                which may not be enough for, say, including a skirt point). So we prune
                the extra points manually. */
            if (!grown_env.contains(from)) {
                // delete second point while the line connecting first to third crosses the
                // boundaries as many times as the current first to second
                while (polyline.points.size() > 2 && intersection((Lines)Line(from, polyline.points[2]), grown_env).size() == 1) {
                    polyline.points.erase(polyline.points.begin() + 1);
                }
            }
            if (!grown_env.contains(to)) {
                while (polyline.points.size() > 2 && intersection((Lines)Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) {
                    polyline.points.erase(polyline.points.end() - 2);
                }
            }
        }
        
        // remove unnecessary vertices
        // Note: this is computationally intensive and does not look very necessary
        // now that we prune the endpoints with the logic above,
        // so we comment it for now until a good test case arises
        //polyline.simplify_by_visibility(grown_env);
    
        /*
        SVG svg("shortest_path.svg");
        svg.draw(grown_env.expolygons);
        svg.arrows = false;
        for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) {
            Point a = graph->nodes[it - graph->adjacency_list.begin()];
            for (std::vector<MotionPlannerGraph::neighbor>::const_iterator n = it->begin(); n != it->end(); ++n) {
                Point b = graph->nodes[n->target];
                svg.draw(Line(a, b));
            }
        }
        svg.arrows = true;
        svg.draw(from);
        svg.draw(inner_from, "red");
        svg.draw(to);
        svg.draw(inner_to, "red");
        svg.draw(polyline, "red");
        svg.Close();
        */
    }
    
    return polyline;
}
コード例 #9
0
ファイル: SLAPrint.cpp プロジェクト: jeffkyjin/Slic3r
void
SLAPrint::slice()
{
    TriangleMesh mesh = this->model->mesh();
    mesh.repair();
    
    // align to origin taking raft into account
    this->bb = mesh.bounding_box();
    if (this->config.raft_layers > 0) {
        this->bb.min.x -= this->config.raft_offset.value;
        this->bb.min.y -= this->config.raft_offset.value;
        this->bb.max.x += this->config.raft_offset.value;
        this->bb.max.y += this->config.raft_offset.value;
    }
    mesh.translate(0, 0, -bb.min.z);
    this->bb.translate(0, 0, -bb.min.z);
    
    // if we are generating a raft, first_layer_height will not affect mesh slicing
    const float lh       = this->config.layer_height.value;
    const float first_lh = this->config.first_layer_height.value;
    
    // generate the list of Z coordinates for mesh slicing
    // (we slice each layer at half of its thickness)
    this->layers.clear();
    {
        const float first_slice_lh = (this->config.raft_layers > 0) ? lh : first_lh;
        this->layers.push_back(Layer(first_slice_lh/2, first_slice_lh));
    }
    while (this->layers.back().print_z + lh/2 <= mesh.stl.stats.max.z) {
        this->layers.push_back(Layer(this->layers.back().print_z + lh/2, this->layers.back().print_z + lh));
    }
    
    // perform slicing and generate layers
    {
        std::vector<float> slice_z;
        for (size_t i = 0; i < this->layers.size(); ++i)
            slice_z.push_back(this->layers[i].slice_z);
        
        std::vector<ExPolygons> slices;
        TriangleMeshSlicer(&mesh).slice(slice_z, &slices);
        
        for (size_t i = 0; i < slices.size(); ++i)
            this->layers[i].slices.expolygons = slices[i];
    }
    
    // generate infill
    if (this->config.fill_density < 100) {
        std::auto_ptr<Fill> fill(Fill::new_from_type(this->config.fill_pattern.value));
        fill->bounding_box.merge(Point::new_scale(bb.min.x, bb.min.y));
        fill->bounding_box.merge(Point::new_scale(bb.max.x, bb.max.y));
        fill->spacing       = this->config.get_abs_value("infill_extrusion_width", this->config.layer_height.value);
        fill->angle         = Geometry::deg2rad(this->config.fill_angle.value);
        fill->density       = this->config.fill_density.value/100;
        
        parallelize<size_t>(
            0,
            this->layers.size()-1,
            boost::bind(&SLAPrint::_infill_layer, this, _1, fill.get()),
            this->config.threads.value
        );
    }
    
    // generate support material
    this->sm_pillars.clear();
    ExPolygons overhangs;
    if (this->config.support_material) {
        // flatten and merge all the overhangs
        {
            Polygons pp;
            for (std::vector<Layer>::const_iterator it = this->layers.begin()+1; it != this->layers.end(); ++it)
                pp += diff(it->slices, (it - 1)->slices);
            overhangs = union_ex(pp);
        }
        
        // generate points following the shape of each island
        Points pillars_pos;
        const coordf_t spacing = scale_(this->config.support_material_spacing);
        const coordf_t radius  = scale_(this->sm_pillars_radius());
        for (ExPolygons::const_iterator it = overhangs.begin(); it != overhangs.end(); ++it) {
            // leave a radius/2 gap between pillars and contour to prevent lateral adhesion
            for (float inset = radius * 1.5;; inset += spacing) {
                // inset according to the configured spacing
                Polygons curr = offset(*it, -inset);
                if (curr.empty()) break;
                
                // generate points along the contours
                for (Polygons::const_iterator pg = curr.begin(); pg != curr.end(); ++pg) {
                    Points pp = pg->equally_spaced_points(spacing);
                    for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p)
                        pillars_pos.push_back(*p);
                }
            }
        }
        
        // for each pillar, check which layers it applies to
        for (Points::const_iterator p = pillars_pos.begin(); p != pillars_pos.end(); ++p) {
            SupportPillar pillar(*p);
            bool object_hit = false;
            
            // check layers top-down
            for (int i = this->layers.size()-1; i >= 0; --i) {
                // check whether point is void in this layer
                if (!this->layers[i].slices.contains(*p)) {
                    // no slice contains the point, so it's in the void
                    if (pillar.top_layer > 0) {
                        // we have a pillar, so extend it
                        pillar.bottom_layer = i + this->config.raft_layers;
                    } else if (object_hit) {
                        // we don't have a pillar and we're below the object, so create one
                        pillar.top_layer = i + this->config.raft_layers;
                    }
                } else {
                    if (pillar.top_layer > 0) {
                        // we have a pillar which is not needed anymore, so store it and initialize a new potential pillar
                        this->sm_pillars.push_back(pillar);
                        pillar = SupportPillar(*p);
                    }
                    object_hit = true;
                }
            }
            if (pillar.top_layer > 0) this->sm_pillars.push_back(pillar);
        }
    }
    
    // generate a solid raft if requested
    // (do this after support material because we take support material shape into account)
    if (this->config.raft_layers > 0) {
        ExPolygons raft = this->layers.front().slices + overhangs;  // take support material into account
        raft = offset_ex(raft, scale_(this->config.raft_offset));
        for (int i = this->config.raft_layers; i >= 1; --i) {
            this->layers.insert(this->layers.begin(), Layer(0, first_lh + lh * (i-1)));
            this->layers.front().slices = raft;
        }
        
        // prepend total raft height to all sliced layers
        for (size_t i = this->config.raft_layers; i < this->layers.size(); ++i)
            this->layers[i].print_z += first_lh + lh * (this->config.raft_layers-1);
    }
}
コード例 #10
0
ファイル: SVGExport.cpp プロジェクト: sapir/Slic3r
void
SVGExport::writeSVG(const std::string &outputfile)
{
    // align to origin taking raft into account
    BoundingBoxf3 bb = this->mesh.bounding_box();
    if (this->config.raft_layers > 0) {
        bb.min.x -= this->config.raft_offset.value;
        bb.min.y -= this->config.raft_offset.value;
        bb.max.x += this->config.raft_offset.value;
        bb.max.y += this->config.raft_offset.value;
    }
    this->mesh.translate(-bb.min.x, -bb.min.y, -bb.min.z);  // align to origin
    bb.translate(-bb.min.x, -bb.min.y, -bb.min.z);          // align to origin
    const Sizef3 size = bb.size();
    
    // if we are generating a raft, first_layer_height will not affect mesh slicing
    const float lh = this->config.layer_height.value;
    const float first_lh = this->config.first_layer_height.value;
    
    // generate the list of Z coordinates for mesh slicing
    // (we slice each layer at half of its thickness)
    std::vector<float> slice_z, layer_z;
    {
        const float first_slice_lh = (this->config.raft_layers > 0) ? lh : first_lh;
        slice_z.push_back(first_slice_lh/2);
        layer_z.push_back(first_slice_lh);
    }
    while (layer_z.back() + lh/2 <= this->mesh.stl.stats.max.z) {
        slice_z.push_back(layer_z.back() + lh/2);
        layer_z.push_back(layer_z.back() + lh);
    }
    
    // perform the slicing
    std::vector<ExPolygons> layers;
    TriangleMeshSlicer(&this->mesh).slice(slice_z, &layers);
    
    // generate a solid raft if requested
    if (this->config.raft_layers > 0) {
        ExPolygons raft = offset_ex(layers.front(), scale_(this->config.raft_offset));
        for (int i = this->config.raft_layers; i >= 1; --i) {
            layer_z.insert(layer_z.begin(), first_lh + lh * (i-1));
            layers.insert(layers.begin(), raft);
        }
        
        // prepend total raft height to all sliced layers
        for (int i = this->config.raft_layers; i < layer_z.size(); ++i)
            layer_z[i] += first_lh + lh * (this->config.raft_layers-1);
    }
    
    // generate support material
    std::vector<Points> support_material(layers.size());
    if (this->config.support_material) {
        // generate a grid of points according to the configured spacing,
        // covering the entire object bounding box
        Points support_material_points;
        for (coordf_t x = bb.min.x; x <= bb.max.x; x += this->config.support_material_spacing) {
            for (coordf_t y = bb.min.y; y <= bb.max.y; y += this->config.support_material_spacing) {
                support_material_points.push_back(Point(scale_(x), scale_(y)));
            }
        }
        
        // check overhangs, starting from the upper layer, and detect which points apply 
        // to each layer
        ExPolygons overhangs;
        for (int i = layer_z.size()-1; i >= 0; --i) {
            overhangs = diff_ex(union_(overhangs, layers[i+1]), layers[i]);
            for (Points::const_iterator it = support_material_points.begin(); it != support_material_points.end(); ++it) {
                for (ExPolygons::const_iterator e = overhangs.begin(); e != overhangs.end(); ++e) {
                    if (e->contains(*it)) {
                        support_material[i].push_back(*it);
                        break;
                    }
                }
            }
        }
    }
    
    double support_material_radius = this->config.support_material_extrusion_width.get_abs_value(this->config.layer_height)/2;
    
    FILE* f = fopen(outputfile.c_str(), "w");
    fprintf(f,
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
        "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
        "<svg width=\"%f\" height=\"%f\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:slic3r=\"http://slic3r.org/namespaces/slic3r\" viewport-fill=\"black\">\n"
        "<!-- Generated using Slic3r %s http://slic3r.org/ -->\n"
        , size.x, size.y, SLIC3R_VERSION);
    
    for (size_t i = 0; i < layer_z.size(); ++i) {
        fprintf(f, "\t<g id=\"layer%zu\" slic3r:z=\"%0.4f\">\n", i, layer_z[i]);
        for (ExPolygons::const_iterator it = layers[i].begin(); it != layers[i].end(); ++it) {
            std::string pd;
            Polygons pp = *it;
            for (Polygons::const_iterator mp = pp.begin(); mp != pp.end(); ++mp) {
                std::ostringstream d;
                d << "M ";
                for (Points::const_iterator p = mp->points.begin(); p != mp->points.end(); ++p) {
                    d << unscale(p->x) << " ";
                    d << unscale(p->y) << " ";
                }
                d << "z";
                pd += d.str() + " ";
            }
            fprintf(f,"\t\t<path d=\"%s\" style=\"fill: %s; stroke: %s; stroke-width: %s; fill-type: evenodd\" slic3r:area=\"%0.4f\" />\n",
                pd.c_str(), "white", "black", "0", unscale(unscale(it->area()))
            );
        }
        for (Points::const_iterator it = support_material[i].begin(); it != support_material[i].end(); ++it) {
            fprintf(f,"\t\t<circle cx=\"%f\" cy=\"%f\" r=\"%f\" stroke-width=\"0\" fill=\"white\" slic3r:type=\"support\" />\n",
                unscale(it->x), unscale(it->y), support_material_radius
            );
        }
        fprintf(f,"\t</g>\n");
    }
    fprintf(f,"</svg>\n");
}