2023-06-10 10:14:12 +08:00
# include "Layer.hpp"
# include "ClipperZUtils.hpp"
# include "ClipperUtils.hpp"
# include "Point.hpp"
# include "Polygon.hpp"
# include "Print.hpp"
# include "Fill/Fill.hpp"
# include "ShortestPath.hpp"
# include "SVG.hpp"
# include "BoundingBox.hpp"
# include "clipper/clipper.hpp"
# include <boost/log/trivial.hpp>
namespace Slic3r {
Layer : : ~ Layer ( )
{
this - > lower_layer = this - > upper_layer = nullptr ;
for ( LayerRegion * region : m_regions )
delete region ;
m_regions . clear ( ) ;
}
// Test whether whether there are any slices assigned to this layer.
bool Layer : : empty ( ) const
{
for ( const LayerRegion * layerm : m_regions )
if ( layerm ! = nullptr & & ! layerm - > slices ( ) . empty ( ) )
// Non empty layer.
return false ;
return true ;
}
LayerRegion * Layer : : add_region ( const PrintRegion * print_region )
{
m_regions . emplace_back ( new LayerRegion ( this , print_region ) ) ;
return m_regions . back ( ) ;
}
// merge all regions' slices to get islands
void Layer : : make_slices ( )
{
{
ExPolygons slices ;
if ( m_regions . size ( ) = = 1 ) {
// optimization: if we only have one region, take its slices
slices = to_expolygons ( m_regions . front ( ) - > slices ( ) . surfaces ) ;
} else {
Polygons slices_p ;
for ( LayerRegion * layerm : m_regions )
polygons_append ( slices_p , to_polygons ( layerm - > slices ( ) . surfaces ) ) ;
slices = union_safety_offset_ex ( slices_p ) ;
}
// lslices are sorted by topological order from outside to inside from the clipper union used above
this - > lslices = slices ;
}
this - > lslice_indices_sorted_by_print_order = chain_expolygons ( this - > lslices ) ;
}
// used by Layer::build_up_down_graph()
// Shrink source polygons one by one, so that they will be separated if they were touching
// at vertices (non-manifold situation).
// Then convert them to Z-paths with Z coordinate indicating index of the source expolygon.
[[nodiscard]] static ClipperLib_Z : : Paths expolygons_to_zpaths_shrunk ( const ExPolygons & expolygons , coord_t isrc )
{
size_t num_paths = 0 ;
for ( const ExPolygon & expolygon : expolygons )
num_paths + = expolygon . num_contours ( ) ;
ClipperLib_Z : : Paths out ;
out . reserve ( num_paths ) ;
ClipperLib : : Paths contours ;
ClipperLib : : Paths holes ;
ClipperLib : : Clipper clipper ;
ClipperLib : : ClipperOffset co ;
ClipperLib : : Paths out2 ;
// Top / bottom surfaces must overlap more than 2um to be chained into a Z graph.
// Also a larger offset will likely be more robust on non-manifold input polygons.
static constexpr const float delta = scaled < float > ( 0.001 ) ;
2023-09-16 16:26:29 +08:00
// Don't scale the miter limit, it is a factor, not an absolute length!
co . MiterLimit = 3. ;
2023-06-10 10:14:12 +08:00
// Use the default zero edge merging distance. For this kind of safety offset the accuracy of normal direction is not important.
// co.ShortestEdgeLength = delta * ClipperOffsetShortestEdgeFactor;
// static constexpr const double accept_area_threshold_ccw = sqr(scaled<double>(0.1 * delta));
// Such a small hole should not survive the shrinkage, it should grow over
// static constexpr const double accept_area_threshold_cw = sqr(scaled<double>(0.2 * delta));
for ( const ExPolygon & expoly : expolygons ) {
contours . clear ( ) ;
co . Clear ( ) ;
co . AddPath ( expoly . contour . points , ClipperLib : : jtMiter , ClipperLib : : etClosedPolygon ) ;
co . Execute ( contours , - delta ) ;
// size_t num_prev = out.size();
if ( ! contours . empty ( ) ) {
holes . clear ( ) ;
for ( const Polygon & hole : expoly . holes ) {
co . Clear ( ) ;
co . AddPath ( hole . points , ClipperLib : : jtMiter , ClipperLib : : etClosedPolygon ) ;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
out2 . clear ( ) ;
co . Execute ( out2 , delta ) ;
append ( holes , std : : move ( out2 ) ) ;
}
// Subtract holes from the contours.
if ( ! holes . empty ( ) ) {
clipper . Clear ( ) ;
clipper . AddPaths ( contours , ClipperLib : : ptSubject , true ) ;
clipper . AddPaths ( holes , ClipperLib : : ptClip , true ) ;
contours . clear ( ) ;
clipper . Execute ( ClipperLib : : ctDifference , contours , ClipperLib : : pftNonZero , ClipperLib : : pftNonZero ) ;
}
for ( const auto & contour : contours ) {
bool accept = true ;
// Trying to get rid of offset artifacts, that may be created due to numerical issues in offsetting algorithm
// or due to self-intersections in the source polygons.
//FIXME how reliable is it? Is it helpful or harmful? It seems to do more harm than good as it tends to punch holes
// into existing ExPolygons.
#if 0
if ( contour . size ( ) < 8 ) {
// Only accept contours with area bigger than some threshold.
double a = ClipperLib : : Area ( contour ) ;
// Polygon has to be bigger than some threshold to be accepted.
// Hole to be accepted has to have an area slightly bigger than the non-hole, so it will not happen due to rounding errors,
// that a hole will be accepted without its outer contour.
accept = a > 0 ? a > accept_area_threshold_ccw : a < - accept_area_threshold_cw ;
}
# endif
if ( accept ) {
out . emplace_back ( ) ;
ClipperLib_Z : : Path & path = out . back ( ) ;
path . reserve ( contour . size ( ) ) ;
for ( const Point & p : contour )
path . push_back ( { p . x ( ) , p . y ( ) , isrc } ) ;
}
}
}
#if 0 // #ifndef NDEBUG
// Test whether the expolygons in a single layer overlap.
Polygons test ;
for ( size_t i = num_prev ; i < out . size ( ) ; + + i )
test . emplace_back ( ClipperZUtils : : from_zpath ( out [ i ] ) ) ;
Polygons outside = diff ( test , to_polygons ( expoly ) ) ;
if ( ! outside . empty ( ) ) {
BoundingBox bbox ( get_extents ( expoly ) ) ;
bbox . merge ( get_extents ( test ) ) ;
SVG svg ( debug_out_path ( " expolygons_to_zpaths_shrunk-self-intersections.svg " ) . c_str ( ) , bbox ) ;
svg . draw ( expoly , " blue " ) ;
svg . draw ( test , " green " ) ;
svg . draw ( outside , " red " ) ;
}
assert ( outside . empty ( ) ) ;
# endif // NDEBUG
+ + isrc ;
}
return out ;
}
// used by Layer::build_up_down_graph()
static void connect_layer_slices (
Layer & below ,
Layer & above ,
const ClipperLib_Z : : PolyTree & polytree ,
const std : : vector < std : : pair < coord_t , coord_t > > & intersections ,
const coord_t offset_below ,
const coord_t offset_above
# ifndef NDEBUG
, const coord_t offset_end
# endif // NDEBUG
)
{
class Visitor {
public :
Visitor ( const std : : vector < std : : pair < coord_t , coord_t > > & intersections ,
Layer & below , Layer & above , const coord_t offset_below , const coord_t offset_above
# ifndef NDEBUG
, const coord_t offset_end
# endif // NDEBUG
) :
m_intersections ( intersections ) , m_below ( below ) , m_above ( above ) , m_offset_below ( offset_below ) , m_offset_above ( offset_above )
# ifndef NDEBUG
, m_offset_end ( offset_end )
# endif // NDEBUG
{ }
void visit ( const ClipperLib_Z : : PolyNode & polynode )
{
# ifndef NDEBUG
auto assert_intersection_valid = [ this ] ( int i , int j ) {
assert ( i < j ) ;
assert ( i > = m_offset_below ) ;
assert ( i < m_offset_above ) ;
assert ( j > = m_offset_above ) ;
assert ( j < m_offset_end ) ;
return true ;
} ;
# endif // NDEBUG
if ( polynode . Contour . size ( ) > = 3 ) {
// If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect.
// Otherwise the contour is fully inside another contour.
auto [ i , j ] = this - > find_top_bottom_contour_ids_strict ( polynode ) ;
bool found = false ;
if ( i < 0 & & j < 0 ) {
// This should not happen. It may only happen if the source contours had just self intersections or intersections with contours at the same layer.
// We may safely ignore such cases where the intersection area is meager.
double a = ClipperLib_Z : : Area ( polynode . Contour ) ;
if ( a < sqr ( scaled < double > ( 0.001 ) ) ) {
// Ignore tiny overlaps. They are not worth resolving.
} else {
// We should not ignore large cases. Try to resolve the conflict by a majority of references.
std : : tie ( i , j ) = this - > find_top_bottom_contour_ids_approx ( polynode ) ;
// At least top or bottom should be resolved.
assert ( i > = 0 | | j > = 0 ) ;
}
}
if ( j < 0 ) {
if ( i < 0 ) {
// this->find_top_bottom_contour_ids_approx() shoudl have made sure this does not happen.
assert ( false ) ;
} else {
assert ( i > = m_offset_below & & i < m_offset_above ) ;
i - = m_offset_below ;
j = this - > find_other_contour_costly ( polynode , m_above , j = = - 2 ) ;
found = j > = 0 ;
}
} else if ( i < 0 ) {
assert ( j > = m_offset_above & & j < m_offset_end ) ;
j - = m_offset_above ;
i = this - > find_other_contour_costly ( polynode , m_below , i = = - 2 ) ;
found = i > = 0 ;
} else {
assert ( assert_intersection_valid ( i , j ) ) ;
i - = m_offset_below ;
j - = m_offset_above ;
assert ( i > = 0 & & i < m_below . lslices_ex . size ( ) ) ;
assert ( j > = 0 & & j < m_above . lslices_ex . size ( ) ) ;
found = true ;
}
if ( found ) {
assert ( i > = 0 & & i < m_below . lslices_ex . size ( ) ) ;
assert ( j > = 0 & & j < m_above . lslices_ex . size ( ) ) ;
// Subtract area of holes from the area of outer contour.
double area = ClipperLib_Z : : Area ( polynode . Contour ) ;
for ( int icontour = 0 ; icontour < polynode . ChildCount ( ) ; + + icontour )
area - = ClipperLib_Z : : Area ( polynode . Childs [ icontour ] - > Contour ) ;
// Store the links and area into the contours.
LayerSlice : : Links & links_below = m_below . lslices_ex [ i ] . overlaps_above ;
LayerSlice : : Links & links_above = m_above . lslices_ex [ j ] . overlaps_below ;
LayerSlice : : Link key { j } ;
auto it_below = std : : lower_bound ( links_below . begin ( ) , links_below . end ( ) , key , [ ] ( auto & l , auto & r ) { return l . slice_idx < r . slice_idx ; } ) ;
if ( it_below ! = links_below . end ( ) & & it_below - > slice_idx = = j ) {
it_below - > area + = area ;
} else {
auto it_above = std : : lower_bound ( links_above . begin ( ) , links_above . end ( ) , key , [ ] ( auto & l , auto & r ) { return l . slice_idx < r . slice_idx ; } ) ;
if ( it_above ! = links_above . end ( ) & & it_above - > slice_idx = = i ) {
it_above - > area + = area ;
} else {
// Insert into one of the two vectors.
bool take_below = false ;
if ( links_below . size ( ) < LayerSlice : : LinksStaticSize )
take_below = false ;
else if ( links_above . size ( ) > = LayerSlice : : LinksStaticSize ) {
size_t shift_below = links_below . end ( ) - it_below ;
size_t shift_above = links_above . end ( ) - it_above ;
take_below = shift_below < shift_above ;
}
if ( take_below )
links_below . insert ( it_below , { j , float ( area ) } ) ;
else
links_above . insert ( it_above , { i , float ( area ) } ) ;
}
}
}
}
for ( int i = 0 ; i < polynode . ChildCount ( ) ; + + i )
for ( int j = 0 ; j < polynode . Childs [ i ] - > ChildCount ( ) ; + + j )
this - > visit ( * polynode . Childs [ i ] - > Childs [ j ] ) ;
}
private :
// Find the indices of the contour below & above for an expolygon created as an intersection of two expolygons, one below, the other above.
// Returns -1 if there is no point on the intersection refering bottom resp. top source expolygon.
// Returns -2 if the intersection refers to multiple source expolygons on bottom resp. top layers.
std : : pair < int32_t , int32_t > find_top_bottom_contour_ids_strict ( const ClipperLib_Z : : PolyNode & polynode ) const
{
// If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect.
// Otherwise the contour is fully inside another contour.
int32_t i = - 1 , j = - 1 ;
auto process_i = [ & i , & j ] ( coord_t k ) {
if ( i = = - 1 )
i = k ;
else if ( i > = 0 ) {
if ( i ! = k ) {
// Error: Intersection contour contains points of two or more source bottom contours.
i = - 2 ;
if ( j = = - 2 )
// break
return true ;
}
} else
assert ( i = = - 2 ) ;
return false ;
} ;
auto process_j = [ & i , & j ] ( coord_t k ) {
if ( j = = - 1 )
j = k ;
else if ( j > = 0 ) {
if ( j ! = k ) {
// Error: Intersection contour contains points of two or more source top contours.
j = - 2 ;
if ( i = = - 2 )
// break
return true ;
}
} else
assert ( j = = - 2 ) ;
return false ;
} ;
for ( int icontour = 0 ; icontour < = polynode . ChildCount ( ) ; + + icontour ) {
const ClipperLib_Z : : Path & contour = icontour = = 0 ? polynode . Contour : polynode . Childs [ icontour - 1 ] - > Contour ;
if ( contour . size ( ) > = 3 ) {
for ( const ClipperLib_Z : : IntPoint & pt : contour )
if ( coord_t k = pt . z ( ) ; k < 0 ) {
const auto & intersection = m_intersections [ - k - 1 ] ;
assert ( intersection . first < = intersection . second ) ;
if ( intersection . first < m_offset_above ? process_i ( intersection . first ) : process_j ( intersection . first ) )
goto end ;
if ( intersection . second < m_offset_above ? process_i ( intersection . second ) : process_j ( intersection . second ) )
goto end ;
} else if ( k < m_offset_above ? process_i ( k ) : process_j ( k ) )
goto end ;
}
}
end :
return { i , j } ;
}
// Find the indices of the contour below & above for an expolygon created as an intersection of two expolygons, one below, the other above.
// This variant expects that the source expolygon assingment is not unique, it counts the majority.
// Returns -1 if there is no point on the intersection refering bottom resp. top source expolygon.
// Returns -2 if the intersection refers to multiple source expolygons on bottom resp. top layers.
std : : pair < int32_t , int32_t > find_top_bottom_contour_ids_approx ( const ClipperLib_Z : : PolyNode & polynode ) const
{
// 1) Collect histogram of contour references.
struct HistoEl {
int32_t id ;
int32_t count ;
} ;
std : : vector < HistoEl > histogram ;
{
auto increment_counter = [ & histogram ] ( const int32_t i ) {
auto it = std : : lower_bound ( histogram . begin ( ) , histogram . end ( ) , i , [ ] ( auto l , auto r ) { return l . id < r ; } ) ;
if ( it = = histogram . end ( ) | | it - > id ! = i )
histogram . insert ( it , HistoEl { i , int32_t ( 1 ) } ) ;
else
+ + it - > count ;
} ;
for ( int icontour = 0 ; icontour < = polynode . ChildCount ( ) ; + + icontour ) {
const ClipperLib_Z : : Path & contour = icontour = = 0 ? polynode . Contour : polynode . Childs [ icontour - 1 ] - > Contour ;
if ( contour . size ( ) > = 3 ) {
for ( const ClipperLib_Z : : IntPoint & pt : contour )
if ( coord_t k = pt . z ( ) ; k < 0 ) {
const auto & intersection = m_intersections [ - k - 1 ] ;
assert ( intersection . first < = intersection . second ) ;
increment_counter ( intersection . first ) ;
increment_counter ( intersection . second ) ;
} else
increment_counter ( k ) ;
}
}
assert ( ! histogram . empty ( ) ) ;
}
int32_t i = - 1 ;
int32_t j = - 1 ;
if ( ! histogram . empty ( ) ) {
// 2) Split the histogram to bottom / top.
auto mid = std : : upper_bound ( histogram . begin ( ) , histogram . end ( ) , m_offset_above , [ ] ( auto l , auto r ) { return l < r . id ; } ) ;
// 3) Sort the bottom / top parts separately.
auto bottom_begin = histogram . begin ( ) ;
auto bottom_end = mid ;
auto top_begin = mid ;
auto top_end = histogram . end ( ) ;
std : : sort ( bottom_begin , bottom_end , [ ] ( auto l , auto r ) { return l . count > r . count ; } ) ;
std : : sort ( top_begin , top_end , [ ] ( auto l , auto r ) { return l . count > r . count ; } ) ;
double i_quality = 0 ;
double j_quality = 0 ;
if ( bottom_begin ! = bottom_end ) {
i = bottom_begin - > id ;
i_quality = std : : next ( bottom_begin ) = = bottom_end ? std : : numeric_limits < double > : : max ( ) : double ( bottom_begin - > count ) / std : : next ( bottom_begin ) - > count ;
}
if ( top_begin ! = top_end ) {
j = top_begin - > id ;
j_quality = std : : next ( top_begin ) = = top_end ? std : : numeric_limits < double > : : max ( ) : double ( top_begin - > count ) / std : : next ( top_begin ) - > count ;
}
// Expected to be called only if there are duplicate references to be resolved by the histogram.
assert ( i > = 0 | | j > = 0 ) ;
assert ( i_quality < std : : numeric_limits < double > : : max ( ) | | j_quality < std : : numeric_limits < double > : : max ( ) ) ;
if ( i > = 0 & & i_quality < j_quality ) {
// Force the caller to resolve the bottom references the costly but robust way.
assert ( j > = 0 ) ;
// Twice the number of references for the best contour.
assert ( j_quality > = 2. ) ;
i = - 2 ;
} else if ( j > = 0 ) {
// Force the caller to resolve the top reference the costly but robust way.
assert ( i > = 0 ) ;
// Twice the number of references for the best contour.
assert ( i_quality > = 2. ) ;
j = - 2 ;
}
}
return { i , j } ;
}
static int32_t find_other_contour_costly ( const ClipperLib_Z : : PolyNode & polynode , const Layer & other_layer , bool other_has_duplicates )
{
if ( ! other_has_duplicates ) {
// The contour below is likely completely inside another contour above. Look-it up in the island above.
Point pt ( polynode . Contour . front ( ) . x ( ) , polynode . Contour . front ( ) . y ( ) ) ;
for ( int i = int ( other_layer . lslices_ex . size ( ) ) - 1 ; i > = 0 ; - - i )
if ( other_layer . lslices_ex [ i ] . bbox . contains ( pt ) & & other_layer . lslices [ i ] . contains ( pt ) )
return i ;
// The following shall not happen now as the source expolygons are being shrunk a bit before intersecting,
// thus each point of each intersection polygon should fit completely inside one of the original (unshrunk) expolygons.
assert ( false ) ;
}
// The comment below may not be valid anymore, see the comment above. However the code is used in case the polynode contains multiple references
// to other_layer expolygons, thus the references are not unique.
//
// The check above might sometimes fail when the polygons overlap only on points, which causes the clipper to detect no intersection.
// The problem happens rarely, mostly on simple polygons (in terms of number of points), but regardless of size!
// example of failing link on two layers, each with single polygon without holes.
// layer A = Polygon{(-24931238,-11153865),(-22504249,-8726874),(-22504249,11477151),(-23261469,12235585),(-23752371,12727276),(-25002495,12727276),(-27502745,10227026),(-27502745,-12727274),(-26504645,-12727274)}
// layer B = Polygon{(-24877897,-11100524),(-22504249,-8726874),(-22504249,11477151),(-23244827,12218916),(-23752371,12727276),(-25002495,12727276),(-27502745,10227026),(-27502745,-12727274),(-26504645,-12727274)}
// note that first point is not identical, and the check above picks (-24877897,-11100524) as the first contour point (polynode.Contour.front()).
// that point is sadly slightly outisde of the layer A, so no link is detected, eventhough they are overlaping "completely"
Polygons contour_poly { Polygon { ClipperZUtils : : from_zpath ( polynode . Contour ) } } ;
BoundingBox contour_aabb { contour_poly . front ( ) . points } ;
int32_t i_largest = - 1 ;
double a_largest = 0 ;
for ( int i = int ( other_layer . lslices_ex . size ( ) ) - 1 ; i > = 0 ; - - i )
if ( contour_aabb . overlap ( other_layer . lslices_ex [ i ] . bbox ) )
// it is potentially slow, but should be executed rarely
if ( Polygons overlap = intersection ( contour_poly , other_layer . lslices [ i ] ) ; ! overlap . empty ( ) ) {
if ( other_has_duplicates ) {
// Find the contour with the largest overlap. It is expected that the other overlap will be very small.
double a = area ( overlap ) ;
if ( a > a_largest ) {
a_largest = a ;
i_largest = i ;
}
} else {
// Most likely there is just one contour that overlaps, however it is not guaranteed.
i_largest = i ;
break ;
}
}
assert ( i_largest > = 0 ) ;
return i_largest ;
}
const std : : vector < std : : pair < coord_t , coord_t > > & m_intersections ;
Layer & m_below ;
Layer & m_above ;
const coord_t m_offset_below ;
const coord_t m_offset_above ;
# ifndef NDEBUG
const coord_t m_offset_end ;
# endif // NDEBUG
} visitor ( intersections , below , above , offset_below , offset_above
# ifndef NDEBUG
, offset_end
# endif // NDEBUG
) ;
for ( int i = 0 ; i < polytree . ChildCount ( ) ; + + i )
visitor . visit ( * polytree . Childs [ i ] ) ;
# ifndef NDEBUG
// Verify that only one directional link is stored: either from bottom slice up or from upper slice down.
for ( int32_t islice = 0 ; islice < below . lslices_ex . size ( ) ; + + islice ) {
LayerSlice : : Links & links1 = below . lslices_ex [ islice ] . overlaps_above ;
for ( LayerSlice : : Link & link1 : links1 ) {
LayerSlice : : Links & links2 = above . lslices_ex [ link1 . slice_idx ] . overlaps_below ;
assert ( ! std : : binary_search ( links2 . begin ( ) , links2 . end ( ) , link1 , [ ] ( auto & l , auto & r ) { return l . slice_idx < r . slice_idx ; } ) ) ;
}
}
for ( int32_t islice = 0 ; islice < above . lslices_ex . size ( ) ; + + islice ) {
LayerSlice : : Links & links1 = above . lslices_ex [ islice ] . overlaps_below ;
for ( LayerSlice : : Link & link1 : links1 ) {
LayerSlice : : Links & links2 = below . lslices_ex [ link1 . slice_idx ] . overlaps_above ;
assert ( ! std : : binary_search ( links2 . begin ( ) , links2 . end ( ) , link1 , [ ] ( auto & l , auto & r ) { return l . slice_idx < r . slice_idx ; } ) ) ;
}
}
# endif // NDEBUG
// Scatter the links, but don't sort them yet.
for ( int32_t islice = 0 ; islice < int32_t ( below . lslices_ex . size ( ) ) ; + + islice )
for ( LayerSlice : : Link & link : below . lslices_ex [ islice ] . overlaps_above )
above . lslices_ex [ link . slice_idx ] . overlaps_below . push_back ( { islice , link . area } ) ;
for ( int32_t islice = 0 ; islice < int32_t ( above . lslices_ex . size ( ) ) ; + + islice )
for ( LayerSlice : : Link & link : above . lslices_ex [ islice ] . overlaps_below )
below . lslices_ex [ link . slice_idx ] . overlaps_above . push_back ( { islice , link . area } ) ;
// Sort the links.
for ( LayerSlice & lslice : below . lslices_ex )
std : : sort ( lslice . overlaps_above . begin ( ) , lslice . overlaps_above . end ( ) , [ ] ( const LayerSlice : : Link & l , const LayerSlice : : Link & r ) { return l . slice_idx < r . slice_idx ; } ) ;
for ( LayerSlice & lslice : above . lslices_ex )
std : : sort ( lslice . overlaps_below . begin ( ) , lslice . overlaps_below . end ( ) , [ ] ( const LayerSlice : : Link & l , const LayerSlice : : Link & r ) { return l . slice_idx < r . slice_idx ; } ) ;
}
void Layer : : build_up_down_graph ( Layer & below , Layer & above )
{
coord_t paths_below_offset = 0 ;
ClipperLib_Z : : Paths paths_below = expolygons_to_zpaths_shrunk ( below . lslices , paths_below_offset ) ;
coord_t paths_above_offset = paths_below_offset + coord_t ( below . lslices . size ( ) ) ;
ClipperLib_Z : : Paths paths_above = expolygons_to_zpaths_shrunk ( above . lslices , paths_above_offset ) ;
# ifndef NDEBUG
coord_t paths_end = paths_above_offset + coord_t ( above . lslices . size ( ) ) ;
# endif // NDEBUG
ClipperLib_Z : : Clipper clipper ;
ClipperLib_Z : : PolyTree result ;
ClipperZUtils : : ClipperZIntersectionVisitor : : Intersections intersections ;
ClipperZUtils : : ClipperZIntersectionVisitor visitor ( intersections ) ;
clipper . ZFillFunction ( visitor . clipper_callback ( ) ) ;
clipper . AddPaths ( paths_below , ClipperLib_Z : : ptSubject , true ) ;
clipper . AddPaths ( paths_above , ClipperLib_Z : : ptClip , true ) ;
clipper . Execute ( ClipperLib_Z : : ctIntersection , result , ClipperLib_Z : : pftNonZero , ClipperLib_Z : : pftNonZero ) ;
connect_layer_slices ( below , above , result , intersections , paths_below_offset , paths_above_offset
# ifndef NDEBUG
, paths_end
# endif // NDEBUG
) ;
}
static inline bool layer_needs_raw_backup ( const Layer * layer )
{
return ! ( layer - > regions ( ) . size ( ) = = 1 & & ( layer - > id ( ) > 0 | | layer - > object ( ) - > config ( ) . elefant_foot_compensation . value = = 0 ) ) ;
}
void Layer : : backup_untyped_slices ( )
{
if ( layer_needs_raw_backup ( this ) ) {
for ( LayerRegion * layerm : m_regions )
layerm - > m_raw_slices = to_expolygons ( layerm - > slices ( ) . surfaces ) ;
} else {
assert ( m_regions . size ( ) = = 1 ) ;
m_regions . front ( ) - > m_raw_slices . clear ( ) ;
}
}
void Layer : : restore_untyped_slices ( )
{
if ( layer_needs_raw_backup ( this ) ) {
for ( LayerRegion * layerm : m_regions )
layerm - > m_slices . set ( layerm - > m_raw_slices , stInternal ) ;
} else {
assert ( m_regions . size ( ) = = 1 ) ;
m_regions . front ( ) - > m_slices . set ( this - > lslices , stInternal ) ;
}
}
// Similar to Layer::restore_untyped_slices()
// To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442.
// Only resetting layerm->slices if Slice::extra_perimeters is always zero or it will not be used anymore
// after the perimeter generator.
void Layer : : restore_untyped_slices_no_extra_perimeters ( )
{
if ( layer_needs_raw_backup ( this ) ) {
for ( LayerRegion * layerm : m_regions )
if ( ! layerm - > region ( ) . config ( ) . extra_perimeters . value )
layerm - > m_slices . set ( layerm - > m_raw_slices , stInternal ) ;
} else {
assert ( m_regions . size ( ) = = 1 ) ;
LayerRegion * layerm = m_regions . front ( ) ;
// This optimization is correct, as extra_perimeters are only reused by prepare_infill() with multi-regions.
//if (! layerm->region().config().extra_perimeters.value)
layerm - > m_slices . set ( this - > lslices , stInternal ) ;
}
}
ExPolygons Layer : : merged ( float offset_scaled ) const
{
assert ( offset_scaled > = 0.f ) ;
// If no offset is set, apply EPSILON offset before union, and revert it afterwards.
float offset_scaled2 = 0 ;
if ( offset_scaled = = 0.f ) {
offset_scaled = float ( EPSILON ) ;
offset_scaled2 = float ( - EPSILON ) ;
}
Polygons polygons ;
for ( LayerRegion * layerm : m_regions ) {
const PrintRegionConfig & config = layerm - > region ( ) . config ( ) ;
// Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty.
if ( config . bottom_solid_layers > 0 | | config . top_solid_layers > 0 | | config . fill_density > 0. | | config . perimeters > 0 )
append ( polygons , offset ( layerm - > slices ( ) . surfaces , offset_scaled ) ) ;
}
ExPolygons out = union_ex ( polygons ) ;
if ( offset_scaled2 ! = 0.f )
out = offset_ex ( out , offset_scaled2 ) ;
return out ;
}
// Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters.
// The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region.
// The resulting fill surface is split back among the originating regions.
void Layer : : make_perimeters ( )
{
BOOST_LOG_TRIVIAL ( trace ) < < " Generating perimeters for layer " < < this - > id ( ) ;
// keep track of regions whose perimeters we have already generated
std : : vector < unsigned char > done ( m_regions . size ( ) , false ) ;
std : : vector < uint32_t > layer_region_ids ;
std : : vector < std : : pair < ExtrusionRange , ExtrusionRange > > perimeter_and_gapfill_ranges ;
ExPolygons fill_expolygons ;
std : : vector < ExPolygonRange > fill_expolygons_ranges ;
SurfacesPtr surfaces_to_merge ;
SurfacesPtr surfaces_to_merge_temp ;
auto layer_region_reset_perimeters = [ ] ( LayerRegion & layerm ) {
layerm . m_perimeters . clear ( ) ;
layerm . m_fills . clear ( ) ;
layerm . m_thin_fills . clear ( ) ;
layerm . m_fill_expolygons . clear ( ) ;
layerm . m_fill_expolygons_bboxes . clear ( ) ;
layerm . m_fill_expolygons_composite . clear ( ) ;
layerm . m_fill_expolygons_composite_bboxes . clear ( ) ;
} ;
// Remove layer islands, remove references to perimeters and fills from these layer islands to LayerRegion ExtrusionEntities.
for ( LayerSlice & lslice : this - > lslices_ex )
lslice . islands . clear ( ) ;
for ( LayerRegionPtrs : : iterator layerm = m_regions . begin ( ) ; layerm ! = m_regions . end ( ) ; + + layerm )
if ( size_t region_id = layerm - m_regions . begin ( ) ; ! done [ region_id ] ) {
layer_region_reset_perimeters ( * * layerm ) ;
if ( ! ( * layerm ) - > slices ( ) . empty ( ) ) {
BOOST_LOG_TRIVIAL ( trace ) < < " Generating perimeters for layer " < < this - > id ( ) < < " , region " < < region_id ;
done [ region_id ] = true ;
const PrintRegionConfig & config = ( * layerm ) - > region ( ) . config ( ) ;
perimeter_and_gapfill_ranges . clear ( ) ;
fill_expolygons . clear ( ) ;
fill_expolygons_ranges . clear ( ) ;
surfaces_to_merge . clear ( ) ;
// find compatible regions
layer_region_ids . clear ( ) ;
layer_region_ids . push_back ( region_id ) ;
for ( LayerRegionPtrs : : const_iterator it = layerm + 1 ; it ! = m_regions . end ( ) ; + + it )
if ( ! ( * it ) - > slices ( ) . empty ( ) ) {
2023-06-27 11:07:34 +08:00
LayerRegion * other_layerm = * it ;
const PrintRegionConfig & other_config = other_layerm - > region ( ) . config ( ) ;
bool dynamic_overhang_speed_compatibility = config . enable_dynamic_overhang_speeds = =
other_config . enable_dynamic_overhang_speeds ;
if ( dynamic_overhang_speed_compatibility & & config . enable_dynamic_overhang_speeds ) {
dynamic_overhang_speed_compatibility = config . overhang_speed_0 = = other_config . overhang_speed_0 & &
config . overhang_speed_1 = = other_config . overhang_speed_1 & &
config . overhang_speed_2 = = other_config . overhang_speed_2 & &
config . overhang_speed_3 = = other_config . overhang_speed_3 ;
}
if ( config . perimeter_extruder = = other_config . perimeter_extruder
2023-06-10 10:14:12 +08:00
& & config . perimeters = = other_config . perimeters
& & config . perimeter_speed = = other_config . perimeter_speed
& & config . external_perimeter_speed = = other_config . external_perimeter_speed
2023-06-27 11:07:34 +08:00
& & dynamic_overhang_speed_compatibility
2023-06-10 10:14:12 +08:00
& & ( config . gap_fill_enabled ? config . gap_fill_speed . value : 0. ) = =
( other_config . gap_fill_enabled ? other_config . gap_fill_speed . value : 0. )
& & config . overhangs = = other_config . overhangs
& & config . opt_serialize ( " perimeter_extrusion_width " ) = = other_config . opt_serialize ( " perimeter_extrusion_width " )
& & config . thin_walls = = other_config . thin_walls
& & config . external_perimeters_first = = other_config . external_perimeters_first
& & config . infill_overlap = = other_config . infill_overlap
& & config . fuzzy_skin = = other_config . fuzzy_skin
& & config . fuzzy_skin_thickness = = other_config . fuzzy_skin_thickness
& & config . fuzzy_skin_point_dist = = other_config . fuzzy_skin_point_dist )
{
layer_region_reset_perimeters ( * other_layerm ) ;
layer_region_ids . push_back ( it - m_regions . begin ( ) ) ;
done [ it - m_regions . begin ( ) ] = true ;
}
}
if ( layer_region_ids . size ( ) = = 1 ) { // optimization
( * layerm ) - > make_perimeters ( ( * layerm ) - > slices ( ) , perimeter_and_gapfill_ranges , fill_expolygons , fill_expolygons_ranges ) ;
this - > sort_perimeters_into_islands ( ( * layerm ) - > slices ( ) , region_id , perimeter_and_gapfill_ranges , std : : move ( fill_expolygons ) , fill_expolygons_ranges , layer_region_ids ) ;
} else {
SurfaceCollection new_slices ;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
uint32_t region_id_config = layer_region_ids . front ( ) ;
LayerRegion * layerm_config = m_regions [ region_id_config ] ;
{
// Merge slices (surfaces) according to number of extra perimeters.
for ( uint32_t region_id : layer_region_ids ) {
LayerRegion & layerm = * m_regions [ region_id ] ;
for ( const Surface & surface : layerm . slices ( ) )
surfaces_to_merge . emplace_back ( & surface ) ;
if ( layerm . region ( ) . config ( ) . fill_density > layerm_config - > region ( ) . config ( ) . fill_density ) {
region_id_config = region_id ;
layerm_config = & layerm ;
}
}
std : : sort ( surfaces_to_merge . begin ( ) , surfaces_to_merge . end ( ) , [ ] ( const Surface * l , const Surface * r ) { return l - > extra_perimeters < r - > extra_perimeters ; } ) ;
for ( size_t i = 0 ; i < surfaces_to_merge . size ( ) ; ) {
size_t j = i ;
const Surface & first = * surfaces_to_merge [ i ] ;
size_t extra_perimeters = first . extra_perimeters ;
for ( ; j < surfaces_to_merge . size ( ) & & surfaces_to_merge [ j ] - > extra_perimeters = = extra_perimeters ; + + j ) ;
if ( i + 1 = = j )
// Nothing to merge, just copy.
new_slices . surfaces . emplace_back ( * surfaces_to_merge [ i ] ) ;
else {
surfaces_to_merge_temp . assign ( surfaces_to_merge . begin ( ) + i , surfaces_to_merge . begin ( ) + j ) ;
new_slices . append ( offset_ex ( surfaces_to_merge_temp , ClipperSafetyOffset ) , first ) ;
}
i = j ;
}
}
// make perimeters
layerm_config - > make_perimeters ( new_slices , perimeter_and_gapfill_ranges , fill_expolygons , fill_expolygons_ranges ) ;
this - > sort_perimeters_into_islands ( new_slices , region_id_config , perimeter_and_gapfill_ranges , std : : move ( fill_expolygons ) , fill_expolygons_ranges , layer_region_ids ) ;
}
}
}
BOOST_LOG_TRIVIAL ( trace ) < < " Generating perimeters for layer " < < this - > id ( ) < < " - Done " ;
}
void Layer : : sort_perimeters_into_islands (
// Slices for which perimeters and fill_expolygons were just created.
// The slices may have been created by merging multiple source slices with the same perimeter parameters.
const SurfaceCollection & slices ,
// Region where the perimeters, gap fills and fill expolygons are stored.
const uint32_t region_id ,
// Perimeters and gap fills produced by the perimeter generator for the slices,
// sorted by the source slices.
const std : : vector < std : : pair < ExtrusionRange , ExtrusionRange > > & perimeter_and_gapfill_ranges ,
// Fill expolygons produced for all source slices above.
ExPolygons & & fill_expolygons ,
// Fill expolygon ranges sorted by the source slices.
const std : : vector < ExPolygonRange > & fill_expolygons_ranges ,
// If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces.
const std : : vector < uint32_t > & layer_region_ids )
{
assert ( perimeter_and_gapfill_ranges . size ( ) = = fill_expolygons_ranges . size ( ) ) ;
assert ( ! layer_region_ids . empty ( ) ) ;
LayerRegion & this_layer_region = * m_regions [ region_id ] ;
// Bounding boxes of fill_expolygons.
BoundingBoxes fill_expolygons_bboxes ;
fill_expolygons_bboxes . reserve ( fill_expolygons . size ( ) ) ;
for ( const ExPolygon & expolygon : fill_expolygons )
fill_expolygons_bboxes . emplace_back ( get_extents ( expolygon ) ) ;
// Take one sample point for each source slice, to be used to sort source slices into layer slices.
// source slice index + its sample.
std : : vector < std : : pair < uint32_t , Point > > perimeter_slices_queue ;
perimeter_slices_queue . reserve ( slices . size ( ) ) ;
for ( uint32_t islice = 0 ; islice < uint32_t ( slices . size ( ) ) ; + + islice ) {
const std : : pair < ExtrusionRange , ExtrusionRange > & extrusions = perimeter_and_gapfill_ranges [ islice ] ;
Point sample ;
bool sample_set = false ;
// Take a sample deep inside its island if available. Infills are usually quite far from the island boundary.
for ( uint32_t iexpoly : fill_expolygons_ranges [ islice ] )
if ( const ExPolygon & expoly = fill_expolygons [ iexpoly ] ; ! expoly . empty ( ) ) {
sample = expoly . contour . points [ expoly . contour . points . size ( ) / 2 ] ;
sample_set = true ;
break ;
}
if ( ! sample_set ) {
// If there is no infill, take a sample of some inner perimeter.
for ( uint32_t iperimeter : extrusions . first ) {
const ExtrusionEntity & ee = * this_layer_region . perimeters ( ) . entities [ iperimeter ] ;
if ( ee . is_collection ( ) ) {
for ( const ExtrusionEntity * ee2 : dynamic_cast < const ExtrusionEntityCollection & > ( ee ) . entities )
if ( ! ee2 - > role ( ) . is_external ( ) ) {
sample = ee2 - > middle_point ( ) ;
sample_set = true ;
goto loop_end ;
}
} else if ( ! ee . role ( ) . is_external ( ) ) {
sample = ee . middle_point ( ) ;
sample_set = true ;
break ;
}
}
loop_end :
if ( ! sample_set ) {
if ( ! extrusions . second . empty ( ) ) {
// If there is no inner perimeter, take a sample of some gap fill extrusion.
sample = this_layer_region . thin_fills ( ) . entities [ * extrusions . second . begin ( ) ] - > middle_point ( ) ;
sample_set = true ;
}
if ( ! sample_set & & ! extrusions . first . empty ( ) ) {
// As a last resort, take a sample of some external perimeter.
sample = this_layer_region . perimeters ( ) . entities [ * extrusions . first . begin ( ) ] - > middle_point ( ) ;
sample_set = true ;
}
}
}
// There may be a valid empty island.
// assert(sample_set);
if ( sample_set )
perimeter_slices_queue . emplace_back ( islice , sample ) ;
}
// Map of source fill_expolygon into region and fill_expolygon of that region.
// -1: not set
struct RegionWithFillIndex {
int region_id { - 1 } ;
int fill_in_region_id { - 1 } ;
} ;
std : : vector < RegionWithFillIndex > map_expolygon_to_region_and_fill ;
const bool has_multiple_regions = layer_region_ids . size ( ) > 1 ;
assert ( has_multiple_regions | | layer_region_ids . size ( ) = = 1 ) ;
// assign fill_surfaces to each layer
if ( ! fill_expolygons . empty ( ) ) {
if ( has_multiple_regions ) {
// Sort the bounding boxes lexicographically.
std : : vector < uint32_t > fill_expolygons_bboxes_sorted ( fill_expolygons_bboxes . size ( ) ) ;
std : : iota ( fill_expolygons_bboxes_sorted . begin ( ) , fill_expolygons_bboxes_sorted . end ( ) , 0 ) ;
std : : sort ( fill_expolygons_bboxes_sorted . begin ( ) , fill_expolygons_bboxes_sorted . end ( ) , [ & fill_expolygons_bboxes ] ( uint32_t lhs , uint32_t rhs ) {
const BoundingBox & bbl = fill_expolygons_bboxes [ lhs ] ;
const BoundingBox & bbr = fill_expolygons_bboxes [ rhs ] ;
return bbl . min < bbr . min | | ( bbl . min = = bbr . min & & bbl . max < bbr . max ) ;
} ) ;
map_expolygon_to_region_and_fill . assign ( fill_expolygons . size ( ) , { } ) ;
for ( uint32_t region_idx : layer_region_ids ) {
LayerRegion & l = * m_regions [ region_idx ] ;
l . m_fill_expolygons = intersection_ex ( l . slices ( ) . surfaces , fill_expolygons ) ;
l . m_fill_expolygons_bboxes . reserve ( l . fill_expolygons ( ) . size ( ) ) ;
for ( const ExPolygon & expolygon : l . fill_expolygons ( ) ) {
BoundingBox bbox = get_extents ( expolygon ) ;
l . m_fill_expolygons_bboxes . emplace_back ( bbox ) ;
auto it_bbox = std : : lower_bound ( fill_expolygons_bboxes_sorted . begin ( ) , fill_expolygons_bboxes_sorted . end ( ) , bbox , [ & fill_expolygons_bboxes ] ( uint32_t lhs , const BoundingBox & bbr ) {
const BoundingBox & bbl = fill_expolygons_bboxes [ lhs ] ;
return bbl . min < bbr . min | | ( bbl . min = = bbr . min & & bbl . max < bbr . max ) ;
} ) ;
if ( it_bbox ! = fill_expolygons_bboxes_sorted . end ( ) )
if ( uint32_t fill_id = * it_bbox ; fill_expolygons_bboxes [ fill_id ] = = bbox ) {
// With a very high probability the two expolygons match exactly. Confirm that.
if ( expolygons_match ( expolygon , fill_expolygons [ fill_id ] ) ) {
RegionWithFillIndex & ref = map_expolygon_to_region_and_fill [ fill_id ] ;
// Only one expolygon produced by intersection with LayerRegion surface may match an expolygon of fill_expolygons.
assert ( ref . region_id = = - 1 & & ref . fill_in_region_id = = - 1 ) ;
ref . region_id = region_idx ;
ref . fill_in_region_id = int ( & expolygon - l . fill_expolygons ( ) . data ( ) ) ;
}
}
}
}
// Check whether any island contains multiple fills that fall into the same region, but not they are not contiguous.
// If so, sort fills in that particular region so that fills of an island become contiguous.
// Index of a region to sort.
int sort_region_id = - 1 ;
// Temporary vector of fills for reordering.
ExPolygons fills_temp ;
// Vector of new positions of the above.
std : : vector < int > new_positions ;
do {
sort_region_id = - 1 ;
for ( size_t source_slice_idx = 0 ; source_slice_idx < fill_expolygons_ranges . size ( ) ; + + source_slice_idx )
if ( ExPolygonRange fill_range = fill_expolygons_ranges [ source_slice_idx ] ; fill_range . size ( ) > 1 ) {
// More than one expolygon exists for a single island. Check whether they are contiguous inside a single LayerRegion::fill_expolygons() vector.
uint32_t fill_idx = * fill_range . begin ( ) ;
if ( const int fill_regon_id = map_expolygon_to_region_and_fill [ fill_idx ] . region_id ; fill_regon_id ! = - 1 ) {
int fill_in_region_id = map_expolygon_to_region_and_fill [ fill_idx ] . fill_in_region_id ;
bool needs_sorting = false ;
for ( + + fill_idx ; fill_idx ! = * fill_range . end ( ) ; + + fill_idx ) {
if ( const RegionWithFillIndex & ref = map_expolygon_to_region_and_fill [ fill_idx ] ; ref . region_id ! = fill_regon_id ) {
// This island has expolygons split among multiple regions.
needs_sorting = false ;
break ;
} else if ( ref . fill_in_region_id ! = + + fill_in_region_id ) {
// This island has all expolygons stored inside the same region, but not sorted.
needs_sorting = true ;
}
}
if ( needs_sorting ) {
sort_region_id = fill_regon_id ;
break ;
}
}
}
if ( sort_region_id ! = - 1 ) {
// Reorder fills in region with sort_region index.
LayerRegion & layerm = * m_regions [ sort_region_id ] ;
new_positions . assign ( layerm . fill_expolygons ( ) . size ( ) , - 1 ) ;
int last = 0 ;
for ( RegionWithFillIndex & ref : map_expolygon_to_region_and_fill )
if ( ref . region_id = = sort_region_id ) {
new_positions [ ref . fill_in_region_id ] = last ;
ref . fill_in_region_id = last + + ;
}
for ( auto & new_pos : new_positions )
if ( new_pos = = - 1 )
// Not referenced by any map_expolygon_to_region_and_fill.
new_pos = last + + ;
// Move just the content of m_fill_expolygons to fills_temp, but don't move the container vector.
auto & fills = layerm . m_fill_expolygons ;
assert ( last = = int ( fills . size ( ) ) ) ;
fills_temp . reserve ( fills . size ( ) ) ;
fills_temp . insert ( fills_temp . end ( ) , std : : make_move_iterator ( fills . begin ( ) ) , std : : make_move_iterator ( fills . end ( ) ) ) ;
for ( ExPolygon & ex : fills )
ex . clear ( ) ;
// Move / reoder the expolygons back into m_fill_expolygons.
for ( size_t old_pos = 0 ; old_pos < new_positions . size ( ) ; + + old_pos )
fills [ new_positions [ old_pos ] ] = std : : move ( fills_temp [ old_pos ] ) ;
}
} while ( sort_region_id ! = - 1 ) ;
} else {
this_layer_region . m_fill_expolygons = std : : move ( fill_expolygons ) ;
this_layer_region . m_fill_expolygons_bboxes = std : : move ( fill_expolygons_bboxes ) ;
}
}
auto insert_into_island = [
// Region where the perimeters, gap fills and fill expolygons are stored.
region_id ,
// Whether there are infills with different regions generated for this LayerSlice.
has_multiple_regions ,
// Perimeters and gap fills to be sorted into islands.
& perimeter_and_gapfill_ranges ,
// Infill regions to be sorted into islands.
& fill_expolygons , & fill_expolygons_bboxes , & fill_expolygons_ranges ,
// Mapping of fill_expolygon to region and its infill.
& map_expolygon_to_region_and_fill ,
// Output
& regions = m_regions , & lslices_ex = this - > lslices_ex ]
( int lslice_idx , int source_slice_idx ) {
lslices_ex [ lslice_idx ] . islands . push_back ( { } ) ;
LayerIsland & island = lslices_ex [ lslice_idx ] . islands . back ( ) ;
island . perimeters = LayerExtrusionRange ( region_id , perimeter_and_gapfill_ranges [ source_slice_idx ] . first ) ;
island . thin_fills = perimeter_and_gapfill_ranges [ source_slice_idx ] . second ;
if ( ExPolygonRange fill_range = fill_expolygons_ranges [ source_slice_idx ] ; ! fill_range . empty ( ) ) {
if ( has_multiple_regions ) {
// Check whether the fill expolygons of this island were split into multiple regions.
island . fill_region_id = LayerIsland : : fill_region_composite_id ;
for ( uint32_t fill_idx : fill_range ) {
if ( const int fill_regon_id = map_expolygon_to_region_and_fill [ fill_idx ] . region_id ;
fill_regon_id = = - 1 | | ( island . fill_region_id ! = LayerIsland : : fill_region_composite_id & & int ( island . fill_region_id ) ! = fill_regon_id ) ) {
island . fill_region_id = LayerIsland : : fill_region_composite_id ;
break ;
} else
island . fill_region_id = fill_regon_id ;
}
if ( island . fill_expolygons_composite ( ) ) {
// They were split, thus store the unsplit "composite" expolygons into the region of perimeters.
LayerRegion & this_layer_region = * regions [ region_id ] ;
auto begin = uint32_t ( this_layer_region . fill_expolygons_composite ( ) . size ( ) ) ;
this_layer_region . m_fill_expolygons_composite . reserve ( this_layer_region . fill_expolygons_composite ( ) . size ( ) + fill_range . size ( ) ) ;
std : : move ( fill_expolygons . begin ( ) + * fill_range . begin ( ) , fill_expolygons . begin ( ) + * fill_range . end ( ) , std : : back_inserter ( this_layer_region . m_fill_expolygons_composite ) ) ;
this_layer_region . m_fill_expolygons_composite_bboxes . insert ( this_layer_region . m_fill_expolygons_composite_bboxes . end ( ) ,
fill_expolygons_bboxes . begin ( ) + * fill_range . begin ( ) , fill_expolygons_bboxes . begin ( ) + * fill_range . end ( ) ) ;
island . fill_expolygons = ExPolygonRange ( begin , uint32_t ( this_layer_region . fill_expolygons_composite ( ) . size ( ) ) ) ;
} else {
// All expolygons are stored inside a single LayerRegion in a contiguous range.
island . fill_expolygons = ExPolygonRange (
map_expolygon_to_region_and_fill [ * fill_range . begin ( ) ] . fill_in_region_id ,
map_expolygon_to_region_and_fill [ * fill_range . end ( ) - 1 ] . fill_in_region_id + 1 ) ;
}
} else {
// Layer island is made of one fill region only.
island . fill_expolygons = fill_range ;
island . fill_region_id = region_id ;
}
}
} ;
// First sort into islands using exact fit.
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
auto point_inside_surface = [ & lslices = this - > lslices , & lslices_ex = this - > lslices_ex ] ( size_t lslice_idx , const Point & point ) {
const BoundingBox & bbox = lslices_ex [ lslice_idx ] . bbox ;
return point . x ( ) > = bbox . min . x ( ) & & point . x ( ) < bbox . max . x ( ) & &
point . y ( ) > = bbox . min . y ( ) & & point . y ( ) < bbox . max . y ( ) & &
// Exact match: Don't just test whether a point is inside the outer contour of an island,
// test also whether the point is not inside some hole of the same expolygon.
// This is unfortunatelly necessary because the point may be inside an expolygon of one of this expolygon's hole
// and missed due to numerical issues.
lslices [ lslice_idx ] . contains ( point ) ;
} ;
for ( int lslice_idx = int ( lslices_ex . size ( ) ) - 1 ; lslice_idx > = 0 & & ! perimeter_slices_queue . empty ( ) ; - - lslice_idx )
for ( auto it_source_slice = perimeter_slices_queue . begin ( ) ; it_source_slice ! = perimeter_slices_queue . end ( ) ; + + it_source_slice )
if ( point_inside_surface ( lslice_idx , it_source_slice - > second ) ) {
insert_into_island ( lslice_idx , it_source_slice - > first ) ;
if ( std : : next ( it_source_slice ) ! = perimeter_slices_queue . end ( ) )
// Remove the current slice & point pair from the queue.
* it_source_slice = perimeter_slices_queue . back ( ) ;
perimeter_slices_queue . pop_back ( ) ;
break ;
}
if ( ! perimeter_slices_queue . empty ( ) ) {
// If the slice sample was not fitted into any slice using exact fit, try to find a closest island as a last resort.
// This should be a rare event especially if the sample point was taken from infill or inner perimeter,
// however we may land here for external perimeter only islands with fuzzy skin applied.
// Check whether fuzzy skin was enabled and adjust the bounding box accordingly.
const PrintConfig & print_config = this - > object ( ) - > print ( ) - > config ( ) ;
const PrintRegionConfig & region_config = this_layer_region . region ( ) . config ( ) ;
const auto bbox_eps = scaled < coord_t > (
EPSILON + print_config . gcode_resolution . value +
( region_config . fuzzy_skin . value = = FuzzySkinType : : None ? 0. : region_config . fuzzy_skin_thickness . value
//FIXME it looks as if Arachne could extend open lines by fuzzy_skin_point_dist, which does not seem right.
+ region_config . fuzzy_skin_point_dist . value ) ) ;
auto point_inside_surface_dist2 =
[ & lslices = this - > lslices , & lslices_ex = this - > lslices_ex , bbox_eps ]
( const size_t lslice_idx , const Point & point ) {
const BoundingBox & bbox = lslices_ex [ lslice_idx ] . bbox ;
return
point . x ( ) < bbox . min . x ( ) - bbox_eps | | point . x ( ) > bbox . max . x ( ) + bbox_eps | |
point . y ( ) < bbox . min . y ( ) - bbox_eps | | point . y ( ) > bbox . max . y ( ) + bbox_eps ?
std : : numeric_limits < double > : : max ( ) :
( lslices [ lslice_idx ] . point_projection ( point ) - point ) . cast < double > ( ) . squaredNorm ( ) ;
} ;
for ( auto it_source_slice = perimeter_slices_queue . begin ( ) ; it_source_slice ! = perimeter_slices_queue . end ( ) ; + + it_source_slice ) {
double d2min = std : : numeric_limits < double > : : max ( ) ;
int lslice_idx_min = - 1 ;
for ( int lslice_idx = int ( lslices_ex . size ( ) ) - 1 ; lslice_idx > = 0 ; - - lslice_idx )
if ( double d2 = point_inside_surface_dist2 ( lslice_idx , it_source_slice - > second ) ; d2 < d2min ) {
d2min = d2 ;
lslice_idx_min = lslice_idx ;
}
if ( lslice_idx_min = = - 1 ) {
// This should not happen, but Arachne seems to produce a perimeter point far outside its source contour.
// As a last resort, find the closest source contours to the sample point.
for ( int lslice_idx = int ( lslices_ex . size ( ) ) - 1 ; lslice_idx > = 0 ; - - lslice_idx )
if ( double d2 = ( lslices [ lslice_idx ] . point_projection ( it_source_slice - > second ) - it_source_slice - > second ) . cast < double > ( ) . squaredNorm ( ) ; d2 < d2min ) {
d2min = d2 ;
lslice_idx_min = lslice_idx ;
}
}
assert ( lslice_idx_min ! = - 1 ) ;
insert_into_island ( lslice_idx_min , it_source_slice - > first ) ;
}
}
}
void Layer : : export_region_slices_to_svg ( const char * path ) const
{
BoundingBox bbox ;
for ( const auto * region : m_regions )
for ( const auto & surface : region - > slices ( ) )
bbox . merge ( get_extents ( surface . expolygon ) ) ;
Point legend_size = export_surface_type_legend_to_svg_box_size ( ) ;
Point legend_pos ( bbox . min ( 0 ) , bbox . max ( 1 ) ) ;
bbox . merge ( Point ( std : : max ( bbox . min ( 0 ) + legend_size ( 0 ) , bbox . max ( 0 ) ) , bbox . max ( 1 ) + legend_size ( 1 ) ) ) ;
SVG svg ( path , bbox ) ;
const float transparency = 0.5f ;
for ( const auto * region : m_regions )
for ( const auto & surface : region - > slices ( ) )
svg . draw ( surface . expolygon , surface_type_to_color_name ( surface . surface_type ) , transparency ) ;
export_surface_type_legend_to_svg ( svg , legend_pos ) ;
svg . Close ( ) ;
}
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void Layer : : export_region_slices_to_svg_debug ( const char * name ) const
{
static size_t idx = 0 ;
this - > export_region_slices_to_svg ( debug_out_path ( " Layer-slices-%s-%d.svg " , name , idx + + ) . c_str ( ) ) ;
}
void Layer : : export_region_fill_surfaces_to_svg ( const char * path ) const
{
BoundingBox bbox ;
for ( const auto * region : m_regions )
for ( const auto & surface : region - > slices ( ) )
bbox . merge ( get_extents ( surface . expolygon ) ) ;
Point legend_size = export_surface_type_legend_to_svg_box_size ( ) ;
Point legend_pos ( bbox . min ( 0 ) , bbox . max ( 1 ) ) ;
bbox . merge ( Point ( std : : max ( bbox . min ( 0 ) + legend_size ( 0 ) , bbox . max ( 0 ) ) , bbox . max ( 1 ) + legend_size ( 1 ) ) ) ;
SVG svg ( path , bbox ) ;
const float transparency = 0.5f ;
for ( const auto * region : m_regions )
for ( const auto & surface : region - > slices ( ) )
svg . draw ( surface . expolygon , surface_type_to_color_name ( surface . surface_type ) , transparency ) ;
export_surface_type_legend_to_svg ( svg , legend_pos ) ;
svg . Close ( ) ;
}
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void Layer : : export_region_fill_surfaces_to_svg_debug ( const char * name ) const
{
static size_t idx = 0 ;
this - > export_region_fill_surfaces_to_svg ( debug_out_path ( " Layer-fill_surfaces-%s-%d.svg " , name , idx + + ) . c_str ( ) ) ;
}
BoundingBox get_extents ( const LayerRegion & layer_region )
{
BoundingBox bbox ;
if ( ! layer_region . slices ( ) . empty ( ) ) {
bbox = get_extents ( layer_region . slices ( ) . surfaces . front ( ) ) ;
for ( auto it = layer_region . slices ( ) . surfaces . cbegin ( ) + 1 ; it ! = layer_region . slices ( ) . surfaces . cend ( ) ; + + it )
bbox . merge ( get_extents ( * it ) ) ;
}
return bbox ;
}
BoundingBox get_extents ( const LayerRegionPtrs & layer_regions )
{
BoundingBox bbox ;
if ( ! layer_regions . empty ( ) ) {
bbox = get_extents ( * layer_regions . front ( ) ) ;
for ( auto it = layer_regions . begin ( ) + 1 ; it ! = layer_regions . end ( ) ; + + it )
bbox . merge ( get_extents ( * * it ) ) ;
}
return bbox ;
}
}