ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) { ClipperLib::Paths retval; for (Polylines::const_iterator it = input.begin(); it != input.end(); ++it) retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(*it)); return retval; }
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) { const double scaleFactor = scale_(line_spacing) / density_adjusted; //scale factor for 5% : 8 712 388 // 1z = 10^-6 mm ? const double z = gridZ / scaleFactor; const double z_sin = sin(z); const double z_cos = cos(z); bool vertical = (std::abs(z_sin) <= std::abs(z_cos)); double lower_bound = 0.; double upper_bound = height; bool flip = true; if (vertical) { flip = false; lower_bound = -M_PI; upper_bound = width - M_PI_2; std::swap(width,height); } std::vector<Vec2d> one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time Polylines result; for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); flip = !flip; // even polylines are a bit shifted one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); return result; }
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); extrusion_path->polyline = *it_polyline; } }
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) { ExtrusionPath* path = this->clone(); path->polyline = *it; collection->entities.push_back(path); } }
Polylines PolylineCollection::chained_path(const Polylines &src, bool no_reverse) { return (src.empty() || src.front().points.empty()) ? Polylines() : _chained_path_from(src, src.front().first_point(), no_reverse #if SLIC3R_CPPVER >= 11 , false #endif ); }
Point PolylineCollection::leftmost_point(const Polylines &polylines) { if (polylines.empty()) CONFESS("leftmost_point() called on empty PolylineCollection"); Polylines::const_iterator it = polylines.begin(); Point p = it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { Point p2 = it->leftmost_point(); if (p2.x < p.x) p = p2; } return p; }
inline Polylines to_polylines(Polygons &&polys) { Polylines polylines; polylines.assign(polys.size(), Polyline()); size_t idx = 0; for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) { Polyline &pl = polylines[idx ++]; pl.points = std::move(it->points); pl.points.push_back(it->points.front()); } assert(idx == polylines.size()); return polylines; }
ExtrusionEntityCollection PerimeterGenerator::_fill_gaps(double min, double max, double w, const Polygons &gaps) const { ExtrusionEntityCollection coll; min *= (1 - INSET_OVERLAP_TOLERANCE); ExPolygons curr = diff_ex( offset2(gaps, -min/2, +min/2), offset2(gaps, -max/2, +max/2), true ); Polylines polylines; for (ExPolygons::const_iterator ex = curr.begin(); ex != curr.end(); ++ex) ex->medial_axis(max, min/2, &polylines); if (polylines.empty()) return coll; #ifdef SLIC3R_DEBUG if (!curr.empty()) printf(" %zu gaps filled with extrusion width = %f\n", curr.size(), w); #endif //my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w); Flow flow( w, this->layer_height, this->solid_infill_flow.nozzle_diameter ); double mm3_per_mm = flow.mm3_per_mm(); for (Polylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { ExtrusionPath path(erGapFill); path.polyline = *p; path.mm3_per_mm = mm3_per_mm; path.width = flow.width; path.height = this->layer_height; if (p->is_valid() && p->first_point().coincides_with(p->last_point())) { // since medial_axis() now returns only Polyline objects, detect loops here ExtrusionLoop loop; loop.paths.push_back(path); coll.append(loop); } else { coll.append(path); } } return coll; }
inline Polylines to_polylines(ExPolygon &&src) { Polylines polylines; polylines.assign(src.holes.size() + 1, Polyline()); size_t idx = 0; Polyline &pl = polylines[idx ++]; pl.points = std::move(src.contour.points); pl.points.push_back(pl.points.front()); for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) { Polyline &pl = polylines[idx ++]; pl.points = std::move(ith->points); pl.points.push_back(ith->points.front()); } assert(idx == polylines.size()); return polylines; }
Polylines PolylineCollection::_chained_path_from( const Polylines &src, Point start_near, bool no_reverse, bool move_from_src) { std::vector<Chaining> endpoints; endpoints.reserve(src.size()); for (size_t i = 0; i < src.size(); ++ i) { Chaining c; c.first = src[i].first_point(); if (! no_reverse) c.last = src[i].last_point(); c.idx = i; endpoints.push_back(c); } Polylines retval; while (! endpoints.empty()) { // find nearest point int endpoint_index = nearest_point_index<double>(endpoints, start_near, no_reverse); assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); if (move_from_src) { retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); } else { retval.push_back(src[endpoints[endpoint_index/2].idx]); } if (endpoint_index & 1) retval.back().reverse(); endpoints.erase(endpoints.begin() + endpoint_index/2); start_near = retval.back().last_point(); } return retval; }
inline Polylines to_polylines(const ExPolygons &src) { Polylines polylines; polylines.assign(number_polygons(src), Polyline()); size_t idx = 0; for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) { Polyline &pl = polylines[idx ++]; pl.points = it->contour.points; pl.points.push_back(pl.points.front()); for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) { Polyline &pl = polylines[idx ++]; pl.points = ith->points; pl.points.push_back(ith->points.front()); } } assert(idx == polylines.size()); return polylines; }
int main() { // Domain (Warning: Sphere_3 constructor uses squared radius !) Mesh_domain domain(sphere_function, K::Sphere_3(Point(1, 0, 0), 6.)); // Mesh criteria Mesh_criteria criteria(edge_size = 0.15, facet_angle = 25, facet_size = 0.15, cell_radius_edge_ratio = 2, cell_size = 0.15); // Create edge that we want to preserve Polylines polylines (1); Polyline_3& polyline = polylines.front(); for(int i = 0; i < 360; ++i) { Point p (1, std::cos(i*CGAL_PI/180), std::sin(i*CGAL_PI/180)); polyline.push_back(p); } polyline.push_back(polyline.front()); // close the line // Insert edge in domain domain.add_features(polylines.begin(), polylines.end()); // Mesh generation without feature preservation C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria, CGAL::parameters::no_features()); std::ofstream medit_file("out-no-protection.mesh"); c3t3.output_to_medit(medit_file); medit_file.close(); c3t3.clear(); // Mesh generation with feature preservation c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria); // Output medit_file.open("out-with-protection.mesh"); c3t3.output_to_medit(medit_file); medit_file.close(); return 0; }
Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, bool safety_offset_) { // convert Lines to Polylines Polylines polylines; polylines.reserve(subject.size()); for (const Line &line : subject) polylines.emplace_back(Polyline(line.a, line.b)); // perform operation polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); // convert Polylines to Lines Lines retval; for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) retval.emplace_back(polyline->operator Line()); return retval; }
Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, bool safety_offset_) { // convert Lines to Polylines Polylines polylines; polylines.reserve(subject.size()); for (Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) polylines.push_back(*line); // perform operation polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); // convert Polylines to Lines Lines retval; for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) retval.push_back(*polyline); return retval; }
int main() { // Define functions Function f1 = cube_function_1; Function f2 = cube_function_2; Function_vector v; v.push_back(f1); v.push_back(f2); std::vector<std::string> vps; vps.push_back("--"); // Domain (Warning: Sphere_3 constructor uses square radius !) Mesh_domain_with_features domain(Function_wrapper(v, vps), K::Sphere_3(CGAL::ORIGIN, 5.*5.)); Polylines polylines; create_polylines(polylines); domain.add_features(polylines.begin(),polylines.end()); // Set mesh criteria Mesh_criteria criteria(edge_size = 0.15, facet_angle = 30, facet_size = 0.2, cell_radius_edge_ratio = 2, cell_size = 0.4); // Mesh generation C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria, no_exude(), no_perturb()); // Perturbation (maximum cpu time: 10s, targeted dihedral angle: default) CGAL::perturb_mesh_3(c3t3, domain, time_limit = 10); // Exudation CGAL::exude_mesh_3(c3t3,12); // Output std::ofstream medit_file("out_cubes_intersection_with_features.mesh"); CGAL::output_to_medit(medit_file, c3t3); return 0; }
static Polylines make_gyroid_waves(double gridZ, double density, double layer_width, double width, double height) { double scaleFactor = scale_(layer_width) / density; double segmentSize = 0.5 * density; //scale factor for 5% : 8 712 388 // 1z = 10^-6 mm ? double z = gridZ / scaleFactor; double z_sin = sin(z); double z_cos = cos(z); Polylines result; if (abs(z_sin) <= abs(z_cos)) { // Vertical wave double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.); bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0; for (; x0 < width - 0.5 * M_PI; x0 += M_PI, flip = ! flip) result.emplace_back(make_wave_vertical(width, height, x0, segmentSize, scaleFactor, z_cos, z_sin, flip)); } else { // Horizontal wave bool flip = true; for (double y0 = 0.; y0 < width; y0 += M_PI, flip = !flip) result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip)); } return result; }
void Octree2::build(const Polylines& lines, const BoundingBox<float2>* customBB) { using namespace std; //------------------ // Initialize OpenCL //------------------ // #ifdef __OPEN_CL_SUPPORT__ // static bool initialized = false; // if (options.gpu && !initialized) { // OpenCLInit(2, o, options.opencl_log); // initialized = true; // } // #endif karras_points.clear(); bb = BoundingBox<float2>(); extra_qpoints.clear(); octree.clear(); const vector<vector<float2>>& polygons = lines.getPolygons(); if (polygons.empty()) { buildOctVertices(); return; } // Get all vertices into a 1D array (karras_points). for (int i = 0; i < polygons.size(); ++i) { const vector<float2>& polygon = polygons[i]; for (int j = 0; j < polygon.size()-1; ++j) { karras_points.push_back(polygon[j]); } karras_points.push_back(polygon.back()); } // Compute bounding box if (customBB) { bb = *customBB; } else { for (int i = 0; i < karras_points.size(); ++i) { bb(karras_points[i]); } } // Karras iterations vector<intn> qpoints = Karras::Quantize(karras_points, resln); int iterations = 0; do { qpoints.insert(qpoints.end(), extra_qpoints.begin(), extra_qpoints.end()); for (const intn& qp : extra_qpoints) { karras_points.push_back(oct2Obj(qp)); } extra_qpoints.clear(); if (qpoints.size() > 1) { octree = Karras::BuildOctreeInParallel(qpoints, resln, true); } else { octree.clear(); } //FindMultiCells(lines); ++iterations; } while (iterations < options.karras_iterations && !extra_qpoints.empty()); //cout << "Karras iterations: " << iterations << endl; // Count the number of cells with multiple intersections int count = 0; for (int i = 0; i < cell_intersections.size(); ++i) { for (int j = 0; j < 4; ++j) { if (cell_intersections[i].is_multi(j)) { ++count; } } } //cout << "Number of multi-intersection cells: " << count << endl; // todo: setup vertices on GPU for rendering buildOctVertices(); //------------------ // Cleanup OpenCL //------------------ // #ifdef __OPEN_CL_SUPPORT__ // if (options.gpu) { // OpenCLCleanup(); // } // #endif }
void FillGyroid::_fill_surface_single( const FillParams ¶ms, 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; } } }
void SVG::draw(const Polylines &polylines, std::string stroke) { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) this->draw(*it, fill); }
void Octree2::FindMultiCells(const Polylines& lines) { using namespace Karras; cell_intersections.clear(); cell_intersections.resize(octree.size(), CellIntersections()); MCData data(cell_intersections); const vector<vector<float2>>& polygons = lines.getPolygons();; // For each line segment in each polygon do a cell walk, updating // the cell_intersections structure. intersections.clear(); for (int j = 0; j < polygons.size(); ++j) { const std::vector<float2>& polygon = polygons[j]; data.cur_label = j; for (int i = 0; i < polygon.size() - 1; ++i) { const floatn a = obj2Oct(polygon[i]); const floatn b = obj2Oct(polygon[i+1]); // Visit each octree cell intersected by segment a-b. The visitor // is MCCallback. CellWalk(a, b, octree, resln, MCCallback, &data); vector<OctreeUtils::CellIntersection> local = Walk(a, b); for (const OctreeUtils::CellIntersection& ci : local) { intersections.push_back(ci.p); } } } // Find points that we want to add in order to run a more effective // Karras octree construction. extra_qpoints.clear(); _origins.clear(); _lengths.clear(); for (int i = 0; i < octree.size(); ++i) { const CellIntersections& intersection = cell_intersections[i]; for (int octant = 0; octant < (1<<DIM); ++octant) { const int num_labels = intersection.num_labels(octant); // cout << "cell = " << i << "/" << octant << ": " << num_labels << endl; // if (intersection.is_multi(octant)) { // { for (int j = 0; j < num_labels; ++j) { // for (int j = 0; j < 1; ++j) { for (int k = j+1; k < num_labels; ++k) { // for (int k = j+1; k < std::min(2, num_labels); ++k) { // We'll compare segments at j and k. FloatSegment segs[2] = { intersection.seg(j, octant), intersection.seg(k, octant) }; vector<floatn> samples; vector<floatn> origins; vector<float> lengths; if (!Geom::multi_intersection(segs[0], segs[1])) { try { if (i == fnode.get_parent_idx() && octant == fnode.get_octant()) { cout << "here in FitBoxes" << endl; write_seg(segs[0], "seg0.dat"); write_seg(segs[1], "seg1.dat"); } Geom::FitBoxes(segs[0], segs[1], 1, &samples, &origins, &lengths); } catch(logic_error& e) { cerr << "segments: " << segs[0] << " " << segs[1] << endl; cerr << "labels: " << intersection.label(0, octant) << " " << intersection.label(1, octant) << endl; write_seg(segs[0], "seg0.dat"); write_seg(segs[1], "seg1.dat"); throw e; } } _origins.insert(_origins.end(), origins.begin(), origins.end()); _lengths.insert(_lengths.end(), lengths.begin(), lengths.end()); for (const floatn& sample : samples) { extra_qpoints.push_back(convert_intn(sample)); } } } } } }
void PolylineCollection::append(const Polylines &pp) { this->polylines.insert(this->polylines.end(), pp.begin(), pp.end()); }
/// The LayerRegion at this point of time may contain /// surfaces of various types (internal/bridge/top/bottom/solid). /// The infills are generated on the groups of surfaces with a compatible type. /// Fills an array of ExtrusionPathCollection objects containing the infills generated now /// and the thin fills generated by generate_perimeters(). void LayerRegion::make_fill() { this->fills.clear(); const double fill_density = this->region()->config.fill_density; const Flow infill_flow = this->flow(frInfill); const Flow solid_infill_flow = this->flow(frSolidInfill); const Flow top_solid_infill_flow = this->flow(frTopSolidInfill); const coord_t perimeter_spacing = this->flow(frPerimeter).scaled_spacing(); SurfaceCollection surfaces; // merge adjacent surfaces // in case of bridge surfaces, the ones with defined angle will be attached to the ones // without any angle (shouldn't this logic be moved to process_external_surfaces()?) { Polygons polygons_bridged; polygons_bridged.reserve(this->fill_surfaces.surfaces.size()); for (Surfaces::const_iterator it = this->fill_surfaces.surfaces.begin(); it != this->fill_surfaces.surfaces.end(); ++it) if (it->is_bridge() && it->bridge_angle >= 0) append_to(polygons_bridged, (Polygons)*it); // group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle) // group is of type SurfaceCollection // FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions. std::vector<SurfacesConstPtr> groups; this->fill_surfaces.group(&groups); // merge compatible solid groups (we can generate continuous infill for them) { // cache flow widths and patterns used for all solid groups // (we'll use them for comparing compatible groups) std::vector<SurfaceGroupAttrib> group_attrib(groups.size()); for (size_t i = 0; i < groups.size(); ++i) { const Surface &surface = *groups[i].front(); // we can only merge solid non-bridge surfaces, so discard // non-solid or bridge surfaces if (!surface.is_solid() || surface.is_bridge()) continue; group_attrib[i].is_solid = true; group_attrib[i].fw = (surface.is_top()) ? top_solid_infill_flow.width : solid_infill_flow.width; group_attrib[i].pattern = surface.is_top() ? this->region()->config.top_infill_pattern.value : surface.is_bottom() ? this->region()->config.bottom_infill_pattern.value : ipRectilinear; } // Loop through solid groups, find compatible groups and append them to this one. for (size_t i = 0; i < groups.size(); ++i) { if (!group_attrib[i].is_solid) continue; for (size_t j = i + 1; j < groups.size();) { if (group_attrib[i] == group_attrib[j]) { // groups are compatible, merge them append_to(groups[i], groups[j]); groups.erase(groups.begin() + j); group_attrib.erase(group_attrib.begin() + j); } else { ++j; } } } } // Give priority to oriented bridges. Process the bridges in the first round, the rest of the surfaces in the 2nd round. for (size_t round = 0; round < 2; ++ round) { for (std::vector<SurfacesConstPtr>::const_iterator it_group = groups.begin(); it_group != groups.end(); ++ it_group) { const SurfacesConstPtr &group = *it_group; const bool is_oriented_bridge = group.front()->is_bridge() && group.front()->bridge_angle >= 0; if (is_oriented_bridge != (round == 0)) continue; // Make a union of polygons defining the infiill regions of a group, use a safety offset. Polygons union_p = union_(to_polygons(group), true); // Subtract surfaces having a defined bridge_angle from any other, use a safety offset. if (!is_oriented_bridge && !polygons_bridged.empty()) union_p = diff(union_p, polygons_bridged, true); // subtract any other surface already processed //FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice! surfaces.append( diff_ex(union_p, to_polygons(surfaces), true), *group.front() // template ); } } } // we need to detect any narrow surfaces that might collapse // when adding spacing below // such narrow surfaces are often generated in sloping walls // by bridge_over_infill() and combine_infill() as a result of the // subtraction of the combinable area from the layer infill area, // which leaves small areas near the perimeters // we are going to grow such regions by overlapping them with the void (if any) // TODO: detect and investigate whether there could be narrow regions without // any void neighbors { coord_t distance_between_surfaces = std::max( std::max(infill_flow.scaled_spacing(), solid_infill_flow.scaled_spacing()), top_solid_infill_flow.scaled_spacing() ); Polygons surfaces_polygons = (Polygons)surfaces; Polygons collapsed = diff( surfaces_polygons, offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), true ); Polygons to_subtract; surfaces.filter_by_type((stInternal | stVoid), &to_subtract); append_to(to_subtract, collapsed); surfaces.append( intersection_ex( offset(collapsed, distance_between_surfaces), to_subtract, true ), (stInternal | stSolid) ); } if (false) { // require "Slic3r/SVG.pm"; // Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", // expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], // red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], // ); } for (Surfaces::const_iterator surface_it = surfaces.surfaces.begin(); surface_it != surfaces.surfaces.end(); ++surface_it) { const Surface &surface = *surface_it; if (surface.surface_type == (stInternal | stVoid)) continue; InfillPattern fill_pattern = this->region()->config.fill_pattern.value; double density = fill_density; FlowRole role = (surface.is_top()) ? frTopSolidInfill : surface.is_solid() ? frSolidInfill : frInfill; const bool is_bridge = this->layer()->id() > 0 && surface.is_bridge(); if (surface.is_solid()) { density = 100.; fill_pattern = (surface.is_top()) ? this->region()->config.top_infill_pattern.value : (surface.is_bottom() && !is_bridge) ? this->region()->config.bottom_infill_pattern.value : ipRectilinear; } else if (density <= 0) continue; // get filler object #if SLIC3R_CPPVER >= 11 std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(fill_pattern)); #else std::auto_ptr<Fill> f = std::auto_ptr<Fill>(Fill::new_from_type(fill_pattern)); #endif // switch to rectilinear if this pattern doesn't support solid infill if (density > 99 && !f->can_solid()) #if SLIC3R_CPPVER >= 11 f = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear)); #else f = std::auto_ptr<Fill>(Fill::new_from_type(ipRectilinear)); #endif f->bounding_box = this->layer()->object()->bounding_box(); // calculate the actual flow we'll be using for this infill coordf_t h = (surface.thickness == -1) ? this->layer()->height : surface.thickness; Flow flow = this->region()->flow( role, h, is_bridge || f->use_bridge_flow(), // bridge flow? this->layer()->id() == 0, // first layer? -1, // auto width *this->layer()->object() ); // calculate flow spacing for infill pattern generation bool using_internal_flow = false; if (!surface.is_solid() && !is_bridge) { // it's internal infill, so we can calculate a generic flow spacing // for all layers, for avoiding the ugly effect of // misaligned infill on first layer because of different extrusion width and // layer height Flow internal_flow = this->region()->flow( frInfill, h, // use the calculated surface thickness here for internal infill instead of the layer height to account for infill_every_layers false, // no bridge false, // no first layer -1, // auto width *this->layer()->object() ); f->min_spacing = internal_flow.spacing(); using_internal_flow = true; } else { f->min_spacing = flow.spacing(); } f->endpoints_overlap = scale_(this->region()->config.get_abs_value("infill_overlap", (unscale(perimeter_spacing) + (f->min_spacing))/2)); f->layer_id = this->layer()->id(); f->z = this->layer()->print_z; f->angle = Geometry::deg2rad(this->region()->config.fill_angle.value); // Maximum length of the perimeter segment linking two infill lines. f->link_max_length = (!is_bridge && density > 80) ? scale_(3 * f->min_spacing) : 0; // Used by the concentric infill pattern to clip the loops to create extrusion paths. f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; // apply half spacing using this flow's own spacing and generate infill f->density = density/100; f->dont_adjust = false; /* std::cout << surface.expolygon.dump_perl() << std::endl << " layer_id: " << f->layer_id << " z: " << f->z << " angle: " << f->angle << " min-spacing: " << f->min_spacing << " endpoints_overlap: " << f->endpoints_overlap << std::endl << std::endl; */ Polylines polylines = f->fill_surface(surface); if (polylines.empty()) continue; // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) if (using_internal_flow) { // if we used the internal flow we're not doing a solid infill // so we can safely ignore the slight variation that might have // been applied to f->spacing() } else { flow = Flow::new_from_spacing(f->spacing(), flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); } // Save into layer. ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(); coll->no_sort = f->no_sort(); this->fills.entities.push_back(coll); { ExtrusionRole role; if (is_bridge) { role = erBridgeInfill; } else if (surface.is_solid()) { role = (surface.is_top()) ? erTopSolidInfill : erSolidInfill; } else { role = erInternalInfill; } ExtrusionPath templ(role); templ.mm3_per_mm = flow.mm3_per_mm(); templ.width = flow.width; templ.height = flow.height; coll->append(STDMOVE(polylines), templ); } } // add thin fill regions // thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection // Unpacks the collection, creates multiple collections per path so that they will // be individually included in the nearest neighbor search. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. for (ExtrusionEntitiesPtr::const_iterator thin_fill = this->thin_fills.entities.begin(); thin_fill != this->thin_fills.entities.end(); ++ thin_fill) { ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(); this->fills.entities.push_back(coll); coll->append(**thin_fill); } }
Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) { // transform input polygons into polylines Polylines polylines; polylines.reserve(subject.size()); for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) polylines.push_back(*polygon); // implicit call to split_at_first_point() // perform clipping Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_); /* If the split_at_first_point() call above happens to split the polygon inside the clipping area we would get two consecutive polylines instead of a single one, so we go through them in order to recombine continuous polylines. */ for (size_t i = 0; i < retval.size(); ++i) { for (size_t j = i+1; j < retval.size(); ++j) { if (retval[i].points.back().coincides_with(retval[j].points.front())) { /* If last point of i coincides with first point of j, append points of j to i and delete j */ retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); retval.erase(retval.begin() + j); --j; } else if (retval[i].points.front().coincides_with(retval[j].points.back())) { /* If first point of i coincides with last point of j, prepend points of j to i and delete j */ retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); retval.erase(retval.begin() + j); --j; } else if (retval[i].points.front().coincides_with(retval[j].points.front())) { /* Since Clipper does not preserve orientation of polylines, also check the case when first point of i coincides with first point of j. */ retval[j].reverse(); retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); retval.erase(retval.begin() + j); --j; } else if (retval[i].points.back().coincides_with(retval[j].points.back())) { /* Since Clipper does not preserve orientation of polylines, also check the case when last point of i coincides with last point of j. */ retval[j].reverse(); retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); retval.erase(retval.begin() + j); --j; } } } return retval; }
void create_polylines (Polylines& polylines) { { Polyline_3 polyline; polyline.push_back(Point(1,1,1)); polyline.push_back(Point(2,1,1)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(2,1,1)); polyline.push_back(Point(2,2,1)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(2,2,1)); polyline.push_back(Point(1,2,1)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(1,2,1)); polyline.push_back(Point(1,1,1)); polylines.push_back(polyline); } //---------- { Polyline_3 polyline; polyline.push_back(Point(1,1,2)); polyline.push_back(Point(2,1,2)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(2,1,2)); polyline.push_back(Point(2,2,2)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(2,2,2)); polyline.push_back(Point(1,2,2)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(1,2,2)); polyline.push_back(Point(1,1,2)); polylines.push_back(polyline); } //---------- { Polyline_3 polyline; polyline.push_back(Point(1,1,1)); polyline.push_back(Point(1,1,2)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(2,1,1)); polyline.push_back(Point(2,1,2)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(2,2,1)); polyline.push_back(Point(2,2,2)); polylines.push_back(polyline); } { Polyline_3 polyline; polyline.push_back(Point(1,2,1)); polyline.push_back(Point(1,2,2)); polylines.push_back(polyline); } }
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; }
Polyline::operator Polylines() const { Polylines polylines; polylines.push_back(*this); return polylines; }
void PerimeterGenerator::process() { // other perimeters this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t pwidth = this->perimeter_flow.scaled_width(); coord_t pspacing = this->perimeter_flow.scaled_spacing(); // external perimeters this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_pwidth = this->ext_perimeter_flow.scaled_width(); coord_t ext_pspacing = this->ext_perimeter_flow.scaled_spacing(); coord_t ext_pspacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); // overhang perimeters this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); // solid infill coord_t ispacing = this->solid_infill_flow.scaled_spacing(); coord_t gap_area_threshold = pwidth * pwidth; // Calculate the minimum required spacing between two adjacent traces. // This should be equal to the nominal flow spacing but we experiment // with some tolerance in order to avoid triggering medial axis when // some squishing might work. Loops are still spaced by the entire // flow spacing; this only applies to collapsing parts. // For ext_min_spacing we use the ext_pspacing calculated for two adjacent // external loops (which is the correct way) instead of using ext_pspacing2 // which is the spacing between external and internal, which is not correct // and would make the collapsing (thus the details resolution) dependent on // internal flow which is unrelated. coord_t min_spacing = pspacing * (1 - INSET_OVERLAP_TOLERANCE); coord_t ext_min_spacing = ext_pspacing * (1 - INSET_OVERLAP_TOLERANCE); // prepare grown lower layer slices for overhang detection if (this->lower_slices != NULL && this->config->overhangs) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); this->_lower_slices_p = offset(*this->lower_slices, scale_(+nozzle_diameter/2)); } // we need to process each island separately because we might have different // extra perimeters for each one for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); surface != this->slices->surfaces.end(); ++surface) { // detect how many perimeters must be generated for this island signed short loop_number = this->config->perimeters + surface->extra_perimeters; loop_number--; // 0-indexed loops Polygons gaps; Polygons last = surface->expolygon.simplify_p(SCALED_RESOLUTION); if (loop_number >= 0) { // no loops = -1 std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops Polylines thin_walls; // we loop one time more than needed in order to find gaps after the last perimeter was applied for (signed short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 Polygons offsets; if (i == 0) { // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 if (this->config->thin_walls) { offsets = offset2( last, -(ext_pwidth/2 + ext_min_spacing/2 - 1), +(ext_min_spacing/2 - 1) ); } else { offsets = offset(last, -ext_pwidth/2); } // look for thin walls if (this->config->thin_walls) { Polygons diffpp = diff( last, offset(offsets, +ext_pwidth/2), true // medial axis requires non-overlapping geometry ); // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = ext_pwidth / 2; ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop Polylines pp; for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) ex->medial_axis(ext_pwidth + ext_pspacing2, min_width, &pp); double threshold = ext_pwidth * 2; for (Polylines::const_iterator p = pp.begin(); p != pp.end(); ++p) { if (p->length() > threshold) { thin_walls.push_back(*p); } } #ifdef DEBUG printf(" %zu thin walls detected\n", thin_walls.size()); #endif /* if (false) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "medial_axis.svg", no_arrows => 1, #expolygons => \@expp, polylines => \@thin_walls, ); } */ } } else { coord_t distance = (i == 1) ? ext_pspacing2 : pspacing; if (this->config->thin_walls) { offsets = offset2( last, -(distance + min_spacing/2 - 1), +(min_spacing/2 - 1) ); } else { offsets = offset( last, -distance ); } // look for gaps if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0) { // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area ExPolygons diff_expp = diff_ex( offset(last, -0.5*distance), offset(offsets, +0.5*distance + 10) // safety offset ); for (ExPolygons::const_iterator ex = diff_expp.begin(); ex != diff_expp.end(); ++ex) { if (fabs(ex->area()) >= gap_area_threshold) { Polygons pp = *ex; gaps.insert(gaps.end(), pp.begin(), pp.end()); } } } } if (offsets.empty()) break; if (i > loop_number) break; // we were only looking for gaps this time last = offsets; for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) { PerimeterGeneratorLoop loop(*polygon, i); loop.is_contour = polygon->is_counter_clockwise(); if (loop.is_contour) { contours[i].push_back(loop); } else { holes[i].push_back(loop); } } } // nest loops: holes first for (signed short d = 0; d <= loop_number; ++d) { PerimeterGeneratorLoops &holes_d = holes[d]; // loop through all holes having depth == d for (signed short i = 0; i < holes_d.size(); ++i) { const PerimeterGeneratorLoop &loop = holes_d[i]; // find the hole loop that contains this one, if any for (signed short t = d+1; t <= loop_number; ++t) { for (signed short j = 0; j < holes[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); --i; goto NEXT_LOOP; } } } // if no hole contains this hole, find the contour loop that contains it for (signed short t = loop_number; t >= 0; --t) { for (signed short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); --i; goto NEXT_LOOP; } } } NEXT_LOOP: ; } } // nest contour loops for (signed short d = loop_number; d >= 1; --d) { PerimeterGeneratorLoops &contours_d = contours[d]; // loop through all contours having depth == d for (signed short i = 0; i < contours_d.size(); ++i) { const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it for (signed short t = d-1; t >= 0; --t) { for (signed short j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); contours_d.erase(contours_d.begin() + i); --i; goto NEXT_CONTOUR; } } } NEXT_CONTOUR: ; } } // at this point, all loops should be in contours[0] ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls); // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order if (this->config->external_perimeters_first || (this->layer_id == 0 && this->print_config->brim_width.value > 0)) entities.reverse(); // append perimeters for this slice as a collection if (!entities.empty()) this->loops->append(entities); } // fill gaps if (!gaps.empty()) { /* if (false) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "gaps.svg", expolygons => union_ex(\@gaps), ); } */ // where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth // where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth std::vector<PerimeterGeneratorGapSize> gap_sizes; gap_sizes.push_back(PerimeterGeneratorGapSize(pwidth, 2*pspacing, 2*pwidth)); gap_sizes.push_back(PerimeterGeneratorGapSize(0.1*pwidth, pwidth, 1*pwidth)); for (std::vector<PerimeterGeneratorGapSize>::const_iterator gap_size = gap_sizes.begin(); gap_size != gap_sizes.end(); ++gap_size) { ExtrusionEntityCollection gap_fill = this->_fill_gaps(gap_size->min, gap_size->max, unscale(gap_size->width), gaps); this->gap_fill->append(gap_fill.entities); // Make sure we don't infill narrow parts that are already gap-filled // (we only consider this surface's gaps to reduce the diff() complexity). // Growing actual extrusions ensures that gaps not filled by medial axis // are not subtracted from fill surfaces (they might be too short gaps // that medial axis skips but infill might join with other infill regions // and use zigzag). coord_t dist = gap_size->width/2; Polygons filled; for (ExtrusionEntitiesPtr::const_iterator it = gap_fill.entities.begin(); it != gap_fill.entities.end(); ++it) { Polygons f; offset((*it)->as_polyline(), &f, dist); filled.insert(filled.end(), f.begin(), f.end()); } last = diff(last, filled); gaps = diff(gaps, filled); // prevent more gap fill here } } // create one more offset to be used as boundary for fill // we offset by half the perimeter spacing (to get to the actual infill boundary) // and then we offset back and forth by half the infill spacing to only consider the // non-collapsing regions coord_t inset = 0; if (loop_number == 0) { // one loop inset += ext_pspacing2/2; } else if (loop_number > 0) { // two or more loops inset += pspacing/2; } // only apply infill overlap if we actually have one perimeter if (inset > 0) inset -= this->config->get_abs_value("infill_overlap", inset + ispacing/2); { ExPolygons expp = union_ex(last); // simplify infill contours according to resolution Polygons pp; for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) ex->simplify_p(SCALED_RESOLUTION, &pp); // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = ispacing * (1 - INSET_OVERLAP_TOLERANCE); expp = offset2_ex( pp, -inset -min_perimeter_infill_spacing/2, +min_perimeter_infill_spacing/2 ); // append infill areas to fill_surfaces for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) this->fill_surfaces->surfaces.push_back(Surface(stInternal, *ex)); // use a bogus surface type } } }
void SVG::draw(const Polylines &polylines, std::string stroke, coordf_t stroke_width) { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) this->draw(*it, stroke, stroke_width); }