2022-05-04 13:05:56 +00:00
# include <catch2/catch.hpp>
# include <numeric>
# include <sstream>
# include "libslic3r/Config.hpp"
# include "libslic3r/ClipperUtils.hpp"
# include "libslic3r/Layer.hpp"
# include "libslic3r/PerimeterGenerator.hpp"
# include "libslic3r/Print.hpp"
# include "libslic3r/PrintConfig.hpp"
# include "libslic3r/SurfaceCollection.hpp"
# include "libslic3r/libslic3r.h"
# include "test_data.hpp"
using namespace Slic3r ;
SCENARIO ( " Perimeter nesting " , " [Perimeters] " )
{
struct TestData {
ExPolygons expolygons ;
// expected number of loops
int total ;
// expected number of external loops
int external ;
// expected external perimeter
std : : vector < bool > ext_order ;
// expected number of internal contour loops
int cinternal ;
// expected number of ccw loops
int ccw ;
// expected ccw/cw order
std : : vector < bool > ccw_order ;
// expected nesting order
std : : vector < std : : vector < int > > nesting ;
} ;
FullPrintConfig config ;
auto test = [ & config ] ( const TestData & data ) {
SurfaceCollection slices ;
slices . append ( data . expolygons , stInternal ) ;
ExtrusionEntityCollection loops ;
ExtrusionEntityCollection gap_fill ;
2022-10-27 17:08:43 +00:00
ExPolygons fill_expolygons ;
2022-10-27 11:04:52 +00:00
Flow flow ( 1. , 1. , 1. ) ;
PerimeterGenerator : : Parameters perimeter_generator_params (
2022-05-04 13:05:56 +00:00
1. , // layer height
2022-10-27 11:04:52 +00:00
- 1 , // layer ID
flow , flow , flow , flow ,
static_cast < const PrintRegionConfig & > ( config ) ,
static_cast < const PrintObjectConfig & > ( config ) ,
static_cast < const PrintConfig & > ( config ) ,
false ) ; // spiral_vase
Polygons lower_layer_polygons_cache ;
2022-11-02 11:59:31 +00:00
for ( const Surface & surface : slices )
2022-06-29 22:22:25 +00:00
// FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
// if (config.perimeter_generator == PerimeterGeneratorType::Arachne)
2022-10-27 11:04:52 +00:00
// PerimeterGenerator::process_arachne();
2022-06-29 22:22:25 +00:00
// else
2022-10-27 11:04:52 +00:00
PerimeterGenerator : : process_classic (
// input:
perimeter_generator_params ,
2022-11-02 11:59:31 +00:00
surface ,
2022-10-27 11:04:52 +00:00
nullptr ,
// cache:
lower_layer_polygons_cache ,
// output:
2022-10-27 17:08:43 +00:00
loops , gap_fill , fill_expolygons ) ;
2022-05-04 13:05:56 +00:00
THEN ( " expected number of collections " ) {
REQUIRE ( loops . entities . size ( ) = = data . expolygons . size ( ) ) ;
}
loops = loops . flatten ( ) ;
THEN ( " expected number of loops " ) {
REQUIRE ( loops . entities . size ( ) = = data . total ) ;
}
THEN ( " expected number of external loops " ) {
size_t num_external = std : : count_if ( loops . entities . begin ( ) , loops . entities . end ( ) ,
2023-01-16 09:14:32 +00:00
[ ] ( const ExtrusionEntity * ee ) { return ee - > role ( ) = = ExtrusionRole : : ExternalPerimeter ; } ) ;
2022-05-04 13:05:56 +00:00
REQUIRE ( num_external = = data . external ) ;
}
THEN ( " expected external order " ) {
std : : vector < bool > ext_order ;
for ( auto * ee : loops . entities )
2023-01-16 09:14:32 +00:00
ext_order . emplace_back ( ee - > role ( ) = = ExtrusionRole : : ExternalPerimeter ) ;
2022-05-04 13:05:56 +00:00
REQUIRE ( ext_order = = data . ext_order ) ;
}
THEN ( " expected number of internal contour loops " ) {
size_t cinternal = std : : count_if ( loops . entities . begin ( ) , loops . entities . end ( ) ,
[ ] ( const ExtrusionEntity * ee ) { return dynamic_cast < const ExtrusionLoop * > ( ee ) - > loop_role ( ) = = elrContourInternalPerimeter ; } ) ;
REQUIRE ( cinternal = = data . cinternal ) ;
}
THEN ( " expected number of ccw loops " ) {
size_t ccw = std : : count_if ( loops . entities . begin ( ) , loops . entities . end ( ) ,
[ ] ( const ExtrusionEntity * ee ) { return dynamic_cast < const ExtrusionLoop * > ( ee ) - > polygon ( ) . is_counter_clockwise ( ) ; } ) ;
REQUIRE ( ccw = = data . ccw ) ;
}
THEN ( " expected ccw/cw order " ) {
std : : vector < bool > ccw_order ;
for ( auto * ee : loops . entities )
ccw_order . emplace_back ( dynamic_cast < const ExtrusionLoop * > ( ee ) - > polygon ( ) . is_counter_clockwise ( ) ) ;
REQUIRE ( ccw_order = = data . ccw_order ) ;
}
THEN ( " expected nesting order " ) {
for ( const std : : vector < int > & nesting : data . nesting ) {
for ( size_t i = 1 ; i < nesting . size ( ) ; + + i )
REQUIRE ( dynamic_cast < const ExtrusionLoop * > ( loops . entities [ nesting [ i - 1 ] ] ) - > polygon ( ) . contains ( loops . entities [ nesting [ i ] ] - > first_point ( ) ) ) ;
}
}
} ;
WHEN ( " Rectangle " ) {
config . perimeters . value = 3 ;
TestData data ;
data . expolygons = {
ExPolygon { Polygon : : new_scale ( { { 0 , 0 } , { 100 , 0 } , { 100 , 100 } , { 0 , 100 } } ) }
} ;
data . total = 3 ;
data . external = 1 ;
data . ext_order = { false , false , true } ;
data . cinternal = 1 ;
data . ccw = 3 ;
data . ccw_order = { true , true , true } ;
data . nesting = { { 2 , 1 , 0 } } ;
test ( data ) ;
}
WHEN ( " Rectangle with hole " ) {
config . perimeters . value = 3 ;
TestData data ;
data . expolygons = {
ExPolygon { Polygon : : new_scale ( { { 0 , 0 } , { 100 , 0 } , { 100 , 100 } , { 0 , 100 } } ) ,
Polygon : : new_scale ( { { 40 , 40 } , { 40 , 60 } , { 60 , 60 } , { 60 , 40 } } ) }
} ;
data . total = 6 ;
data . external = 2 ;
data . ext_order = { false , false , true , false , false , true } ;
data . cinternal = 1 ;
data . ccw = 3 ;
data . ccw_order = { false , false , false , true , true , true } ;
data . nesting = { { 5 , 4 , 3 , 0 , 1 , 2 } } ;
test ( data ) ;
}
WHEN ( " Nested rectangles with holes " ) {
config . perimeters . value = 3 ;
TestData data ;
data . expolygons = {
ExPolygon { Polygon : : new_scale ( { { 0 , 0 } , { 200 , 0 } , { 200 , 200 } , { 0 , 200 } } ) ,
Polygon : : new_scale ( { { 20 , 20 } , { 20 , 180 } , { 180 , 180 } , { 180 , 20 } } ) } ,
ExPolygon { Polygon : : new_scale ( { { 50 , 50 } , { 150 , 50 } , { 150 , 150 } , { 50 , 150 } } ) ,
Polygon : : new_scale ( { { 80 , 80 } , { 80 , 120 } , { 120 , 120 } , { 120 , 80 } } ) }
} ;
data . total = 4 * 3 ;
data . external = 4 ;
data . ext_order = { false , false , true , false , false , true , false , false , true , false , false , true } ;
data . cinternal = 2 ;
data . ccw = 2 * 3 ;
data . ccw_order = { false , false , false , true , true , true , false , false , false , true , true , true } ;
test ( data ) ;
}
WHEN ( " Rectangle with multiple holes " ) {
config . perimeters . value = 2 ;
TestData data ;
ExPolygon expoly { Polygon : : new_scale ( { { 0 , 0 } , { 50 , 0 } , { 50 , 50 } , { 0 , 50 } } ) } ;
expoly . holes . emplace_back ( Polygon : : new_scale ( { { 7.5 , 7.5 } , { 7.5 , 12.5 } , { 12.5 , 12.5 } , { 12.5 , 7.5 } } ) ) ;
expoly . holes . emplace_back ( Polygon : : new_scale ( { { 7.5 , 17.5 } , { 7.5 , 22.5 } , { 12.5 , 22.5 } , { 12.5 , 17.5 } } ) ) ;
expoly . holes . emplace_back ( Polygon : : new_scale ( { { 7.5 , 27.5 } , { 7.5 , 32.5 } , { 12.5 , 32.5 } , { 12.5 , 27.5 } } ) ) ;
expoly . holes . emplace_back ( Polygon : : new_scale ( { { 7.5 , 37.5 } , { 7.5 , 42.5 } , { 12.5 , 42.5 } , { 12.5 , 37.5 } } ) ) ;
expoly . holes . emplace_back ( Polygon : : new_scale ( { { 17.5 , 7.5 } , { 17.5 , 12.5 } , { 22.5 , 12.5 } , { 22.5 , 7.5 } } ) ) ;
data . expolygons = { expoly } ;
data . total = 12 ;
data . external = 6 ;
data . ext_order = { false , true , false , true , false , true , false , true , false , true , false , true } ;
data . cinternal = 1 ;
data . ccw = 2 ;
data . ccw_order = { false , false , false , false , false , false , false , false , false , false , true , true } ;
data . nesting = { { 0 , 1 } , { 2 , 3 } , { 4 , 5 } , { 6 , 7 } , { 8 , 9 } } ;
test ( data ) ;
} ;
}
SCENARIO ( " Perimeters " , " [Perimeters] " )
{
auto config = Slic3r : : DynamicPrintConfig : : full_print_config_with ( {
{ " skirts " , 0 } ,
{ " fill_density " , 0 } ,
{ " perimeters " , 3 } ,
{ " top_solid_layers " , 0 } ,
{ " bottom_solid_layers " , 0 } ,
// to prevent speeds from being altered
{ " cooling " , " 0 " } ,
// to prevent speeds from being altered
{ " first_layer_speed " , " 100% " }
} ) ;
WHEN ( " Bridging perimeters disabled " ) {
std : : string gcode = Slic3r : : Test : : slice ( { Slic3r : : Test : : TestMesh : : overhang } , config ) ;
THEN ( " all perimeters extruded ccw " ) {
GCodeReader parser ;
bool has_cw_loops = false ;
Polygon current_loop ;
parser . parse_buffer ( gcode , [ & has_cw_loops , & current_loop ] ( Slic3r : : GCodeReader & self , const Slic3r : : GCodeReader : : GCodeLine & line )
{
if ( line . extruding ( self ) & & line . dist_XY ( self ) > 0 ) {
if ( current_loop . empty ( ) )
current_loop . points . emplace_back ( self . xy_scaled ( ) ) ;
current_loop . points . emplace_back ( line . new_XY_scaled ( self ) ) ;
} else if ( ! line . cmd_is ( " M73 " ) ) {
// skips remaining time lines (M73)
if ( ! current_loop . empty ( ) & & current_loop . is_clockwise ( ) )
has_cw_loops = true ;
current_loop . clear ( ) ;
}
} ) ;
REQUIRE ( ! has_cw_loops ) ;
}
}
auto test = [ & config ] ( Test : : TestMesh model ) {
// we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong)
std : : string gcode = Slic3r : : Test : : slice ( { model , model } , config ) ;
GCodeReader parser ;
bool has_cw_loops = false ;
bool has_outwards_move = false ;
bool starts_on_convex_point = false ;
// print_z => count of external loops
std : : map < coord_t , int > external_loops ;
Polygon current_loop ;
const double external_perimeter_speed = config . get_abs_value ( " external_perimeter_speed " ) * 60. ;
parser . parse_buffer ( gcode , [ & has_cw_loops , & has_outwards_move , & starts_on_convex_point , & external_loops , & current_loop , external_perimeter_speed , model ]
( Slic3r : : GCodeReader & self , const Slic3r : : GCodeReader : : GCodeLine & line )
{
if ( line . extruding ( self ) & & line . dist_XY ( self ) > 0 ) {
if ( current_loop . empty ( ) )
current_loop . points . emplace_back ( self . xy_scaled ( ) ) ;
current_loop . points . emplace_back ( line . new_XY_scaled ( self ) ) ;
} else if ( ! line . cmd_is ( " M73 " ) ) {
// skips remaining time lines (M73)
if ( ! current_loop . empty ( ) ) {
if ( current_loop . is_clockwise ( ) )
has_cw_loops = true ;
if ( is_approx < double > ( self . f ( ) , external_perimeter_speed ) ) {
// reset counter for second object
coord_t z = scaled < coord_t > ( self . z ( ) ) ;
auto it = external_loops . find ( z ) ;
if ( it = = external_loops . end ( ) )
it = external_loops . insert ( std : : make_pair ( z , 0 ) ) . first ;
else if ( it - > second = = 2 )
it - > second = 0 ;
+ + it - > second ;
bool is_contour = it - > second = = 2 ;
bool is_hole = it - > second = = 1 ;
// Testing whether the move point after loop ends up inside the extruded loop.
bool loop_contains_point = current_loop . contains ( line . new_XY_scaled ( self ) ) ;
if ( // contour should include destination
( ! loop_contains_point & & is_contour ) | |
// hole should not
( loop_contains_point & & is_hole ) )
has_outwards_move = true ;
if ( model = = Test : : TestMesh : : cube_with_concave_hole ) {
// check that loop starts at a concave vertex
double cross = cross2 ( ( current_loop . points . front ( ) - current_loop . points [ current_loop . points . size ( ) - 2 ] ) . cast < double > ( ) , ( current_loop . points [ 1 ] - current_loop . points . front ( ) ) . cast < double > ( ) ) ;
bool convex = cross > 0. ;
if ( ( convex & & is_contour ) | | ( ! convex & & is_hole ) )
starts_on_convex_point = true ;
}
}
current_loop . clear ( ) ;
}
}
} ) ;
THEN ( " all perimeters extruded ccw " ) {
REQUIRE ( ! has_cw_loops ) ;
}
2022-06-29 22:22:25 +00:00
// FIXME Lukas H.: Arachne is printing external loops before hole loops in this test case.
if ( config . opt_enum < PerimeterGeneratorType > ( " perimeter_generator " ) = = Slic3r : : PerimeterGeneratorType : : Arachne ) {
THEN ( " move outwards after completing external loop " ) {
// REQUIRE(! has_outwards_move);
}
// FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
THEN ( " loops start on concave point if any " ) {
// REQUIRE(! starts_on_convex_point);
}
} else {
THEN ( " move inwards after completing external loop " ) {
REQUIRE ( ! has_outwards_move ) ;
}
THEN ( " loops start on concave point if any " ) {
REQUIRE ( ! starts_on_convex_point ) ;
}
2022-05-04 13:05:56 +00:00
}
} ;
// Reusing the config above.
config . set_deserialize_strict ( {
{ " external_perimeter_speed " , 68 }
} ) ;
GIVEN ( " Cube with hole " ) { test ( Test : : TestMesh : : cube_with_hole ) ; }
GIVEN ( " Cube with concave hole " ) { test ( Test : : TestMesh : : cube_with_concave_hole ) ; }
WHEN ( " Bridging perimeters enabled " ) {
// Reusing the config above.
config . set_deserialize_strict ( {
{ " perimeters " , 1 } ,
{ " perimeter_speed " , 77 } ,
{ " external_perimeter_speed " , 66 } ,
2022-12-11 22:04:40 +00:00
{ " enable_dynamic_overhang_speeds " , false } ,
2022-05-04 13:05:56 +00:00
{ " bridge_speed " , 99 } ,
{ " cooling " , " 1 " } ,
{ " fan_below_layer_time " , " 0 " } ,
{ " slowdown_below_layer_time " , " 0 " } ,
{ " bridge_fan_speed " , " 100 " } ,
// arbitrary value
{ " bridge_flow_ratio " , 33 } ,
{ " overhangs " , true }
} ) ;
std : : string gcode = Slic3r : : Test : : slice ( { mesh ( Slic3r : : Test : : TestMesh : : overhang ) } , config ) ;
THEN ( " Bridging is applied to bridging perimeters " ) {
GCodeReader parser ;
// print Z => speeds
std : : map < coord_t , std : : set < double > > layer_speeds ;
int fan_speed = 0 ;
const double perimeter_speed = config . opt_float ( " perimeter_speed " ) * 60. ;
const double external_perimeter_speed = config . get_abs_value ( " external_perimeter_speed " ) * 60. ;
const double bridge_speed = config . opt_float ( " bridge_speed " ) * 60. ;
const double nozzle_dmr = config . opt < ConfigOptionFloats > ( " nozzle_diameter " ) - > get_at ( 0 ) ;
const double filament_dmr = config . opt < ConfigOptionFloats > ( " filament_diameter " ) - > get_at ( 0 ) ;
const double bridge_mm_per_mm = sqr ( nozzle_dmr / filament_dmr ) * config . opt_float ( " bridge_flow_ratio " ) ;
2022-12-11 22:04:40 +00:00
parser . parse_buffer ( gcode , [ & layer_speeds , & fan_speed , perimeter_speed , external_perimeter_speed , bridge_speed , nozzle_dmr , filament_dmr , bridge_mm_per_mm ]
2022-05-04 13:05:56 +00:00
( Slic3r : : GCodeReader & self , const Slic3r : : GCodeReader : : GCodeLine & line )
{
if ( line . cmd_is ( " M107 " ) )
fan_speed = 0 ;
else if ( line . cmd_is ( " M106 " ) )
line . has_value ( ' S ' , fan_speed ) ;
else if ( line . extruding ( self ) & & line . dist_XY ( self ) > 0 ) {
double feedrate = line . new_F ( self ) ;
2022-12-11 22:04:40 +00:00
REQUIRE ( ( is_approx ( feedrate , perimeter_speed ) | | is_approx ( feedrate , external_perimeter_speed ) | | is_approx ( feedrate , bridge_speed ) ) ) ;
2022-05-04 13:05:56 +00:00
layer_speeds [ self . z ( ) ] . insert ( feedrate ) ;
bool bridging = is_approx ( feedrate , bridge_speed ) ;
double mm_per_mm = line . dist_E ( self ) / line . dist_XY ( self ) ;
// Fan enabled at full speed when bridging, disabled when not bridging.
REQUIRE ( ( ! bridging | | fan_speed = = 255 ) ) ;
2022-12-11 22:04:40 +00:00
REQUIRE ( ( bridging | | fan_speed = = 0 ) ) ;
2022-05-04 13:05:56 +00:00
// When bridging, bridge flow is applied.
REQUIRE ( ( ! bridging | | std : : abs ( mm_per_mm - bridge_mm_per_mm ) < = 0.01 ) ) ;
}
} ) ;
// only overhang layer has more than one speed
size_t num_overhangs = std : : count_if ( layer_speeds . begin ( ) , layer_speeds . end ( ) , [ ] ( const std : : pair < double , std : : set < double > > & v ) { return v . second . size ( ) > 1 ; } ) ;
REQUIRE ( num_overhangs = = 1 ) ;
}
}
GIVEN ( " iPad stand " ) {
WHEN ( " Extra perimeters enabled " ) {
auto config = Slic3r : : DynamicPrintConfig : : full_print_config_with ( {
{ " skirts " , 0 } ,
{ " perimeters " , 3 } ,
{ " layer_height " , 0.4 } ,
{ " first_layer_height " , 0.35 } ,
{ " extra_perimeters " , 1 } ,
// to prevent speeds from being altered
{ " cooling " , " 0 " } ,
// to prevent speeds from being altered
{ " first_layer_speed " , " 100% " } ,
{ " perimeter_speed " , 99 } ,
{ " external_perimeter_speed " , 99 } ,
{ " small_perimeter_speed " , 99 } ,
{ " thin_walls " , 0 } ,
} ) ;
std : : string gcode = Slic3r : : Test : : slice ( { Slic3r : : Test : : TestMesh : : ipadstand } , config ) ;
// z => number of loops
std : : map < coord_t , int > perimeters ;
bool in_loop = false ;
const double perimeter_speed = config . opt_float ( " perimeter_speed " ) * 60. ;
GCodeReader parser ;
parser . parse_buffer ( gcode , [ & perimeters , & in_loop , perimeter_speed ] ( Slic3r : : GCodeReader & self , const Slic3r : : GCodeReader : : GCodeLine & line )
{
if ( line . extruding ( self ) & & line . dist_XY ( self ) > 0 & & is_approx < double > ( line . new_F ( self ) , perimeter_speed ) ) {
if ( ! in_loop ) {
coord_t z = scaled < coord_t > ( self . z ( ) ) ;
auto it = perimeters . find ( z ) ;
if ( it = = perimeters . end ( ) )
it = perimeters . insert ( std : : make_pair ( z , 0 ) ) . first ;
+ + it - > second ;
}
in_loop = true ;
} else if ( ! line . cmd_is ( " M73 " ) ) {
// skips remaining time lines (M73)
in_loop = false ;
}
} ) ;
THEN ( " no superfluous extra perimeters " ) {
const int num_perimeters = config . opt_int ( " perimeters " ) ;
size_t extra_perimeters = std : : count_if ( perimeters . begin ( ) , perimeters . end ( ) , [ num_perimeters ] ( const std : : pair < const coord_t , int > & v ) { return ( v . second % num_perimeters ) > 0 ; } ) ;
REQUIRE ( extra_perimeters = = 0 ) ;
}
}
}
}
SCENARIO ( " Some weird coverage test " , " [Perimeters] " )
{
auto config = Slic3r : : DynamicPrintConfig : : full_print_config_with ( {
{ " nozzle_diameter " , " 0.4 " } ,
{ " perimeters " , 2 } ,
{ " perimeter_extrusion_width " , 0.4 } ,
{ " external_perimeter_extrusion_width " , 0.4 } ,
{ " infill_extrusion_width " , 0.53 } ,
{ " solid_infill_extrusion_width " , 0.53 }
} ) ;
// we just need a pre-filled Print object
Print print ;
Model model ;
Slic3r : : Test : : init_print ( { Test : : TestMesh : : cube_20x20x20 } , print , model , config ) ;
// override a layer's slices
ExPolygon expolygon ;
expolygon . contour = {
{ - 71974463 , - 139999376 } , { - 71731792 , - 139987456 } , { - 71706544 , - 139985616 } , { - 71682119 , - 139982639 } , { - 71441248 , - 139946912 } , { - 71417487 , - 139942895 } , { - 71379384 , - 139933984 } , { - 71141800 , - 139874480 } ,
{ - 71105247 , - 139862895 } , { - 70873544 , - 139779984 } , { - 70838592 , - 139765856 } , { - 70614943 , - 139660064 } , { - 70581783 , - 139643567 } , { - 70368368 , - 139515680 } , { - 70323751 , - 139487872 } , { - 70122160 , - 139338352 } ,
{ - 70082399 , - 139306639 } , { - 69894800 , - 139136624 } , { - 69878679 , - 139121327 } , { - 69707992 , - 138933008 } , { - 69668575 , - 138887343 } , { - 69518775 , - 138685359 } , { - 69484336 , - 138631632 } , { - 69356423 , - 138418207 } ,
{ - 69250040 , - 138193296 } , { - 69220920 , - 138128976 } , { - 69137992 , - 137897168 } , { - 69126095 , - 137860255 } , { - 69066568 , - 137622608 } , { - 69057104 , - 137582511 } , { - 69053079 , - 137558751 } , { - 69017352 , - 137317872 } ,
{ - 69014392 , - 137293456 } , { - 69012543 , - 137268207 } , { - 68999369 , - 137000000 } , { - 63999999 , - 137000000 } , { - 63705947 , - 136985551 } , { - 63654984 , - 136977984 } , { - 63414731 , - 136942351 } , { - 63364756 , - 136929840 } ,
{ - 63129151 , - 136870815 } , { - 62851950 , - 136771631 } , { - 62585807 , - 136645743 } , { - 62377483 , - 136520895 } , { - 62333291 , - 136494415 } , { - 62291908 , - 136463728 } , { - 62096819 , - 136319023 } , { - 62058644 , - 136284432 } ,
{ - 61878676 , - 136121328 } , { - 61680968 , - 135903184 } , { - 61650275 , - 135861807 } , { - 61505591 , - 135666719 } , { - 61354239 , - 135414191 } , { - 61332211 , - 135367615 } , { - 61228359 , - 135148063 } , { - 61129179 , - 134870847 } ,
{ - 61057639 , - 134585262 } , { - 61014451 , - 134294047 } , { - 61000000 , - 134000000 } , { - 61000000 , - 107999999 } , { - 61014451 , - 107705944 } , { - 61057639 , - 107414736 } , { - 61129179 , - 107129152 } , { - 61228359 , - 106851953 } ,
{ - 61354239 , - 106585808 } , { - 61505591 , - 106333288 } , { - 61680967 , - 106096816 } , { - 61878675 , - 105878680 } , { - 62096820 , - 105680967 } , { - 62138204 , - 105650279 } , { - 62333292 , - 105505591 } , { - 62585808 , - 105354239 } ,
{ - 62632384 , - 105332207 } , { - 62851951 , - 105228360 } , { - 62900463 , - 105211008 } , { - 63129152 , - 105129183 } , { - 63414731 , - 105057640 } , { - 63705947 , - 105014448 } , { - 63999999 , - 105000000 } , { - 68999369 , - 105000000 } ,
{ - 69012543 , - 104731792 } , { - 69014392 , - 104706544 } , { - 69017352 , - 104682119 } , { - 69053079 , - 104441248 } , { - 69057104 , - 104417487 } , { - 69066008 , - 104379383 } , { - 69125528 , - 104141799 } , { - 69137111 , - 104105248 } ,
{ - 69220007 , - 103873544 } , { - 69234136 , - 103838591 } , { - 69339920 , - 103614943 } , { - 69356415 , - 103581784 } , { - 69484328 , - 103368367 } , { - 69512143 , - 103323752 } , { - 69661647 , - 103122160 } , { - 69693352 , - 103082399 } ,
{ - 69863383 , - 102894800 } , { - 69878680 , - 102878679 } , { - 70066999 , - 102707992 } , { - 70112656 , - 102668576 } , { - 70314648 , - 102518775 } , { - 70368367 , - 102484336 } , { - 70581783 , - 102356424 } , { - 70806711 , - 102250040 } ,
{ - 70871040 , - 102220919 } , { - 71102823 , - 102137992 } , { - 71139752 , - 102126095 } , { - 71377383 , - 102066568 } , { - 71417487 , - 102057104 } , { - 71441248 , - 102053079 } , { - 71682119 , - 102017352 } , { - 71706535 , - 102014392 } ,
{ - 71731784 , - 102012543 } , { - 71974456 , - 102000624 } , { - 71999999 , - 102000000 } , { - 104000000 , - 102000000 } , { - 104025536 , - 102000624 } , { - 104268207 , - 102012543 } , { - 104293455 , - 102014392 } ,
{ - 104317880 , - 102017352 } , { - 104558751 , - 102053079 } , { - 104582512 , - 102057104 } , { - 104620616 , - 102066008 } , { - 104858200 , - 102125528 } , { - 104894751 , - 102137111 } , { - 105126455 , - 102220007 } ,
{ - 105161408 , - 102234136 } , { - 105385056 , - 102339920 } , { - 105418215 , - 102356415 } , { - 105631632 , - 102484328 } , { - 105676247 , - 102512143 } , { - 105877839 , - 102661647 } , { - 105917600 , - 102693352 } ,
{ - 106105199 , - 102863383 } , { - 106121320 , - 102878680 } , { - 106292007 , - 103066999 } , { - 106331424 , - 103112656 } , { - 106481224 , - 103314648 } , { - 106515663 , - 103368367 } , { - 106643575 , - 103581783 } ,
{ - 106749959 , - 103806711 } , { - 106779080 , - 103871040 } , { - 106862007 , - 104102823 } , { - 106873904 , - 104139752 } , { - 106933431 , - 104377383 } , { - 106942896 , - 104417487 } , { - 106946920 , - 104441248 } ,
{ - 106982648 , - 104682119 } , { - 106985607 , - 104706535 } , { - 106987456 , - 104731784 } , { - 107000630 , - 105000000 } , { - 112000000 , - 105000000 } , { - 112294056 , - 105014448 } , { - 112585264 , - 105057640 } ,
{ - 112870848 , - 105129184 } , { - 112919359 , - 105146535 } , { - 113148048 , - 105228360 } , { - 113194624 , - 105250392 } , { - 113414191 , - 105354239 } , { - 113666711 , - 105505591 } , { - 113708095 , - 105536279 } ,
{ - 113903183 , - 105680967 } , { - 114121320 , - 105878679 } , { - 114319032 , - 106096816 } , { - 114349720 , - 106138200 } , { - 114494408 , - 106333288 } , { - 114645760 , - 106585808 } , { - 114667792 , - 106632384 } ,
{ - 114771640 , - 106851952 } , { - 114788991 , - 106900463 } , { - 114870815 , - 107129151 } , { - 114942359 , - 107414735 } , { - 114985551 , - 107705943 } , { - 115000000 , - 107999999 } , { - 115000000 , - 134000000 } ,
{ - 114985551 , - 134294048 } , { - 114942359 , - 134585263 } , { - 114870816 , - 134870847 } , { - 114853464 , - 134919359 } , { - 114771639 , - 135148064 } , { - 114645759 , - 135414192 } , { - 114494407 , - 135666720 } ,
{ - 114319031 , - 135903184 } , { - 114121320 , - 136121327 } , { - 114083144 , - 136155919 } , { - 113903184 , - 136319023 } , { - 113861799 , - 136349712 } , { - 113666711 , - 136494416 } , { - 113458383 , - 136619264 } ,
{ - 113414192 , - 136645743 } , { - 113148049 , - 136771631 } , { - 112870848 , - 136870815 } , { - 112820872 , - 136883327 } , { - 112585264 , - 136942351 } , { - 112534303 , - 136949920 } , { - 112294056 , - 136985551 } ,
{ - 112000000 , - 137000000 } , { - 107000630 , - 137000000 } , { - 106987456 , - 137268207 } , { - 106985608 , - 137293440 } , { - 106982647 , - 137317872 } , { - 106946920 , - 137558751 } , { - 106942896 , - 137582511 } ,
{ - 106933991 , - 137620624 } , { - 106874471 , - 137858208 } , { - 106862888 , - 137894751 } , { - 106779992 , - 138126463 } , { - 106765863 , - 138161424 } , { - 106660080 , - 138385055 } , { - 106643584 , - 138418223 } ,
{ - 106515671 , - 138631648 } , { - 106487855 , - 138676256 } , { - 106338352 , - 138877839 } , { - 106306647 , - 138917600 } , { - 106136616 , - 139105199 } , { - 106121320 , - 139121328 } , { - 105933000 , - 139291999 } ,
{ - 105887344 , - 139331407 } , { - 105685351 , - 139481232 } , { - 105631632 , - 139515663 } , { - 105418216 , - 139643567 } , { - 105193288 , - 139749951 } , { - 105128959 , - 139779072 } , { - 104897175 , - 139862016 } ,
{ - 104860247 , - 139873904 } , { - 104622616 , - 139933423 } , { - 104582511 , - 139942896 } , { - 104558751 , - 139946912 } , { - 104317880 , - 139982656 } , { - 104293463 , - 139985616 } , { - 104268216 , - 139987456 } ,
{ - 104025544 , - 139999376 } , { - 104000000 , - 140000000 } , { - 71999999 , - 140000000 }
} ;
expolygon . holes = {
{ { - 105000000 , - 138000000 } , { - 105000000 , - 104000000 } , { - 71000000 , - 104000000 } , { - 71000000 , - 138000000 } } ,
{ { - 69000000 , - 132000000 } , { - 69000000 , - 110000000 } , { - 64991180 , - 110000000 } , { - 64991180 , - 132000000 } } ,
{ { - 111008824 , - 132000000 } , { - 111008824 , - 110000000 } , { - 107000000 , - 110000000 } , { - 107000000 , - 132000000 } }
} ;
PrintObject * object = print . get_object ( 0 ) ;
object - > slice ( ) ;
Layer * layer = object - > get_layer ( 1 ) ;
LayerRegion * layerm = layer - > get_region ( 0 ) ;
2022-10-26 16:41:39 +00:00
layerm - > m_slices . clear ( ) ;
layerm - > m_slices . append ( { expolygon } , stInternal ) ;
2022-11-14 14:17:04 +00:00
layer - > lslices = { expolygon } ;
layer - > lslices_ex = { { get_extents ( expolygon ) } } ;
2022-05-04 13:05:56 +00:00
// make perimeters
layer - > make_perimeters ( ) ;
// compute the covered area
Flow pflow = layerm - > flow ( frPerimeter ) ;
Flow iflow = layerm - > flow ( frInfill ) ;
Polygons covered_by_perimeters ;
Polygons covered_by_infill ;
{
Polygons acc ;
2022-10-26 16:41:39 +00:00
for ( const ExtrusionEntity * ee : layerm - > perimeters ( ) )
2022-05-04 13:05:56 +00:00
for ( const ExtrusionEntity * ee : dynamic_cast < const ExtrusionEntityCollection * > ( ee ) - > entities )
append ( acc , offset ( dynamic_cast < const ExtrusionLoop * > ( ee ) - > polygon ( ) . split_at_first_point ( ) , float ( pflow . scaled_width ( ) / 2.f + SCALED_EPSILON ) ) ) ;
covered_by_perimeters = union_ ( acc ) ;
}
{
Polygons acc ;
2022-10-26 16:41:39 +00:00
for ( const ExPolygon & expolygon : layerm - > fill_expolygons ( ) )
append ( acc , to_polygons ( expolygon ) ) ;
for ( const ExtrusionEntity * ee : layerm - > thin_fills ( ) . entities )
2022-05-04 13:05:56 +00:00
append ( acc , offset ( dynamic_cast < const ExtrusionPath * > ( ee ) - > polyline , float ( iflow . scaled_width ( ) / 2.f + SCALED_EPSILON ) ) ) ;
covered_by_infill = union_ ( acc ) ;
}
// compute the non covered area
2022-10-26 16:41:39 +00:00
ExPolygons non_covered = diff_ex ( to_polygons ( layerm - > slices ( ) . surfaces ) , union_ ( covered_by_perimeters , covered_by_infill ) ) ;
2022-05-04 13:05:56 +00:00
/*
if ( 0 ) {
printf " max non covered = %f \n " , List : : Util : : max ( map unscale unscale $ _ - > area , @ $ non_covered ) ;
require " Slic3r/SVG.pm " ;
Slic3r : : SVG : : output (
" gaps.svg " ,
expolygons = > [ map $ _ - > expolygon , @ { $ layerm - > slices } ] ,
red_expolygons = > union_ex ( [ map @ $ _ , ( @ $ covered_by_perimeters , @ $ covered_by_infill ) ] ) ,
green_expolygons = > union_ex ( $ non_covered ) ,
no_arrows = > 1 ,
polylines = > [
map $ _ - > polygon - > split_at_first_point , map @ $ _ , @ { $ layerm - > perimeters } ,
] ,
) ;
}
*/
THEN ( " no gap between perimeters and infill " ) {
2022-10-26 16:41:39 +00:00
size_t num_non_convered = std : : count_if ( non_covered . begin ( ) , non_covered . end ( ) ,
[ & iflow ] ( const ExPolygon & ex ) { return ex . area ( ) > sqr ( double ( iflow . scaled_width ( ) ) ) ; } ) ;
2022-05-04 13:05:56 +00:00
REQUIRE ( num_non_convered = = 0 ) ;
}
}
SCENARIO ( " Perimeters3 " , " [Perimeters] " )
{
auto config = Slic3r : : DynamicPrintConfig : : full_print_config_with ( {
{ " skirts " , 0 } ,
{ " perimeters " , 3 } ,
{ " layer_height " , 0.4 } ,
2022-12-14 15:40:50 +00:00
{ " bridge_speed " , 99 } ,
2022-12-11 22:04:40 +00:00
{ " enable_dynamic_overhang_speeds " , false } ,
2022-05-04 13:05:56 +00:00
// to prevent bridging over sparse infill
{ " fill_density " , 0 } ,
{ " overhangs " , true } ,
// to prevent speeds from being altered
{ " cooling " , " 0 " } ,
// to prevent speeds from being altered
{ " first_layer_speed " , " 100% " }
} ) ;
auto test = [ & config ] ( const Vec3d & scale ) {
std : : string gcode = Slic3r : : Test : : slice ( { mesh ( Slic3r : : Test : : TestMesh : : V , Vec3d : : Zero ( ) , scale ) } , config ) ;
GCodeReader parser ;
std : : set < coord_t > z_with_bridges ;
2022-12-11 22:04:40 +00:00
const double bridge_speed = config . opt_float ( " bridge_speed " ) * 60. ;
parser . parse_buffer ( gcode , [ & z_with_bridges , bridge_speed ] ( Slic3r : : GCodeReader & self , const Slic3r : : GCodeReader : : GCodeLine & line )
2022-05-04 13:05:56 +00:00
{
2022-12-11 22:04:40 +00:00
if ( line . extruding ( self ) & & line . dist_XY ( self ) > 0 & & is_approx < double > ( line . new_F ( self ) , bridge_speed ) )
2022-05-04 13:05:56 +00:00
z_with_bridges . insert ( scaled < coord_t > ( self . z ( ) ) ) ;
} ) ;
return z_with_bridges . size ( ) ;
} ;
2022-12-11 22:04:40 +00:00
GIVEN ( " V shape, unscaled " ) {
int n = test ( Vec3d ( 1. , 1. , 1. ) ) ;
// except for the two internal solid layers above void
THEN ( " no overhangs printed with bridge speed " ) {
REQUIRE ( n = = 1 ) ;
2022-05-04 13:05:56 +00:00
}
}
GIVEN ( " V shape, scaled 3x in X " ) {
int n = test ( Vec3d ( 3. , 1. , 1. ) ) ;
2022-12-11 22:04:40 +00:00
// except for the two internal solid layers above void
THEN ( " overhangs printed with bridge speed " ) {
2022-05-04 13:05:56 +00:00
REQUIRE ( n > 2 ) ;
}
}
}
SCENARIO ( " Perimeters4 " , " [Perimeters] " )
{
auto config = Slic3r : : DynamicPrintConfig : : full_print_config_with ( {
{ " seam_position " , " random " }
} ) ;
std : : string gcode = Slic3r : : Test : : slice ( { Slic3r : : Test : : TestMesh : : cube_20x20x20 } , config ) ;
THEN ( " successful generation of G-code with seam_position = random " ) {
REQUIRE ( ! gcode . empty ( ) ) ;
}
}
SCENARIO ( " Seam alignment " , " [Perimeters] " )
{
auto test = [ ] ( Test : : TestMesh model ) {
auto config = Slic3r : : DynamicPrintConfig : : full_print_config_with ( {
{ " seam_position " , " aligned " } ,
{ " skirts " , 0 } ,
{ " perimeters " , 1 } ,
{ " fill_density " , 0 } ,
{ " top_solid_layers " , 0 } ,
{ " bottom_solid_layers " , 0 } ,
{ " retract_layer_change " , " 0 " }
} ) ;
std : : string gcode = Slic3r : : Test : : slice ( { model } , config ) ;
bool was_extruding = false ;
Points seam_points ;
GCodeReader parser ;
parser . parse_buffer ( gcode , [ & was_extruding , & seam_points ] ( Slic3r : : GCodeReader & self , const Slic3r : : GCodeReader : : GCodeLine & line )
{
if ( line . extruding ( self ) ) {
if ( ! was_extruding )
seam_points . emplace_back ( self . xy_scaled ( ) ) ;
was_extruding = true ;
} else if ( ! line . cmd_is ( " M73 " ) ) {
// skips remaining time lines (M73)
was_extruding = false ;
}
} ) ;
THEN ( " seam is aligned " ) {
size_t num_not_aligned = 0 ;
for ( size_t i = 1 ; i < seam_points . size ( ) ; + + i ) {
double d = ( seam_points [ i ] - seam_points [ i - 1 ] ) . cast < double > ( ) . norm ( ) ;
// Seams shall be aligned up to 3mm.
if ( d > scaled < double > ( 3. ) )
+ + num_not_aligned ;
}
REQUIRE ( num_not_aligned = = 0 ) ;
}
} ;
GIVEN ( " 20mm cube " ) {
test ( Slic3r : : Test : : TestMesh : : cube_20x20x20 ) ;
}
GIVEN ( " small_dorito " ) {
test ( Slic3r : : Test : : TestMesh : : small_dorito ) ;
}
}