diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 9b7cfcddf..f99e9f65c 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -147,9 +147,7 @@ package Slic3r::Filler; sub fill_surface { my ($self, $surface, %args) = @_; - $self->set_width($args{width}) if defined($args{width}); $self->set_density($args{density}) if defined($args{density}); - $self->set_distance($args{distance}) if defined($args{distance}); $self->set_dont_connect($args{dont_connect}) if defined($args{dont_connect}); $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); $self->set_complete($args{complete}) if defined($args{complete}); diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp new file mode 100644 index 000000000..19ba1c6be --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -0,0 +1,286 @@ +#include + +#include "../ClipperUtils.hpp" +#include "../Surface.hpp" +#include "../PrintConfig.hpp" + +#include "FillBase.hpp" + +namespace Slic3r { + +#if 0 +// Generate infills for Slic3r::Layer::Region. +// The Slic3r::Layer::Region 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. +// Returns an array of Slic3r::ExtrusionPath::Collection objects containing the infills generaed now +// and the thin fills generated by generate_perimeters(). +void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) +{ +// Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id; + + double fill_density = layerm.region()->config.fill_density; + Flow infill_flow = layerm.flow(frInfill); + Flow solid_infill_flow = layerm.flow(frSolidInfill); + Flow top_solid_infill_flow = layerm.flow(frTopSolidInfill); + + Surfaces 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()?) + { + SurfacesPtr surfaces_with_bridge_angle; + surfaces_with_bridge_angle.reserve(layerm->fill_surfaces.surfaces.size()); + for (Surfaces::iterator it = layerm->fill_surfaces.surfaces.begin(); it != layerm->fill_surfaces.surfaces.end(); ++ it) + if (it->bridge_angle >= 0) + surfaces_with_bridge_angle.push_back(&(*it)); + + // group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle) + // group is of type Slic3r::SurfaceCollection + //FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions. + std::vector groups; + layerm->fill_surfaces.group(&groups); + + // merge compatible 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) + my @is_solid = my @fw = my @pattern = (); + for (my $i = 0; $i <= $num_ groups; $i++) { + // we can only merge solid non-bridge surfaces, so discard + // non-solid surfaces + if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->layer->id == 0)) { + $is_solid[$i] = 1; + $fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP) + ? $top_solid_infill_flow->width + : $solid_infill_flow->width; + $pattern[$i] = $groups[$i][0]->is_external + ? $layerm->region->config->external_fill_pattern + : 'rectilinear'; + } else { + $is_solid[$i] = 0; + $fw[$i] = 0; + $pattern[$i] = 'none'; + } + } + + // loop through solid groups + for (my $i = 0; $i <= $num_groups; $i++) { + next if !$is_solid[$i]; + + // find compatible groups and append them to this one + for (my $j = $i+1; $j <= $num_groups; $j++) { + next if !$is_solid[$j]; + + if ($fw[$i] == $fw[$j] && $pattern[$i] eq $pattern[$j]) { + // groups are compatible, merge them + push @{$groups[$i]}, @{$groups[$j]}; + splice @groups, $j, 1; + splice @is_solid, $j, 1; + splice @fw, $j, 1; + splice @pattern, $j, 1; + } + } + } + } + + // give priority to bridges + @groups = sort { ($a->[0]->bridge_angle >= 0) ? -1 : 0 } @groups; + + foreach my $group (@groups) { + // Make a union of polygons defining the infiill regions of a group, use a safety offset. + my $union_p = union([ map $_->p, @$group ], 1); + + // Subtract surfaces having a defined bridge_angle from any other, use a safety offset. + if (@surfaces_with_bridge_angle && $group->[0]->bridge_angle < 0) { + $union_p = diff( + $union_p, + [ map $_->p, @surfaces_with_bridge_angle ], + 1, + ); + } + + // subtract any other surface already processed + //FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice! + my $union = diff_ex( + $union_p, + [ map $_->p, @surfaces ], + 1, + ); + + push @surfaces, map $group->[0]->clone(expolygon => $_), @$union; + } + } + + // 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 + { + my $distance_between_surfaces = max( + $infill_flow->scaled_spacing, + $solid_infill_flow->scaled_spacing, + $top_solid_infill_flow->scaled_spacing, + ); + my $collapsed = diff( + [ map @{$_->expolygon}, @surfaces ], + offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2), + 1, + ); + push @surfaces, map Slic3r::Surface->new( + expolygon => $_, + surface_type => S_TYPE_INTERNALSOLID, + ), @{intersection_ex( + offset($collapsed, $distance_between_surfaces), + [ + (map @{$_->expolygon}, grep $_->surface_type == S_TYPE_INTERNALVOID, @surfaces), + (@$collapsed), + ], + 1, + )}; + } + + if (0) { + 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 ], + ); + } + + SURFACE: foreach my $surface (@surfaces) { + next if $surface->surface_type == S_TYPE_INTERNALVOID; + my $filler = $layerm->region->config->fill_pattern; + my $density = $fill_density; + my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL + : $surface->is_solid ? FLOW_ROLE_SOLID_INFILL + : FLOW_ROLE_INFILL; + my $is_bridge = $layerm->layer->id > 0 && $surface->is_bridge; + my $is_solid = $surface->is_solid; + + if ($surface->is_solid) { + $density = 100; + $filler = 'rectilinear'; + if ($surface->is_external && !$is_bridge) { + $filler = $layerm->region->config->external_fill_pattern; + } + } else { + next SURFACE unless $density > 0; + } + + // get filler object + my $f = $self->filler($filler); + + // calculate the actual flow we'll be using for this infill + my $h = $surface->thickness == -1 ? $layerm->layer->height : $surface->thickness; + my $flow = $layerm->region->flow( + $role, + $h, + $is_bridge || $f->use_bridge_flow, + $layerm->layer->id == 0, + -1, + $layerm->layer->object, + ); + + // calculate flow spacing for infill pattern generation + my $using_internal_flow = 0; + if (!$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 + my $internal_flow = $layerm->region->flow( + FLOW_ROLE_INFILL, + $layerm->layer->object->config->layer_height, // TODO: handle infill_every_layers? + 0, // no bridge + 0, // no first layer + -1, // auto width + $layerm->layer->object, + ); + $f->set_spacing($internal_flow->spacing); + $using_internal_flow = 1; + } else { + $f->set_spacing($flow->spacing); + } + + my $link_max_length = 0; + if (! $is_bridge) { + $link_max_length = $layerm->region->config->get_abs_value_over($surface->is_external ? 'external_fill_link_max_length' : 'fill_link_max_length', $flow->spacing); + print "flow spacing: ", $flow->spacing, " is_external: ", $surface->is_external, ", link_max_length: $link_max_length\n"; + } + + $f->set_layer_id($layerm->layer->id); + $f->set_z($layerm->layer->print_z); + $f->set_angle(deg2rad($layerm->region->config->fill_angle)); + // Maximum length of the perimeter segment linking two infill lines. + $f->set_link_max_length(scale($link_max_length)); + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + $f->set_loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + + // apply half spacing using this flow's own spacing and generate infill + my @polylines = $f->fill_surface( + $surface, + density => $density/100, + layer_height => $h, + ); + next unless @polylines; + + + // 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->flow_spacing + } else { + $flow = Slic3r::Flow->new_from_spacing( + spacing => $f->spacing, + nozzle_diameter => $flow->nozzle_diameter, + layer_height => $h, + bridge => $is_bridge || $f->use_bridge_flow, + ); + } + + // save into layer + { + my $role = $is_bridge ? EXTR_ROLE_BRIDGE + : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) + : EXTR_ROLE_FILL; + + out. + push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; + // Only concentric fills are not sorted. + $collection->no_sort($f->no_sort); + $collection->append( + map Slic3r::ExtrusionPath->new( + polyline => $_, + role => $role, + mm3_per_mm => $flow->mm3_per_mm, + width => $flow->width, + height => $flow->height, + ), map @$_, @polylines, + ); + } + } + + // add thin fill regions + // thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection + // Unpacks the collection, creates multiple collections per path. + // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. + // Why the paths are unpacked? + for (ExtrusionEntitiesPtr::iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) { + // ExtrusionEntityCollection + out.append(new ExtrusionEntityCollection->new($thin_fill); + } + + return @fills; +} +#endif + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/Fill.hpp b/xs/src/libslic3r/Fill/Fill.hpp new file mode 100644 index 000000000..d3a436719 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill.hpp @@ -0,0 +1,33 @@ +#ifndef slic3r_Fill_hpp_ +#define slic3r_Fill_hpp_ + +#include +#include +#include + +#include "../libslic3r.h" +#include "../BoundingBox.hpp" +#include "../PrintConfig.hpp" + +#include "FillBase.hpp" + +namespace Slic3r { + +class Surface; + +// An interface class to Perl, aggregating an instance of a Fill and a FillData. +class Filler +{ +public: + Filler() : fill(NULL) {} + ~Filler() { + delete fill; + fill = NULL; + } + Fill *fill; + FillParams params; +}; + +} // namespace Slic3r + +#endif // slic3r_Fill_hpp_ diff --git a/xs/src/libslic3r/Fill/FillBase.hpp b/xs/src/libslic3r/Fill/FillBase.hpp index 820a77814..17d963bed 100644 --- a/xs/src/libslic3r/Fill/FillBase.hpp +++ b/xs/src/libslic3r/Fill/FillBase.hpp @@ -17,10 +17,8 @@ struct FillParams { FillParams() { memset(this, 0, sizeof(FillParams)); } - coordf_t width; - // Fraction in <0, 1> + // Fill density, fraction in <0, 1> float density; - coordf_t distance; // Don't connect the fill lines around the inner perimeter. bool dont_connect; @@ -45,9 +43,13 @@ public: coordf_t spacing; // in radians, ccw, 0 = East float angle; - // in scaled coordinates + // In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines. + // Used by the FillRectilinear2, FillGrid2, FillTriangles and FillCubic. + // If left to zero, the links will not be limited. + coord_t link_max_length; + // In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths. coord_t loop_clipping; - // in scaled coordinates + // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; public: @@ -74,6 +76,7 @@ protected: spacing(0.f), // Initial angle is undefined. angle(FLT_MAX), + link_max_length(0), loop_clipping(0), // The initial bounding box is empty, therefore undefined. bounding_box(Point(0, 0), Point(-1, -1)) @@ -116,19 +119,6 @@ protected: { return Point(_align_to_grid(coord.x, spacing.x, base.x), _align_to_grid(coord.y, spacing.y, base.y)); } }; -// An interface class to Perl, aggregating an instance of a Fill and a FillData. -class Filler -{ -public: - Filler() : fill(NULL) {} - ~Filler() { - delete fill; - fill = NULL; - } - Fill *fill; - FillParams params; -}; - } // namespace Slic3r #endif // slic3r_FillBase_hpp_ diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.cpp b/xs/src/libslic3r/Fill/FillRectilinear2.cpp index bcebc5a00..4fc4a9b46 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear2.cpp @@ -713,6 +713,31 @@ static inline void emit_perimeter_prev_next_segment( out.points.push_back(Point(il2.pos, itsct2.pos())); } +static inline coordf_t measure_perimeter_segment_on_vertical_line_length( + const ExPolygonWithOffset &poly_with_offset, + const std::vector &segs, + size_t iVerticalLine, + size_t iInnerContour, + size_t iIntersection, + size_t iIntersection2, + bool forward) +{ + const SegmentedIntersectionLine &il = segs[iVerticalLine]; + const SegmentIntersection &itsct = il.intersections[iIntersection]; + const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; + const Polygon &poly = poly_with_offset.contour(iInnerContour); + myassert(itsct.is_inner()); + myassert(itsct2.is_inner()); + myassert(itsct.type != itsct2.type); + myassert(itsct.iContour == iInnerContour); + myassert(itsct.iContour == itsct2.iContour); + Point p1(il.pos, itsct.pos()); + Point p2(il.pos, itsct2.pos()); + return forward ? + segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) : + segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1); +} + // Append the points of a perimeter segment when going from iIntersection to iIntersection2. // The first point (the point of iIntersection) will not be inserted, // the last point will be inserted. @@ -1300,8 +1325,19 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP (distNext < distPrev) : intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK; myassert(intrsctn->is_inner()); - polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); - emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next); + bool skip = params.dont_connect || (link_max_length > 0 && (take_next ? distNext : distPrev) > link_max_length); + if (skip) { + // Just skip the connecting contour and start a new path. + goto dont_connect; + polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); + polylines_out.push_back(Polyline()); + polyline_current = &polylines_out.back(); + const SegmentedIntersectionLine &il2 = segs[take_next ? (i_vline + 1) : (i_vline - 1)]; + polyline_current->points.push_back(Point(il2.pos, il2.intersections[take_next ? iNext : iPrev].pos())); + } else { + polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); + emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next); + } // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. if (iPrev != -1) segs[i_vline-1].intersections[iPrev].consumed_perimeter_right = true; @@ -1350,9 +1386,23 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP (distance_of_segmens(poly, intrsctn->iSegment, iSegNext, true) < distance_of_segmens(poly, intrsctn->iSegment, iSegNext, false)) : (vert_seg_dir_valid_mask == DIR_FORWARD); - // Consume the connecting contour and the next segment. + // Skip this perimeter line? + bool skip = params.dont_connect; + if (! skip && link_max_length > 0) { + coordf_t link_length = measure_perimeter_segment_on_vertical_line_length( + poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, dir_forward); + skip = link_length > link_max_length; + } polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); - emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward); + if (skip) { + // Just skip the connecting contour and start a new path. + polylines_out.push_back(Polyline()); + polyline_current = &polylines_out.back(); + polyline_current->points.push_back(Point(seg.pos, seg.intersections[iNext].pos())); + } else { + // Consume the connecting contour and the next segment. + emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward); + } // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. // If there are any outer intersection points skipped (bypassed) by the contour, // mark them as processed. @@ -1374,7 +1424,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP continue; } } - + dont_connect: // No way to continue the current polyline. Take the rest of the line up to the outer contour. // This will finish the polyline, starting another polyline at a new point. if (going_up) @@ -1450,9 +1500,12 @@ Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParam Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams ¶ms) { + // Each linear fill covers half of the target coverage. + FillParams params2 = params; + params2.density *= 0.5f; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out) || - ! fill_surface_by_lines(surface, params, float(M_PI / 2.), 0.f, polylines_out)) { + if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out) || + ! fill_surface_by_lines(surface, params2, float(M_PI / 2.), 0.f, polylines_out)) { printf("FillGrid2::fill_surface() failed to fill a region.\n"); } return polylines_out; @@ -1460,10 +1513,13 @@ Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams ¶ Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams ¶ms) { + // Each linear fill covers 1/3 of the target coverage. + FillParams params2 = params; + params2.density *= 0.333333333f; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params, 0.f, 0., polylines_out) || - ! fill_surface_by_lines(surface, params, float(M_PI / 3.), 0., polylines_out) || - ! fill_surface_by_lines(surface, params, float(2. * M_PI / 3.), 0., polylines_out)) { + if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || + ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || + ! fill_surface_by_lines(surface, params2, float(2. * M_PI / 3.), 0., polylines_out)) { printf("FillTriangles::fill_surface() failed to fill a region.\n"); } return polylines_out; @@ -1471,11 +1527,14 @@ Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams & Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ms) { + // Each linear fill covers 1/3 of the target coverage. + FillParams params2 = params; + params2.density *= 0.333333333f; Polylines polylines_out; - if (! fill_surface_by_lines(surface, params, 0.f, z, polylines_out) || - ! fill_surface_by_lines(surface, params, float(M_PI / 3.), -z, polylines_out) || + if (! fill_surface_by_lines(surface, params2, 0.f, z, polylines_out) || + ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), -z, polylines_out) || // Rotated by PI*2/3 + PI to achieve reverse sloping wall. - ! fill_surface_by_lines(surface, params, float(M_PI * 2. / 3.), z, polylines_out)) { + ! fill_surface_by_lines(surface, params2, float(M_PI * 2. / 3.), z, polylines_out)) { printf("FillCubic::fill_surface() failed to fill a region.\n"); } return polylines_out; diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp index d03113bb1..95fcfcb82 100644 --- a/xs/xsp/Filler.xsp +++ b/xs/xsp/Filler.xsp @@ -2,7 +2,7 @@ %{ #include -#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Fill/Fill.hpp" #include "libslic3r/PolylineCollection.hpp" %} @@ -21,6 +21,8 @@ %code{% THIS->fill->z = z; %}; void set_angle(float angle) %code{% THIS->fill->angle = angle; %}; + void set_link_max_length(coordf_t len) + %code{% THIS->fill->link_max_length = len; %}; void set_loop_clipping(coordf_t clipping) %code{% THIS->fill->loop_clipping = clipping; %}; @@ -29,12 +31,8 @@ bool no_sort() %code{% RETVAL = THIS->fill->no_sort(); %}; - void set_width(float width) - %code{% THIS->params.width = width; %}; void set_density(float density) %code{% THIS->params.density = density; %}; - void set_distance(float distance) - %code{% THIS->params.distance = distance; %}; void set_dont_connect(bool dont_connect) %code{% THIS->params.dont_connect = dont_connect; %}; void set_dont_adjust(bool dont_adjust)