diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 0c35c93d98..b7a7da7727 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -5,7 +5,7 @@ use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( - offset offset2 + offset offset_ex offset2_ex diff_ex diff union_ex intersection_ex JT_ROUND JT_MITER JT_SQUARE diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 7370881ea3..0385f88b88 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -7,7 +7,7 @@ use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(scale epsilon); use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex - offset offset2 offset_ex offset2_ex JT_MITER); + offset offset_ex offset2_ex JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini index ff03672910..c1b763879c 100644 --- a/resources/profiles/Anycubic.ini +++ b/resources/profiles/Anycubic.ini @@ -1163,7 +1163,7 @@ before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z] default_filament_profile = Generic PLA @MEGA default_print_profile = 0.15mm QUALITY @MEGA deretract_speed = 40 -end_gcode = G1 E-1.0 F2100 ; retract\nG92 E0.0\nG1{if max_layer_z < max_print_height} Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} ; move print head up\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y105 F3000 ; park print head\nM84 ; disable motors +end_gcode = G1 E-1.0 F2100 ; retract\nG92 E0.0\nG1{if max_layer_z < max_print_height} Z{z_offset+min(max_layer_z+30, max_print_height)}{endif} E-34.0 F720 ; move print head up & retract filament\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y105 F3000 ; park print head\nM84 ; disable motors extruder_colour = #808080 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] @@ -1178,7 +1178,7 @@ retract_lift = 0.2 retract_lift_below = 204 retract_speed = 70 silent_mode = 0 -start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nG28 ; home all\nG1 Y2.0 Z0.2 F1000 ; move print head up\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG92 E0.0\nG1 X60.0 E9.0 F1000 ; intro line\nG1 X100.0 E12.5 F1000 ; intro line\nG92 E0.0 +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nG28 ; home all\nG1 Y1.0 Z0.3 F1000 ; move print head up\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG92 E0.0\n; initial load\nG1 X205.0 E19 F1000\nG1 Y1.6\nG1 X5.0 E19 F1000\nG92 E0.0\n; intro line\nG1 Y2.0 Z0.2 F1000\nG1 X65.0 E9.0 F1000\nG1 X105.0 E12.5 F1000\nG92 E0.0 thumbnails = 16x16,220x124 use_relative_e_distances = 1 wipe = 1 @@ -1892,4 +1892,4 @@ default_print_profile = 0.24mm 0.8 nozzle DETAILED QUALITY @PREDATOR ######################################### ########## end printer presets ########## -#########################################"do not" cause ' is bad for syntax highlighting +######################################### diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 5248155250..717b495581 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -39,7 +39,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER3MAX] name = Creality Ender-3 Max @@ -48,7 +48,7 @@ technology = FFF family = ENDER bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER4] name = Creality Ender-4 @@ -57,7 +57,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -66,7 +66,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -75,7 +75,7 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER6] name = Creality Ender-6 @@ -84,7 +84,7 @@ technology = FFF family = ENDER bed_model = ender6_bed.stl bed_texture = ender6.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -93,7 +93,7 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PRO] name = Creality CR-5 Pro @@ -102,7 +102,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR5PROH] name = Creality CR-5 Pro H @@ -111,7 +111,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6SE] name = Creality CR-6 SE @@ -120,7 +120,7 @@ technology = FFF family = CR bed_model = cr6se_bed.stl bed_texture = cr6se.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR6MAX] name = Creality CR-6 Max @@ -129,7 +129,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -138,7 +138,7 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10MAX] name = Creality CR-10 Max @@ -147,7 +147,7 @@ technology = FFF family = CR bed_model = cr10max_bed.stl bed_texture = cr10max.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -156,7 +156,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -165,7 +165,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -174,7 +174,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -183,7 +183,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -192,7 +192,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -201,7 +201,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -210,7 +210,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -219,7 +219,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -228,7 +228,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -237,7 +237,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR200B] name = Creality CR-200B @@ -246,7 +246,7 @@ technology = FFF family = CR bed_model = cr200b_bed.stl bed_texture = cr200b.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY [printer_model:CR8] name = Creality CR-8 @@ -255,7 +255,7 @@ technology = FFF family = CR bed_model = cr8_bed.stl bed_texture = cr8.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -264,7 +264,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -273,7 +273,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -326,7 +326,7 @@ notes = overhangs = 0 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +output_filename_format = {input_filename_base}_{print_time}_{layer_height}mm_{temperature[0]}C_{filament_type[0]}_{printer_model}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -639,6 +639,18 @@ filament_density = 1.24 filament_colour = #FF0000 filament_spool_weight = 256 +[filament:Devil Design PLA Matt @CREALITY] +inherits = *PLA* +filament_vendor = Devil Design +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_cost = 20.00 +filament_density = 1.38 +filament_colour = #FF0000 +filament_spool_weight = 256 + [filament:Devil Design PLA Galaxy @CREALITY] inherits = *PLA* renamed_from = "Devil Design PLA (Galaxy) @CREALITY" diff --git a/resources/profiles/PrusaResearch/mk2.svg b/resources/profiles/PrusaResearch/mk2.svg index 6214a55523..1fe5b4c57d 100644 --- a/resources/profiles/PrusaResearch/mk2.svg +++ b/resources/profiles/PrusaResearch/mk2.svg @@ -47,7 +47,8 @@ stroke-linecap="round" stroke-linejoin="round" stroke-width="1.43643" - id="line3904" /> + id="line3904" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3906" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3908" + style="stroke-width:1.42;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3924" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3926" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3928" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3932" + style="stroke-width:1.42;stroke-miterlimit:4;stroke-dasharray:none" /> + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3956" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3958" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3960" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3964" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3966" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3968" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3970" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3972" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3974" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3976" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3978" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3980" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3982" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3984" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3986" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3988" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3990" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3992" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3994" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3996" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line3998" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4000" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4002" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4004" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4006" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4008" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4010" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4012" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4014" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4018" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4020" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4022" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4024" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4026" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line4028" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2794" + style="stroke-width:1.42;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2796" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2798" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2800" + style="stroke-width:1.42;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2816" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2818" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2820" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2822" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2824" + style="stroke-width:1.433;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2844" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2848" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2850" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2852" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2854" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2856" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2858" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2860" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2862" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2864" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2866" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2868" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2870" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2872" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2874" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2876" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2878" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2880" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2882" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2884" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2886" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2888" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2890" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2892" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2894" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2896" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2898" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2900" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2902" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2904" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2906" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2908" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2910" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2912" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2914" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2916" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2918" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> + id="line2920" + style="stroke-width:0.374;stroke-miterlimit:4;stroke-dasharray:none" /> _anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices), true); + this->_anchor_regions = intersection_ex(grown, union_safety_offset(this->lower_slices)); /* if (0) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 16299f442d..0cf67597a3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -163,6 +163,7 @@ add_library(libslic3r STATIC AppConfig.hpp Print.cpp Print.hpp + PrintApply.cpp PrintBase.cpp PrintBase.hpp PrintConfig.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 8bca3b25a3..7242246040 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -137,13 +137,23 @@ static ClipperLib::Paths safety_offset(PathsProvider &&paths) for (const ClipperLib::Path &path : paths) { co.Clear(); co.MiterLimit = 2.; + // 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. co.AddPath(path, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); - co.Execute(out_this, ClipperSafetyOffset); + bool ccw = ClipperLib::Orientation(path); + co.Execute(out_this, ccw ? ClipperSafetyOffset : - ClipperSafetyOffset); + if (! ccw) { + // Reverse the resulting contours. + for (ClipperLib::Path &path : out_this) + std::reverse(path.begin(), path.end()); + } append(out, std::move(out_this)); } return out; } +// Only safe for a single path. template ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { @@ -161,12 +171,14 @@ ClipperLib::Paths _offset(PathsProvider &&input, ClipperLib::EndType endType, co return retval; } -Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) +Slic3r::Polygons offset(const Slic3r::Polygon& polygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return to_polygons(_offset(ClipperUtils::SinglePathProvider(polygon.points), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } #ifdef CLIPPERUTILS_UNSAFE_OFFSET Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return to_polygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } #endif // CLIPPERUTILS_UNSAFE_OFFSET Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit) @@ -174,11 +186,6 @@ Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, Cli Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return to_polygons(_offset(ClipperUtils::PolylinesProvider(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) - { return ClipperPaths_to_Slic3rExPolygons(_offset(ClipperUtils::PolygonsProvider(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } -#endif // CLIPPERUTILS_UNSAFE_OFFSET - // returns number of expolygons collected (0 or 1). static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::Paths &out) { @@ -313,6 +320,18 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return ClipperPaths_to_Slic3rExPolygons(_offset(surfaces, delta, joinType, miterLimit)); } +#ifdef CLIPPERUTILS_UNSAFE_OFFSET +Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &polygons) + { return offset(polygons, ClipperSafetyOffset); } +Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) + { return offset_ex(polygons, ClipperSafetyOffset); } +#endif // CLIPPERUTILS_UNSAFE_OFFSET + +Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons) + { return offset(expolygons, ClipperSafetyOffset); } +Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) + { return offset_ex(expolygons, ClipperSafetyOffset); } + ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { // prepare ClipperOffset object @@ -382,12 +401,12 @@ TResult _clipper_do( TSubj && subject, TClip && clip, const ClipperLib::PolyFillType fillType, - const bool do_safety_offset) + const ApplySafetyOffset do_safety_offset) { - return do_safety_offset ? - (clipType == ClipperLib::ctUnion ? - _clipper_do(clipType, safety_offset(std::forward(subject)), std::forward(clip), fillType) : - _clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType)) : + // Safety offset only allowed on intersection and difference. + assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); + return do_safety_offset == ApplySafetyOffset::Yes ? + _clipper_do(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : _clipper_do(clipType, std::forward(subject), std::forward(clip), fillType); } @@ -426,107 +445,103 @@ inline ClipperLib::PolyTree _clipper_do_polytree2( PathProvider1 &&subject, PathProvider2 &&clip, const ClipperLib::PolyFillType fillType, - const bool do_safety_offset) + const ApplySafetyOffset do_safety_offset) { - return do_safety_offset ? - (clipType == ClipperLib::ctUnion ? - _clipper_do_polytree2(clipType, safety_offset(std::forward(subject)), std::forward(clip), fillType) : - _clipper_do_polytree2(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType)) : + assert(do_safety_offset == ApplySafetyOffset::No || clipType != ClipperLib::ctUnion); + return do_safety_offset == ApplySafetyOffset::Yes ? + _clipper_do_polytree2(clipType, std::forward(subject), safety_offset(std::forward(clip)), fillType) : _clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), fillType); } template -static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, bool do_safety_offset) +static inline Polygons _clipper(ClipperLib::ClipType clipType, TSubj &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) { return to_polygons(_clipper_do(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } -Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset) +Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool do_safety_offset) - { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } -Slic3r::Polygons union_(const Slic3r::ExPolygons &subject, bool do_safety_offset) - { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } -Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool do_safety_offset) - { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), do_safety_offset); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } +Slic3r::Polygons union_(const Slic3r::ExPolygons &subject) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) + { return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); } template -static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, bool do_safety_offset) +static ExPolygons _clipper_ex(ClipperLib::ClipType clipType, TSubject &&subject, TClip &&clip, ApplySafetyOffset do_safety_offset) { return PolyTreeToExPolygons(_clipper_do_polytree2(clipType, std::forward(subject), std::forward(clip), ClipperLib::pftNonZero, do_safety_offset)); } -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } -Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } -Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) +Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool do_safety_offset) - { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), do_safety_offset); } +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject) + { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); } Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& subject) { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } Slic3r::ExPolygons union_ex(const Slic3r::Surfaces& subject) { return PolyTreeToExPolygons(_clipper_do_polytree2(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } template -Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip, bool do_safety_offset) +Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) { ClipperLib::Clipper clipper; clipper.AddPaths(std::forward(subject), ClipperLib::ptSubject, false); - if (do_safety_offset) - clipper.AddPaths(safety_offset(std::forward(clip)), ClipperLib::ptClip, true); - else - clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); + clipper.AddPaths(std::forward(clip), ClipperLib::ptClip, true); ClipperLib::PolyTree retval; clipper.Execute(clipType, retval, ClipperLib::pftNonZero, ClipperLib::pftNonZero); return PolyTreeToPolylines(std::move(retval)); @@ -571,7 +586,7 @@ static void _clipper_pl_recombine(Polylines &polylines) } template -Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip, bool do_safety_offset) +Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subject, PathProvider2 &&clip) { // Transform input polygons into open paths. ClipperLib::Paths paths; @@ -585,27 +600,27 @@ Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subj path.emplace_back(poly.front()); } // perform clipping - Polylines retval = _clipper_pl_open(clipType, paths, std::forward(clip), do_safety_offset); + Polylines retval = _clipper_pl_open(clipType, paths, std::forward(clip)); _clipper_pl_recombine(retval); return retval; } -Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset) - { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset) - { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); } -Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) - { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) - { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset) - { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } -Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset) - { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } -Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset) - { return _clipper_pl_closed(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip)); } +Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip) + { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip) + { return _clipper_pl_closed(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } -Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, bool do_safety_offset) +Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip) { // convert Lines to Polylines Polylines polylines; @@ -614,7 +629,7 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol polylines.emplace_back(Polyline(line.a, line.b)); // perform operation - polylines = _clipper_pl_open(clipType, ClipperUtils::PolylinesProvider(polylines), ClipperUtils::PolygonsProvider(clip), do_safety_offset); + polylines = _clipper_pl_open(clipType, ClipperUtils::PolylinesProvider(polylines), ClipperUtils::PolygonsProvider(clip)); // convert Polylines to Lines Lines retval; diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index c64828644b..fc9677d8b9 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -13,6 +13,10 @@ using Slic3r::ClipperLib::jtRound; using Slic3r::ClipperLib::jtSquare; static constexpr const float ClipperSafetyOffset = 10.f; +enum class ApplySafetyOffset { + No, + Yes +}; #define CLIPPERUTILS_UNSAFE_OFFSET @@ -37,7 +41,7 @@ namespace ClipperUtils { constexpr bool operator==(const iterator &rhs) const { return true; } constexpr bool operator!=(const iterator &rhs) const { return false; } const Points& operator++(int) { assert(false); return s_empty_points; } - constexpr iterator& operator++() { assert(false); return *this; } + const iterator& operator++() { assert(false); return *this; } }; constexpr EmptyPathsProvider() {} @@ -262,10 +266,6 @@ ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); // offset Polygons Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -#ifdef CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -#endif // CLIPPERUTILS_UNSAFE_OFFSET - // offset Polylines Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); @@ -276,79 +276,86 @@ Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons); +Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons); Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); #ifdef CLIPPERUTILS_UNSAFE_OFFSET +Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); +Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons); +Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons); #endif // CLIPPERUTILS_UNSAFE_OFFSET -Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip); -Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); -Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset = false); -Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +// Safety offset is applied to the clipping polygons only. +Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); +Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); +Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); -inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) +inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip) { - return _clipper_ln(ClipperLib::ctDifference, subject, clip, do_safety_offset); + return _clipper_ln(ClipperLib::ctDifference, subject, clip); } -Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, bool do_safety_offset = false); -Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, bool do_safety_offset = false); -Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); -Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip, bool do_safety_offset = false); -Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false); +// Safety offset is applied to the clipping polygons only. +Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); -inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) +inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip) { - return _clipper_ln(ClipperLib::ctIntersection, subject, clip, do_safety_offset); + return _clipper_ln(ClipperLib::ctIntersection, subject, clip); } -inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip, bool do_safety_offset = false) +inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip) { Slic3r::Lines lines; lines.emplace_back(subject); - return _clipper_ln(ClipperLib::ctIntersection, lines, clip, do_safety_offset); + return _clipper_ln(ClipperLib::ctIntersection, lines, clip); } -Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool do_safety_offset = false); -Slic3r::Polygons union_(const Slic3r::ExPolygons &subject, bool do_safety_offset = false); -Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool do_safety_offset = false); -Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool do_safety_offset = false); +Slic3r::Polygons union_(const Slic3r::Polygons &subject); +Slic3r::Polygons union_(const Slic3r::ExPolygons &subject); +Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2); +Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject); Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject); Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index c49c492a3f..adda2654ed 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1390,22 +1390,7 @@ public: } // Map from an enum name to an enum integer value. - static const t_config_enum_names& get_enum_names() - { - static t_config_enum_names names; - if (names.empty()) { - // Initialize the map. - const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); - int cnt = 0; - for (const auto& kvp : enum_keys_map) - cnt = std::max(cnt, kvp.second); - cnt += 1; - names.assign(cnt, ""); - for (const auto& kvp : enum_keys_map) - names[kvp.second] = kvp.first; - } - return names; - } + static const t_config_enum_names& get_enum_names(); // Map from an enum name to an enum integer value. static const t_config_enum_values& get_enum_values(); diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index b9e9ec3dd8..1385a51d8d 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -546,7 +546,7 @@ bool EdgeGrid::Grid::inside(const Point &pt_src) return false; coord_t ix = p(0) / m_resolution; coord_t iy = p(1) / m_resolution; - if (ix >= this->m_cols || iy >= this->m_rows) + if (ix >= m_cols || iy >= m_rows) return false; size_t i_closest = (size_t)-1; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 579259a5fc..7ba6de7d47 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -122,10 +122,10 @@ std::vector group_fills(const Layer &layer) if (surface.surface_type == stInternalVoid) has_internal_voids = true; else { - const PrintRegionConfig ®ion_config = layerm.region()->config(); + const PrintRegionConfig ®ion_config = layerm.region().config(); FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill); bool is_bridge = layer.id() > 0 && surface.is_bridge(); - params.extruder = layerm.region()->extruder(extrusion_role); + params.extruder = layerm.region().extruder(extrusion_role); params.pattern = region_config.fill_pattern.value; params.density = float(region_config.fill_density); @@ -160,11 +160,9 @@ std::vector group_fills(const Layer &layer) params.anchor_length = 1000.f; params.anchor_length_max = 1000.f; } else { - // 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 - params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing(); + // Internal infill. Calculating infill line spacing independent of the current layer height and 1st layer status, + // so that internall infill will be aligned over all layers of the current region. + params.spacing = layerm.region().flow(*layer.object(), frInfill, layer.object()->config().layer_height, false).spacing(); // Anchor a sparse infill to inner perimeters with the following anchor length: params.anchor_length = float(region_config.infill_anchor); if (region_config.infill_anchor.percent) @@ -213,7 +211,7 @@ std::vector group_fills(const Layer &layer) Polygons polys = to_polygons(std::move(fill.expolygons)); // Make a union of polygons, use a safety offset, subtract the preceding polygons. // Bridges are processed first (see SurfaceFill::operator<()) - fill.expolygons = all_polygons.empty() ? union_ex(polys, true) : diff_ex(polys, all_polygons, true); + fill.expolygons = all_polygons.empty() ? union_safety_offset_ex(polys) : diff_ex(polys, all_polygons, ApplySafetyOffset::Yes); append(all_polygons, std::move(polys)); } else if (&fill != &surface_fills.back()) append(all_polygons, to_polygons(fill.expolygons)); @@ -254,12 +252,11 @@ std::vector group_fills(const Layer &layer) // Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2 Polygons collapsed = diff( surfaces_polygons, - offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2), - true); + offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2 + ClipperSafetyOffset)); //FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being // added if two offsetted void regions merge. // polygons_append(voids, collapsed); - ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, true); + ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, ApplySafetyOffset::Yes); // Now find an internal infill SurfaceFill to add these extrusions to. SurfaceFill *internal_solid_fill = nullptr; unsigned int region_id = 0; @@ -277,11 +274,11 @@ std::vector group_fills(const Layer &layer) } if (internal_solid_fill == nullptr) { // Produce another solid fill. - params.extruder = layerm.region()->extruder(frSolidInfill); - params.pattern = layerm.region()->config().top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; + params.extruder = layerm.region().extruder(frSolidInfill); + params.pattern = layerm.region().config().top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear; params.density = 100.f; params.extrusion_role = erInternalInfill; - params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); + params.angle = float(Geometry::deg2rad(layerm.region().config().fill_angle.value)); // calculate the actual flow we'll be using for this infill params.flow = layerm.flow(frSolidInfill); params.spacing = params.flow.spacing(); @@ -504,7 +501,7 @@ void Layer::make_ironing() for (LayerRegion *layerm : m_regions) if (! layerm->slices.empty()) { IroningParams ironing_params; - const PrintRegionConfig &config = layerm->region()->config(); + const PrintRegionConfig &config = layerm->region().config(); if (config.ironing && (config.ironing_type == IroningType::AllSolid || (config.top_solid_layers > 0 && @@ -559,7 +556,7 @@ void Layer::make_ironing() Polygons infills; for (size_t k = i; k < j; ++ k) { const IroningParams &ironing_params = by_extruder[k]; - const PrintRegionConfig ®ion_config = ironing_params.layerm->region()->config(); + const PrintRegionConfig ®ion_config = ironing_params.layerm->region().config(); bool iron_everything = region_config.ironing_type == IroningType::AllSolid; bool iron_completely = iron_everything; if (iron_everything) { @@ -596,7 +593,7 @@ void Layer::make_ironing() // For IroningType::AllSolid only: // Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill). append(infills, to_polygons(std::move(ironing_areas))); - ironing_areas = union_ex(infills, true); + ironing_areas = union_safety_offset_ex(infills); } } diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 6b303e636a..3eabc6106c 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -291,13 +291,13 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob double extrusion_width; }; std::vector region_fill_data; - region_fill_data.reserve(print_object.print()->regions().size()); + region_fill_data.reserve(print_object.num_printing_regions()); bool build_octree = false; const std::vector &nozzle_diameters = print_object.print()->config().nozzle_diameter.values; double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()); double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter)); - for (const PrintRegion *region : print_object.print()->regions()) { - const PrintRegionConfig &config = region->config(); + for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { + const PrintRegionConfig &config = print_object.printing_region(region_id).config(); bool nonempty = config.fill_density > 0; bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic; bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic; diff --git a/src/libslic3r/Fill/FillLine.cpp b/src/libslic3r/Fill/FillLine.cpp index f6431a3333..b7ab5430e4 100644 --- a/src/libslic3r/Fill/FillLine.cpp +++ b/src/libslic3r/Fill/FillLine.cpp @@ -58,7 +58,7 @@ void FillLine::_fill_surface_single( pts.push_back(it->a); pts.push_back(it->b); } - Polylines polylines = intersection_pl(polylines_src, offset(expolygon, scale_(0.02)), false); + Polylines polylines = intersection_pl(polylines_src, offset(expolygon, scale_(0.02))); // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! const float INFILL_OVERLAP_OVER_SPACING = 0.3f; diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 6385a880e9..f1e3884bc5 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -33,18 +33,16 @@ void FillPlanePath::_fill_surface_single( coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)), coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines))); - Polylines polylines; if (pts.size() >= 2) { // Convert points to a polyline, upscale. - polylines.push_back(Polyline()); - Polyline &polyline = polylines.back(); + Polylines polylines(1, Polyline()); + Polyline &polyline = polylines.front(); polyline.points.reserve(pts.size()); for (const Vec2d &pt : pts) - polyline.points.push_back(Point( - coord_t(floor(pt.x() * distance_between_lines + 0.5)), - coord_t(floor(pt.y() * distance_between_lines + 0.5)))); -// intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), &polylines); - polylines = intersection_pl(std::move(polylines), expolygon); + polyline.points.emplace_back( + coord_t(floor(pt.x() * distance_between_lines + 0.5)), + coord_t(floor(pt.y() * distance_between_lines + 0.5))); + polylines = intersection_pl(polylines, expolygon); Polylines chained; if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1) chained = chain_polylines(std::move(polylines)); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c5b28b3a01..4368783a69 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -614,9 +614,44 @@ namespace DoExport { static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) { const GCodeProcessor::Result& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + } + + static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) + { + const GCodeProcessor::Result& result = processor.get_result(); + print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? + get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + + // update filament statictics + double total_extruded_volume = 0.0; + double total_used_filament = 0.0; + double total_weight = 0.0; + double total_cost = 0.0; + for (auto volume : result.print_statistics.volumes_per_extruder) { + total_extruded_volume += volume.second; + + size_t extruder_id = volume.first; + auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) { return extr.id() == extruder_id; }); + if (extruder == extruders.end()) + continue; + + double s = PI * sqr(0.5* extruder->filament_diameter()); + double weight = volume.second * extruder->filament_density() * 0.001; + total_used_filament += volume.second/s; + total_weight += weight; + total_cost += weight * extruder->filament_cost() * 0.001; + } + + print_statistics.total_extruded_volume = total_extruded_volume; + print_statistics.total_used_filament = total_used_filament; + print_statistics.total_weight = total_weight; + print_statistics.total_cost = total_cost; + + print_statistics.filament_stats = result.print_statistics.volumes_per_extruder; } #if ENABLE_VALIDATE_CUSTOM_GCODE @@ -754,7 +789,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); - DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); +// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); + DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); #if ENABLE_GCODE_WINDOW if (result != nullptr) { *result = std::move(m_processor.extract_result()); @@ -796,19 +832,19 @@ namespace DoExport { // get the minimum cross-section used in the print std::vector mm3_per_mm; for (auto object : print.objects()) { - for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { - const PrintRegion* region = print.regions()[region_id]; + for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object->printing_region(region_id); for (auto layer : object->layers()) { const LayerRegion* layerm = layer->regions()[region_id]; - if (region->config().get_abs_value("perimeter_speed") == 0 || - region->config().get_abs_value("small_perimeter_speed") == 0 || - region->config().get_abs_value("external_perimeter_speed") == 0 || - region->config().get_abs_value("bridge_speed") == 0) + if (region.config().get_abs_value("perimeter_speed") == 0 || + region.config().get_abs_value("small_perimeter_speed") == 0 || + region.config().get_abs_value("external_perimeter_speed") == 0 || + region.config().get_abs_value("bridge_speed") == 0) mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); - if (region->config().get_abs_value("infill_speed") == 0 || - region->config().get_abs_value("solid_infill_speed") == 0 || - region->config().get_abs_value("top_solid_infill_speed") == 0 || - region->config().get_abs_value("bridge_speed") == 0) + if (region.config().get_abs_value("infill_speed") == 0 || + region.config().get_abs_value("solid_infill_speed") == 0 || + region.config().get_abs_value("top_solid_infill_speed") == 0 || + region.config().get_abs_value("bridge_speed") == 0) { // Minimal volumetric flow should not be calculated over ironing extrusions. // Use following lambda instead of the built-it method. @@ -887,8 +923,7 @@ namespace DoExport { if (thumbnail_cb != nullptr) { const size_t max_row_length = 78; - ThumbnailsList thumbnails; - thumbnail_cb(thumbnails, sizes, true, true, true, true); + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); for (const ThumbnailData& data : thumbnails) { if (data.is_valid()) @@ -958,7 +993,6 @@ namespace DoExport { dst.first += buf; ++ dst.second; }; - print_statistics.filament_stats.insert(std::pair{extruder.id(), (float)used_filament}); append(out_filament_used_mm, "%.2lf", used_filament); append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); if (filament_weight > 0.) { @@ -1113,16 +1147,17 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu const double layer_height = first_object->config().layer_height.value; assert(! print.config().first_layer_height.percent); const double first_layer_height = print.config().first_layer_height.value; - for (const PrintRegion* region : print.regions()) { - _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width()); - _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width()); - _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(*first_object, frInfill, layer_height).width()); - _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(*first_object, frSolidInfill, layer_height).width()); - _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(*first_object, frTopSolidInfill, layer_height).width()); + for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { + const PrintRegion ®ion = print.get_print_region(region_id); + _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); + _write_format(file, "; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); + _write_format(file, "; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); + _write_format(file, "; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); + _write_format(file, "; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); if (print.has_support_material()) _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); if (print.config().first_layer_extrusion_width.value > 0) - _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, first_layer_height, true).width()); + _write_format(file, "; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width()); _write_format(file, "\n"); } print.throw_if_canceled(); @@ -1936,7 +1971,7 @@ void GCode::process_layer( bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); if (enable) { for (const LayerRegion *layer_region : layer.regions()) - if (size_t(layer_region->region()->config().bottom_solid_layers.value) > layer.id() || + if (size_t(layer_region->region().config().bottom_solid_layers.value) > layer.id() || layer_region->perimeters.items_count() > 1u || layer_region->fills.items_count() > 0) { enable = false; @@ -2110,7 +2145,9 @@ void GCode::process_layer( const LayerRegion *layerm = layer.regions()[region_id]; if (layerm == nullptr) continue; - const PrintRegion ®ion = *print.regions()[region_id]; + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. // It is also necessary to save which extrusions are part of MM wiping and which are not. @@ -2168,8 +2205,8 @@ void GCode::process_layer( // extrusions->first_point fits inside ith slice point_inside_surface(island_idx, extrusions->first_point())) { if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region_id].append(entity_type, extrusions, entity_overrides); + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); break; } } @@ -2571,7 +2608,7 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorconfig()); + m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); for (const ExtrusionEntity *ee : region.perimeters) gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid); } @@ -2592,7 +2629,7 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorrole() == erIroning) == ironing) extrusions.emplace_back(ee); if (! extrusions.empty()) { - m_config.apply(print.regions()[®ion - &by_region.front()]->config()); + m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); for (const ExtrusionEntity *fill : extrusions) { auto *eec = dynamic_cast(fill); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 006595eb7d..917f84f40b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -186,6 +186,72 @@ void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() times = std::vector>(); } +void GCodeProcessor::UsedFilaments::reset() +{ + color_change_cache = 0.0f; + volumes_per_color_change = std::vector(); + + tool_change_cache = 0.0f; + volumes_per_extruder.clear(); + + role_cache = 0.0f; + filaments_per_role.clear(); +} + +void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume) +{ + color_change_cache += extruded_volume; + tool_change_cache += extruded_volume; + role_cache += extruded_volume; +} + +void GCodeProcessor::UsedFilaments::process_color_change_cache() +{ + if (color_change_cache != 0.0f) { + volumes_per_color_change.push_back(color_change_cache); + color_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_extruder_cache(GCodeProcessor* processor) +{ + size_t active_extruder_id = processor->m_extruder_id; + if (tool_change_cache != 0.0f) { + if (volumes_per_extruder.find(active_extruder_id) != volumes_per_extruder.end()) + volumes_per_extruder[active_extruder_id] += tool_change_cache; + else + volumes_per_extruder[active_extruder_id] = tool_change_cache; + tool_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor) +{ + if (role_cache != 0.0f) { + std::pair filament = { 0.0f, 0.0f }; + + double s = PI * sqr(0.5 * processor->m_filament_diameters[processor->m_extruder_id]); + filament.first = role_cache/s * 0.001; + filament.second = role_cache * processor->m_filament_densities[processor->m_extruder_id] * 0.001; + + ExtrusionRole active_role = processor->m_extrusion_role; + if (filaments_per_role.find(active_role) != filaments_per_role.end()) { + filaments_per_role[active_role].first += filament.first; + filaments_per_role[active_role].second += filament.second; + } + else + filaments_per_role[active_role] = filament; + role_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) +{ + process_color_change_cache(); + process_extruder_cache(processor); + process_role_cache(processor); +} + void GCodeProcessor::TimeMachine::reset() { enabled = false; @@ -348,10 +414,10 @@ void GCodeProcessor::TimeProcessor::reset() machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { machines[i].reset(); } - machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].enabled = true; + machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER @@ -416,19 +482,19 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) size_t g1_lines_counter = 0; // keeps track of last exported pair #if ENABLE_EXTENDED_M73_LINES - std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_main; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; } // keeps track of last exported remaining time to next printer stop - std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_stop; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported_stop[i] = time_in_minutes(machines[i].time); } #else - std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported[i] = { 0, time_in_minutes(machines[i].time) }; } #endif // ENABLE_EXTENDED_M73_LINES @@ -451,7 +517,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) line = line.substr(1); if (export_remaining_time_enabled && (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { #if ENABLE_EXTENDED_M73_LINES @@ -486,7 +552,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { #else if (export_remaining_time_enabled && (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag)) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { ret += format_line_M73(machine.line_m73_mask.c_str(), @@ -497,13 +563,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) } else if (line == Estimated_Printing_Time_Placeholder_Tag) { #endif // ENABLE_VALIDATE_CUSTOM_GCODE - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; - PrintEstimatedTimeStatistics::ETimeMode mode = static_cast(i); - if (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal || machine.enabled) { + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { char buf[128]; sprintf(buf, "; estimated printing time (%s mode) = %s\n", - (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent", + (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", get_time_dhms(machine.time).c_str()); ret += buf; } @@ -545,7 +611,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) unsigned int exported_lines_count = 0; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER if (export_remaining_time_enabled) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { // export pair @@ -789,13 +855,13 @@ GCodeProcessor::GCodeProcessor() { reset(); #if ENABLE_EXTENDED_M73_LINES - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; #else - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; #endif // ENABLE_EXTENDED_M73_LINES } @@ -826,6 +892,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } + m_filament_densities.resize(config.filament_density.values.size()); + for (size_t i = 0; i < config.filament_density.values.size(); ++i) { + m_filament_densities[i] = static_cast(config.filament_density.values[i]); + } + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { m_time_processor.machine_limits = reinterpret_cast(config); if (m_flavor == gcfMarlinLegacy) { @@ -846,7 +917,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); } - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -896,6 +967,13 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } + const ConfigOptionFloats* filament_densities = config.option("filament_density"); + if (filament_densities != nullptr) { + for (double dens : filament_densities->values) { + m_filament_densities.push_back(static_cast(dens)); + } + } + m_result.extruders_count = config.option("nozzle_diameter")->values.size(); const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); @@ -1026,7 +1104,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; } - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -1051,7 +1129,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) void GCodeProcessor::enable_stealth_time_estimator(bool enabled) { - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled = enabled; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled = enabled; } void GCodeProcessor::reset() @@ -1096,6 +1174,7 @@ void GCodeProcessor::reset() } m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); + m_filament_densities = std::vector(Min_Extruder_Count, 1.245f); m_extruded_last_z = 0.0f; #if ENABLE_START_GCODE_VISUALIZATION m_first_layer_height = 0.0f; @@ -1109,6 +1188,7 @@ void GCodeProcessor::reset() m_producers_enabled = false; m_time_processor.reset(); + m_used_filaments.reset(); m_result.reset(); m_result.id = ++s_result_id; @@ -1186,7 +1266,7 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr } // process the time blocks - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; machine.calculate_time(); @@ -1194,6 +1274,8 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); } + m_used_filaments.process_caches(this); + update_estimated_times_stats(); // post-process to add M73 lines into the gcode @@ -1216,20 +1298,20 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr #endif // ENABLE_GCODE_VIEWER_STATISTICS } -float GCodeProcessor::get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; } -std::string GCodeProcessor::get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } -std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const +std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const { std::vector>> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; float total_time = 0.0f; for (const auto& [type, time] : machine.gcode_time.times) { @@ -1241,10 +1323,10 @@ std::vector>> GCodeProcesso return ret; } -std::vector> GCodeProcessor::get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; if (time > 0.0f) @@ -1254,10 +1336,10 @@ std::vector> GCodeProcessor::get_moves_time(PrintEst return ret; } -std::vector> GCodeProcessor::get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; if (time > 0.0f) @@ -1267,9 +1349,9 @@ std::vector> GCodeProcessor::get_roles_time(Prin return ret; } -std::vector GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].layers_time : std::vector(); } @@ -1461,6 +1543,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_VALIDATE_CUSTOM_GCODE // extrusion role tag if (boost::starts_with(comment, reserved_tag(ETags::Role))) { + m_used_filaments.process_role_cache(this); m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length())); #if ENABLE_SEAMS_VISUALIZATION if (m_extrusion_role == erExternalPerimeter) @@ -1546,7 +1629,8 @@ void GCodeProcessor::process_tags(const std::string_view comment) extruder_id = static_cast(eid); } - m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + if (extruder_id < m_extruder_colors.size()) + m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; if (m_cp_color.counter == UCHAR_MAX) m_cp_color.counter = 0; @@ -1557,6 +1641,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) } process_custom_gcode_time(CustomGCode::ColorChange); + process_filaments(CustomGCode::ColorChange); return; } @@ -1686,6 +1771,10 @@ bool GCodeProcessor::process_cura_tags(const std::string_view comment) BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; } +#if ENABLE_SEAMS_VISUALIZATION + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); +#endif // ENABLE_SEAMS_VISUALIZATION return true; } @@ -1750,6 +1839,9 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment) pos = cmt.find(" outer perimeter"); if (pos == 0) { m_extrusion_role = erExternalPerimeter; +#if ENABLE_SEAMS_VISUALIZATION + m_seams_detector.activate(true); +#endif // ENABLE_SEAMS_VISUALIZATION return true; } @@ -1904,6 +1996,11 @@ bool GCodeProcessor::process_craftware_tags(const std::string_view comment) BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; } +#if ENABLE_SEAMS_VISUALIZATION + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); +#endif // ENABLE_SEAMS_VISUALIZATION + return true; } @@ -1942,6 +2039,11 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string_view comment) m_extrusion_role = erNone; BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; } + +#if ENABLE_SEAMS_VISUALIZATION + if (m_extrusion_role == erExternalPerimeter) + m_seams_detector.activate(true); +#endif // ENABLE_SEAMS_VISUALIZATION return true; } @@ -2010,6 +2112,9 @@ bool GCodeProcessor::process_kissslicer_tags(const std::string_view comment) pos = comment.find(" 'Perimeter Path'"); if (pos == 0) { m_extrusion_role = erExternalPerimeter; +#if ENABLE_SEAMS_VISUALIZATION + m_seams_detector.activate(true); +#endif // ENABLE_SEAMS_VISUALIZATION return true; } @@ -2174,6 +2279,9 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; + // save extruded volume to the cache + m_used_filaments.increase_caches(volume_extruded_filament); + // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; #if ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -2234,7 +2342,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) assert(distance != 0.0f); float inv_distance = 1.0f / distance; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2244,8 +2352,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) std::vector& blocks = machine.blocks; curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : - minimum_feedrate(static_cast(i), m_feedrate); + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); TimeBlock block; block.move_type = type; @@ -2263,7 +2371,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } @@ -2280,13 +2388,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // calculates block acceleration float acceleration = - (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : (is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i))); + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration; } @@ -2297,7 +2405,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.safe_feedrate = block.feedrate_profile.cruise; for (unsigned char a = X; a <= E; ++a) { - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (curr.abs_axis_feedrate[a] > axis_max_jerk) curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); } @@ -2345,7 +2453,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // axis reversal std::max(-v_exit, v_entry)); - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; limited = true; @@ -2385,27 +2493,28 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } #if ENABLE_SEAMS_VISUALIZATION - // check for seam starting vertex - if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && m_seams_detector.is_active() && !m_seams_detector.has_first_vertex()) - m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); - // check for seam ending vertex and store the resulting move - else if ((type != EMoveType::Extrude || m_extrusion_role != erExternalPerimeter) && m_seams_detector.is_active()) { - auto set_end_position = [this](const Vec3f& pos) { - m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); - }; + if (m_seams_detector.is_active()) { + // check for seam starting vertex + if (type == EMoveType::Extrude && m_extrusion_role == erExternalPerimeter && !m_seams_detector.has_first_vertex()) + m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); + // check for seam ending vertex and store the resulting move + else if ((type != EMoveType::Extrude || m_extrusion_role != erExternalPerimeter) && m_seams_detector.has_first_vertex()) { + auto set_end_position = [this](const Vec3f& pos) { + m_end_position[X] = pos.x(); m_end_position[Y] = pos.y(); m_end_position[Z] = pos.z(); + }; - assert(m_seams_detector.has_first_vertex()); - const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); - const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; - const std::optional first_vertex = m_seams_detector.get_first_vertex(); - // the threshold value = 0.25 is arbitrary, we may find some smarter condition later - if ((new_pos - *first_vertex).norm() < 0.25f) { - set_end_position(0.5f * (new_pos + *first_vertex)); - store_move_vertex(EMoveType::Seam); - set_end_position(curr_pos); + const Vec3f curr_pos(m_end_position[X], m_end_position[Y], m_end_position[Z]); + const Vec3f new_pos = m_result.moves.back().position - m_extruder_offsets[m_extruder_id]; + const std::optional first_vertex = m_seams_detector.get_first_vertex(); + // the threshold value = 0.0625f == 0.25 * 0.25 is arbitrary, we may find some smarter condition later + if ((new_pos - *first_vertex).squaredNorm() < 0.0625f) { + set_end_position(0.5f * (new_pos + *first_vertex)); + store_move_vertex(EMoveType::Seam); + set_end_position(curr_pos); + } + + m_seams_detector.activate(false); } - - m_seams_detector.activate(false); } #endif // ENABLE_SEAMS_VISUALIZATION @@ -2629,8 +2738,8 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); @@ -2657,8 +2766,8 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // http://smoothieware.org/supported-g-codes float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); @@ -2678,27 +2787,27 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) { float value; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_value('S', value)) { // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware // It is also generated by PrusaSlicer to control acceleration per extrusion type // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. - set_acceleration(static_cast(i), value); - set_travel_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); if (line.has_value('T', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); } else { // New acceleration format, compatible with the upstream Marlin. if (line.has_value('P', value)) - set_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); if (line.has_value('R', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); if (line.has_value('T', value)) // Interpret the T value as the travel acceleration in the new Marlin format. - set_travel_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); } } } @@ -2706,8 +2815,8 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) { float max_jerk = line.x(); @@ -2740,7 +2849,7 @@ void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) float value_t; if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { value_s *= 0.01f; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].extrude_factor_override_percentage = value_s; } } @@ -2791,7 +2900,7 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); @@ -2842,6 +2951,7 @@ void GCodeProcessor::process_T(const std::string_view command) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { unsigned char old_extruder_id = m_extruder_id; + process_filaments(CustomGCode::ToolChange); m_extruder_id = id; m_cp_color.current = m_extruder_colors[id]; // Specific to the MK3 MMU2: @@ -2899,7 +3009,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #if ENABLE_EXTENDED_M73_LINES // stores stop time placeholders for later use if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2910,7 +3020,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #endif // ENABLE_EXTENDED_M73_LINES } -float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) return feedrate; @@ -2918,7 +3028,7 @@ float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode m return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); } -float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) return feedrate; @@ -2926,7 +3036,7 @@ float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETim return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); } -float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2938,7 +3048,7 @@ float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeM } } -float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2950,7 +3060,7 @@ float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ET } } -float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2962,18 +3072,18 @@ float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode } } -float GCodeProcessor::get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast(mode)); } -float GCodeProcessor::get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; } -void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +void GCodeProcessor::set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -2983,13 +3093,13 @@ void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mo } } -float GCodeProcessor::get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } -void GCodeProcessor::set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +void GCodeProcessor::set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -3017,7 +3127,7 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id) void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -3034,17 +3144,26 @@ void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) } } +void GCodeProcessor::process_filaments(CustomGCode::Type code) +{ + if (code == CustomGCode::ColorChange) + m_used_filaments.process_color_change_cache(); + + if (code == CustomGCode::ToolChange) + m_used_filaments.process_extruder_cache(this); +} + void GCodeProcessor::simulate_st_synchronize(float additional_time) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].simulate_st_synchronize(additional_time); } } void GCodeProcessor::update_estimated_times_stats() { - auto update_mode = [this](PrintEstimatedTimeStatistics::ETimeMode mode) { - PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast(mode)]; + auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { + PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); @@ -3052,11 +3171,15 @@ void GCodeProcessor::update_estimated_times_stats() data.layers_times = get_layers_time(mode); }; - update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled) - update_mode(PrintEstimatedTimeStatistics::ETimeMode::Stealth); + update_mode(PrintEstimatedStatistics::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled) + update_mode(PrintEstimatedStatistics::ETimeMode::Stealth); else - m_result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].reset(); + m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); + + m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; + m_result.print_statistics.volumes_per_extruder = m_used_filaments.volumes_per_extruder; + m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; } } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 75ec1546b3..8975255ecf 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -36,7 +36,7 @@ namespace Slic3r { Count }; - struct PrintEstimatedTimeStatistics + struct PrintEstimatedStatistics { enum class ETimeMode : unsigned char { @@ -62,14 +62,21 @@ namespace Slic3r { } }; + std::vector volumes_per_color_change; + std::map volumes_per_extruder; + std::map> used_filaments_per_role; + std::array(ETimeMode::Count)> modes; - PrintEstimatedTimeStatistics() { reset(); } + PrintEstimatedStatistics() { reset(); } void reset() { for (auto m : modes) { m.reset(); } + volumes_per_color_change.clear(); + volumes_per_extruder.clear(); + used_filaments_per_role.clear(); } }; @@ -314,7 +321,7 @@ namespace Slic3r { // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; std::vector filament_unload_times; - std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines; + std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; void reset(); @@ -327,6 +334,30 @@ namespace Slic3r { #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER }; + struct UsedFilaments // filaments per ColorChange + { + double color_change_cache; + std::vector volumes_per_color_change; + + double tool_change_cache; + std::map volumes_per_extruder; + + double role_cache; + // ExtrusionRole : + std::map> filaments_per_role; + + void reset(); + + void increase_caches(double extruded_volume); + + void process_color_change_cache(); + void process_extruder_cache(GCodeProcessor* processor); + void process_role_cache(GCodeProcessor* processor); + void process_caches(GCodeProcessor* processor); + + friend class GCodeProcessor; + }; + public: #if !ENABLE_GCODE_LINES_ID_IN_H_SLIDER struct MoveVertex @@ -372,7 +403,7 @@ namespace Slic3r { SettingsIds settings_ids; size_t extruders_count; std::vector extruder_colors; - PrintEstimatedTimeStatistics time_statistics; + PrintEstimatedStatistics print_statistics; #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; @@ -519,6 +550,7 @@ namespace Slic3r { ExtruderColors m_extruder_colors; ExtruderTemps m_extruder_temps; std::vector m_filament_diameters; + std::vector m_filament_densities; float m_extruded_last_z; #if ENABLE_START_GCODE_VISUALIZATION float m_first_layer_height; // mm @@ -550,6 +582,7 @@ namespace Slic3r { bool m_producers_enabled; TimeProcessor m_time_processor; + UsedFilaments m_used_filaments; Result m_result; static unsigned int s_result_id; @@ -566,7 +599,7 @@ namespace Slic3r { void apply_config(const PrintConfig& config); void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { - return m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled; + return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void enable_producers(bool enabled) { m_producers_enabled = enabled; } @@ -579,13 +612,13 @@ namespace Slic3r { // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void process_file(const std::string& filename, bool apply_postprocess, std::function cancel_callback = nullptr); - float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const; + float get_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; - std::vector> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; private: void apply_config(const DynamicPrintConfig& config); @@ -701,20 +734,21 @@ namespace Slic3r { void store_move_vertex(EMoveType type); - float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; - float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); - float get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); + float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); void process_custom_gcode_time(CustomGCode::Type code); + void process_filaments(CustomGCode::Type code); // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f); diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp index 2a302ed855..195dfe6336 100644 --- a/src/libslic3r/GCode/ThumbnailData.hpp +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -19,8 +19,18 @@ struct ThumbnailData bool is_valid() const; }; -typedef std::vector ThumbnailsList; -typedef std::function ThumbnailsGeneratorCallback; +using ThumbnailsList = std::vector; + +struct ThumbnailsParams +{ + const Vec2ds sizes; + bool printable_only; + bool parts_only; + bool show_bed; + bool transparent_background; +}; + +typedef std::function ThumbnailsGeneratorCallback; } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index c45e260158..9dc9f3f96e 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -223,11 +223,8 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto layer_tools.extruder_override = extruder_override; // What extruders are required to print this object layer? - for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) { - const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; - if (layerm == nullptr) - continue; - const PrintRegion ®ion = *object.print()->regions()[region_id]; + for (const LayerRegion *layerm : layer->regions()) { + const PrintRegion ®ion = layerm->region(); if (! layerm->perimeters.entities.empty()) { bool something_nonoverriddable = true; @@ -688,16 +685,14 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves for (unsigned int copy = 0; copy < num_of_copies; ++copy) { - - for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { - const auto& region = *object->print()->regions()[region_id]; - + for (const LayerRegion *layerm : this_layer->regions()) { + const auto ®ion = layerm->region(); if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) continue; bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill; if (print.config().infill_first != perimeters_done || wipe_into_infill_only) { - for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections + for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); if (!is_overriddable(*fill, print.config(), *object, region)) @@ -721,7 +716,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // Now the same for perimeters - see comments above for explanation: if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done) { - for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { + for (const ExtrusionEntity* ee : layerm->perimeters.entities) { auto* fill = dynamic_cast(ee); if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { set_extruder_override(fill, copy, new_extruder, num_of_copies); @@ -762,13 +757,12 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) size_t num_of_copies = object->instances().size(); for (size_t copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves - for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { - const auto& region = *object->print()->regions()[region_id]; - + for (const LayerRegion *layerm : this_layer->regions()) { + const auto ®ion = layerm->region(); if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) continue; - for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections + for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); if (!is_overriddable(*fill, print.config(), *object, region) @@ -791,7 +785,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) } // Now the same for perimeters - see comments above for explanation: - for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections + for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index ae800a5ff0..fc6a15b656 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -500,9 +500,9 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, ToolChangeResult result; result.priming = priming; result.initial_tool = int(old_tool); - result.new_tool = int(this->m_current_tool); - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; + result.new_tool = int(m_current_tool); + result.print_z = m_z_pos; + result.layer_height = m_layer_height; result.elapsed_time = writer.elapsed_time(); result.start_pos = writer.start_pos_rotated(); result.end_pos = priming ? writer.pos() : writer.pos_rotated(); @@ -630,7 +630,7 @@ std::vector WipeTower::prime( bool /*last_wipe_inside_wipe_tower*/) { this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - this->m_current_tool = tools.front(); + m_current_tool = tools.front(); // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210]. // Due to the XYZ calibration, this working space may shrink slightly from all directions, diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 6fd7a8e211..b0c5111aaf 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -164,10 +164,9 @@ public: m_current_layer_finished = false; m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; if (is_first_layer) { - this->m_num_layer_changes = 0; - this->m_num_tool_changes = 0; - } - else + m_num_layer_changes = 0; + m_num_tool_changes = 0; + } else ++ m_num_layer_changes; // Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height: diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index d5ef41125a..e60eb01b69 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1083,8 +1083,7 @@ MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* pol } } -bool -MedialAxis::validate_edge(const VD::edge_type* edge) +bool MedialAxis::validate_edge(const VD::edge_type* edge) { // prevent overflows and detect almost-infinite edges #ifndef CLIPPERLIB_INT32 diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index e8e3c4275c..3f23276866 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -27,7 +27,7 @@ bool Layer::empty() const return true; } -LayerRegion* Layer::add_region(PrintRegion* print_region) +LayerRegion* Layer::add_region(const PrintRegion *print_region) { m_regions.emplace_back(new LayerRegion(this, print_region)); return m_regions.back(); @@ -102,7 +102,7 @@ ExPolygons Layer::merged(float offset_scaled) const } Polygons polygons; for (LayerRegion *layerm : m_regions) { - const PrintRegionConfig &config = layerm->region()->config(); + 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)); @@ -134,7 +134,7 @@ void Layer::make_perimeters() continue; BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; done[region_id] = true; - const PrintRegionConfig &config = (*layerm)->region()->config(); + const PrintRegionConfig &config = (*layerm)->region().config(); // find compatible regions LayerRegionPtrs layerms; @@ -142,7 +142,7 @@ void Layer::make_perimeters() for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) if (! (*it)->slices.empty()) { LayerRegion* other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region()->config(); + const PrintRegionConfig &other_config = other_layerm->region().config(); if (config.perimeter_extruder == other_config.perimeter_extruder && config.perimeters == other_config.perimeters && config.perimeter_speed == other_config.perimeter_speed @@ -180,7 +180,7 @@ void Layer::make_perimeters() for (LayerRegion *layerm : layerms) { for (Surface &surface : layerm->slices.surfaces) slices[surface.extra_perimeters].emplace_back(surface); - if (layerm->region()->config().fill_density > layerm_config->region()->config().fill_density) + if (layerm->region().config().fill_density > layerm_config->region().config().fill_density) layerm_config = layerm; } // merge the surfaces assigned to each group diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 2e3e29eab8..102a991ca2 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -22,8 +22,7 @@ class LayerRegion public: Layer* layer() { return m_layer; } const Layer* layer() const { return m_layer; } - PrintRegion* region() { return m_region; } - const PrintRegion* region() const { return m_region; } + const PrintRegion& region() const { return *m_region; } // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal @@ -86,12 +85,12 @@ public: protected: friend class Layer; - LayerRegion(Layer *layer, PrintRegion *region) : m_layer(layer), m_region(region) {} + LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} ~LayerRegion() {} private: - Layer *m_layer; - PrintRegion *m_region; + Layer *m_layer; + const PrintRegion *m_region; }; @@ -126,9 +125,9 @@ public: std::vector lslices_bboxes; size_t region_count() const { return m_regions.size(); } - const LayerRegion* get_region(int idx) const { return m_regions.at(idx); } + const LayerRegion* get_region(int idx) const { return m_regions[idx]; } LayerRegion* get_region(int idx) { return m_regions[idx]; } - LayerRegion* add_region(PrintRegion* print_region); + LayerRegion* add_region(const PrintRegion *print_region); const LayerRegionPtrs& regions() const { return m_regions; } // Test whether whether there are any slices assigned to this layer. bool empty() const; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 5b4a021b02..356811b74d 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -27,13 +27,14 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const Flow LayerRegion::bridging_flow(FlowRole role) const { - const PrintRegion ®ion = *this->region(); + const PrintRegion ®ion = this->region(); const PrintRegionConfig ®ion_config = region.config(); - if (this->layer()->object()->config().thick_bridges) { + const PrintObject &print_object = *this->layer()->object(); + if (print_object.config().thick_bridges) { // The old Slic3r way (different from all other slicers): Use rounded extrusions. // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - auto nozzle_diameter = float(region.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); + auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); // Applies default bridge spacing. return Flow::bridging_flow(float(sqrt(region_config.bridge_flow_ratio)) * nozzle_diameter, nozzle_diameter); } else { @@ -69,7 +70,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec this->thin_fills.clear(); const PrintConfig &print_config = this->layer()->object()->print()->config(); - const PrintRegionConfig ®ion_config = this->region()->config(); + const PrintRegionConfig ®ion_config = this->region().config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! bool spiral_vase = print_config.spiral_vase && //FIXME account for raft layers. @@ -110,7 +111,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { - const bool has_infill = this->region()->config().fill_density.value > 0.; + const bool has_infill = this->region().config().fill_density.value > 0.; const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -178,11 +179,11 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly if (bridges.empty()) { - fill_boundaries = union_(fill_boundaries, true); + fill_boundaries = union_safety_offset(fill_boundaries); } else { // 1) Calculate the inflated bridge regions, each constrained to its island. - ExPolygons fill_boundaries_ex = union_ex(fill_boundaries, true); + ExPolygons fill_boundaries_ex = union_safety_offset_ex(fill_boundaries); std::vector bridges_grown; std::vector bridge_bboxes; @@ -237,7 +238,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly for (size_t j = i + 1; j < bridges.size(); ++ j) { if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) continue; - if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) + if (intersection(bridges_grown[i], bridges_grown[j]).empty()) continue; // The two bridge regions intersect. Give them the same group id. if (bridge_group[j] != size_t(-1)) { @@ -284,7 +285,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly #ifdef SLIC3R_DEBUG printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif - double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); + double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); if (bd.detect_angle(custom_angle)) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->has_support()) { @@ -297,7 +298,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges[idx_last].bridge_angle = custom_angle; } // without safety offset, artifacts are generated (GH #2494) - surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); + surfaces_append(bottom, union_safety_offset_ex(grown), bridges[idx_last]); } fill_boundaries = to_polygons(fill_boundaries_ex); @@ -337,7 +338,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly surfaces_append( new_surfaces, // Don't use a safety offset as fill_boundaries were already united using the safety offset. - intersection_ex(polys, fill_boundaries, false), + intersection_ex(polys, fill_boundaries), s1); } } @@ -383,21 +384,21 @@ void LayerRegion::prepare_fill_surfaces() bool spiral_vase = this->layer()->object()->print()->config().spiral_vase; // if no solid layers are requested, turn top/bottom surfaces to internal - if (! spiral_vase && this->region()->config().top_solid_layers == 0) { + if (! spiral_vase && this->region().config().top_solid_layers == 0) { for (Surface &surface : this->fill_surfaces.surfaces) if (surface.is_top()) surface.surface_type = this->layer()->object()->config().infill_only_where_needed ? stInternalVoid : stInternal; } - if (this->region()->config().bottom_solid_layers == 0) { + if (this->region().config().bottom_solid_layers == 0) { for (Surface &surface : this->fill_surfaces.surfaces) if (surface.is_bottom()) // (surface.surface_type == stBottom) surface.surface_type = stInternal; } // turn too small internal regions into solid regions according to the user setting - if (! spiral_vase && this->region()->config().fill_density.value > 0) { + if (! spiral_vase && this->region().config().fill_density.value > 0) { // scaling an area requires two calls! - double min_area = scale_(scale_(this->region()->config().solid_infill_below_area.value)); + double min_area = scale_(scale_(this->region().config().solid_infill_below_area.value)); for (Surface &surface : this->fill_surfaces.surfaces) if (surface.surface_type == stInternal && surface.area() <= min_area) surface.surface_type = stInternalSolid; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8b829fc138..bfad61a90d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1813,7 +1813,7 @@ void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_le this->set_mesh(std::move(mesh)); TriangleMesh convex_hull = this->get_convex_hull(); convex_hull.transform(mesh_trafo, fix_left_handed); - this->m_convex_hull = std::make_shared(std::move(convex_hull)); + m_convex_hull = std::make_shared(std::move(convex_hull)); // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded. this->set_new_unique_id(); } @@ -1825,7 +1825,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand this->set_mesh(std::move(mesh)); TriangleMesh convex_hull = this->get_convex_hull(); convex_hull.transform(matrix, fix_left_handed); - this->m_convex_hull = std::make_shared(std::move(convex_hull)); + m_convex_hull = std::make_shared(std::move(convex_hull)); // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded. this->set_new_unique_id(); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 50797aeeb5..a65332272c 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -180,8 +180,8 @@ private: class LayerHeightProfile final : public ObjectWithTimestamp { public: // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } std::vector get() const throw() { return m_data; } bool empty() const throw() { return m_data.empty(); } @@ -504,8 +504,8 @@ enum class ConversionType : int { class FacetsAnnotation final : public ObjectWithTimestamp { public: // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { this->m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } const std::map>& get_data() const throw() { return m_data; } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; @@ -680,6 +680,7 @@ protected: friend class SLAPrint; friend class Model; friend class ModelObject; + friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new); // Copies IDs of both the ModelVolume and its config. explicit ModelVolume(const ModelVolume &rhs) = default; diff --git a/src/libslic3r/MutablePolygon.hpp b/src/libslic3r/MutablePolygon.hpp index ef2b612211..14d7787cf8 100644 --- a/src/libslic3r/MutablePolygon.hpp +++ b/src/libslic3r/MutablePolygon.hpp @@ -52,7 +52,7 @@ public: PointType* operator->() const { return &m_data->at(m_idx).point; } MutablePolygon& polygon() const { assert(this->valid()); return *m_data; } IndexType size() const { assert(this->valid()); return m_data->size(); } - iterator& remove() { this->m_idx = m_data->remove(*this).m_idx; return *this; } + iterator& remove() { m_idx = m_data->remove(*this).m_idx; return *this; } iterator insert(const PointType pt) const { return m_data->insert(*this, pt); } private: iterator(MutablePolygon *data, IndexType idx) : m_data(data), m_idx(idx) {} @@ -162,10 +162,10 @@ public: return out; }; - bool empty() const { return this->m_size == 0; } - size_t size() const { return this->m_size; } - size_t capacity() const { return this->m_data.capacity(); } - bool valid() const { return this->m_size >= 3; } + bool empty() const { return m_size == 0; } + size_t size() const { return m_size; } + size_t capacity() const { return m_data.capacity(); } + bool valid() const { return m_size >= 3; } void clear() { m_data.clear(); m_size = 0; m_head = IndexType(-1); m_head_free = IndexType(-1); } iterator begin() { return { this, m_head }; } diff --git a/src/libslic3r/Optimize/NLoptOptimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp index 826b1632ae..f5d3140463 100644 --- a/src/libslic3r/Optimize/NLoptOptimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -121,8 +121,8 @@ protected: if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); - if(this->m_stopcr.max_iterations() > 0) - nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations()); + if(m_stopcr.max_iterations() > 0) + nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations()); } template diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp index 3308f1fd42..51bf7de7c3 100644 --- a/src/libslic3r/PNGReadWrite.cpp +++ b/src/libslic3r/PNGReadWrite.cpp @@ -103,6 +103,7 @@ bool decode_png(IStream &in_buf, ImageGreyscale &out_img) // Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. // Based on https://www.lemoda.net/c/write-png/ // png_color_type is PNG_COLOR_TYPE_RGB or PNG_COLOR_TYPE_GRAY +//FIXME maybe better to use tdefl_write_image_to_png_file_in_memory() instead? static bool write_rgb_or_gray_to_file(const char *file_name_utf8, size_t width, size_t height, int png_color_type, const uint8_t *data) { bool result = false; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index a459b90fad..3190845bd2 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -349,7 +349,7 @@ void PerimeterGenerator::process() coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); ExPolygons expp = offset2_ex( // medial axis requires non-overlapping geometry - diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.)), true), + diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), - float(min_width / 2.), float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) @@ -496,8 +496,7 @@ void PerimeterGenerator::process() ExPolygons gaps_ex = diff_ex( //FIXME offset2 would be enough and cheaper. offset2_ex(gaps, - float(min / 2.), float(min / 2.)), - offset2_ex(gaps, - float(max / 2.), float(max / 2.)), - true); + offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); ThickPolylines polylines; for (const ExPolygon &ex : gaps_ex) ex.medial_axis(max, min, &polylines); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index e744272ac0..5b180afcac 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -33,32 +33,16 @@ Polyline Polygon::split_at_index(int index) const return polyline; } -/* -int64_t Polygon::area2x() const -{ - size_t n = poly.size(); - if (n < 3) - return 0; - - int64_t a = 0; - for (size_t i = 0, j = n - 1; i < n; ++i) - a += int64_t(poly[j](0) + poly[i](0)) * int64_t(poly[j](1) - poly[i](1)); - j = i; - } - return -a * 0.5; -} -*/ - double Polygon::area(const Points &points) { - size_t n = points.size(); - if (n < 3) - return 0.; - double a = 0.; - for (size_t i = 0, j = n - 1; i < n; ++i) { - a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1)); - j = i; + if (points.size() >= 3) { + Vec2d p1 = points.back().cast(); + for (const Point &p : points) { + Vec2d p2 = p.cast(); + a += cross2(p1, p2); + p1 = p2; + } } return 0.5 * a; } @@ -169,19 +153,22 @@ void Polygon::triangulate_convex(Polygons* polygons) const } // center of mass +// source: https://en.wikipedia.org/wiki/Centroid Point Polygon::centroid() const { - double area_temp = this->area(); - double x_temp = 0; - double y_temp = 0; - - Polyline polyline = this->split_at_first_point(); - for (Points::const_iterator point = polyline.points.begin(); point != polyline.points.end() - 1; ++point) { - x_temp += (double)( point->x() + (point+1)->x() ) * ( (double)point->x()*(point+1)->y() - (double)(point+1)->x()*point->y() ); - y_temp += (double)( point->y() + (point+1)->y() ) * ( (double)point->x()*(point+1)->y() - (double)(point+1)->x()*point->y() ); + double area_sum = 0.; + Vec2d c(0., 0.); + if (points.size() >= 3) { + Vec2d p1 = points.back().cast(); + for (const Point &p : points) { + Vec2d p2 = p.cast(); + double a = cross2(p1, p2); + area_sum += a; + c += (p1 + p2) * a; + p1 = p2; + } } - - return Point(x_temp/(6*area_temp), y_temp/(6*area_temp)); + return Point(Vec2d(c / (3. * area_sum))); } // find all concave vertices (i.e. having an internal angle greater than the supplied angle) diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 88f910590b..51dcf9d36d 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -78,6 +78,9 @@ public: bool is_closed() const { return this->points.front() == this->points.back(); } }; +inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } +inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.points != rhs.points; } + // Don't use this class in production code, it is used exclusively by the Perl binding for unit tests! #ifdef PERL_UCHAR_MIN class PolylineCollection diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 99492864b0..7a258182e1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1079,7 +1079,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl size_t PresetCollection::first_visible_idx() const { size_t idx = m_default_suppressed ? m_num_default_presets : 0; - for (; idx < this->m_presets.size(); ++ idx) + for (; idx < m_presets.size(); ++ idx) if (m_presets[idx].is_visible) break; if (idx == m_presets.size()) @@ -1294,7 +1294,7 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe assert(it != new_vendors.end()); preset.vendor = &it->second; } - this->m_presets.emplace(it, std::move(preset)); + m_presets.emplace(it, std::move(preset)); } else duplicates.emplace_back(std::move(preset.name)); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 7fcb752978..538ee60097 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -31,6 +31,9 @@ namespace Slic3r { template class PrintState; template class PrintState; +PrintRegion::PrintRegion(const PrintRegionConfig &config) : PrintRegion(config, config.hash()) {} +PrintRegion::PrintRegion(PrintRegionConfig &&config) : PrintRegion(std::move(config), config.hash()) {} + void Print::clear() { tbb::mutex::scoped_lock lock(this->state_mutex()); @@ -39,24 +42,10 @@ void Print::clear() for (PrintObject *object : m_objects) delete object; m_objects.clear(); - for (PrintRegion *region : m_regions) - delete region; - m_regions.clear(); + m_print_regions.clear(); m_model.clear_objects(); } -PrintRegion* Print::add_region() -{ - m_regions.emplace_back(new PrintRegion(this)); - return m_regions.back(); -} - -PrintRegion* Print::add_region(const PrintRegionConfig &config) -{ - m_regions.emplace_back(new PrintRegion(this, config)); - return m_regions.back(); -} - // Called by Print::apply(). // This method only accepts PrintConfig option keys. bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* new_config */, const std::vector &opt_keys) @@ -273,15 +262,10 @@ bool Print::is_step_done(PrintObjectStep step) const std::vector Print::object_extruders() const { std::vector extruders; - extruders.reserve(m_regions.size() * 3); - std::vector region_used(m_regions.size(), false); + extruders.reserve(m_print_regions.size() * m_objects.size() * 3); for (const PrintObject *object : m_objects) - for (const std::vector> &volumes_per_region : object->region_volumes) - if (! volumes_per_region.empty()) - region_used[&volumes_per_region - &object->region_volumes.front()] = true; - for (size_t idx_region = 0; idx_region < m_regions.size(); ++ idx_region) - if (region_used[idx_region]) - m_regions[idx_region]->collect_object_printing_extruders(extruders); + for (const PrintRegion ®ion : object->all_regions()) + region.collect_object_printing_extruders(*this, extruders); sort_remove_duplicates(extruders); return extruders; } @@ -345,242 +329,6 @@ double Print::max_allowed_layer_height() const return nozzle_diameter_max; } -// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new -// in the exact order and with the same IDs. -// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. -void Print::model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) -{ - typedef std::pair ModelVolumeWithStatus; - std::vector old_volumes; - old_volumes.reserve(model_object_dst.volumes.size()); - for (const ModelVolume *model_volume : model_object_dst.volumes) - old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); - auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; - auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; - std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); - model_object_dst.volumes.clear(); - model_object_dst.volumes.reserve(model_object_new.volumes.size()); - for (const ModelVolume *model_volume_src : model_object_new.volumes) { - ModelVolumeWithStatus key(model_volume_src, false); - auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); - if (it != old_volumes.end() && model_volume_equal(*it, key)) { - // The volume was found in the old list. Just copy it. - assert(! it->second); // not consumed yet - it->second = true; - ModelVolume *model_volume_dst = const_cast(it->first); - // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. - assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); - model_object_dst.volumes.emplace_back(model_volume_dst); - if (model_volume_dst->is_support_modifier()) { - // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. - model_volume_dst->set_type(model_volume_src->type()); - model_volume_dst->set_transformation(model_volume_src->get_transformation()); - } - assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); - } else { - // The volume was not found in the old list. Create a new copy. - assert(model_volume_src->is_support_modifier()); - model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); - model_object_dst.volumes.back()->set_model_object(&model_object_dst); - } - } - // Release the non-consumed old volumes (those were deleted from the new list). - for (ModelVolumeWithStatus &mv_with_status : old_volumes) - if (! mv_with_status.second) - delete mv_with_status.first; -} - -static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) -{ - size_t i_src, i_dst; - for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { - const ModelVolume &mv_src = *model_object_src.volumes[i_src]; - ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; - if (mv_src.type() != type) { - ++ i_src; - continue; - } - if (mv_dst.type() != type) { - ++ i_dst; - continue; - } - assert(mv_src.id() == mv_dst.id()); - // Copy the ModelVolume data. - mv_dst.name = mv_src.name; - mv_dst.config.assign_config(mv_src.config); - assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); - mv_dst.supported_facets.assign(mv_src.supported_facets); - assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); - mv_dst.seam_facets.assign(mv_src.seam_facets); - //FIXME what to do with the materials? - // mv_dst.m_material_id = mv_src.m_material_id; - ++ i_src; - ++ i_dst; - } -} - -static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) -{ - assert(lr_dst.size() == lr_src.size()); - auto it_src = lr_src.cbegin(); - for (auto &kvp_dst : lr_dst) { - const auto &kvp_src = *it_src ++; - assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); - assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); - // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. - // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); - kvp_dst.second = kvp_src.second; - } -} - -static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) -{ - typedef Transform3d::Scalar T; - const T *lv = lhs.data(); - const T *rv = rhs.data(); - for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { - if (*lv < *rv) - return true; - else if (*lv > *rv) - return false; - } - return false; -} - -static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) -{ - typedef Transform3d::Scalar T; - const T *lv = lhs.data(); - const T *rv = rhs.data(); - for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) - if (*lv != *rv) - return false; - return true; -} - -struct PrintObjectTrafoAndInstances -{ - Transform3d trafo; - PrintInstances instances; - bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } -}; - -// Generate a list of trafos and XY offsets for instances of a ModelObject -static std::vector print_objects_from_model_object(const ModelObject &model_object) -{ - std::set trafos; - PrintObjectTrafoAndInstances trafo; - for (ModelInstance *model_instance : model_object.instances) - if (model_instance->is_printable()) { - trafo.trafo = model_instance->get_matrix(); - auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); - // Reset the XY axes of the transformation. - trafo.trafo.data()[12] = 0; - trafo.trafo.data()[13] = 0; - // Search or insert a trafo. - auto it = trafos.emplace(trafo).first; - const_cast(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift }); - } - return std::vector(trafos.begin(), trafos.end()); -} - -// Compare just the layer ranges and their layer heights, not the associated configs. -// Ignore the layer heights if check_layer_heights is false. -static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) -{ - if (lr1.size() != lr2.size()) - return false; - auto it2 = lr2.begin(); - for (const auto &kvp1 : lr1) { - const auto &kvp2 = *it2 ++; - if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || - std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || - (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) - return false; - } - return true; -} - -// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. -static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) -{ - auto it_a = va.begin(); - auto it_b = vb.begin(); - while (it_a != va.end() || it_b != vb.end()) { - if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. - ++ it_a; - continue; - } - if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. - ++ it_b; - continue; - } - if (it_a == va.end() || it_b == vb.end()) - // va or vb contains more Tool Changes than the other. - return true; - assert(it_a->type == CustomGCode::ToolChange); - assert(it_b->type == CustomGCode::ToolChange); - if (*it_a != *it_b) - // The two Tool Changes differ. - return true; - ++ it_a; - ++ it_b; - } - // There is no change in custom Tool Changes. - return false; -} - -// Collect diffs of configuration values at various containers, -// resolve the filament rectract overrides of extruder retract values. -void Print::config_diffs( - const DynamicPrintConfig &new_full_config, - t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, - t_config_option_keys &full_config_diff, - DynamicPrintConfig &filament_overrides) const -{ - // Collect changes to print config, account for overrides of extruder retract values by filament presets. - { - const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); - const std::string filament_prefix = "filament_"; - for (const t_config_option_key &opt_key : m_config.keys()) { - const ConfigOption *opt_old = m_config.option(opt_key); - assert(opt_old != nullptr); - const ConfigOption *opt_new = new_full_config.option(opt_key); - // assert(opt_new != nullptr); - if (opt_new == nullptr) - //FIXME This may happen when executing some test cases. - continue; - const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; - if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { - // An extruder retract override is available at some of the filament presets. - if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) { - auto opt_copy = opt_new->clone(); - opt_copy->apply_override(opt_new_filament); - if (*opt_old == *opt_copy) - delete opt_copy; - else { - filament_overrides.set_key_value(opt_key, opt_copy); - print_diff.emplace_back(opt_key); - } - } - } else if (*opt_new != *opt_old) - print_diff.emplace_back(opt_key); - } - } - // Collect changes to object and region configs. - object_diff = m_default_object_config.diff(new_full_config); - region_diff = m_default_region_config.diff(new_full_config); - // Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. - for (const t_config_option_key &opt_key : new_full_config.keys()) { - const ConfigOption *opt_old = m_full_print_config.option(opt_key); - const ConfigOption *opt_new = new_full_config.option(opt_key); - if (opt_old == nullptr || *opt_new != *opt_old) - full_config_diff.emplace_back(opt_key); - } -} - std::vector Print::print_object_ids() const { std::vector out; @@ -591,594 +339,6 @@ std::vector Print::print_object_ids() const return out; } -Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) -{ -#ifdef _DEBUG - check_model_ids_validity(model); -#endif /* _DEBUG */ - - // Normalize the config. - new_full_config.option("print_settings_id", true); - new_full_config.option("filament_settings_id", true); - new_full_config.option("printer_settings_id", true); - new_full_config.option("physical_printer_settings_id", true); - new_full_config.normalize_fdm(); - - // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. - t_config_option_keys print_diff, object_diff, region_diff, full_config_diff; - DynamicPrintConfig filament_overrides; - this->config_diffs(new_full_config, print_diff, object_diff, region_diff, full_config_diff, filament_overrides); - - // Do not use the ApplyStatus as we will use the max function when updating apply_status. - unsigned int apply_status = APPLY_STATUS_UNCHANGED; - auto update_apply_status = [&apply_status](bool invalidated) - { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; - if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) - update_apply_status(false); - - // Grab the lock for the Print / PrintObject milestones. - tbb::mutex::scoped_lock lock(this->state_mutex()); - - // The following call may stop the background processing. - if (! print_diff.empty()) - update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff)); - - // Apply variables to placeholder parser. The placeholder parser is used by G-code export, - // which should be stopped if print_diff is not empty. - size_t num_extruders = m_config.nozzle_diameter.size(); - bool num_extruders_changed = false; - if (! full_config_diff.empty()) { - update_apply_status(this->invalidate_step(psGCodeExport)); - // Set the profile aliases for the PrintBase::output_filename() - m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); - m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); - m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); - m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); - // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. - // see "Placeholders do not respect filament overrides." GH issue #3649 - m_placeholder_parser.apply_config(filament_overrides); - // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. - m_config.apply_only(new_full_config, print_diff, true); - //FIXME use move semantics once ConfigBase supports it. - m_config.apply(filament_overrides); - // Handle changes to object config defaults - m_default_object_config.apply_only(new_full_config, object_diff, true); - // Handle changes to regions config defaults - m_default_region_config.apply_only(new_full_config, region_diff, true); - m_full_print_config = std::move(new_full_config); - if (num_extruders != m_config.nozzle_diameter.size()) { - num_extruders = m_config.nozzle_diameter.size(); - num_extruders_changed = true; - } - } - - class LayerRanges - { - public: - LayerRanges() {} - // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. - void assign(const t_layer_config_ranges &in) { - m_ranges.clear(); - m_ranges.reserve(in.size()); - // Input ranges are sorted lexicographically. First range trims the other ranges. - coordf_t last_z = 0; - for (const std::pair &range : in) - if (range.first.second > last_z) { - coordf_t min_z = std::max(range.first.first, 0.); - if (min_z > last_z + EPSILON) { - m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); - last_z = min_z; - } - if (range.first.second > last_z + EPSILON) { - const DynamicPrintConfig *cfg = &range.second.get(); - m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); - last_z = range.first.second; - } - } - if (m_ranges.empty()) - m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); - else if (m_ranges.back().second == nullptr) - m_ranges.back().first.second = DBL_MAX; - else - m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); - } - - const DynamicPrintConfig* config(const t_layer_height_range &range) const { - auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); - // #ys_FIXME_COLOR - // assert(it != m_ranges.end()); - // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); - // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); - if (it == m_ranges.end() || - std::abs(it->first.first - range.first) > EPSILON || - std::abs(it->first.second - range.second) > EPSILON ) - return nullptr; // desired range doesn't found - return (it == m_ranges.end()) ? nullptr : it->second; - } - std::vector>::const_iterator begin() const { return m_ranges.cbegin(); } - std::vector>::const_iterator end() const { return m_ranges.cend(); } - private: - std::vector> m_ranges; - }; - struct ModelObjectStatus { - enum Status { - Unknown, - Old, - New, - Moved, - Deleted, - }; - ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} - ObjectID id; - Status status; - LayerRanges layer_ranges; - // Search by id. - bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } - }; - std::set model_object_status; - - // 1) Synchronize model objects. - if (model.id() != m_model.id()) { - // Kill everything, initialize from scratch. - // Stop background processing. - this->call_cancel_callback(); - update_apply_status(this->invalidate_all_steps()); - for (PrintObject *object : m_objects) { - model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); - update_apply_status(object->invalidate_all_steps()); - delete object; - } - m_objects.clear(); - for (PrintRegion *region : m_regions) - delete region; - m_regions.clear(); - m_model.assign_copy(model); - for (const ModelObject *model_object : m_model.objects) - model_object_status.emplace(model_object->id(), ModelObjectStatus::New); - } else { - if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - update_apply_status(num_extruders_changed || - // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. - //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable - // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. - (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? - // The Tool Ordering and the Wipe Tower are no more valid. - this->invalidate_steps({ psWipeTower, psGCodeExport }) : - // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. - this->invalidate_step(psGCodeExport)); - m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; - } - if (model_object_list_equal(m_model, model)) { - // The object list did not change. - for (const ModelObject *model_object : m_model.objects) - model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); - } else if (model_object_list_extended(m_model, model)) { - // Add new objects. Their volumes and configs will be synchronized later. - update_apply_status(this->invalidate_step(psGCodeExport)); - for (const ModelObject *model_object : m_model.objects) - model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); - for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { - model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New); - m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); - m_model.objects.back()->set_model(&m_model); - } - } else { - // Reorder the objects, add new objects. - // First stop background processing before shuffling or deleting the PrintObjects in the object list. - this->call_cancel_callback(); - update_apply_status(this->invalidate_step(psGCodeExport)); - // Second create a new list of objects. - std::vector model_objects_old(std::move(m_model.objects)); - m_model.objects.clear(); - m_model.objects.reserve(model.objects.size()); - auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; - std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); - for (const ModelObject *mobj : model.objects) { - auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); - if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { - // New ModelObject added. - m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); - m_model.objects.back()->set_model(&m_model); - model_object_status.emplace(mobj->id(), ModelObjectStatus::New); - } else { - // Existing ModelObject re-added (possibly moved in the list). - m_model.objects.emplace_back(*it); - model_object_status.emplace(mobj->id(), ModelObjectStatus::Moved); - } - } - bool deleted_any = false; - for (ModelObject *&model_object : model_objects_old) { - if (model_object_status.find(ModelObjectStatus(model_object->id())) == model_object_status.end()) { - model_object_status.emplace(model_object->id(), ModelObjectStatus::Deleted); - deleted_any = true; - } else - // Do not delete this ModelObject instance. - model_object = nullptr; - } - if (deleted_any) { - // Delete PrintObjects of the deleted ModelObjects. - PrintObjectPtrs print_objects_old = std::move(m_objects); - m_objects.clear(); - m_objects.reserve(print_objects_old.size()); - for (PrintObject *print_object : print_objects_old) { - auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); - assert(it_status != model_object_status.end()); - if (it_status->status == ModelObjectStatus::Deleted) { - update_apply_status(print_object->invalidate_all_steps()); - delete print_object; - } else - m_objects.emplace_back(print_object); - } - for (ModelObject *model_object : model_objects_old) - delete model_object; - } - } - } - - // 2) Map print objects including their transformation matrices. - struct PrintObjectStatus { - enum Status { - Unknown, - Deleted, - Reused, - New - }; - PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : - id(print_object->model_object()->id()), - print_object(print_object), - trafo(print_object->trafo()), - status(status) {} - PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} - // ID of the ModelObject & PrintObject - ObjectID id; - // Pointer to the old PrintObject - PrintObject *print_object; - // Trafo generated with model_object->world_matrix(true) - Transform3d trafo; - Status status; - // Search by id. - bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } - }; - std::multiset print_object_status; - for (PrintObject *print_object : m_objects) - print_object_status.emplace(PrintObjectStatus(print_object)); - - // 3) Synchronize ModelObjects & PrintObjects. - for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { - ModelObject &model_object = *m_model.objects[idx_model_object]; - auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); - assert(it_status != model_object_status.end()); - assert(it_status->status != ModelObjectStatus::Deleted); - const ModelObject& model_object_new = *model.objects[idx_model_object]; - const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); - if (it_status->status == ModelObjectStatus::New) - // PrintObject instances will be added in the next loop. - continue; - // Update the ModelObject instance, possibly invalidate the linked PrintObjects. - assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); - // Check whether a model part volume was added or removed, their transformations or order changed. - // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. - bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); - bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); - bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || - model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); - if (model_parts_differ || modifiers_differ || - model_object.origin_translation != model_object_new.origin_translation || - ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile) || - ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { - // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. - auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); - for (auto it = range.first; it != range.second; ++ it) { - update_apply_status(it->print_object->invalidate_all_steps()); - const_cast(*it).status = PrintObjectStatus::Deleted; - } - // Copy content of the ModelObject including its ID, do not change the parent. - model_object.assign_copy(model_object_new); - } else if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { - // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. - if (supports_differ) { - this->call_cancel_callback(); - update_apply_status(false); - } - // Invalidate just the supports step. - auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); - for (auto it = range.first; it != range.second; ++ it) - update_apply_status(it->print_object->invalidate_step(posSupportMaterial)); - if (supports_differ) { - // Copy just the support volumes. - model_volume_list_update_supports(model_object, model_object_new); - } - } else if (model_custom_seam_data_changed(model_object, model_object_new)) { - update_apply_status(this->invalidate_step(psGCodeExport)); - } - if (! model_parts_differ && ! modifiers_differ) { - // Synchronize Object's config. - bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); - if (object_config_changed) - model_object.config.assign_config(model_object_new.config); - if (! object_diff.empty() || object_config_changed || num_extruders_changed) { - PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); - auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); - for (auto it = range.first; it != range.second; ++ it) { - t_config_option_keys diff = it->print_object->config().diff(new_config); - if (! diff.empty()) { - update_apply_status(it->print_object->invalidate_state_by_config_options(it->print_object->config(), new_config, diff)); - it->print_object->config_apply_only(new_config, diff, true); - } - } - } - // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data). - //FIXME What to do with m_material_id? - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); - layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); - // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. - model_object.name = model_object_new.name; - model_object.input_file = model_object_new.input_file; - // Only refresh ModelInstances if there is any change. - if (model_object.instances.size() != model_object_new.instances.size() || - ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) { - // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list. - update_apply_status(this->invalidate_step(psGCodeExport)); - model_object.clear_instances(); - model_object.instances.reserve(model_object_new.instances.size()); - for (const ModelInstance *model_instance : model_object_new.instances) { - model_object.instances.emplace_back(new ModelInstance(*model_instance)); - model_object.instances.back()->set_model_object(&model_object); - } - } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), - [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && - l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { - // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. - // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. - model_object.invalidate_bounding_box(); - // Synchronize the content of instances. - auto new_instance = model_object_new.instances.begin(); - for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { - (*old_instance)->set_transformation((*new_instance)->get_transformation()); - (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; - (*old_instance)->printable = (*new_instance)->printable; - } - } - } - } - - // 4) Generate PrintObjects from ModelObjects and their instances. - { - PrintObjectPtrs print_objects_new; - print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); - bool new_objects = false; - // Walk over all new model objects and check, whether there are matching PrintObjects. - for (ModelObject *model_object : m_model.objects) { - auto range = print_object_status.equal_range(PrintObjectStatus(model_object->id())); - std::vector old; - if (range.first != range.second) { - old.reserve(print_object_status.count(PrintObjectStatus(model_object->id()))); - for (auto it = range.first; it != range.second; ++ it) - if (it->status != PrintObjectStatus::Deleted) - old.emplace_back(&(*it)); - } - // Generate a list of trafos and XY offsets for instances of a ModelObject - // Producing the config for PrintObject on demand, caching it at print_object_last. - const PrintObject *print_object_last = nullptr; - auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject* print_object) { - print_object->config_apply(print_object_last ? - print_object_last->config() : - PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); - print_object_last = print_object; - }; - std::vector new_print_instances = print_objects_from_model_object(*model_object); - if (old.empty()) { - // Simple case, just generate new instances. - for (PrintObjectTrafoAndInstances &print_instances : new_print_instances) { - PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); - print_object_apply_config(print_object); - print_objects_new.emplace_back(print_object); - // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); - new_objects = true; - } - continue; - } - // Complex case, try to merge the two lists. - // Sort the old lexicographically by their trafos. - std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); - // Merge the old / new lists. - auto it_old = old.begin(); - for (PrintObjectTrafoAndInstances &new_instances : new_print_instances) { - for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); - if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { - // This is a new instance (or a set of instances with the same trafo). Just add it. - PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); - print_object_apply_config(print_object); - print_objects_new.emplace_back(print_object); - // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); - new_objects = true; - if (it_old != old.end()) - const_cast(*it_old)->status = PrintObjectStatus::Deleted; - } else { - // The PrintObject already exists and the copies differ. - PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); - if (status != PrintBase::APPLY_STATUS_UNCHANGED) - update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); - print_objects_new.emplace_back((*it_old)->print_object); - const_cast(*it_old)->status = PrintObjectStatus::Reused; - } - } - } - if (m_objects != print_objects_new) { - this->call_cancel_callback(); - update_apply_status(this->invalidate_all_steps()); - m_objects = print_objects_new; - // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; - for (auto &pos : print_object_status) - if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { - update_apply_status(pos.print_object->invalidate_all_steps()); - delete pos.print_object; - deleted_objects = true; - } - if (new_objects || deleted_objects) - update_apply_status(this->invalidate_steps({ psSkirt, psBrim, psWipeTower, psGCodeExport })); - if (new_objects) - update_apply_status(false); - } - print_object_status.clear(); - } - - // 5) Synchronize configs of ModelVolumes, synchronize AMF / 3MF materials (and their configs), refresh PrintRegions. - // Update reference counts of regions from the remaining PrintObjects and their volumes. - // Regions with zero references could and should be reused. - for (PrintRegion *region : m_regions) - region->m_refcnt = 0; - for (PrintObject *print_object : m_objects) { - int idx_region = 0; - for (const auto &volumes : print_object->region_volumes) { - if (! volumes.empty()) - ++ m_regions[idx_region]->m_refcnt; - ++ idx_region; - } - } - - // All regions now have distinct settings. - // Check whether applying the new region config defaults we'd get different regions. - for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) { - PrintRegion ®ion = *m_regions[region_id]; - PrintRegionConfig this_region_config; - bool this_region_config_set = false; - for (PrintObject *print_object : m_objects) { - const LayerRanges *layer_ranges; - { - auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); - assert(it_status != model_object_status.end()); - assert(it_status->status != ModelObjectStatus::Deleted); - layer_ranges = &it_status->layer_ranges; - } - if (region_id < print_object->region_volumes.size()) { - for (const std::pair &volume_and_range : print_object->region_volumes[region_id]) { - const ModelVolume &volume = *print_object->model_object()->volumes[volume_and_range.second]; - const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_and_range.first); - if (this_region_config_set) { - // If the new config for this volume differs from the other - // volume configs currently associated to this region, it means - // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders))) - // Regions were split. Reset this print_object. - goto print_object_end; - } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); - for (size_t i = 0; i < region_id; ++ i) { - const PrintRegion ®ion_other = *m_regions[i]; - if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) - // Regions were merged. Reset this print_object. - goto print_object_end; - } - this_region_config_set = true; - } - } - } - continue; - print_object_end: - update_apply_status(print_object->invalidate_all_steps()); - // Decrease the references to regions from this volume. - int ireg = 0; - for (const std::vector> &volumes : print_object->region_volumes) { - if (! volumes.empty()) - -- m_regions[ireg]->m_refcnt; - ++ ireg; - } - print_object->region_volumes.clear(); - } - if (this_region_config_set) { - t_config_option_keys diff = region.config().diff(this_region_config); - if (! diff.empty()) { - // Stop the background process before assigning new configuration to the regions. - for (PrintObject *print_object : m_objects) - if (region_id < print_object->region_volumes.size() && ! print_object->region_volumes[region_id].empty()) - update_apply_status(print_object->invalidate_state_by_config_options(region.config(), this_region_config, diff)); - region.config_apply_only(this_region_config, diff, false); - } - } - } - - // Possibly add new regions for the newly added or resetted PrintObjects. - for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) { - PrintObject &print_object0 = *m_objects[idx_print_object]; - const ModelObject &model_object = *print_object0.model_object(); - const LayerRanges *layer_ranges; - { - auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); - assert(it_status != model_object_status.end()); - assert(it_status->status != ModelObjectStatus::Deleted); - layer_ranges = &it_status->layer_ranges; - } - std::vector regions_in_object; - regions_in_object.reserve(64); - for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) { - PrintObject &print_object = *m_objects[i]; - bool fresh = print_object.region_volumes.empty(); - unsigned int volume_id = 0; - unsigned int idx_region_in_object = 0; - for (const ModelVolume *volume : model_object.volumes) { - if (! volume->is_model_part() && ! volume->is_modifier()) { - ++ volume_id; - continue; - } - // Filter the layer ranges, so they do not overlap and they contain at least a single layer. - // Now insert a volume with a layer range to its own region. - for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { - int region_id = -1; - if (&print_object == &print_object0) { - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); - // Find an existing print region with the same config. - int idx_empty_slot = -1; - for (int i = 0; i < (int)m_regions.size(); ++ i) { - if (m_regions[i]->m_refcnt == 0) { - if (idx_empty_slot == -1) - idx_empty_slot = i; - } else if (config.equals(m_regions[i]->config())) { - region_id = i; - break; - } - } - // If no region exists with the same config, create a new one. - if (region_id == -1) { - if (idx_empty_slot == -1) { - region_id = (int)m_regions.size(); - this->add_region(config); - } else { - region_id = idx_empty_slot; - m_regions[region_id]->set_config(std::move(config)); - } - } - regions_in_object.emplace_back(region_id); - } else - region_id = regions_in_object[idx_region_in_object ++]; - // Assign volume to a region. - if (fresh) { - if ((size_t)region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) - ++ m_regions[region_id]->m_refcnt; - print_object.add_region_volume(region_id, volume_id, it_range->first); - } - } - ++ volume_id; - } - } - } - - // Update SlicingParameters for each object where the SlicingParameters is not valid. - // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use - // (posSlicing and posSupportMaterial was invalidated). - for (PrintObject *object : m_objects) - object->update_slicing_parameters(); - -#ifdef _DEBUG - check_model_ids_equal(m_model, model); -#endif /* _DEBUG */ - - return static_cast(apply_status); -} - bool Print::has_infinite_skirt() const { return (m_config.draft_shield && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1); @@ -1253,10 +413,12 @@ static inline bool sequential_print_vertical_clearance_valid(const Print &print) // Precondition: Print::validate() requires the Print::apply() to be called its invocation. std::string Print::validate(std::string* warning) const { + std::vector extruders = this->extruders(); + if (m_objects.empty()) return L("All objects are outside of the print volume."); - if (extruders().empty()) + if (extruders.empty()) return L("The supplied settings will cause an empty print."); if (m_config.complete_objects) { @@ -1275,20 +437,16 @@ std::string Print::validate(std::string* warning) const return L("Only a single object may be printed at a time in Spiral Vase mode. " "Either remove all but the last object, or enable sequential mode by \"complete_objects\"."); assert(m_objects.size() == 1); - size_t num_regions = 0; - for (const std::vector> &volumes_per_region : m_objects.front()->region_volumes) - if (! volumes_per_region.empty()) - ++ num_regions; - if (num_regions > 1) + if (m_objects.front()->all_regions().size() > 1) return L("The Spiral Vase option can only be used when printing single material objects."); } if (this->has_wipe_tower() && ! m_objects.empty()) { // Make sure all extruders use same diameter filament and have the same nozzle diameter // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments - double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders().front()); - double first_filament_diam = m_config.filament_diameter.get_at(extruders().front()); - for (const auto& extruder_idx : extruders()) { + double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders.front()); + double first_filament_diam = m_config.filament_diameter.get_at(extruders.front()); + for (const auto& extruder_idx : extruders) { double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); double filament_diam = m_config.filament_diameter.get_at(extruder_idx); if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam @@ -1306,7 +464,7 @@ std::string Print::validate(std::string* warning) const return L("Ooze prevention is currently not supported with the wipe tower enabled."); if (m_config.use_volumetric_e) return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0)."); - if (m_config.complete_objects && extruders().size() > 1) + if (m_config.complete_objects && extruders.size() > 1) return L("The Wipe Tower is currently not supported for multimaterial sequential prints."); if (m_objects.size() > 1) { @@ -1386,8 +544,6 @@ std::string Print::validate(std::string* warning) const } { - std::vector extruders = this->extruders(); - // Find the smallest used nozzle diameter and the number of unique nozzle diameters. double min_nozzle_diameter = std::numeric_limits::max(); double max_nozzle_diameter = 0; @@ -1494,8 +650,8 @@ std::string Print::validate(std::string* warning) const if ((object->has_support() || object->has_raft()) && ! validate_extrusion_width(object->config(), "support_material_extrusion_width", layer_height, err_msg)) return err_msg; for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" }) - for (size_t i = 0; i < object->region_volumes.size(); ++ i) - if (! object->region_volumes[i].empty() && ! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg)) + for (const PrintRegion ®ion : object->all_regions()) + if (! validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) return err_msg; } } @@ -1570,7 +726,7 @@ Flow Print::brim_flow() const { ConfigOptionFloatOrPercent width = m_config.first_layer_extrusion_width; if (width.value == 0) - width = m_regions.front()->config().perimeter_extrusion_width; + width = m_print_regions.front()->config().perimeter_extrusion_width; if (width.value == 0) width = m_objects.front()->config().extrusion_width; @@ -1582,7 +738,7 @@ Flow Print::brim_flow() const return Flow::new_from_config_width( frPerimeter, width, - (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), + (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().perimeter_extruder-1), (float)this->skirt_first_layer_height()); } @@ -1590,7 +746,7 @@ Flow Print::skirt_flow() const { ConfigOptionFloatOrPercent width = m_config.first_layer_extrusion_width; if (width.value == 0) - width = m_regions.front()->config().perimeter_extrusion_width; + width = m_print_regions.front()->config().perimeter_extrusion_width; if (width.value == 0) width = m_objects.front()->config().extrusion_width; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 91f86d0105..3fdc49db82 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -15,6 +15,9 @@ #include "libslic3r.h" +#include +#include + namespace Slic3r { class Print; @@ -57,12 +60,20 @@ enum PrintObjectStep { // sharing the same config (including the same assigned extruder(s)) class PrintRegion { - friend class Print; +public: + PrintRegion() = default; + PrintRegion(const PrintRegionConfig &config); + PrintRegion(const PrintRegionConfig &config, const size_t config_hash) : m_config(config), m_config_hash(config_hash) {} + PrintRegion(PrintRegionConfig &&config); + PrintRegion(PrintRegionConfig &&config, const size_t config_hash) : m_config(std::move(config)), m_config_hash(config_hash) {} + ~PrintRegion() = default; // Methods NOT modifying the PrintRegion's state: public: - const Print* print() const { return m_print; } - const PrintRegionConfig& config() const { return m_config; } + const PrintRegionConfig& config() const throw() { return m_config; } + size_t config_hash() const throw() { return m_config_hash; } + // Identifier of this PrintRegion in the list of Print::m_print_regions. + int print_region_id() const throw() { return m_print_region_id; } // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; @@ -72,29 +83,25 @@ public: coordf_t bridging_height_avg(const PrintConfig &print_config) const; // Collect 0-based extruder indices used to print this region's object. - void collect_object_printing_extruders(std::vector &object_extruders) const; + void collect_object_printing_extruders(const Print &print, std::vector &object_extruders) const; static void collect_object_printing_extruders(const PrintConfig &print_config, const PrintRegionConfig ®ion_config, const bool has_brim, std::vector &object_extruders); // Methods modifying the PrintRegion's state: public: - Print* print() { return m_print; } - void set_config(const PrintRegionConfig &config) { m_config = config; } - void set_config(PrintRegionConfig &&config) { m_config = std::move(config); } + void set_config(const PrintRegionConfig &config) { m_config = config; m_config_hash = m_config.hash(); } + void set_config(PrintRegionConfig &&config) { m_config = std::move(config); m_config_hash = m_config.hash(); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) - { this->m_config.apply_only(other, keys, ignore_nonexistent); } - -protected: - size_t m_refcnt; - + { m_config.apply_only(other, keys, ignore_nonexistent); m_config_hash = m_config.hash(); } private: - Print *m_print; + friend Print; PrintRegionConfig m_config; - - PrintRegion(Print* print) : m_refcnt(0), m_print(print) {} - PrintRegion(Print* print, const PrintRegionConfig &config) : m_refcnt(0), m_print(print), m_config(config) {} - ~PrintRegion() = default; + size_t m_config_hash; + int m_print_region_id = -1; }; +inline bool operator==(const PrintRegion &lhs, const PrintRegion &rhs) { return lhs.config_hash() == rhs.config_hash() && lhs.config() == rhs.config(); } +inline bool operator!=(const PrintRegion &lhs, const PrintRegion &rhs) { return ! (lhs == rhs); } + template class ConstVectorOfPtrsAdaptor { public: @@ -145,15 +152,40 @@ struct PrintInstance typedef std::vector PrintInstances; +// Region and its volumes (printing volumes or modifier volumes) +struct PrintRegionVolumes +{ + // Single volume + Z range assigned to a region. + struct VolumeWithZRange { + // Z range to slice this ModelVolume over. + t_layer_height_range layer_height_range; + // Index of a ModelVolume inside its parent ModelObject. + int volume_idx; + }; + + // Overriding one region with some other extruder, producing another region. + // The region is owned by PrintObject::m_all_regions. + struct ExtruderOverride { + unsigned int extruder; +// const PrintRegion *region; + }; + + // The region is owned by PrintObject::m_all_regions. +// const PrintRegion *region; + // Possible overrides of the default region extruder. + std::vector overrides; + // List of ModelVolume indices and layer ranges of thereof. + std::vector volumes; + // Is this region printing in any layer? +// bool printing { false }; +}; + class PrintObject : public PrintObjectBaseWithState { private: // Prevents erroneous use by other classes. typedef PrintObjectBaseWithState Inherited; public: - // vector of (layer height ranges and vectors of volume ids), indexed by region_id - std::vector>> region_volumes; - // Size of an object: XYZ in scaled coordinates. The size might not be quite snug in XY plane. const Vec3crd& size() const { return m_size; } const PrintObjectConfig& config() const { return m_config; } @@ -180,9 +212,9 @@ public: // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { - if (region_id >= region_volumes.size()) - region_volumes.resize(region_id + 1); - region_volumes[region_id].emplace_back(layer_range, volume_id); + if (region_id >= m_region_volumes.size()) + m_region_volumes.resize(region_id + 1); + m_region_volumes[region_id].volumes.push_back({ layer_range, volume_id }); } // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id @@ -213,7 +245,7 @@ public: // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. // Returns true, if the layer_height_profile was changed. - static bool update_layer_height_profile(const ModelObject &model_object, const SlicingParameters &slicing_parameters, std::vector &layer_height_profile); + static bool update_layer_height_profile(const ModelObject &model_object, const SlicingParameters &slicing_parameters, std::vector &layer_height_profile); // Collect the slicing parameters, to be used by variable layer thickness algorithm, // by the interactive layer height editor and by the printing process itself. @@ -222,6 +254,11 @@ public: const SlicingParameters& slicing_parameters() const { return m_slicing_params; } static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z); + size_t num_printing_regions() const throw() { return m_all_regions.size(); } + const PrintRegion& printing_region(size_t idx) const throw() { return *m_all_regions[idx]; } + //FIXME returing all possible regions before slicing, thus some of the regions may not be slicing at the end. + std::vector> all_regions() const; + bool has_support() const { return m_config.support_material || m_config.support_material_enforce_layers > 0; } bool has_raft() const { return m_config.raft_layers > 0; } bool has_support_material() const { return this->has_support() || this->has_raft(); } @@ -247,8 +284,8 @@ private: PrintObject(Print* print, ModelObject* model_object, const Transform3d& trafo, PrintInstances&& instances); ~PrintObject() = default; - void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } - void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } + void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } + void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_config.apply_only(other, keys, ignore_nonexistent); } PrintBase::ApplyStatus set_instances(PrintInstances &&instances); // Invalidates the step, and its depending steps in PrintObject and Print. bool invalidate_step(PrintObjectStep step); @@ -296,6 +333,10 @@ private: // This is the adjustment of the the Object's coordinate system towards PrintObject's coordinate system. Point m_center_offset; + std::vector> m_all_regions; + // vector of (layer height ranges and vectors of volume ids), indexed by region_id + std::vector m_region_volumes; + SlicingParameters m_slicing_params; LayerPtrs m_layers; SupportLayerPtrs m_support_layers; @@ -366,7 +407,7 @@ struct PrintStatistics double total_weight; double total_wipe_tower_cost; double total_wipe_tower_filament; - std::map filament_stats; + std::map filament_stats; // Config with the filled in print statistics. DynamicConfig config() const; @@ -395,11 +436,13 @@ class ConstPrintObjectPtrsAdaptor : public ConstVectorOfPtrsAdaptor }; typedef std::vector PrintRegionPtrs; +/* typedef std::vector ConstPrintRegionPtrs; class ConstPrintRegionPtrsAdaptor : public ConstVectorOfPtrsAdaptor { friend Print; ConstPrintRegionPtrsAdaptor(const PrintRegionPtrs *data) : ConstVectorOfPtrsAdaptor(data) {} }; +*/ // The complete print tray with possibly multiple objects. class Print : public PrintBaseWithState @@ -470,14 +513,14 @@ public: [object_id](const PrintObject *obj) { return obj->id() == object_id; }); return (it == m_objects.end()) ? nullptr : *it; } - ConstPrintRegionPtrsAdaptor regions() const { return ConstPrintRegionPtrsAdaptor(&m_regions); } +// ConstPrintRegionPtrsAdaptor regions() const { return ConstPrintRegionPtrsAdaptor(&m_regions); } // How many of PrintObject::copies() over all print objects are there? // If zero, then the print is empty and the print shall not be executed. unsigned int num_object_instances() const; // For Perl bindings. PrintObjectPtrs& objects_mutable() { return m_objects; } - PrintRegionPtrs& regions_mutable() { return m_regions; } + PrintRegionPtrs& print_regions_mutable() { return m_print_regions; } const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } @@ -498,26 +541,15 @@ public: std::string output_filename(const std::string &filename_base = std::string()) const override; - // Accessed by SupportMaterial - const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; } - const ToolOrdering& get_tool_ordering() const { return m_wipe_tower_data.tool_ordering; } // #ys_FIXME just for testing + size_t num_print_regions() const throw() { return m_print_regions.size(); } + const PrintRegion& get_print_region(size_t idx) const { return *m_print_regions[idx]; } + const ToolOrdering& get_tool_ordering() const { return m_wipe_tower_data.tool_ordering; } protected: - // methods for handling regions - PrintRegion* get_region(size_t idx) { return m_regions[idx]; } - PrintRegion* add_region(); - PrintRegion* add_region(const PrintRegionConfig &config); - // Invalidates the step, and its depending steps in Print. bool invalidate_step(PrintStep step); private: - void config_diffs( - const DynamicPrintConfig &new_full_config, - t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, - t_config_option_keys &full_config_diff, - DynamicPrintConfig &filament_overrides) const; - bool invalidate_state_by_config_options(const ConfigOptionResolver &new_config, const std::vector &opt_keys); void _make_skirt(); @@ -529,14 +561,11 @@ private: // Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim. std::vector first_layer_wipe_tower_corners() const; - // Declared here to have access to Model / ModelObject / ModelInstance - static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src); - PrintConfig m_config; PrintObjectConfig m_default_object_config; PrintRegionConfig m_default_region_config; PrintObjectPtrs m_objects; - PrintRegionPtrs m_regions; + PrintRegionPtrs m_print_regions; // Ordered collections of extrusion paths to build skirt loops and brim. ExtrusionEntityCollection m_skirt; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp new file mode 100644 index 0000000000..f0e262fdcd --- /dev/null +++ b/src/libslic3r/PrintApply.cpp @@ -0,0 +1,824 @@ +#include "Model.hpp" +#include "Print.hpp" + +namespace Slic3r { + +// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new +// in the exact order and with the same IDs. +// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. +// Friend to ModelVolume to allow copying. +// static is not accepted by gcc if declared as a friend of ModelObject. +/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) +{ + typedef std::pair ModelVolumeWithStatus; + std::vector old_volumes; + old_volumes.reserve(model_object_dst.volumes.size()); + for (const ModelVolume *model_volume : model_object_dst.volumes) + old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); + auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; + auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; + std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); + model_object_dst.volumes.clear(); + model_object_dst.volumes.reserve(model_object_new.volumes.size()); + for (const ModelVolume *model_volume_src : model_object_new.volumes) { + ModelVolumeWithStatus key(model_volume_src, false); + auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); + if (it != old_volumes.end() && model_volume_equal(*it, key)) { + // The volume was found in the old list. Just copy it. + assert(! it->second); // not consumed yet + it->second = true; + ModelVolume *model_volume_dst = const_cast(it->first); + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); + model_object_dst.volumes.emplace_back(model_volume_dst); + if (model_volume_dst->is_support_modifier()) { + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + model_volume_dst->set_type(model_volume_src->type()); + model_volume_dst->set_transformation(model_volume_src->get_transformation()); + } + assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); + } else { + // The volume was not found in the old list. Create a new copy. + assert(model_volume_src->is_support_modifier()); + model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); + model_object_dst.volumes.back()->set_model_object(&model_object_dst); + } + } + // Release the non-consumed old volumes (those were deleted from the new list). + for (ModelVolumeWithStatus &mv_with_status : old_volumes) + if (! mv_with_status.second) + delete mv_with_status.first; +} + +static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) +{ + size_t i_src, i_dst; + for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { + const ModelVolume &mv_src = *model_object_src.volumes[i_src]; + ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; + if (mv_src.type() != type) { + ++ i_src; + continue; + } + if (mv_dst.type() != type) { + ++ i_dst; + continue; + } + assert(mv_src.id() == mv_dst.id()); + // Copy the ModelVolume data. + mv_dst.name = mv_src.name; + mv_dst.config.assign_config(mv_src.config); + assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); + mv_dst.supported_facets.assign(mv_src.supported_facets); + assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); + mv_dst.seam_facets.assign(mv_src.seam_facets); + //FIXME what to do with the materials? + // mv_dst.m_material_id = mv_src.m_material_id; + ++ i_src; + ++ i_dst; + } +} + +static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) +{ + assert(lr_dst.size() == lr_src.size()); + auto it_src = lr_src.cbegin(); + for (auto &kvp_dst : lr_dst) { + const auto &kvp_src = *it_src ++; + assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); + assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); + // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. + // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); + kvp_dst.second = kvp_src.second; + } +} + +static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { + if (*lv < *rv) + return true; + else if (*lv > *rv) + return false; + } + return false; +} + +static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) + if (*lv != *rv) + return false; + return true; +} + +struct PrintObjectTrafoAndInstances +{ + Transform3d trafo; + PrintInstances instances; + bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } +}; + +// Generate a list of trafos and XY offsets for instances of a ModelObject +static std::vector print_objects_from_model_object(const ModelObject &model_object) +{ + std::set trafos; + PrintObjectTrafoAndInstances trafo; + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + trafo.trafo = model_instance->get_matrix(); + auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); + // Reset the XY axes of the transformation. + trafo.trafo.data()[12] = 0; + trafo.trafo.data()[13] = 0; + // Search or insert a trafo. + auto it = trafos.emplace(trafo).first; + const_cast(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift }); + } + return std::vector(trafos.begin(), trafos.end()); +} + +// Compare just the layer ranges and their layer heights, not the associated configs. +// Ignore the layer heights if check_layer_heights is false. +static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +{ + if (lr1.size() != lr2.size()) + return false; + auto it2 = lr2.begin(); + for (const auto &kvp1 : lr1) { + const auto &kvp2 = *it2 ++; + if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || + std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || + (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) + return false; + } + return true; +} + +// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. +static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) +{ + auto it_a = va.begin(); + auto it_b = vb.begin(); + while (it_a != va.end() || it_b != vb.end()) { + if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_a; + continue; + } + if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_b; + continue; + } + if (it_a == va.end() || it_b == vb.end()) + // va or vb contains more Tool Changes than the other. + return true; + assert(it_a->type == CustomGCode::ToolChange); + assert(it_b->type == CustomGCode::ToolChange); + if (*it_a != *it_b) + // The two Tool Changes differ. + return true; + ++ it_a; + ++ it_b; + } + // There is no change in custom Tool Changes. + return false; +} + +// Collect changes to print config, account for overrides of extruder retract values by filament presets. +static t_config_option_keys print_config_diffs( + const PrintConfig ¤t_config, + const DynamicPrintConfig &new_full_config, + DynamicPrintConfig &filament_overrides) +{ + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + t_config_option_keys print_diff; + for (const t_config_option_key &opt_key : current_config.keys()) { + const ConfigOption *opt_old = current_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; + const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + // An extruder retract override is available at some of the filament presets. + if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_filament); + if (*opt_old == *opt_copy) + delete opt_copy; + else { + filament_overrides.set_key_value(opt_key, opt_copy); + print_diff.emplace_back(opt_key); + } + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + + return print_diff; + } + +// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. +static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig ¤t_full_config, const DynamicPrintConfig &new_full_config) +{ + t_config_option_keys full_config_diff; + for (const t_config_option_key &opt_key : new_full_config.keys()) { + const ConfigOption *opt_old = current_full_config.option(opt_key); + const ConfigOption *opt_new = new_full_config.option(opt_key); + if (opt_old == nullptr || *opt_new != *opt_old) + full_config_diff.emplace_back(opt_key); + } + return full_config_diff; +} + +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) +{ +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // Normalize the config. + new_full_config.option("print_settings_id", true); + new_full_config.option("filament_settings_id", true); + new_full_config.option("printer_settings_id", true); + new_full_config.option("physical_printer_settings_id", true); + new_full_config.normalize_fdm(); + + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. + DynamicPrintConfig filament_overrides; + t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); + t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config); + // Collect changes to object and region configs. + t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); + t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. + unsigned int apply_status = APPLY_STATUS_UNCHANGED; + auto update_apply_status = [&apply_status](bool invalidated) + { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; + if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) + update_apply_status(false); + + // Grab the lock for the Print / PrintObject milestones. + tbb::mutex::scoped_lock lock(this->state_mutex()); + + // The following call may stop the background processing. + if (! print_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff)); + + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, + // which should be stopped if print_diff is not empty. + size_t num_extruders = m_config.nozzle_diameter.size(); + bool num_extruders_changed = false; + if (! full_config_diff.empty()) { + update_apply_status(this->invalidate_step(psGCodeExport)); + // Set the profile aliases for the PrintBase::output_filename() + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); + // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. + // see "Placeholders do not respect filament overrides." GH issue #3649 + m_placeholder_parser.apply_config(filament_overrides); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(new_full_config, print_diff, true); + //FIXME use move semantics once ConfigBase supports it. + m_config.apply(filament_overrides); + // Handle changes to object config defaults + m_default_object_config.apply_only(new_full_config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(new_full_config, region_diff, true); + m_full_print_config = std::move(new_full_config); + if (num_extruders != m_config.nozzle_diameter.size()) { + num_extruders = m_config.nozzle_diameter.size(); + num_extruders_changed = true; + } + } + + class LayerRanges + { + public: + LayerRanges() {} + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig *cfg = &range.second.get(); + m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); + last_z = range.first.second; + } + } + if (m_ranges.empty()) + m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); + else if (m_ranges.back().second == nullptr) + m_ranges.back().first.second = DBL_MAX; + else + m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); + } + + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); + // #ys_FIXME_COLOR + // assert(it != m_ranges.end()); + // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + if (it == m_ranges.end() || + std::abs(it->first.first - range.first) > EPSILON || + std::abs(it->first.second - range.second) > EPSILON ) + return nullptr; // desired range doesn't found + return (it == m_ranges.end()) ? nullptr : it->second; + } + std::vector>::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector>::const_iterator end() const { return m_ranges.cend(); } + private: + std::vector> m_ranges; + }; + struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; + Status status; + LayerRanges layer_ranges; + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } + }; + std::set model_object_status; + + // 1) Synchronize model objects. + if (model.id() != m_model.id()) { + // Kill everything, initialize from scratch. + // Stop background processing. + this->call_cancel_callback(); + update_apply_status(this->invalidate_all_steps()); + for (PrintObject *object : m_objects) { + model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); + update_apply_status(object->invalidate_all_steps()); + delete object; + } + m_objects.clear(); + m_model.assign_copy(model); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::New); + } else { + if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { + update_apply_status(num_extruders_changed || + // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. + //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable + // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. + (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? + // The Tool Ordering and the Wipe Tower are no more valid. + this->invalidate_steps({ psWipeTower, psGCodeExport }) : + // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. + this->invalidate_step(psGCodeExport)); + m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + } + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + update_apply_status(this->invalidate_step(psGCodeExport)); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { + model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New); + m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); + m_model.objects.back()->set_model(&m_model); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + this->call_cancel_callback(); + update_apply_status(this->invalidate_step(psGCodeExport)); + // Second create a new list of objects. + std::vector model_objects_old(std::move(m_model.objects)); + m_model.objects.clear(); + m_model.objects.reserve(model.objects.size()); + auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; + std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); + if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); + m_model.objects.back()->set_model(&m_model); + model_object_status.emplace(mobj->id(), ModelObjectStatus::New); + } else { + // Existing ModelObject re-added (possibly moved in the list). + m_model.objects.emplace_back(*it); + model_object_status.emplace(mobj->id(), ModelObjectStatus::Moved); + } + } + bool deleted_any = false; + for (ModelObject *&model_object : model_objects_old) { + if (model_object_status.find(ModelObjectStatus(model_object->id())) == model_object_status.end()) { + model_object_status.emplace(model_object->id(), ModelObjectStatus::Deleted); + deleted_any = true; + } else + // Do not delete this ModelObject instance. + model_object = nullptr; + } + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + PrintObjectPtrs print_objects_old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(print_objects_old.size()); + for (PrintObject *print_object : print_objects_old) { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + if (it_status->status == ModelObjectStatus::Deleted) { + update_apply_status(print_object->invalidate_all_steps()); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + for (ModelObject *model_object : model_objects_old) + delete model_object; + } + } + } + + // 2) Map print objects including their transformation matrices. + struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + // ID of the ModelObject & PrintObject + ObjectID id; + // Pointer to the old PrintObject + PrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } + }; + std::multiset print_object_status; + for (PrintObject *print_object : m_objects) + print_object_status.emplace(PrintObjectStatus(print_object)); + + // 3) Synchronize ModelObjects & PrintObjects. + for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { + ModelObject &model_object = *m_model.objects[idx_model_object]; + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + const ModelObject& model_object_new = *model.objects[idx_model_object]; + const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); + if (it_status->status == ModelObjectStatus::New) + // PrintObject instances will be added in the next loop. + continue; + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); + // Check whether a model part volume was added or removed, their transformations or order changed. + // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); + bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); + bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || + model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); + if (model_parts_differ || modifiers_differ || + model_object.origin_translation != model_object_new.origin_translation || + ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile) || + ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) { + update_apply_status(it->print_object->invalidate_all_steps()); + const_cast(*it).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { + // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. + if (supports_differ) { + this->call_cancel_callback(); + update_apply_status(false); + } + // Invalidate just the supports step. + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) + update_apply_status(it->print_object->invalidate_step(posSupportMaterial)); + if (supports_differ) { + // Copy just the support volumes. + model_volume_list_update_supports(model_object, model_object_new); + } + } else if (model_custom_seam_data_changed(model_object, model_object_new)) { + update_apply_status(this->invalidate_step(psGCodeExport)); + } + if (! model_parts_differ && ! modifiers_differ) { + // Synchronize Object's config. + bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); + if (object_config_changed) + model_object.config.assign_config(model_object_new.config); + if (! object_diff.empty() || object_config_changed || num_extruders_changed) { + PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) { + t_config_option_keys diff = it->print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(it->print_object->invalidate_state_by_config_options(it->print_object->config(), new_config, diff)); + it->print_object->config_apply_only(new_config, diff, true); + } + } + } + // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data). + //FIXME What to do with m_material_id? + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); + layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); + // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + // Only refresh ModelInstances if there is any change. + if (model_object.instances.size() != model_object_new.instances.size() || + ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) { + // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list. + update_apply_status(this->invalidate_step(psGCodeExport)); + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), + [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && + l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { + // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. + // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. + model_object.invalidate_bounding_box(); + // Synchronize the content of instances. + auto new_instance = model_object_new.instances.begin(); + for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { + (*old_instance)->set_transformation((*new_instance)->get_transformation()); + (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; + (*old_instance)->printable = (*new_instance)->printable; + } + } + } + } + + // 4) Generate PrintObjects from ModelObjects and their instances. + bool print_regions_reshuffled = false; + { + PrintObjectPtrs print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + bool new_objects = false; + // Walk over all new model objects and check, whether there are matching PrintObjects. + for (ModelObject *model_object : m_model.objects) { + auto range = print_object_status.equal_range(PrintObjectStatus(model_object->id())); + std::vector old; + if (range.first != range.second) { + old.reserve(print_object_status.count(PrintObjectStatus(model_object->id()))); + for (auto it = range.first; it != range.second; ++ it) + if (it->status != PrintObjectStatus::Deleted) + old.emplace_back(&(*it)); + } + // Generate a list of trafos and XY offsets for instances of a ModelObject + // Producing the config for PrintObject on demand, caching it at print_object_last. + const PrintObject *print_object_last = nullptr; + auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject* print_object) { + print_object->config_apply(print_object_last ? + print_object_last->config() : + PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); + print_object_last = print_object; + }; + std::vector new_print_instances = print_objects_from_model_object(*model_object); + if (old.empty()) { + // Simple case, just generate new instances. + for (PrintObjectTrafoAndInstances &print_instances : new_print_instances) { + PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); + print_object_apply_config(print_object); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + } + continue; + } + // Complex case, try to merge the two lists. + // Sort the old lexicographically by their trafos. + std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); + // Merge the old / new lists. + auto it_old = old.begin(); + for (PrintObjectTrafoAndInstances &new_instances : new_print_instances) { + for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); + if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { + // This is a new instance (or a set of instances with the same trafo). Just add it. + PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); + print_object_apply_config(print_object); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + if (it_old != old.end()) + const_cast(*it_old)->status = PrintObjectStatus::Deleted; + } else { + // The PrintObject already exists and the copies differ. + PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); + if (status != PrintBase::APPLY_STATUS_UNCHANGED) + update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); + print_objects_new.emplace_back((*it_old)->print_object); + const_cast(*it_old)->status = PrintObjectStatus::Reused; + } + } + } + if (m_objects != print_objects_new) { + this->call_cancel_callback(); + update_apply_status(this->invalidate_all_steps()); + m_objects = print_objects_new; + // Delete the PrintObjects marked as Unknown or Deleted. + bool deleted_objects = false; + for (auto &pos : print_object_status) + if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { + update_apply_status(pos.print_object->invalidate_all_steps()); + delete pos.print_object; + deleted_objects = true; + } + if (new_objects || deleted_objects) + update_apply_status(this->invalidate_steps({ psSkirt, psBrim, psWipeTower, psGCodeExport })); + if (new_objects) + update_apply_status(false); + print_regions_reshuffled = true; + } + print_object_status.clear(); + } + + // All regions now have distinct settings. + // Check whether applying the new region config defaults we'd get different regions. + for (PrintObject *print_object : m_objects) { + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } + bool some_object_region_modified = false; + bool regions_merged = false; + for (size_t region_id = 0; region_id < print_object->m_region_volumes.size(); ++ region_id) { + PrintRegion ®ion = *print_object->m_all_regions[region_id]; + PrintRegionConfig region_config; + bool region_config_set = false; + for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : print_object->m_region_volumes[region_id].volumes) { + const ModelVolume &volume = *print_object->model_object()->volumes[volume_w_zrange.volume_idx]; + const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_w_zrange.layer_height_range); + PrintRegionConfig this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); + if (region_config_set) { + if (this_region_config != region_config) { + regions_merged = true; + break; + } + } else { + region_config = std::move(this_region_config); + region_config_set = true; + } + } + if (regions_merged) + break; + size_t region_config_hash = region_config.hash(); + bool modified = region.config_hash() != region_config_hash || region.config() != region_config; + some_object_region_modified |= modified; + if (some_object_region_modified) + // Verify whether this region was not merged with some other region. + for (size_t i = 0; i < region_id; ++ i) { + const PrintRegion ®ion_other = *print_object->m_all_regions[i]; + if (region_other.config_hash() == region_config_hash && region_other.config() == region_config) { + // Regions were merged. Reset this print_object. + regions_merged = true; + break; + } + } + if (modified) { + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.config().diff(region_config); + update_apply_status(print_object->invalidate_state_by_config_options(region.config(), region_config, diff)); + region.config_apply_only(region_config, diff, false); + } + } + if (regions_merged) { + // Two regions of a single object were either split or merged. This invalidates the whole slicing. + update_apply_status(print_object->invalidate_all_steps()); + print_object->m_region_volumes.clear(); + } + } + + // Possibly add new regions for the newly added or resetted PrintObjects. + for (size_t idx_print_object = 0; idx_print_object < m_objects.size();) { + PrintObject &print_object0 = *m_objects[idx_print_object]; + const ModelObject &model_object = *print_object0.model_object(); + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } + if (print_object0.m_region_volumes.empty()) { + // Fresh or completely invalidated print_object. Assign regions. + unsigned int volume_id = 0; + for (const ModelVolume *volume : model_object.volumes) { + if (! volume->is_model_part() && ! volume->is_modifier()) { + ++ volume_id; + continue; + } + // Filter the layer ranges, so they do not overlap and they contain at least a single layer. + // Now insert a volume with a layer range to its own region. + for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { + int region_id = -1; + // Get the config applied to this volume. + PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); + size_t hash = config.hash(); + for (size_t i = 0; i < print_object0.m_all_regions.size(); ++ i) + if (hash == print_object0.m_all_regions[i]->config_hash() && config == *print_object0.m_all_regions[i]) { + region_id = int(i); + break; + } + // If no region exists with the same config, create a new one. + if (region_id == -1) { + region_id = int(print_object0.m_all_regions.size()); + print_object0.m_all_regions.emplace_back(std::make_unique(std::move(config), hash)); + } + print_object0.add_region_volume(region_id, volume_id, it_range->first); + } + ++ volume_id; + } + print_regions_reshuffled = true; + } + for (++ idx_print_object; idx_print_object < m_objects.size() && m_objects[idx_print_object]->model_object() == &model_object; ++ idx_print_object) { + PrintObject &print_object = *m_objects[idx_print_object]; + if (print_object.m_region_volumes.empty()) { + // Copy region volumes and regions from print_object0. + print_object.m_region_volumes = print_object0.m_region_volumes; + print_object.m_all_regions.reserve(print_object0.m_all_regions.size()); + for (const std::unique_ptr ®ion : print_object0.m_all_regions) + print_object.m_all_regions.emplace_back(std::make_unique(*region)); + print_regions_reshuffled = true; + } + } + } + + if (print_regions_reshuffled) { + // Update Print::m_print_regions from objects. + struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } }; + std::set region_set; + m_print_regions.clear(); + for (PrintObject *print_object : m_objects) + for (std::unique_ptr &print_region : print_object->m_all_regions) + if (auto it = region_set.find(print_region.get()); it == region_set.end()) { + int print_region_id = int(m_print_regions.size()); + m_print_regions.emplace_back(print_region.get()); + print_region->m_print_region_id = print_region_id; + } else { + print_region->m_print_region_id = (*it)->print_region_id(); + } + } + + // Update SlicingParameters for each object where the SlicingParameters is not valid. + // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use + // (posSlicing and posSupportMaterial was invalidated). + for (PrintObject *object : m_objects) + object->update_slicing_parameters(); + +#ifdef _DEBUG + check_model_ids_equal(m_model, model); +#endif /* _DEBUG */ + + return static_cast(apply_status); +} + +} // namespace Slic3r diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f2c5f70a97..72722e6fc4 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,152 @@ namespace Slic3r { #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) +static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &enum_keys_map) +{ + t_config_enum_names names; + int cnt = 0; + for (const auto& kvp : enum_keys_map) + cnt = std::max(cnt, kvp.second); + cnt += 1; + names.assign(cnt, ""); + for (const auto& kvp : enum_keys_map) + names[kvp.second] = kvp.first; + return names; +} + +#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \ + static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \ + template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ + template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } + +static t_config_enum_values s_keys_map_PrinterTechnology { + { "FFF", ptFFF }, + { "SLA", ptSLA } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) + +static t_config_enum_values s_keys_map_GCodeFlavor { + { "reprap", gcfRepRapSprinter }, + { "reprapfirmware", gcfRepRapFirmware }, + { "repetier", gcfRepetier }, + { "teacup", gcfTeacup }, + { "makerware", gcfMakerWare }, + { "marlin", gcfMarlinLegacy }, + { "marlinfirmware", gcfMarlinFirmware }, + { "sailfish", gcfSailfish }, + { "smoothie", gcfSmoothie }, + { "mach3", gcfMach3 }, + { "machinekit", gcfMachinekit }, + { "no-extrusion", gcfNoExtrusion } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) + +static t_config_enum_values s_keys_map_MachineLimitsUsage { + { "emit_to_gcode", int(MachineLimitsUsage::EmitToGCode) }, + { "time_estimate_only", int(MachineLimitsUsage::TimeEstimateOnly) }, + { "ignore", int(MachineLimitsUsage::Ignore) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) + +static t_config_enum_values s_keys_map_PrintHostType { + { "octoprint", htOctoPrint }, + { "duet", htDuet }, + { "flashair", htFlashAir }, + { "astrobox", htAstroBox }, + { "repetier", htRepetier } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) + +static t_config_enum_values s_keys_map_AuthorizationType { + { "key", atKeyPassword }, + { "user", atUserPassword } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) + +static t_config_enum_values s_keys_map_FuzzySkinType { + { "none", int(FuzzySkinType::None) }, + { "external", int(FuzzySkinType::External) }, + { "all", int(FuzzySkinType::All) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) + +static t_config_enum_values s_keys_map_InfillPattern { + { "rectilinear", ipRectilinear }, + { "monotonic", ipMonotonic }, + { "alignedrectilinear", ipAlignedRectilinear }, + { "grid", ipGrid }, + { "triangles", ipTriangles }, + { "stars", ipStars }, + { "cubic", ipCubic }, + { "line", ipLine }, + { "concentric", ipConcentric }, + { "honeycomb", ipHoneycomb }, + { "3dhoneycomb", ip3DHoneycomb }, + { "gyroid", ipGyroid }, + { "hilbertcurve", ipHilbertCurve }, + { "archimedeanchords", ipArchimedeanChords }, + { "octagramspiral", ipOctagramSpiral }, + { "adaptivecubic", ipAdaptiveCubic }, + { "supportcubic", ipSupportCubic } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) + +static t_config_enum_values s_keys_map_IroningType { + { "top", int(IroningType::TopSurfaces) }, + { "topmost", int(IroningType::TopmostOnly) }, + { "solid", int(IroningType::AllSolid) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType) + +static t_config_enum_values s_keys_map_SupportMaterialPattern { + { "rectilinear", smpRectilinear }, + { "rectilinear-grid", smpRectilinearGrid }, + { "honeycomb", smpHoneycomb } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) + +static t_config_enum_values s_keys_map_SupportMaterialStyle { + { "grid", smsGrid }, + { "snug", smsSnug } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) + +static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { + { "auto", smipAuto }, + { "rectilinear", smipRectilinear }, + { "concentric", smipConcentric } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern) + +static t_config_enum_values s_keys_map_SeamPosition { + { "random", spRandom }, + { "nearest", spNearest }, + { "aligned", spAligned }, + { "rear", spRear } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SeamPosition) + +static const t_config_enum_values s_keys_map_SLADisplayOrientation = { + { "landscape", sladoLandscape}, + { "portrait", sladoPortrait} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLADisplayOrientation) + +static const t_config_enum_values s_keys_map_SLAPillarConnectionMode = { + {"zigzag", slapcmZigZag}, + {"cross", slapcmCross}, + {"dynamic", slapcmDynamic} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAPillarConnectionMode) + +static const t_config_enum_values s_keys_map_BrimType = { + {"no_brim", btNoBrim}, + {"outer_only", btOuterOnly}, + {"inner_only", btInnerOnly}, + {"outer_and_inner", btOuterAndInner} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType) + static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -3790,19 +3937,21 @@ std::string validate(const FullPrintConfig &cfg) return ""; } -// Declare the static caches for each StaticPrintConfig derived class. -StaticPrintConfig::StaticCache PrintObjectConfig::s_cache_PrintObjectConfig; -StaticPrintConfig::StaticCache PrintRegionConfig::s_cache_PrintRegionConfig; -StaticPrintConfig::StaticCache MachineEnvelopeConfig::s_cache_MachineEnvelopeConfig; -StaticPrintConfig::StaticCache GCodeConfig::s_cache_GCodeConfig; -StaticPrintConfig::StaticCache PrintConfig::s_cache_PrintConfig; -StaticPrintConfig::StaticCache FullPrintConfig::s_cache_FullPrintConfig; - -StaticPrintConfig::StaticCache SLAMaterialConfig::s_cache_SLAMaterialConfig; -StaticPrintConfig::StaticCache SLAPrintConfig::s_cache_SLAPrintConfig; -StaticPrintConfig::StaticCache SLAPrintObjectConfig::s_cache_SLAPrintObjectConfig; -StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; -StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; +// Declare and initialize static caches of StaticPrintConfig derived classes. +#define PRINT_CONFIG_CACHE_ELEMENT_DEFINITION(r, data, CLASS_NAME) StaticPrintConfig::StaticCache BOOST_PP_CAT(CLASS_NAME::s_cache_, CLASS_NAME); +#define PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION(r, data, CLASS_NAME) Slic3r::CLASS_NAME::initialize_cache(); +#define PRINT_CONFIG_CACHE_INITIALIZE(CLASSES_SEQ) \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_DEFINITION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ + int print_config_static_initializer() { \ + /* Putting a trace here to avoid the compiler to optimize out this function. */ \ + BOOST_LOG_TRIVIAL(trace) << "Initializing StaticPrintConfigs"; \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CACHE_ELEMENT_INITIALIZATION, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_SEQ)) \ + return 1; \ + } +PRINT_CONFIG_CACHE_INITIALIZE(( + PrintObjectConfig, PrintRegionConfig, MachineEnvelopeConfig, GCodeConfig, PrintConfig, FullPrintConfig, + SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig)) +static int print_config_static_initialized = print_config_static_initializer(); CLIActionsConfigDef::CLIActionsConfigDef() { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 7209bea899..2ba6d4cbd5 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -111,178 +111,27 @@ enum BrimType { btOuterAndInner, }; -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["FFF"] = ptFFF; - keys_map["SLA"] = ptSLA; - } - return keys_map; -} +#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ + template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ + template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["reprap"] = gcfRepRapSprinter; - keys_map["reprapfirmware"] = gcfRepRapFirmware; - keys_map["repetier"] = gcfRepetier; - keys_map["teacup"] = gcfTeacup; - keys_map["makerware"] = gcfMakerWare; - keys_map["marlin"] = gcfMarlinLegacy; - keys_map["marlinfirmware"] = gcfMarlinFirmware; - keys_map["sailfish"] = gcfSailfish; - keys_map["smoothie"] = gcfSmoothie; - keys_map["mach3"] = gcfMach3; - keys_map["machinekit"] = gcfMachinekit; - keys_map["no-extrusion"] = gcfNoExtrusion; - } - return keys_map; -} +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(IroningType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialPattern) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialStyle) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportMaterialInterfacePattern) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["emit_to_gcode"] = int(MachineLimitsUsage::EmitToGCode); - keys_map["time_estimate_only"] = int(MachineLimitsUsage::TimeEstimateOnly); - keys_map["ignore"] = int(MachineLimitsUsage::Ignore); - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["octoprint"] = htOctoPrint; - keys_map["duet"] = htDuet; - keys_map["flashair"] = htFlashAir; - keys_map["astrobox"] = htAstroBox; - keys_map["repetier"] = htRepetier; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["key"] = atKeyPassword; - keys_map["user"] = atUserPassword; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["none"] = int(FuzzySkinType::None); - keys_map["external"] = int(FuzzySkinType::External); - keys_map["all"] = int(FuzzySkinType::All); - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["rectilinear"] = ipRectilinear; - keys_map["monotonic"] = ipMonotonic; - keys_map["alignedrectilinear"] = ipAlignedRectilinear; - keys_map["grid"] = ipGrid; - keys_map["triangles"] = ipTriangles; - keys_map["stars"] = ipStars; - keys_map["cubic"] = ipCubic; - keys_map["line"] = ipLine; - keys_map["concentric"] = ipConcentric; - keys_map["honeycomb"] = ipHoneycomb; - keys_map["3dhoneycomb"] = ip3DHoneycomb; - keys_map["gyroid"] = ipGyroid; - keys_map["hilbertcurve"] = ipHilbertCurve; - keys_map["archimedeanchords"] = ipArchimedeanChords; - keys_map["octagramspiral"] = ipOctagramSpiral; - keys_map["adaptivecubic"] = ipAdaptiveCubic; - keys_map["supportcubic"] = ipSupportCubic; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["top"] = int(IroningType::TopSurfaces); - keys_map["topmost"] = int(IroningType::TopmostOnly); - keys_map["solid"] = int(IroningType::AllSolid); - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["rectilinear"] = smpRectilinear; - keys_map["rectilinear-grid"] = smpRectilinearGrid; - keys_map["honeycomb"] = smpHoneycomb; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["grid"] = smsGrid; - keys_map["snug"] = smsSnug; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["auto"] = smipAuto; - keys_map["rectilinear"] = smipRectilinear; - keys_map["concentric"] = smipConcentric; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static t_config_enum_values keys_map; - if (keys_map.empty()) { - keys_map["random"] = spRandom; - keys_map["nearest"] = spNearest; - keys_map["aligned"] = spAligned; - keys_map["rear"] = spRear; - } - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static const t_config_enum_values keys_map = { - { "landscape", sladoLandscape}, - { "portrait", sladoPortrait} - }; - - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static const t_config_enum_values keys_map = { - {"zigzag", slapcmZigZag}, - {"cross", slapcmCross}, - {"dynamic", slapcmDynamic} - }; - - return keys_map; -} - -template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { - static const t_config_enum_values keys_map = { - {"no_brim", btNoBrim}, - {"outer_only", btOuterOnly}, - {"inner_only", btInnerOnly}, - {"outer_and_inner", btOuterAndInner} - }; - - return keys_map; -} +#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Does not store the actual values, but defines default values. @@ -459,10 +308,12 @@ public: \ /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \ const t_config_option_keys& keys_ref() const override { return s_cache_##CLASS_NAME.keys(); } \ - static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \ + static const CLASS_NAME& defaults() { assert(s_cache_##CLASS_NAME.initialized()); return s_cache_##CLASS_NAME.defaults(); } \ private: \ + friend int print_config_static_initializer(); \ static void initialize_cache() \ { \ + assert(! s_cache_##CLASS_NAME.initialized()); \ if (! s_cache_##CLASS_NAME.initialized()) { \ CLASS_NAME *inst = new CLASS_NAME(1); \ inst->initialize(s_cache_##CLASS_NAME, (const char*)inst); \ @@ -476,7 +327,7 @@ private: \ STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ public: \ /* Public default constructor will initialize the key/option cache and the default object copy if needed. */ \ - CLASS_NAME() { initialize_cache(); *this = s_cache_##CLASS_NAME.defaults(); } \ + CLASS_NAME() { assert(s_cache_##CLASS_NAME.initialized()); *this = s_cache_##CLASS_NAME.defaults(); } \ protected: \ /* Protected constructor to be called when compounded. */ \ CLASS_NAME(int) {} @@ -534,7 +385,7 @@ protected: \ #define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_REGISTRATION, PARAMETER_HASHES, PARAMETER_EQUALS) \ class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) { \ STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ - CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) { initialize_cache(); *this = s_cache_##CLASS_NAME.defaults(); } \ + CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) { assert(s_cache_##CLASS_NAME.initialized()); *this = s_cache_##CLASS_NAME.defaults(); } \ public: \ PARAMETER_DEFINITION \ size_t hash() const throw() \ diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8445855586..8fefd4beb8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -97,6 +97,15 @@ PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) return status; } +std::vector> PrintObject::all_regions() const +{ + std::vector> out; + out.reserve(m_all_regions.size()); + for (size_t i = 0; i < m_all_regions.size(); ++ i) + out.emplace_back(*m_all_regions[i]); + return out; +} + // Called by make_perimeters() // 1) Decides Z positions of the layers, // 2) Initializes layers and their regions @@ -173,8 +182,8 @@ void PrintObject::make_perimeters() // but we don't generate any extra perimeter if fill density is zero, as they would be floating // inside the object - infill_only_where_needed should be the method of choice for printing // hollow objects - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = this->printing_region(region_id); if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2) continue; @@ -294,7 +303,7 @@ void PrintObject::prepare_infill() // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final"); @@ -313,7 +322,7 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final"); @@ -332,7 +341,7 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final"); @@ -351,7 +360,7 @@ void PrintObject::prepare_infill() m_print->throw_if_canceled(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("9_prepare_infill-final"); @@ -716,10 +725,10 @@ bool PrintObject::invalidate_step(PrintObjectStep step) } else if (step == posSlice) { invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial }); invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); - this->m_slicing_params.valid = false; + m_slicing_params.valid = false; } else if (step == posSupportMaterial) { invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); - this->m_slicing_params.valid = false; + m_slicing_params.valid = false; } // Wipe tower depends on the ordering of extruders, which in turn depends on everything. @@ -736,19 +745,11 @@ bool PrintObject::invalidate_all_steps() // First call the "invalidate" functions, which may cancel background processing. bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); // Then reset some of the depending values. - this->m_slicing_params.valid = false; - this->region_volumes.clear(); + m_slicing_params.valid = false; + m_region_volumes.clear(); return result; } -static const PrintRegion* first_printing_region(const PrintObject &print_object) -{ - for (size_t idx_region = 0; idx_region < print_object.region_volumes.size(); ++ idx_region) - if (!print_object.region_volumes.empty()) - return print_object.print()->regions()[idx_region]; - return nullptr; -} - // This function analyzes slices of a region (SurfaceCollection slices). // Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. // Initially all slices are of type stInternal. @@ -769,13 +770,13 @@ void PrintObject::detect_surfaces_type() // should be visible. bool spiral_vase = this->print()->config().spiral_vase.value; bool interface_shells = ! spiral_vase && m_config.interface_shells.value; - size_t num_layers = spiral_vase ? std::min(size_t(first_printing_region(*this)->config().bottom_solid_layers), m_layers.size()) : m_layers.size(); + size_t num_layers = spiral_vase ? std::min(size_t(this->printing_region(0).config().bottom_solid_layers), m_layers.size()) : m_layers.size(); - for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { - BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : m_layers) - layer->m_regions[idx_region]->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-initial"); + layer->m_regions[region_id]->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // If interface shells are allowed, the region->surfaces cannot be overwritten as they may be used by other threads. @@ -791,7 +792,7 @@ void PrintObject::detect_surfaces_type() ((num_layers > 1) ? num_layers - 1 : num_layers) : // In non-spiral vase mode, go over all layers. m_layers.size()), - [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range& range) { + [this, region_id, interface_shells, &surfaces_new](const tbb::blocked_range& range) { // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating // the support from the print. SurfaceType surface_type_bottom_other = @@ -799,9 +800,9 @@ void PrintObject::detect_surfaces_type() stBottom : stBottomBridge; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); - // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z; + // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << region_id << " and layer " << layer->print_z; Layer *layer = m_layers[idx_layer]; - LayerRegion *layerm = layer->m_regions[idx_region]; + LayerRegion *layerm = layer->m_regions[region_id]; // comparison happens against the *full* slices (considering all regions) // unless internal shells are requested Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? m_layers[idx_layer + 1] : nullptr; @@ -814,8 +815,8 @@ void PrintObject::detect_surfaces_type() Surfaces top; if (upper_layer) { ExPolygons upper_slices = interface_shells ? - diff_ex(layerm->slices.surfaces, upper_layer->m_regions[idx_region]->slices.surfaces, true) : - diff_ex(layerm->slices.surfaces, upper_layer->lslices, true); + diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : + diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); surfaces_append(top, offset2_ex(upper_slices, -offset, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid @@ -831,7 +832,7 @@ void PrintObject::detect_surfaces_type() #if 0 //FIXME Why is this branch failing t\multi.t ? Polygons lower_slices = interface_shells ? - to_polygons(lower_layer->get_region(idx_region)->slices.surfaces) : + to_polygons(lower_layer->get_region(region_id)->slices.surfaces) : to_polygons(lower_layer->slices); surfaces_append(bottom, offset2_ex(diff(layerm->slices.surfaces, lower_slices, true), -offset, offset), @@ -841,7 +842,7 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, offset2_ex( - diff_ex(layerm->slices.surfaces, lower_layer->lslices, true), + diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), -offset, offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -854,8 +855,8 @@ void PrintObject::detect_surfaces_type() offset2_ex( diff_ex( intersection(layerm->slices.surfaces, lower_layer->lslices), // supported - lower_layer->m_regions[idx_region]->slices.surfaces, - true), + lower_layer->m_regions[region_id]->slices.surfaces, + ApplySafetyOffset::Yes), -offset, offset), stBottom); } @@ -877,9 +878,7 @@ void PrintObject::detect_surfaces_type() // if $Slic3r::debug; Polygons top_polygons = to_polygons(std::move(top)); top.clear(); - surfaces_append(top, - diff_ex(top_polygons, bottom, false), - stTop); + surfaces_append(top, diff_ex(top_polygons, bottom), stTop); } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -889,7 +888,7 @@ void PrintObject::detect_surfaces_type() expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green"))); expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown"))); expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices.surfaces), SVG::ExPolygonAttributes("black"))); - SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, idx_region, layer->print_z).c_str(), expolygons_with_attributes); + SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, region_id, layer->print_z).c_str(), expolygons_with_attributes); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ @@ -906,7 +905,7 @@ void PrintObject::detect_surfaces_type() { Polygons topbottom = to_polygons(top); polygons_append(topbottom, to_polygons(bottom)); - surfaces_append(surfaces_out, diff_ex(surfaces_prev, topbottom, false), stInternal); + surfaces_append(surfaces_out, diff_ex(surfaces_prev, topbottom), stInternal); } surfaces_append(surfaces_out, std::move(top)); @@ -926,25 +925,25 @@ void PrintObject::detect_surfaces_type() if (interface_shells) { // Move surfaces_new to layerm->slices.surfaces for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) - m_layers[idx_layer]->m_regions[idx_region]->slices.surfaces = std::move(surfaces_new[idx_layer]); + m_layers[idx_layer]->m_regions[region_id]->slices.surfaces = std::move(surfaces_new[idx_layer]); } if (spiral_vase) { if (num_layers > 1) // Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern. - m_layers[num_layers - 1]->m_regions[idx_region]->slices.set_type(stTop); + m_layers[num_layers - 1]->m_regions[region_id]->slices.set_type(stTop); for (size_t i = num_layers; i < m_layers.size(); ++ i) - m_layers[i]->m_regions[idx_region]->slices.set_type(stInternal); + m_layers[i]->m_regions[region_id]->slices.set_type(stInternal); } - BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - start"; + BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start"; // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this, idx_region](const tbb::blocked_range& range) { + [this, region_id](const tbb::blocked_range& range) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); - LayerRegion *layerm = m_layers[idx_layer]->m_regions[idx_region]; + LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id]; layerm->slices_to_fill_surfaces_clipped(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final"); @@ -952,7 +951,7 @@ void PrintObject::detect_surfaces_type() } // for each layer of a region }); m_print->throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end"; } // for each this->print->region_count // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) @@ -968,8 +967,8 @@ void PrintObject::process_external_surfaces() // Is there any printing region, that has zero infill? If so, then we don't want the expansion to be performed over the complete voids, but only // over voids, which are supported by the layer below. bool has_voids = false; - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) - if (! this->region_volumes.empty() && this->print()->regions()[region_id]->config().fill_density == 0) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) + if (this->printing_region(region_id).config().fill_density == 0) { has_voids = true; break; } @@ -1005,12 +1004,12 @@ void PrintObject::process_external_surfaces() m_print->throw_if_canceled(); Polygons voids; for (const LayerRegion *layerm : m_layers[layer_idx]->regions()) { - if (layerm->region()->config().fill_density.value == 0.) + if (layerm->region().config().fill_density.value == 0.) for (const Surface &surface : layerm->fill_surfaces.surfaces) // Shrink the holes, let the layer above expand slightly inside the unsupported areas. polygons_append(voids, offset(surface.expolygon, unsupported_width)); } - surfaces_covered[layer_idx] = diff(this->m_layers[layer_idx]->lslices, voids); + surfaces_covered[layer_idx] = diff(m_layers[layer_idx]->lslices, voids); } } ); @@ -1018,7 +1017,7 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end"; } - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -1026,7 +1025,7 @@ void PrintObject::process_external_surfaces() for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z; - m_layers[layer_idx]->get_region((int)region_id)->process_external_surfaces( + m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces( (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); } @@ -1051,7 +1050,7 @@ void PrintObject::discover_vertical_shells() Polygons holes; }; bool spiral_vase = this->print()->config().spiral_vase.value; - size_t num_layers = spiral_vase ? std::min(size_t(first_printing_region(*this)->config().bottom_solid_layers), m_layers.size()) : m_layers.size(); + size_t num_layers = spiral_vase ? std::min(size_t(this->printing_region(0).config().bottom_solid_layers), m_layers.size()) : m_layers.size(); coordf_t min_layer_height = this->slicing_parameters().min_layer_height; // Does this region possibly produce more than 1 top or bottom layer? auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) { @@ -1066,14 +1065,14 @@ void PrintObject::discover_vertical_shells() num_extra_layers(config.bottom_solid_layers, config.bottom_solid_min_thickness) > 0; }; std::vector cache_top_botom_regions(num_layers, DiscoverVerticalShellsCacheEntry()); - bool top_bottom_surfaces_all_regions = this->region_volumes.size() > 1 && ! m_config.interface_shells.value; + bool top_bottom_surfaces_all_regions = this->num_printing_regions() > 1 && ! m_config.interface_shells.value; if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. // Is the "ensure vertical wall thickness" applicable to any region? bool has_extra_layers = false; - for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++idx_region) { - const PrintRegionConfig &config = m_print->get_region(idx_region)->config(); + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + const PrintRegionConfig &config = this->printing_region(region_id).config(); if (config.ensure_vertical_shell_thickness.value && has_extra_layers_fn(config)) { has_extra_layers = true; break; @@ -1089,7 +1088,7 @@ void PrintObject::discover_vertical_shells() tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; - const size_t num_regions = this->region_volumes.size(); + const size_t num_regions = this->num_printing_regions(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); const Layer &layer = *m_layers[idx_layer]; @@ -1101,8 +1100,8 @@ void PrintObject::discover_vertical_shells() static size_t debug_idx = 0; ++ debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - for (size_t idx_region = 0; idx_region < num_regions; ++ idx_region) { - LayerRegion &layerm = *layer.m_regions[idx_region]; + for (size_t region_id = 0; region_id < num_regions; ++ region_id) { + LayerRegion &layerm = *layer.m_regions[region_id]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing)); @@ -1115,7 +1114,7 @@ void PrintObject::discover_vertical_shells() unsigned int perimeters = 0; for (Surface &s : layerm.slices.surfaces) perimeters = std::max(perimeters, s.extra_perimeters); - perimeters += layerm.region()->config().perimeters.value; + perimeters += layerm.region().config().perimeters.value; // Then calculate the infill offset. if (perimeters > 0) { Flow extflow = layerm.flow(frExternalPerimeter); @@ -1127,8 +1126,8 @@ void PrintObject::discover_vertical_shells() polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); } // Save some computing time by reducing the number of polygons. - cache.top_surfaces = union_(cache.top_surfaces, false); - cache.bottom_surfaces = union_(cache.bottom_surfaces, false); + cache.top_surfaces = union_(cache.top_surfaces); + cache.bottom_surfaces = union_(cache.bottom_surfaces); // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.lslices are forced to merge by expanding them first. @@ -1143,17 +1142,17 @@ void PrintObject::discover_vertical_shells() } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } - cache.holes = union_(cache.holes, false); + cache.holes = union_(cache.holes); } }); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } - for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { PROFILE_BLOCK(discover_vertical_shells_region); - const PrintRegion ®ion = *m_print->get_region(idx_region); + const PrintRegion ®ion = this->printing_region(region_id); if (! region.config().ensure_vertical_shell_thickness.value) // This region will be handled by discover_horizontal_shells(). continue; @@ -1167,15 +1166,15 @@ void PrintObject::discover_vertical_shells() if (! top_bottom_surfaces_all_regions) { // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness // is calculated over a single material. - BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom"; + BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom"; tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), - [this, idx_region, &cache_top_botom_regions](const tbb::blocked_range& range) { + [this, region_id, &cache_top_botom_regions](const tbb::blocked_range& range) { const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); Layer &layer = *m_layers[idx_layer]; - LayerRegion &layerm = *layer.m_regions[idx_region]; + LayerRegion &layerm = *layer.m_regions[region_id]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; @@ -1184,21 +1183,21 @@ void PrintObject::discover_vertical_shells() // Bottom surfaces. cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); - // Holes over all regions. Only collect them once, they are valid for all idx_region iterations. + // Holes over all regions. Only collect them once, they are valid for all region_id iterations. if (cache.holes.empty()) { - for (size_t idx_region = 0; idx_region < layer.regions().size(); ++ idx_region) - polygons_append(cache.holes, to_polygons(layer.regions()[idx_region]->fill_expolygons)); + for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) + polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons)); } } }); m_print->throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - end : cache top / bottom"; + BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom"; } - BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness"; + BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness"; tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), - [this, idx_region, &cache_top_botom_regions] + [this, region_id, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { @@ -1210,8 +1209,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Layer *layer = m_layers[idx_layer]; - LayerRegion *layerm = layer->m_regions[idx_region]; - const PrintRegionConfig ®ion_config = layerm->region()->config(); + LayerRegion *layerm = layer->m_regions[region_id]; + const PrintRegionConfig ®ion_config = layerm->region().config(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); @@ -1267,7 +1266,7 @@ void PrintObject::discover_vertical_shells() polygons_append(shell, cache.top_surfaces); // Running the union_ using the Clipper library piece by piece is cheaper // than running the union_ all at once. - shell = union_(shell, false); + shell = union_(shell); } } } @@ -1286,7 +1285,7 @@ void PrintObject::discover_vertical_shells() polygons_append(shell, cache.bottom_surfaces); // Running the union_ using the Clipper library piece by piece is cheaper // than running the union_ all at once. - shell = union_(shell, false); + shell = union_(shell); } } } @@ -1306,7 +1305,7 @@ void PrintObject::discover_vertical_shells() } #endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - shell_ex = union_ex(shell, true); + shell_ex = union_safety_offset_ex(shell); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } @@ -1352,7 +1351,7 @@ void PrintObject::discover_vertical_shells() // Trim the shells region by the internal & internal void surfaces. const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); - shell = intersection(shell, polygonsInternal, true); + shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) continue; @@ -1390,14 +1389,14 @@ void PrintObject::discover_vertical_shells() polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal)); } #endif - ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell, false); + ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); // Source shell. - svg.draw(union_ex(shell_before, true)); + svg.draw(union_safety_offset_ex(shell_before)); // Shell trimmed to the internal surfaces. - svg.draw_outline(union_ex(shell, true), "black", "blue", scale_(0.05)); + svg.draw_outline(union_safety_offset_ex(shell), "black", "blue", scale_(0.05)); // Regularized infill region. svg.draw_outline(new_internal_solid, "red", "magenta", scale_(0.05)); svg.Close(); @@ -1425,11 +1424,11 @@ void PrintObject::discover_vertical_shells() } // for each layer }); m_print->throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++idx_layer) { - LayerRegion *layerm = m_layers[idx_layer]->get_region(idx_region); + LayerRegion *layerm = m_layers[idx_layer]->get_region(region_id); layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-final"); layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-final"); } @@ -1447,8 +1446,8 @@ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = this->printing_region(region_id); // skip bridging in case there are no voids if (region.config().fill_density.value == 100) @@ -1511,8 +1510,8 @@ void PrintObject::bridge_over_infill() #endif // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, true); - to_bridge = intersection_ex(to_bridge, internal_solid, true); + ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); + to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); // build the new collection of fill_surfaces layerm->fill_surfaces.remove_type(stInternalSolid); for (ExPolygon &ex : to_bridge) @@ -1664,6 +1663,7 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full object_extruders); } sort_remove_duplicates(object_extruders); + //FIXME add painting extruders if (object_max_z <= 0.f) object_max_z = (float)model_object.raw_bounding_box().size().z(); @@ -1674,10 +1674,9 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full std::vector PrintObject::object_extruders() const { std::vector extruders; - extruders.reserve(this->region_volumes.size() * 3); - for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) - if (! this->region_volumes[idx_region].empty()) - m_print->get_region(idx_region)->collect_object_printing_extruders(extruders); + extruders.reserve(this->all_regions().size() * 3); + for (const PrintRegion ®ion : this->all_regions()) + region.collect_object_printing_extruders(*this->print(), extruders); sort_remove_duplicates(extruders); return extruders; } @@ -1745,8 +1744,8 @@ void PrintObject::_slice(const std::vector &layer_height_profile) layer->lower_layer = prev; } // Make sure all layers contain layer region objects for all regions. - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) - layer->add_region(this->print()->get_region(region_id)); + for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) + layer->add_region(&this->print()->get_print_region(region_id)); prev = layer; } } @@ -1756,16 +1755,15 @@ void PrintObject::_slice(const std::vector &layer_height_profile) bool has_z_ranges = false; size_t num_volumes = 0; size_t num_modifiers = 0; - for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { + for (int region_id = 0; region_id < int(m_region_volumes.size()); ++ region_id) { int last_volume_id = -1; - for (const std::pair &volume_and_range : this->region_volumes[region_id]) { - const int volume_id = volume_and_range.second; - const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : m_region_volumes[region_id].volumes) { + const ModelVolume *model_volume = this->model_object()->volumes[volume_w_zrange.volume_idx]; if (model_volume->is_model_part()) { - if (last_volume_id == volume_id) { + if (last_volume_id == volume_w_zrange.volume_idx) { has_z_ranges = true; } else { - last_volume_id = volume_id; + last_volume_id = volume_w_zrange.volume_idx; if (all_volumes_single_region == -2) // first model volume met all_volumes_single_region = region_id; @@ -1788,14 +1786,14 @@ void PrintObject::_slice(const std::vector &layer_height_profile) if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { // Cheap path: Slice regions without mutual clipping. // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; // slicing in parallel size_t slicing_mode_normal_below_layer = 0; if (spiral_vase) { // Slice the bottom layers with SlicingMode::Regular. // This needs to be in sync with LayerRegion::make_perimeters() spiral_vase! - const PrintRegionConfig &config = this->print()->regions()[region_id]->config(); + const PrintRegionConfig &config = this->print()->get_print_region(region_id).config(); slicing_mode_normal_below_layer = size_t(config.bottom_solid_layers.value); for (; slicing_mode_normal_below_layer < slice_zs.size() && slice_zs[slicing_mode_normal_below_layer] < config.bottom_solid_min_thickness - EPSILON; ++ slicing_mode_normal_below_layer); @@ -1821,22 +1819,22 @@ void PrintObject::_slice(const std::vector &layer_height_profile) }; std::vector sliced_volumes; sliced_volumes.reserve(num_volumes); - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; - for (size_t i = 0; i < volumes_and_ranges.size(); ) { - int volume_id = volumes_and_ranges[i].second; + for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) { + const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) { + int volume_id = volumes_and_ranges.volumes[i].volume_idx; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id; // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. std::vector ranges; - ranges.emplace_back(volumes_and_ranges[i].first); + ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range); size_t j = i + 1; - for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) - if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) - ranges.back().second = volumes_and_ranges[j].first.second; + for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges.volumes[j].layer_height_range.first) < EPSILON) + ranges.back().second = volumes_and_ranges.volumes[j].layer_height_range.second; else - ranges.emplace_back(volumes_and_ranges[j].first); + ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); // slicing in parallel sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, slicing_mode, *model_volume)); i = j; @@ -1873,7 +1871,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } } // Collect and union volumes of a single region. - for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { + for (int region_id = 0; region_id < int(m_region_volumes.size()); ++ region_id) { ExPolygons expolygons; size_t num_volumes = 0; for (SlicedVolume &sliced_volume : sliced_volumes) @@ -1894,8 +1892,8 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } // Slice all modifier volumes. - if (this->region_volumes.size() > 1) { - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + if (m_region_volumes.size() > 1) { + for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; // slicing in parallel std::vector expolygons_by_layer = this->slice_modifiers(region_id, slice_zs); @@ -1908,7 +1906,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) tbb::blocked_range(0, m_layers.size()), [this, &expolygons_by_layer, region_id](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { - for (size_t other_region_id = 0; other_region_id < this->region_volumes.size(); ++ other_region_id) { + for (size_t other_region_id = 0; other_region_id < m_region_volumes.size(); ++ other_region_id) { if (region_id == other_region_id) continue; Layer *layer = m_layers[layer_id]; @@ -2048,9 +2046,9 @@ end: std::vector PrintObject::slice_region(size_t region_id, const std::vector &z, SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below) const { std::vector volumes; - if (region_id < this->region_volumes.size()) { - for (const std::pair &volume_and_range : this->region_volumes[region_id]) { - const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (region_id < m_region_volumes.size()) { + for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : m_region_volumes[region_id].volumes) { + const ModelVolume *volume = this->model_object()->volumes[volume_w_zrange.volume_idx]; if (volume->is_model_part()) volumes.emplace_back(volume); } @@ -2058,27 +2056,27 @@ std::vector PrintObject::slice_region(size_t region_id, const std::v return this->slice_volumes(z, mode, slicing_mode_normal_below_layer, mode_below, volumes); } -// Z ranges are not applicable to modifier meshes, therefore a single volume will be found in volume_and_range at most once. +// Z ranges are not applicable to modifier meshes, therefore a single volume will be found in volume_w_zrange at most once. std::vector PrintObject::slice_modifiers(size_t region_id, const std::vector &slice_zs) const { std::vector out; - if (region_id < this->region_volumes.size()) + if (region_id < m_region_volumes.size()) { std::vector> volume_ranges; - const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; - volume_ranges.reserve(volumes_and_ranges.size()); - for (size_t i = 0; i < volumes_and_ranges.size(); ) { - int volume_id = volumes_and_ranges[i].second; + const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id]; + volume_ranges.reserve(volumes_and_ranges.volumes.size()); + for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) { + int volume_id = volumes_and_ranges.volumes[i].volume_idx; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_modifier()) { std::vector ranges; - ranges.emplace_back(volumes_and_ranges[i].first); + ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range); size_t j = i + 1; - for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) { - if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) - ranges.back().second = volumes_and_ranges[j].first.second; + for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) { + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges.volumes[j].layer_height_range.first) < EPSILON) + ranges.back().second = volumes_and_ranges.volumes[j].layer_height_range.second; else - ranges.emplace_back(volumes_and_ranges[j].first); + ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); } volume_ranges.emplace_back(std::move(ranges)); i = j; @@ -2100,8 +2098,8 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) { // No modifier in this region was split to layer spans. std::vector volumes; - for (const std::pair &volume_and_range : this->region_volumes[region_id]) { - const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + for (const PrintRegionVolumes::VolumeWithZRange &volume_w_zrange : m_region_volumes[region_id].volumes) { + const ModelVolume *volume = this->model_object()->volumes[volume_w_zrange.volume_idx]; if (volume->is_modifier()) volumes.emplace_back(volume); } @@ -2109,19 +2107,19 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std } else { // Some modifier in this region was split to layer spans. std::vector merge; - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; - for (size_t i = 0; i < volumes_and_ranges.size(); ) { - int volume_id = volumes_and_ranges[i].second; + for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) { + const PrintRegionVolumes &volumes_and_ranges = m_region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.volumes.size(); ) { + int volume_id = volumes_and_ranges.volumes[i].volume_idx; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_modifier()) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id; // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. std::vector ranges; - ranges.emplace_back(volumes_and_ranges[i].first); + ranges.emplace_back(volumes_and_ranges.volumes[i].layer_height_range); size_t j = i + 1; - for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) - ranges.emplace_back(volumes_and_ranges[j].first); + for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) + ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); // slicing in parallel std::vector this_slices = this->slice_volume(slice_zs, ranges, SlicingMode::Regular, *model_volume); // Variable this_slices could be empty if no value of slice_zs is within any of the ranges of this volume. @@ -2339,7 +2337,7 @@ std::string PrintObject::_fix_slicing_errors() if (lower_surfaces) for (const auto &surface : *lower_surfaces) polygons_append(holes, surface.expolygon.holes); - layerm->slices.set(diff_ex(union_(outer), holes, false), stInternal); + layerm->slices.set(diff_ex(union_(outer), holes), stInternal); } // Update layer slices after repairing the single regions. layer->make_slices(); @@ -2397,9 +2395,15 @@ void PrintObject::simplify_slices(double distance) // fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. void PrintObject::clip_fill_surfaces() { - if (! m_config.infill_only_where_needed.value || - ! std::any_of(this->print()->regions().begin(), this->print()->regions().end(), - [](const PrintRegion *region) { return region->config().fill_density > 0; })) + if (! m_config.infill_only_where_needed.value) + return; + bool has_infill = false; + for (size_t i = 0; i < this->num_printing_regions(); ++ i) + if (this->printing_region(i).config().fill_density > 0) { + has_infill = true; + break; + } + if (! has_infill) return; // We only want infill under ceilings; this is almost like an @@ -2452,7 +2456,7 @@ void PrintObject::clip_fill_surfaces() upper_internal = intersection(overhangs, lower_layer_internal_surfaces); // Apply new internal infill to regions. for (LayerRegion *layerm : lower_layer->m_regions) { - if (layerm->region()->config().fill_density.value == 0) + if (layerm->region().config().fill_density.value == 0) continue; SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; @@ -2460,8 +2464,8 @@ void PrintObject::clip_fill_surfaces() if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(internal, std::move(surface.expolygon)); layerm->fill_surfaces.remove_types(internal_surface_types, 2); - layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, true), stInternal); - layerm->fill_surfaces.append(diff_ex (internal, upper_internal, true), stInternalVoid); + layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); + layerm->fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); // If there are voids it means that our internal infill is not adjacent to // perimeters. In this case it would be nice to add a loop around infill to // make it more robust and nicer. TODO. @@ -2477,12 +2481,12 @@ void PrintObject::discover_horizontal_shells() { BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); Layer *layer = m_layers[i]; LayerRegion *layerm = layer->regions()[region_id]; - const PrintRegionConfig ®ion_config = layerm->region()->config(); + const PrintRegionConfig ®ion_config = layerm->region().config(); if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 && (i % region_config.solid_infill_every_layers) == 0) { // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. @@ -2558,7 +2562,7 @@ void PrintObject::discover_horizontal_shells() for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid) polygons_append(internal, to_polygons(surface.expolygon)); - new_internal_solid = intersection(solid, internal, true); + new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes); } if (new_internal_solid.empty()) { // No internal solid needed on this layer. In order to decide whether to continue @@ -2585,8 +2589,7 @@ void PrintObject::discover_horizontal_shells() float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin, jtMiter, 5), - true); + offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, jtMiter, 5)); // Trim the regularized region by the original region. if (! too_narrow.empty()) new_internal_solid = solid = diff(new_internal_solid, too_narrow); @@ -2605,8 +2608,7 @@ void PrintObject::discover_horizontal_shells() // have the same angle, so the next shell would be grown even more and so on. Polygons too_narrow = diff( new_internal_solid, - offset2(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5), - true); + offset2(new_internal_solid, -margin, +margin + ClipperSafetyOffset, ClipperLib::jtMiter, 5)); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -2634,12 +2636,12 @@ void PrintObject::discover_horizontal_shells() // and new ones SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces); polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid))); - ExPolygons internal_solid = union_ex(new_internal_solid, false); + ExPolygons internal_solid = union_ex(new_internal_solid); // assign new internal-solid surfaces to layer neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid); // subtract intersections from layer surfaces to get resulting internal surfaces Polygons polygons_internal = to_polygons(std::move(internal_solid)); - ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, true); + ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes); // assign resulting internal surfaces to layer neighbor_layerm->fill_surfaces.append(internal, stInternal); polygons_append(polygons_internal, to_polygons(std::move(internal))); @@ -2660,7 +2662,7 @@ void PrintObject::discover_horizontal_shells() } // for each region #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { const LayerRegion *layerm = layer->m_regions[region_id]; layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells"); @@ -2676,16 +2678,16 @@ void PrintObject::discover_horizontal_shells() void PrintObject::combine_infill() { // Work on each region separately. - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - const PrintRegion *region = this->print()->regions()[region_id]; - const size_t every = region->config().infill_every_layers.value; - if (every < 2 || region->config().fill_density == 0.) + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = this->printing_region(region_id); + const size_t every = region.config().infill_every_layers.value; + if (every < 2 || region.config().fill_density == 0.) continue; // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. //FIXME limit the layer height to max_layer_height double nozzle_diameter = std::min( - this->print()->config().nozzle_diameter.get_at(region->config().infill_extruder.value - 1), - this->print()->config().nozzle_diameter.get_at(region->config().solid_infill_extruder.value - 1)); + this->print()->config().nozzle_diameter.get_at(region.config().infill_extruder.value - 1), + this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_extruder.value - 1)); // define the combinations std::vector combine(m_layers.size(), 0); { @@ -2749,18 +2751,18 @@ void PrintObject::combine_infill() 0.5f * layerms.back()->flow(frPerimeter).scaled_width() + // Because fill areas for rectilinear and honeycomb are grown // later to overlap perimeters, we need to counteract that too. - ((region->config().fill_pattern == ipRectilinear || - region->config().fill_pattern == ipMonotonic || - region->config().fill_pattern == ipGrid || - region->config().fill_pattern == ipLine || - region->config().fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) * + ((region.config().fill_pattern == ipRectilinear || + region.config().fill_pattern == ipMonotonic || + region.config().fill_pattern == ipGrid || + region.config().fill_pattern == ipLine || + region.config().fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) * layerms.back()->flow(frSolidInfill).scaled_width(); for (ExPolygon &expoly : intersection) polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); for (LayerRegion *layerm : layerms) { Polygons internal = to_polygons(std::move(layerm->fill_surfaces.filter_by_type(stInternal))); layerm->fill_surfaces.remove_type(stInternal); - layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance, false), stInternal); + layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal); if (layerm == layerms.back()) { // Apply surfaces back with adjusted depth to the uppermost layer. Surface templ(stInternal, ExPolygon()); @@ -2772,7 +2774,7 @@ void PrintObject::combine_infill() } else { // Save void surfaces. layerm->fill_surfaces.append( - intersection_ex(internal, intersection_with_clearance, false), + intersection_ex(internal, intersection_with_clearance), stInternalVoid); } } diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 837200984a..5dba1316e6 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -20,11 +20,12 @@ unsigned int PrintRegion::extruder(FlowRole role) const Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const { - ConfigOptionFloatOrPercent config_width; + const PrintConfig &print_config = object.print()->config(); + ConfigOptionFloatOrPercent config_width; // Get extrusion width from configuration. // (might be an absolute value, or a percent value, or zero for auto) - if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) { - config_width = m_print->config().first_layer_extrusion_width; + if (first_layer && print_config.first_layer_extrusion_width.value > 0) { + config_width = print_config.first_layer_extrusion_width; } else if (role == frExternalPerimeter) { config_width = m_config.external_perimeter_extrusion_width; } else if (role == frPerimeter) { @@ -44,7 +45,7 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1)); + auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1)); return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } @@ -76,17 +77,17 @@ void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_con emplace_extruder(region_config.solid_infill_extruder); } -void PrintRegion::collect_object_printing_extruders(std::vector &object_extruders) const +void PrintRegion::collect_object_printing_extruders(const Print &print, std::vector &object_extruders) const { // PrintRegion, if used by some PrintObject, shall have all the extruders set to an existing printer extruder. // If not, then there must be something wrong with the Print::apply() function. #ifndef NDEBUG - auto num_extruders = (int)print()->config().nozzle_diameter.size(); + auto num_extruders = int(print.config().nozzle_diameter.size()); assert(this->config().perimeter_extruder <= num_extruders); assert(this->config().infill_extruder <= num_extruders); assert(this->config().solid_infill_extruder <= num_extruders); #endif - collect_object_printing_extruders(print()->config(), this->config(), print()->has_brim(), object_extruders); + collect_object_printing_extruders(print.config(), this->config(), print.has_brim(), object_extruders); } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 9ceda7896b..441b82de1f 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -90,7 +90,7 @@ public: float overlap_area(const Structure &rhs) const { double out = 0.; if (this->bbox.overlap(rhs.bbox)) { - Polygons polys = intersection(*this->polygon, *rhs.polygon, false); + Polygons polys = intersection(*this->polygon, *rhs.polygon); for (const Polygon &poly : polys) out += poly.area(); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a94eb35fa4..f5f422b3d8 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -267,7 +267,7 @@ protected: void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) - { this->m_config.apply_only(other, keys, ignore_nonexistent); } + { m_config.apply_only(other, keys, ignore_nonexistent); } void set_trafo(const Transform3d& trafo, bool left_handed) { m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index b4f5fefae2..5eb9c9433a 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -345,17 +345,14 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object // Evaluate the XY gap between the object outer perimeters and the support structures. // Evaluate the XY gap between the object outer perimeters and the support structures. coordf_t external_perimeter_width = 0.; - size_t num_nonempty_regions = 0; coordf_t bridge_flow_ratio = 0; - for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) - if (! object->region_volumes[region_id].empty()) { - ++ num_nonempty_regions; - const PrintRegion ®ion = *object->print()->get_region(region_id); - external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); - bridge_flow_ratio += region.config().bridge_flow_ratio; - } + for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object->printing_region(region_id); + external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width())); + bridge_flow_ratio += region.config().bridge_flow_ratio; + } m_support_params.gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); - bridge_flow_ratio /= num_nonempty_regions; + bridge_flow_ratio /= object->num_printing_regions(); m_support_params.support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ? m_support_params.support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) : @@ -364,7 +361,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object m_support_params.can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; if (!m_support_params.can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { // One of the support extruders is of "don't care" type. - auto object_extruders = m_object->print()->object_extruders(); + auto object_extruders = m_object->object_extruders(); if (object_extruders.size() == 1 && *object_extruders.begin() == std::max(m_object_config->support_material_extruder.value, m_object_config->support_material_interface_extruder.value)) // Object is printed with the same extruder as the support. @@ -801,7 +798,7 @@ public: Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); #endif // SUPPORT_USE_AGG_RASTERIZER - ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); + ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons); // Extract polygons, which contain some of the island_samples. Polygons out; @@ -1254,7 +1251,7 @@ namespace SupportMaterialInternal { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. Polygons lower_grown_slices = offset(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region().config().perimeter_extruder-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way @@ -1307,7 +1304,7 @@ namespace SupportMaterialInternal { // Offset unsupported edges into polygons. offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. - contact_polygons = diff(contact_polygons, bridges, true); + contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); #ifdef SLIC3R_DEBUG static int iRun = 0; @@ -1338,7 +1335,7 @@ std::vector PrintObjectSupportMaterial::buildplate_covered(const Print Polygons &covered = buildplate_covered[layer_id]; covered = buildplate_covered[layer_id - 1]; polygons_append(covered, offset(lower_layer.lslices, scale_(0.01))); - covered = union_(covered, false); // don't apply the safety offset. + covered = union_(covered); } BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::buildplate_covered() - end"; } @@ -1640,7 +1637,7 @@ static inline std::pairregion()->bridging_height_avg(print_config); + bridging_height += region->region().bridging_height_avg(print_config); bridging_height /= coordf_t(layer.regions().size()); coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; if (bridging_print_z >= slicing_params.first_print_layer_height - EPSILON) { @@ -1981,7 +1978,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( if (top.empty()) return nullptr; - Polygons touching = intersection(top, supports_projected, false); + Polygons touching = intersection(top, supports_projected); if (touching.empty()) return nullptr; @@ -2080,7 +2077,7 @@ static inline std::pair project_support_to_grid(const Layer // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. // Polygons trimming = union_(to_polygons(layer.slices), touching, true); Polygons trimming = layer_buildplate_covered ? std::move(*layer_buildplate_covered) : offset(layer.lslices, float(SCALED_EPSILON)); - Polygons overhangs_projection = diff(overhangs, trimming, false); + Polygons overhangs_projection = diff(overhangs, trimming); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z), @@ -2685,7 +2682,7 @@ void PrintObjectSupportMaterial::generate_base_layers( layer_intermediate.polygons = diff( polygons_new, polygons_trimming, - true); // safety offset to merge the touching source polygons + ApplySafetyOffset::Yes); // safety offset to merge the touching source polygons layer_intermediate.layer_type = sltBase; #if 0 @@ -2767,13 +2764,13 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( const Layer &object_layer = *object.layers()[i]; bool some_region_overlaps = false; for (LayerRegion *region : object_layer.regions()) { - coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config); + coordf_t bridging_height = region->region().bridging_height_avg(*m_print_config); if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) break; some_region_overlaps = true; polygons_append(polygons_trimming, offset(region->fill_surfaces.filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); - if (region->region()->config().overhangs.value) + if (region->region().config().overhangs.value) // Add bridging perimeters. SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); } @@ -2988,9 +2985,9 @@ std::pairlayer = rhs.layer; this->extrusions = std::move(rhs.extrusions); - this->m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); + m_polygons_to_extrude = std::move(rhs.m_polygons_to_extrude); rhs.layer = nullptr; return *this; } @@ -3220,21 +3217,21 @@ struct MyLayerExtruded m_polygons_to_extrude = std::make_unique(this->layer->polygons); } Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); - *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); other.m_polygons_to_extrude.reset(); } else if (m_polygons_to_extrude != nullptr) { assert(other.m_polygons_to_extrude == nullptr); // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). assert(other.extrusions.empty()); Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons); - *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true); + *m_polygons_to_extrude = union_safety_offset(*m_polygons_to_extrude); } // 2) Merge the extrusions. this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); other.extrusions.clear(); // 3) Merge the infill polygons. Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); - this->layer->polygons = union_(this->layer->polygons, true); + this->layer->polygons = union_safety_offset(this->layer->polygons); other.layer->polygons.clear(); } @@ -3614,8 +3611,8 @@ void modulate_extrusion_by_overlapping_layers( const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); - frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming, false); - path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming, false); + frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming); + path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). assert(this_layer.print_z > overlapping_layer.print_z); frag.height = float(this_layer.print_z - overlapping_layer.print_z); @@ -4038,7 +4035,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Destination layer_ex.extrusions, // Regions to fill - union_ex(layer_ex.polygons_to_extrude(), true), + union_safety_offset_ex(layer_ex.polygons_to_extrude()), // Filler and its parameters filler_interface.get(), float(density), // Extrusion parameters @@ -4060,7 +4057,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( base_interface_layer.extrusions, //base_layer_interface.extrusions, // Regions to fill - union_ex(base_interface_layer.polygons_to_extrude(), true), + union_safety_offset_ex(base_interface_layer.polygons_to_extrude()), // Filler and its parameters filler, float(interface_density), // Extrusion parameters diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 1d4c63fb62..4cc5fbfec2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,8 @@ #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) // Enable a modified version of automatic downscale on load of objects too big #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) +// Enable scrollable legend in preview +#define ENABLE_SCROLLABLE_LEGEND (1 && ENABLE_2_4_0_ALPHA0) // Enable visualization of start gcode as regular toolpaths #define ENABLE_START_GCODE_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) // Enable visualization of seams in preview diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5b904c87d0..aa3bf01c4a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -191,6 +191,8 @@ set(SLIC3R_GUI_SOURCES GUI/ExtraRenderers.hpp GUI/ProjectDirtyStateManager.hpp GUI/ProjectDirtyStateManager.cpp + GUI/DesktopIntegrationDialog.cpp + GUI/DesktopIntegrationDialog.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 089fba6566..2869f11c83 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -145,7 +145,7 @@ void BackgroundSlicingProcess::process_fff() // Passing the timestamp evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp)); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); - m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb); + m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); }); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); @@ -221,21 +221,14 @@ void BackgroundSlicingProcess::process_sla() const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); + ThumbnailsList thumbnails = this->render_thumbnails( + ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); + Zipper zipper(export_path); - m_sla_archive.export_print(zipper, *m_sla_print); - - if (m_thumbnail_cb != nullptr) - { - ThumbnailsList thumbnails; - m_thumbnail_cb(thumbnails, current_print()->full_print_config().option("thumbnails")->values, true, true, true, true); -// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option("thumbnails")->values, true, false, true, true); // renders also supports and pad - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - write_thumbnail(zipper, data); - } - } - + m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) + write_thumbnail(zipper, data); zipper.finalize(); m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); @@ -362,16 +355,19 @@ bool BackgroundSlicingProcess::start() return true; } +// To be called on the UI thread. bool BackgroundSlicingProcess::stop() { // m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it. std::unique_lock lck(m_mutex); if (m_state == STATE_INITIAL) { -// this->m_export_path.clear(); +// m_export_path.clear(); return false; } // assert(this->running()); if (m_state == STATE_STARTED || m_state == STATE_RUNNING) { + // Cancel any task planned by the background thread on UI thread. + cancel_ui_task(m_ui_task); m_print->cancel(); // Wait until the background processing stops by being canceled. m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; }); @@ -383,7 +379,7 @@ bool BackgroundSlicingProcess::stop() m_state = STATE_IDLE; m_print->set_cancel_callback([](){}); } -// this->m_export_path.clear(); +// m_export_path.clear(); return true; } @@ -396,7 +392,7 @@ bool BackgroundSlicingProcess::reset() return stopped; } -// To be called by Print::apply() through the Print::m_cancel_callback to stop the background +// To be called by Print::apply() on the UI thread through the Print::m_cancel_callback to stop the background // processing before changing any data of running or finalized milestones. // This function shall not trigger any UI update through the wxWidgets event. void BackgroundSlicingProcess::stop_internal() @@ -408,6 +404,8 @@ void BackgroundSlicingProcess::stop_internal() std::unique_lock lck(m_mutex); assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED); if (m_state == STATE_STARTED || m_state == STATE_RUNNING) { + // Cancel any task planned by the background thread on UI thread. + cancel_ui_task(m_ui_task); // At this point of time the worker thread may be blocking on m_print->state_mutex(). // Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up, // it throws the CanceledException(). @@ -424,6 +422,60 @@ void BackgroundSlicingProcess::stop_internal() m_print->set_cancel_callback([](){}); } +// Execute task from background thread on the UI thread. Returns true if processed, false if cancelled. +bool BackgroundSlicingProcess::execute_ui_task(std::function task) +{ + bool running = false; + if (m_mutex.try_lock()) { + // Cancellation is either not in process, or already canceled and waiting for us to finish. + // There must be no UI task planned. + assert(! m_ui_task); + if (! m_print->canceled()) { + running = true; + m_ui_task = std::make_shared(); + } + m_mutex.unlock(); + } else { + // Cancellation is in process. + } + + bool result = false; + if (running) { + std::shared_ptr ctx = m_ui_task; + GUI::wxGetApp().mainframe->m_plater->CallAfter([task, ctx]() { + // Running on the UI thread, thus ctx->state does not need to be guarded with mutex against ::cancel_ui_task(). + assert(ctx->state == UITask::Planned || ctx->state == UITask::Canceled); + if (ctx->state == UITask::Planned) { + task(); + std::unique_lock lck(ctx->mutex); + ctx->state = UITask::Finished; + } + // Wake up the worker thread from the UI thread. + ctx->condition.notify_all(); + }); + + { + std::unique_lock lock(ctx->mutex); + ctx->condition.wait(lock, [&ctx]{ return ctx->state == UITask::Finished || ctx->state == UITask::Canceled; }); + } + result = ctx->state == UITask::Finished; + m_ui_task.reset(); + } + + return result; +} + +// To be called on the UI thread from ::stop() and ::stop_internal(). +void BackgroundSlicingProcess::cancel_ui_task(std::shared_ptr task) +{ + if (task) { + std::unique_lock lck(task->mutex); + task->state = UITask::Canceled; + lck.unlock(); + task->condition.notify_all(); + } +} + bool BackgroundSlicingProcess::empty() const { assert(m_print != nullptr); @@ -444,7 +496,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn assert(config.opt_enum("printer_technology") == m_print->technology()); Print::ApplyStatus invalidated = m_print->apply(model, config); if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF && - !this->m_fff_print->is_step_done(psGCodeExport)) { + !m_fff_print->is_step_done(psGCodeExport)) { // Some FFF status was invalidated, and the G-code was not exported yet. // Let the G-code preview UI know that the final G-code preview is not valid. // In addition, this early memory deallocation reduces memory footprint. @@ -546,19 +598,14 @@ void BackgroundSlicingProcess::prepare_upload() } else { m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); + ThumbnailsList thumbnails = this->render_thumbnails( + ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); + // true, false, true, true); // renders also supports and pad Zipper zipper{source_path.string()}; m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); - if (m_thumbnail_cb != nullptr) - { - ThumbnailsList thumbnails; - m_thumbnail_cb(thumbnails, current_print()->full_print_config().option("thumbnails")->values, true, true, true, true); -// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option("thumbnails")->values, true, false, true, true); // renders also supports and pad - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - write_thumbnail(zipper, data); - } - } + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) + write_thumbnail(zipper, data); zipper.finalize(); } @@ -569,4 +616,13 @@ void BackgroundSlicingProcess::prepare_upload() GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job)); } +// Executed by the background thread, to start a task on the UI thread. +ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams ¶ms) +{ + ThumbnailsList thumbnails; + if (m_thumbnail_cb) + this->execute_ui_task([this, ¶ms, &thumbnails](){ thumbnails = m_thumbnail_cb(params); }); + return thumbnails; +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index d3819f15c6..dc24ec0add 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -212,6 +212,23 @@ private: std::condition_variable m_condition; State m_state = STATE_INITIAL; + // For executing tasks from the background thread on UI thread synchronously (waiting for result) using wxWidgets CallAfter(). + // When the background proces is canceled, the UITask has to be invalidated as well, so that it will not be + // executed on the UI thread referencing invalid data. + struct UITask { + enum State { + Planned, + Finished, + Canceled, + }; + State state = Planned; + std::mutex mutex; + std::condition_variable condition; + }; + // Only one UI task may be planned by the background thread to be executed on the UI thread, as the background + // thread is blocking until the UI thread calculation finishes. + std::shared_ptr m_ui_task; + PrintState m_step_state; mutable tbb::mutex m_step_state_mutex; bool set_step_started(BackgroundSlicingProcessStep step); @@ -222,6 +239,12 @@ private: // If the background processing stop was requested, throw CanceledException. void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } void prepare_upload(); + // To be executed at the background thread. + ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms); + // Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task. + bool execute_ui_task(std::function task); + // To be called from inside m_mutex to cancel a planned UI task. + static void cancel_ui_task(std::shared_ptr task); // wxWidgets command ID to be sent to the plater to inform that the slicing is finished, and the G-code export will continue. int m_event_slicing_completed_id = 0; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index bd6986d3ef..52df39fd5f 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -26,14 +26,18 @@ #include #include +#include "libslic3r/Platform.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Config.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" #include "GUI_ObjectManipulation.hpp" +#include "Field.hpp" +#include "DesktopIntegrationDialog.hpp" #include "slic3r/Config/Snapshot.hpp" #include "slic3r/Utils/PresetUpdater.hpp" +#include "format.hpp" #if defined(__linux__) && defined(__WXGTK3__) #define wxLinux_gtk3 true @@ -450,7 +454,6 @@ void ConfigWizardPage::append_spacer(int space) content->AddSpacer(space); } - // Wizard pages PageWelcome::PageWelcome(ConfigWizard *parent) @@ -469,9 +472,21 @@ PageWelcome::PageWelcome(ConfigWizard *parent) , cbox_reset(append( new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) )) + , cbox_integrate(append( + new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) + )) { welcome_text->Hide(); cbox_reset->Hide(); +#ifdef __linux__ + if (!DesktopIntegrationDialog::is_integrated()) + cbox_integrate->Show(true); + else + cbox_integrate->Hide(); +#else + cbox_integrate->Hide(); +#endif + } void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) @@ -479,6 +494,14 @@ void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; welcome_text->Show(data_empty); cbox_reset->Show(!data_empty); +#ifdef __linux__ + if (!DesktopIntegrationDialog::is_integrated()) + cbox_integrate->Show(true); + else + cbox_integrate->Hide(); +#else + cbox_integrate->Hide(); +#endif } @@ -1361,20 +1384,39 @@ void PageBedShape::apply_custom_config(DynamicPrintConfig &config) config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); } +static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +{ + e.Skip(); + wxString str = ctrl->GetValue(); + // Replace the first occurence of comma in decimal number. + bool was_replace = str.Replace(",", ".", false) > 0; + double val = 0.0; + if (!str.ToCDouble(&val)) { + if (val == 0.0) + val = def_value; + ctrl->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + ctrl->SetFocus(); + } + else if (was_replace) + ctrl->SetValue(double_to_string(val)); +} + PageDiameters::PageDiameters(ConfigWizard *parent) : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)) - , spin_filam(new wxSpinCtrlDouble(this, wxID_ANY)) + , diam_nozzle(new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord))) + , diam_filam (new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord))) { - spin_nozzle->SetDigits(2); - spin_nozzle->SetIncrement(0.1); auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + diam_nozzle->SetValue(value); - spin_filam->SetDigits(2); - spin_filam->SetIncrement(0.25); auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + diam_filam->SetValue(value); + + diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); + diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); append_text(_L("Enter the diameter of your printer's hot end nozzle.")); @@ -1383,7 +1425,7 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_nozzle->AddGrowableCol(0, 1); sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(spin_nozzle); + sizer_nozzle->Add(diam_nozzle); sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_nozzle); @@ -1397,16 +1439,21 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_filam->AddGrowableCol(0, 1); sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(spin_filam); + sizer_filam->Add(diam_filam); sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_filam); } void PageDiameters::apply_custom_config(DynamicPrintConfig &config) { - auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue()); + double val = 0.0; + diam_nozzle->GetValue().ToCDouble(&val); + auto *opt_nozzle = new ConfigOptionFloats(1, val); config.set_key_value("nozzle_diameter", opt_nozzle); - auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue()); + + val = 0.0; + diam_filam->GetValue().ToCDouble(&val); + auto * opt_filam = new ConfigOptionFloats(1, val); config.set_key_value("filament_diameter", opt_filam); auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { @@ -2373,6 +2420,12 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } +#ifdef __linux__ + // Desktop integration on Linux + if (page_welcome->integrate_desktop()) + DesktopIntegrationDialog::perform_desktop_integration(); +#endif + // Decide whether to create snapshot based on run_reason and the reset profile checkbox bool snapshot = true; Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; @@ -2490,7 +2543,6 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Update the selections from the compatibilty. preset_bundle->export_selections(*app_config); } - void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) { const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; diff --git a/src/slic3r/GUI/ConfigWizard.hpp b/src/slic3r/GUI/ConfigWizard.hpp index 942f4b4ce8..86245836b2 100644 --- a/src/slic3r/GUI/ConfigWizard.hpp +++ b/src/slic3r/GUI/ConfigWizard.hpp @@ -45,7 +45,6 @@ public: bool run(RunReason reason, StartPage start_page = SP_WELCOME); static const wxString& name(const bool from_menu = false); - protected: void on_dpi_changed(const wxRect &suggested_rect) override ; diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index eee906ae77..ea39e04ab3 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -227,10 +227,12 @@ struct PageWelcome: ConfigWizardPage { wxStaticText *welcome_text; wxCheckBox *cbox_reset; + wxCheckBox *cbox_integrate; PageWelcome(ConfigWizard *parent); bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; } + bool integrate_desktop() const { return cbox_integrate != nullptr ? cbox_integrate->GetValue() : false; } virtual void set_run_reason(ConfigWizard::RunReason run_reason) override; }; @@ -449,8 +451,8 @@ struct PageBedShape: ConfigWizardPage struct PageDiameters: ConfigWizardPage { - wxSpinCtrlDouble *spin_nozzle; - wxSpinCtrlDouble *spin_filam; + wxTextCtrl *diam_nozzle; + wxTextCtrl *diam_filam; PageDiameters(ConfigWizard *parent); virtual void apply_custom_config(DynamicPrintConfig &config); @@ -615,7 +617,9 @@ struct ConfigWizard::priv void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); // #ys_FIXME_alise void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add); - +#ifdef __linux__ + void perform_desktop_integration() const; +#endif bool check_fff_selected(); // Used to decide whether to display Filaments page bool check_sla_selected(); // Used to decide whether to display SLA Materials page diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp new file mode 100644 index 0000000000..a2f7c89337 --- /dev/null +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -0,0 +1,408 @@ +#ifdef __linux__ +#include "DesktopIntegrationDialog.hpp" +#include "GUI_App.hpp" +#include "format.hpp" +#include "I18N.hpp" +#include "NotificationManager.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Platform.hpp" + +#include +#include + +#include +#include + +namespace Slic3r { +namespace GUI { + +namespace integrate_desktop_internal{ +// Disects path strings stored in system variable divided by ':' and adds into vector +static void resolve_path_from_var(const std::string& var, std::vector& paths) +{ + wxString wxdirs; + if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() ) + return; + std::string dirs = boost::nowide::narrow(wxdirs); + for (size_t i = dirs.find(':'); i != std::string::npos; i = dirs.find(':')) + { + paths.push_back(dirs.substr(0, i)); + if (dirs.size() > i+1) + dirs = dirs.substr(i+1); + } + paths.push_back(dirs); +} +// Return true if directory in path p+dir_name exists +static bool contains_path_dir(const std::string& p, const std::string& dir_name) +{ + if (p.empty() || dir_name.empty()) + return false; + boost::filesystem::path path(p + (p[p.size()-1] == '/' ? "" : "/") + dir_name); + if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) { + //BOOST_LOG_TRIVIAL(debug) << path.string() << " " << std::oct << boost::filesystem::status(path).permissions(); + return true; //boost::filesystem::status(path).permissions() & boost::filesystem::owner_write; + } else + BOOST_LOG_TRIVIAL(debug) << path.string() << " doesnt exists"; + return false; +} +// Creates directory in path if not exists yet +static void create_dir(const boost::filesystem::path& path) +{ + if (boost::filesystem::exists(path)) + return; + BOOST_LOG_TRIVIAL(debug)<< "creating " << path.string(); + boost::system::error_code ec; + boost::filesystem::create_directory(path, ec); + if (ec) + BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message(); +} +// Starts at basic_path (excluded) and creates all directories in dir_path +static void create_path(const std::string& basic_path, const std::string& dir_path) +{ + if (basic_path.empty() || dir_path.empty()) + return; + + boost::filesystem::path path(basic_path); + std::string dirs = dir_path; + for (size_t i = dirs.find('/'); i != std::string::npos; i = dirs.find('/')) + { + std::string dir = dirs.substr(0, i); + path = boost::filesystem::path(path.string() +"/"+ dir); + create_dir(path); + dirs = dirs.substr(i+1); + } + path = boost::filesystem::path(path.string() +"/"+ dirs); + create_dir(path); +} +// Calls our internal copy_file function to copy file at icon_path to dest_path +static bool copy_icon(const std::string& icon_path, const std::string& dest_path) +{ + BOOST_LOG_TRIVIAL(debug) <<"icon from "<< icon_path; + BOOST_LOG_TRIVIAL(debug) <<"icon to "<< dest_path; + std::string error_message; + auto cfr = copy_file(icon_path, dest_path, error_message, false); + if (cfr) { + BOOST_LOG_TRIVIAL(debug) << "Copy icon fail(" << cfr << "): " << error_message; + return false; + } + BOOST_LOG_TRIVIAL(debug) << "Copy icon success."; + return true; +} +// Creates new file filled with data. +static bool create_desktop_file(const std::string& path, const std::string& data) +{ + BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path; + std::ofstream output(path); + output << data; + struct stat buffer; + if (stat(path.c_str(), &buffer) == 0) + { + BOOST_LOG_TRIVIAL(debug) << "Desktop file created."; + return true; + } + BOOST_LOG_TRIVIAL(debug) << "Desktop file NOT created."; + return false; +} +} // namespace integratec_desktop_internal + +// methods that actually do / undo desktop integration. Static to be accesible from anywhere. +bool DesktopIntegrationDialog::is_integrated() +{ + const char *appimage_env = std::getenv("APPIMAGE"); + if (!appimage_env) + return false; + + const AppConfig *app_config = wxGetApp().app_config; + std::string path(app_config->get("desktop_integration_app_path")); + BOOST_LOG_TRIVIAL(debug) << "Desktop integration desktop file path: " << path; + + if (path.empty()) + return false; + + // confirmation that PrusaSlicer.desktop exists + struct stat buffer; + return (stat (path.c_str(), &buffer) == 0); +} +bool DesktopIntegrationDialog::integration_possible() +{ + + const char *appimage_env = std::getenv("APPIMAGE"); + if (!appimage_env) + return false; + return true; +} +void DesktopIntegrationDialog::perform_desktop_integration() +{ + BOOST_LOG_TRIVIAL(debug) << "performing desktop integration"; + + // Path to appimage + const char *appimage_env = std::getenv("APPIMAGE"); + std::string appimage_path; + if (appimage_env) { + try { + appimage_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string(); + } catch (std::exception &) { + } + } else { + // not appimage - not performing + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - not Appimage executable."; + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail); + return; + } + + // Find directories icons and applications + // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. + // If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. + // $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory. + // The directories in $XDG_DATA_DIRS should be seperated with a colon ':'. + // If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used. + std::vectortarget_candidates; + integrate_desktop_internal::resolve_path_from_var("XDG_DATA_HOME", target_candidates); + integrate_desktop_internal::resolve_path_from_var("XDG_DATA_DIRS", target_candidates); + + AppConfig *app_config = wxGetApp().app_config; + // suffix string to create different desktop file for alpha, beta. + + std::string version_suffix; + std::string name_suffix; + std::string version(SLIC3R_VERSION); + if (version.find("alpha") != std::string::npos) + { + version_suffix = "-alpha"; + name_suffix = " - alpha"; + }else if (version.find("beta") != std::string::npos) + { + version_suffix = "-beta"; + name_suffix = " - beta"; + } + + // theme path to icon destination + std::string icon_theme_path; + std::string icon_theme_dirs; + + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + icon_theme_path = "hicolor/96x96/apps/"; + icon_theme_dirs = "/hicolor/96x96/apps"; + } + + + std::string target_dir_icons; + std::string target_dir_desktop; + + // slicer icon + // iterate thru target_candidates to find icons folder + for (size_t i = 0; i < target_candidates.size(); ++i) { + // Copy icon PrusaSlicer.png from resources_dir()/icons to target_dir_icons/icons/ + if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "icons")) { + target_dir_icons = target_candidates[i]; + std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir()); + std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix); + if (integrate_desktop_internal::copy_icon(icon_path, dest_path)) + break; // success + else + target_dir_icons.clear(); // copying failed + // if all failed - try creating default home folder + if (i == target_candidates.size() - 1) { + // create $HOME/.local/share + integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs); + // copy icon + target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); + std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir()); + std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix); + if (!integrate_desktop_internal::contains_path_dir(target_dir_icons, "icons") + || !integrate_desktop_internal::copy_icon(icon_path, dest_path)) { + // every attempt failed - icon wont be present + target_dir_icons.clear(); + } + } + } + } + if(target_dir_icons.empty()) { + BOOST_LOG_TRIVIAL(error) << "Copying PrusaSlicer icon to icons directory failed."; + } else + // save path to icon + app_config->set("desktop_integration_icon_slicer_path", GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix)); + + // desktop file + // iterate thru target_candidates to find applications folder + for (size_t i = 0; i < target_candidates.size(); ++i) + { + if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "applications")) { + target_dir_desktop = target_candidates[i]; + // Write slicer desktop file + std::string desktop_file = GUI::format( + "[Desktop Entry]\n" + "Name=PrusaSlicer%1%\n" + "GenericName=3D Printing Software\n" + "Icon=PrusaSlicer%2%\n" + "Exec=%3% %%F\n" + "Terminal=false\n" + "Type=Application\n" + "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n" + "Categories=Graphics;3DGraphics;Engineering;\n" + "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n" + "StartupNotify=false\n" + "StartupWMClass=prusa-slicer", name_suffix, version_suffix, appimage_path); + + std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); + if (integrate_desktop_internal::create_desktop_file(path, desktop_file)){ + BOOST_LOG_TRIVIAL(debug) << "PrusaSlicer.desktop file installation success."; + break; + } else { + // write failed - try another path + BOOST_LOG_TRIVIAL(error) << "PrusaSlicer.desktop file installation failed."; + target_dir_desktop.clear(); + } + // if all failed - try creating default home folder + if (i == target_candidates.size() - 1) { + // create $HOME/.local/share + integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications"); + // create desktop file + target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); + std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); + if (integrate_desktop_internal::contains_path_dir(target_dir_desktop, "applications")) { + if (!integrate_desktop_internal::create_desktop_file(path, desktop_file)) { + // Desktop file not written - end desktop integration + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file"; + return; + } + } else { + // Desktop file not written - end desktop integration + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory"; + return; + } + } + } + } + if(target_dir_desktop.empty()) { + // Desktop file not written - end desktop integration + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory"; + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail); + return; + } + // save path to desktop file + app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix)); + + // Repeat for Gcode viewer - use same paths as for slicer files + // Icon + if (!target_dir_icons.empty()) + { + std::string icon_path = GUI::format("%1%/icons/PrusaSlicer-gcodeviewer_192px.png",resources_dir()); + std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix); + if (integrate_desktop_internal::copy_icon(icon_path, dest_path)) + // save path to icon + app_config->set("desktop_integration_icon_viewer_path", dest_path); + else + BOOST_LOG_TRIVIAL(error) << "Copying Gcode Viewer icon to icons directory failed."; + } + + // Desktop file + std::string desktop_file = GUI::format( + "[Desktop Entry]\n" + "Name=Prusa Gcode Viewer%1%\n" + "GenericName=3D Printing Software\n" + "Icon=PrusaSlicer-gcodeviewer%2%\n" + "Exec=%3% --gcodeviwer %%F\n" + "Terminal=false\n" + "Type=Application\n" + "MimeType=text/x.gcode;\n" + "Categories=Graphics;3DGraphics;\n" + "Keywords=3D;Printing;Slicer;\n" + "StartupNotify=false", name_suffix, version_suffix, appimage_path); + + std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix); + if (integrate_desktop_internal::create_desktop_file(desktop_path, desktop_file)) + // save path to desktop file + app_config->set("desktop_integration_app_viewer_path", desktop_path); + else { + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could create gcode viewer desktop file"; + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail); + } + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess); +} +void DesktopIntegrationDialog::undo_desktop_intgration() +{ + const char *appimage_env = std::getenv("APPIMAGE"); + if (!appimage_env) { + BOOST_LOG_TRIVIAL(error) << "Undo desktop integration failed - not Appimage executable."; + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationFail); + return; + } + const AppConfig *app_config = wxGetApp().app_config; + // slicer .desktop + std::string path = std::string(app_config->get("desktop_integration_app_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } + // slicer icon + path = std::string(app_config->get("desktop_integration_icon_slicer_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } + // gcode viwer .desktop + path = std::string(app_config->get("desktop_integration_app_viewer_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } + // gcode viewer icon + path = std::string(app_config->get("desktop_integration_icon_viewer_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess); +} + +DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent) +: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +{ + bool can_undo = DesktopIntegrationDialog::is_integrated(); + + wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); + + + wxString text = _L("Desktop Integration sets this binary to be searchable by the system.\n\nPress \"Perform\" to proceed."); + if (can_undo) + text += "\nPress \"Undo\" to remove previous integration."; + + vbox->Add( + new wxStaticText( this, wxID_ANY, text), + // , wxDefaultPosition, wxSize(100,50), wxTE_MULTILINE), + 1, // make vertically stretchable + wxEXPAND | // make horizontally stretchable + wxALL, // and make border all around + 10 ); // set border width to 10 + + + wxBoxSizer *btn_szr = new wxBoxSizer(wxHORIZONTAL); + wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform")); + btn_szr->Add(btn_perform, 0, wxALL, 10); + + btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); }); + + if (can_undo){ + wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo")); + btn_szr->Add(btn_undo, 0, wxALL, 10); + btn_undo->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::undo_desktop_intgration(); EndModal(wxID_ANY); }); + } + wxButton *btn_cancel = new wxButton(this, wxID_ANY, _L("Cancel")); + btn_szr->Add(btn_cancel, 0, wxALL, 10); + btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_ANY); }); + + vbox->Add(btn_szr, 0, wxALIGN_CENTER); + + SetSizerAndFit(vbox); +} + +DesktopIntegrationDialog::~DesktopIntegrationDialog() +{ + +} + +} // namespace GUI +} // namespace Slic3r +#endif // __linux__ \ No newline at end of file diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.hpp b/src/slic3r/GUI/DesktopIntegrationDialog.hpp new file mode 100644 index 0000000000..74a0a68f99 --- /dev/null +++ b/src/slic3r/GUI/DesktopIntegrationDialog.hpp @@ -0,0 +1,39 @@ +#ifdef __linux__ +#ifndef slic3r_DesktopIntegrationDialog_hpp_ +#define slic3r_DesktopIntegrationDialog_hpp_ + +#include + +namespace Slic3r { +namespace GUI { +class DesktopIntegrationDialog : public wxDialog +{ +public: + DesktopIntegrationDialog(wxWindow *parent); + DesktopIntegrationDialog(DesktopIntegrationDialog &&) = delete; + DesktopIntegrationDialog(const DesktopIntegrationDialog &) = delete; + DesktopIntegrationDialog &operator=(DesktopIntegrationDialog &&) = delete; + DesktopIntegrationDialog &operator=(const DesktopIntegrationDialog &) = delete; + ~DesktopIntegrationDialog(); + + // methods that actually do / undo desktop integration. Static to be accesible from anywhere. + + // returns true if path to PrusaSlicer.desktop is stored in App Config and existence of desktop file. + // Does not check if desktop file leads to this binary or existence of icons and viewer desktop file. + static bool is_integrated(); + // true if appimage + static bool integration_possible(); + // Creates Desktop files and icons for both PrusaSlicer and GcodeViewer. + // Stores paths into App Config. + // Rewrites if files already existed. + static void perform_desktop_integration(); + // Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config. + static void undo_desktop_intgration(); +private: + +}; +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_DesktopIntegrationDialog_hpp_ +#endif // __linux__ \ No newline at end of file diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ca1617bc29..233bdf1cdb 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -17,6 +17,7 @@ #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" +#include "GUI_ObjectManipulation.hpp" #include #include @@ -687,13 +688,13 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); } - m_time_statistics = gcode_result.time_statistics; + m_print_statistics = gcode_result.print_statistics; - if (m_time_estimate_mode != PrintEstimatedTimeStatistics::ETimeMode::Normal) { - float time = m_time_statistics.modes[static_cast(m_time_estimate_mode)].time; + if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { + float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; if (time == 0.0f || - short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time))) - m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal; + short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) + m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; } } @@ -788,7 +789,7 @@ void GCodeViewer::reset() m_layers.reset(); m_layers_z_range = { 0, 0 }; m_roles = std::vector(); - m_time_statistics.reset(); + m_print_statistics.reset(); #if ENABLE_GCODE_WINDOW m_sequential_view.gcode_window.reset(); #endif // ENABLE_GCODE_WINDOW @@ -4051,14 +4052,25 @@ void GCodeViewer::render_legend() const if (!m_legend_enabled) return; +#if ENABLE_SCROLLABLE_LEGEND + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); +#endif // ENABLE_SCROLLABLE_LEGEND + ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); +#if ENABLE_SCROLLABLE_LEGEND + const float max_height = 0.75f * static_cast(cnv_size.get_height()); + const float child_height = 0.3333f * max_height; + ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); +#else imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImDrawList* draw_list = ImGui::GetWindowDrawList(); +#endif // ENABLE_SCROLLABLE_LEGEND enum class EItemType : unsigned char { @@ -4068,95 +4080,126 @@ void GCodeViewer::render_legend() const Line }; - const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast(m_time_estimate_mode)]; + const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; +#if ENABLE_SCROLLABLE_LEGEND + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#endif // ENABLE_SCROLLABLE_LEGEND - float icon_size = ImGui::GetTextLineHeight(); - float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); + const float icon_size = ImGui::GetTextLineHeight(); + const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); - auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, - bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f }, + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + +#if ENABLE_SCROLLABLE_LEGEND + auto append_item = [this, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, +#else + auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, +#endif // ENABLE_SCROLLABLE_LEGEND + bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, + double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) { - default: - case EItemType::Rect: { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - radius = 0.5f * icon_size * 0.01f * 33.0f; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - break; - } - case EItemType::Hexagon: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } - - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - // to avoid the tooltip to change size when moving the mouse - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - } - - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } +#if ENABLE_SCROLLABLE_LEGEND + ImDrawList* draw_list = ImGui::GetWindowDrawList(); +#endif // ENABLE_SCROLLABLE_LEGEND + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) { + default: + case EItemType::Rect: { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; + } + case EItemType::Circle: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } else - imgui.text(label); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - if (!visible) - ImGui::PopStyleVar(); + break; + } + case EItemType::Hexagon: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } + case EItemType::Line: { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } + + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + ImGui::SameLine(offsets[2]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[3]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } + else { + imgui.text(label); + if (used_filament_m > 0.0) { + char buf[64]; + ImGui::SameLine(offsets[0]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[1]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } + + if (!visible) + ImGui::PopStyleVar(); }; auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -4174,19 +4217,20 @@ void GCodeViewer::render_legend() const append_range_item(0, range.min, decimals); } else { - float step_size = range.step_size(); + const float step_size = range.step_size(); for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { append_range_item(i, range.min + static_cast(i) * step_size, decimals); } } }; - auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { - imgui.text(texts[0]); - ImGui::SameLine(offsets[0]); - imgui.text(texts[1]); - ImGui::SameLine(offsets[1]); - imgui.text(texts[2]); + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { + size_t i = 0; + for (; i < offsets.size(); i++) { + imgui.text(texts[i]); + ImGui::SameLine(offsets[i]); + } + imgui.text(texts[i]); ImGui::Separator(); }; @@ -4199,11 +4243,12 @@ void GCodeViewer::render_legend() const }; auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, - const std::array& titles, float extra_size = 0.0f) { + const std::array& titles, float extra_size = 0.0f) { const ImGuiStyle& style = ImGui::GetStyle(); - std::array ret = { 0.0f, 0.0f }; + std::array ret = { 0.0f, 0.0f, 0.0f, 0.0f }; ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; - ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x; + for (size_t i = 1; i < titles.size(); i++) + ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x; return ret; }; @@ -4223,8 +4268,8 @@ void GCodeViewer::render_legend() const if (lower_b == zs.end()) continue; - double current_z = *lower_b; - double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); + const double current_z = *lower_b; + const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); // to avoid duplicate values, check adding values if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) @@ -4254,16 +4299,27 @@ void GCodeViewer::render_legend() const return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; - auto role_time_and_percent = [ time_mode](ExtrusionRole role) { + auto role_time_and_percent = [time_mode](ExtrusionRole role) { auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); }; + auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { + auto it = m_print_statistics.used_filaments_per_role.find(role); + if (it == m_print_statistics.used_filaments_per_role.end()) + return std::make_pair(0.0, 0.0); + + double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0; + return std::make_pair(it->second.first * koef, it->second.second); + }; + // data used to properly align items in columns when showing time - std::array offsets = { 0.0f, 0.0f }; + std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; std::vector labels; std::vector times; std::vector percents; + std::vector used_filaments_m; + std::vector used_filaments_g; float max_percent = 0.0f; if (m_view_type == EViewType::FeatureType) { @@ -4276,10 +4332,73 @@ void GCodeViewer::render_legend() const times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); percents.push_back(percent); max_percent = std::max(max_percent, percent); + auto [used_filament_m, used_filament_g] = used_filament_per_role(role); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); } } - offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); + std::string longest_percentage_string; + for (double item : percents) { + char buffer[64]; + ::sprintf(buffer, "%.2f %%", item); + if (::strlen(buffer) > longest_percentage_string.length()) + longest_percentage_string = buffer; + } + longest_percentage_string += " "; + if (_u8L("Percentage").length() > longest_percentage_string.length()) + longest_percentage_string = _u8L("Percentage"); + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size); + } + + // get used filament (meters and grams) from used volume in respect to the active extruder + auto get_used_filament_from_volume = [imperial_units](double volume, int extruder_id) { + const std::vector& filament_presets = wxGetApp().preset_bundle->filament_presets; + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + + double koef = imperial_units ? 1.0/ObjectManipulation::in_to_mm : 0.001; + + std::pair ret = { 0.0, 0.0 }; + if (const Preset* filament_preset = filaments.find_preset(filament_presets[extruder_id], false)) { + double filament_radius = 0.5 * filament_preset->config.opt_float("filament_diameter", 0); + double s = PI * sqr(filament_radius); + ret.first = volume / s * koef; + double filament_density = filament_preset->config.opt_float("filament_density", 0); + ret.second = volume * filament_density * 0.001; + } + return ret; + }; + + if (m_view_type == EViewType::Tool) { + // calculate used filaments data + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) + continue; + double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); + + auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); + } + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); } // extrusion paths section -> title @@ -4287,7 +4406,7 @@ void GCodeViewer::render_legend() const { case EViewType::FeatureType: { - append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); + append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); break; } case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } @@ -4296,7 +4415,11 @@ void GCodeViewer::render_legend() const case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } + case EViewType::Tool: + { + append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); + break; + } case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } @@ -4310,9 +4433,9 @@ void GCodeViewer::render_legend() const ExtrusionRole role = m_roles[i]; if (role >= erCount) continue; - bool visible = is_visible(role); + const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { + visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { Extrusions* extrusions = const_cast(&m_extrusions); extrusions->role_visibility_flags = visible ? extrusions->role_visibility_flags & ~(1 << role) : extrusions->role_visibility_flags | (1 << role); // update buffers' render paths @@ -4334,16 +4457,33 @@ void GCodeViewer::render_legend() const case EViewType::Tool: { // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); + size_t i = 0; + for (unsigned char extruder_id : m_extruder_ids) { + append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), + true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); + i++; } break; } case EViewType::ColorPrint: { +#if ENABLE_SCROLLABLE_LEGEND const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + size_t total_items = 1; + for (unsigned char i : m_extruder_ids) { + total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); + } + + const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + + // add scrollable region, if needed + if (need_scrollable) + ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); +#else + const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; +#endif // ENABLE_SCROLLABLE_LEGEND if (m_extruders_count == 1) { // single extruder use case - std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); @@ -4366,7 +4506,7 @@ void GCodeViewer::render_legend() const else { // multi extruder use case // shows only extruders actually used for (unsigned char i : m_extruder_ids) { - std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); @@ -4392,6 +4532,10 @@ void GCodeViewer::render_legend() const } } } +#if ENABLE_SCROLLABLE_LEGEND + if (need_scrollable) + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND break; } @@ -4417,10 +4561,11 @@ void GCodeViewer::render_legend() const Color color1; Color color2; Times times; + std::pair used_filament {0.0f, 0.0f}; }; using PartialTimes = std::vector; - auto generate_partial_times = [this](const TimesList& times) { + auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; @@ -4430,6 +4575,7 @@ void GCodeViewer::render_legend() const last_color[i] = m_tool_colors[i]; } int last_extruder_id = 1; + int color_change_idx = 0; for (const auto& time_rec : times) { switch (time_rec.first) { @@ -4445,14 +4591,14 @@ void GCodeViewer::render_legend() const case CustomGCode::ColorChange: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) }); items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); last_color[it->extruder - 1] = decode_color(it->color); last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); break; } @@ -4463,7 +4609,7 @@ void GCodeViewer::render_legend() const return items; }; - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -4482,7 +4628,7 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui](const Color& color, const std::array& offsets, const Times& times) { + auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -4498,9 +4644,19 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(times.second))); ImGui::SameLine(offsets[1]); imgui.text(short_time(get_time_dhms(times.first))); + if (used_filament.first > 0.0f) { + char buffer[64]; + ImGui::SameLine(offsets[2]); + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); + imgui.text(buffer); + + ImGui::SameLine(offsets[3]); + ::sprintf(buffer, "%.2f g", used_filament.second); + imgui.text(buffer); + } }; - PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times); + PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); if (!partial_times.empty()) { labels.clear(); times.clear(); @@ -4514,15 +4670,34 @@ void GCodeViewer::render_legend() const } times.push_back(short_time(get_time_dhms(item.times.second))); } - offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size); + + + std::string longest_used_filament_string; + for (const PartialTime& item : partial_times) { + if (item.used_filament.first > 0.0f) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + } + + offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); +#if ENABLE_SCROLLABLE_LEGEND + const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + if (need_scrollable) + // add scrollable region + ImGui::BeginChild("events", { -1.0f, child_height }, false); +#endif // ENABLE_SCROLLABLE_LEGEND + for (const PartialTime& item : partial_times) { switch (item.type) { case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times); + append_print(item.color1, offsets, item.times, item.used_filament); break; } case PartialTime::EType::Pause: { @@ -4537,6 +4712,11 @@ void GCodeViewer::render_legend() const } } } + +#if ENABLE_SCROLLABLE_LEGEND + if (need_scrollable) + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND } } @@ -4680,10 +4860,14 @@ void GCodeViewer::render_legend() const } // total estimated printing time section +#if ENABLE_SCROLLABLE_LEGEND + if (show_estimated_time) { +#else if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { ImGui::Spacing(); +#endif // ENABLE_SCROLLABLE_LEGEND ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); ImGui::Separator(); @@ -4693,12 +4877,12 @@ void GCodeViewer::render_legend() const ImGui::AlignTextToFramePadding(); switch (m_time_estimate_mode) { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: + case PrintEstimatedStatistics::ETimeMode::Normal: { imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); break; } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + case PrintEstimatedStatistics::ETimeMode::Stealth: { imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); break; @@ -4708,18 +4892,18 @@ void GCodeViewer::render_legend() const ImGui::SameLine(); imgui.text(short_time(get_time_dhms(time_mode.time))); - auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { bool show = false; - for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { if (i != static_cast(mode) && - short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { show = true; break; } } - if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + if (show && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { if (imgui.button(label)) { - *const_cast(&m_time_estimate_mode) = mode; + *const_cast(&m_time_estimate_mode) = mode; wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } @@ -4727,12 +4911,12 @@ void GCodeViewer::render_legend() const }; switch (m_time_estimate_mode) { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + case PrintEstimatedStatistics::ETimeMode::Normal: { + show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); break; } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + case PrintEstimatedStatistics::ETimeMode::Stealth: { + show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); break; } default : { assert(false); break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 2ccda6f5de..112c681d48 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -696,8 +696,8 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; - PrintEstimatedTimeStatistics m_time_statistics; - PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal }; + PrintEstimatedStatistics m_print_statistics; + PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b81b7f58a3..39214221f8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3162,7 +3162,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } if (m_gizmos.on_mouse(evt)) { - if (wxWindow::FindFocus() != this->m_canvas) + if (wxWindow::FindFocus() != m_canvas) // Grab keyboard focus for input in gizmo dialogs. m_canvas->SetFocus(); @@ -3185,7 +3185,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_move_start_threshold_position_2D_as_invalid(); } - if (evt.ButtonDown() && wxWindow::FindFocus() != this->m_canvas) + if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas) // Grab keyboard focus on any mouse click event. m_canvas->SetFocus(); @@ -4785,8 +4785,16 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) if (m_canvas == nullptr && m_context == nullptr) return; +#if ENABLE_SCROLLABLE_LEGEND + const std::array new_size = { w, h }; + if (m_old_size == new_size) + return; + + m_old_size = new_size; +#endif // ENABLE_SCROLLABLE_LEGEND + auto *imgui = wxGetApp().imgui(); - imgui->set_display_size((float)w, (float)h); + imgui->set_display_size(static_cast(w), static_cast(h)); const float font_size = 1.5f * wxGetApp().em_unit(); #if ENABLE_RETINA_GL imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); @@ -4794,6 +4802,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); #endif +#if ENABLE_SCROLLABLE_LEGEND + this->request_extra_frame(); +#endif // ENABLE_SCROLLABLE_LEGEND + // ensures that this canvas is current _set_current(); } @@ -4834,8 +4846,7 @@ void GLCanvas3D::_update_camera_zoom(double zoom) void GLCanvas3D::_refresh_if_shown_on_screen() { - if (_is_shown_on_screen()) - { + if (_is_shown_on_screen()) { const Size& cnv_size = get_canvas_size(); _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); @@ -5940,7 +5951,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c { if (layerm->slices.surfaces.empty()) continue; - const PrintRegionConfig& cfg = layerm->region()->config(); + const PrintRegionConfig& cfg = layerm->region().config(); if (cfg.perimeter_extruder.value == m_selected_extruder || cfg.infill_extruder.value == m_selected_extruder || cfg.solid_infill_extruder.value == m_selected_extruder ) { @@ -5963,7 +5974,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c for (const LayerRegion *layerm : layer->regions()) { if (is_selected_separate_extruder) { - const PrintRegionConfig& cfg = layerm->region()->config(); + const PrintRegionConfig& cfg = layerm->region().config(); if (cfg.perimeter_extruder.value != m_selected_extruder || cfg.infill_extruder.value != m_selected_extruder || cfg.solid_infill_extruder.value != m_selected_extruder) @@ -5971,7 +5982,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } if (ctxt.has_perimeters) _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0)); + volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. @@ -5980,8 +5991,8 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, volume(idx_layer, is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config().solid_infill_extruder : - layerm->region()->config().infill_extruder, + layerm->region().config().solid_infill_extruder : + layerm->region().config().infill_extruder, 1)); } } @@ -6206,7 +6217,7 @@ void GLCanvas3D::_load_sla_shells() #else v.indexed_vertex_array.load_mesh(mesh); #endif // ENABLE_SMOOTH_NORMALS - v.indexed_vertex_array.finalize_geometry(this->m_initialized); + v.indexed_vertex_array.finalize_geometry(m_initialized); v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0)); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9e9a2501e1..0e1bb7229c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -474,6 +474,10 @@ private: Model* m_model; BackgroundSlicingProcess *m_process; +#if ENABLE_SCROLLABLE_LEGEND + std::array m_old_size{ 0, 0 }; +#endif // ENABLE_SCROLLABLE_LEGEND + // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8b091e7b8a..803ab5a143 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,7 @@ #include "UnsavedChangesDialog.hpp" #include "SavePresetDialog.hpp" #include "PrintHostDialogs.hpp" +#include "DesktopIntegrationDialog.hpp" #include "BitmapCache.hpp" @@ -633,8 +635,17 @@ void GUI_App::post_init() //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. this->mainframe->load_config_file(this->init_params->load_configs.back()); // If loading a 3MF file, the config is loaded from the last one. - if (! this->init_params->input_files.empty()) - this->plater()->load_files(this->init_params->input_files, true, true); + if (!this->init_params->input_files.empty()) { + const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); + if (!res.empty() && this->init_params->input_files.size() == 1) { + // Update application titlebar when opening a project file + const std::string& filename = this->init_params->input_files.front(); + if (boost::algorithm::iends_with(filename, ".amf") || + boost::algorithm::iends_with(filename, ".amf.xml") || + boost::algorithm::iends_with(filename, ".3mf")) + this->plater()->set_project_filename(filename); + } + } if (! this->init_params->extra_config.empty()) this->mainframe->load_config(this->init_params->extra_config); } @@ -1632,6 +1643,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for updates"), _L("Check for configuration updates")); +#ifdef __linux__ + if (DesktopIntegrationDialog::integration_possible()) + local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); +#endif local_menu->AppendSeparator(); } local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + @@ -1672,6 +1687,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) case ConfigMenuUpdate: check_updates(true); break; +#ifdef __linux__ + case ConfigMenuDesktopIntegration: + show_desktop_integration_dialog(); + break; +#endif case ConfigMenuTakeSnapshot: // Take a configuration snapshot. #if ENABLE_PROJECT_DIRTY_STATE @@ -2121,6 +2141,15 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage return res; } +void GUI_App::show_desktop_integration_dialog() +{ +#ifdef __linux__ + //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + DesktopIntegrationDialog dialog(mainframe); + dialog.ShowModal(); +#endif //__linux__ +} + #if ENABLE_THUMBNAIL_GENERATOR_DEBUG void GUI_App::gcode_thumbnails_debug() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index d1df4212b8..2613c51b1f 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -76,6 +76,7 @@ enum ConfigMenuIDs { ConfigMenuSnapshots, ConfigMenuTakeSnapshot, ConfigMenuUpdate, + ConfigMenuDesktopIntegration, ConfigMenuPreferences, ConfigMenuModeSimple, ConfigMenuModeAdvanced, @@ -276,6 +277,7 @@ public: void open_web_page_localized(const std::string &http_address); bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); + void show_desktop_integration_dialog(); #if ENABLE_THUMBNAIL_GENERATOR_DEBUG // temporary and debug only -> extract thumbnails from selected gcode and save them as png files diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f687dc38e8..4bf3bdfd19 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2476,28 +2476,22 @@ void ObjectList::unselect_objects() m_prevent_list_events = false; } -void ObjectList::select_current_object(int idx) +void ObjectList::select_object_item(bool is_msr_gizmo) { - m_prevent_list_events = true; - UnselectAll(); - if (idx >= 0) - Select(m_objects_model->GetItemById(idx)); - part_selection_changed(); - m_prevent_list_events = false; -} + if (wxDataViewItem item = GetSelection()) { + ItemType type = m_objects_model->GetItemType(item); + bool is_volume_item = type == itVolume || type == itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume; + if (is_msr_gizmo && is_volume_item || type == itObject) + return; -void ObjectList::select_current_volume(int idx, int vol_idx) -{ - if (vol_idx < 0) { - select_current_object(idx); - return; + if (wxDataViewItem obj_item = m_objects_model->GetTopParent(item)) { + m_prevent_list_events = true; + UnselectAll(); + Select(obj_item); + part_selection_changed(); + m_prevent_list_events = false; + } } - m_prevent_list_events = true; - UnselectAll(); - if (idx >= 0) - Select(m_objects_model->GetItemByVolumeId(idx, vol_idx)); - part_selection_changed(); - m_prevent_list_events = false; } static void update_selection(wxDataViewItemArray& sels, ObjectList::SELECTION_MODE mode, ObjectDataViewModel* model) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 5812e26f75..8bcfec11c4 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -291,10 +291,9 @@ public: // #ys_FIXME_to_delete // Unselect all objects in the list on c++ side void unselect_objects(); - // Select current object in the list on c++ side - void select_current_object(int idx); - // Select current volume in the list on c++ side - void select_current_volume(int idx, int vol_idx); + // Select object item in the ObjectList, when some gizmo is activated + // "is_msr_gizmo" indicates if Move/Scale/Rotate gizmo was activated + void select_object_item(bool is_msr_gizmo); // Remove objects/sub-object from the list void remove(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ea289bb146..52223b3d47 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -643,7 +643,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (sla_print_technology) m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times); else { - auto print_mode_stat = m_gcode_result->time_statistics.modes.front(); + auto print_mode_stat = m_gcode_result->print_statistics.modes.front(); m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 2f1396d634..bd02cbbbb7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -1084,8 +1085,10 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) return; size_t idx = get_gizmo_idx_from_mouse(mouse_pos); - if (idx != Undefined && m_gizmos[idx]->is_activable() && m_hover == idx) + if (idx != Undefined && m_gizmos[idx]->is_activable() && m_hover == idx) { activate_gizmo(m_current == idx ? Undefined : (EType)idx); + wxGetApp().obj_list()->select_object_item((EType)idx <= Rotate); + } } std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 17657948e6..1bcb93de0d 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -82,7 +82,13 @@ enum class NotificationType // Notification emitted by Print::validate PrintValidateWarning, // Notification telling user to quit SLA supports manual editing - QuitSLAManualMode + QuitSLAManualMode, + // Desktop integration basic info + DesktopIntegrationSuccess, + DesktopIntegrationFail, + UndoDesktopIntegrationSuccess, + UndoDesktopIntegrationFail + }; class NotificationManager @@ -514,6 +520,14 @@ private: "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, _u8L("This model doesn't allow to automatically add the color changes") }, + {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + _u8L("Desktop integration was successful.") }, + {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + _u8L("Desktop integration failed.") }, + {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + _u8L("Undo desktop integration was successful.") }, + {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + _u8L("Undo desktop integration failed.") }, //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 11a52d6485..96fe94e0ff 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -228,7 +228,7 @@ public: int config_type() const throw() { return m_config_type; } const t_opt_map& opt_map() const throw() { return m_opt_map; } - void set_config_category_and_type(const wxString &category, int type) { this->m_config_category = category; this->m_config_type = type; } + void set_config_category_and_type(const wxString &category, int type) { m_config_category = category; m_config_type = type; } void set_config(DynamicPrintConfig* config) { m_config = config; m_modelconfig = nullptr; } Option get_option(const std::string& opt_key, int opt_index = -1); Line create_single_option_line(const std::string& title, const wxString& path = wxEmptyString, int idx = -1) /*const*/{ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c57f4a9858..b2f335a54e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1175,10 +1175,10 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); wxString info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef, - (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef, - ps.total_wipe_tower_filament / /*1000*/koef) : - wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef); + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / koef, + (ps.total_used_filament - ps.total_wipe_tower_filament) / koef, + ps.total_wipe_tower_filament / koef) : + wxString::Format("%.2f", ps.total_used_filament / koef); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f; @@ -1206,7 +1206,7 @@ void Sidebar::update_sliced_info_sizer() filament_weight = ps.total_weight; else { double filament_density = filament_preset->config.opt_float("filament_density", 0); - filament_weight = filament.second * filament_density * 2.4052f * 0.001; // assumes 1.75mm filament diameter; + filament_weight = filament.second * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter; new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1); info_text += wxString::Format("\n%.2f", filament_weight); @@ -1360,7 +1360,8 @@ void Sidebar::update_ui_from_settings() update_sliced_info_sizer(); // update Cut gizmo, if it's open p->plater->canvas3D()->update_gizmos_on_off_state(); - p->plater->canvas3D()->request_extra_frame(); + p->plater->set_current_canvas_as_dirty(); + p->plater->get_current_canvas3D()->request_extra_frame(); } std::vector& Sidebar::combos_filament() @@ -1626,8 +1627,8 @@ struct Plater::priv void redo(); void undo_redo_to(size_t time_to_load); - void suppress_snapshots() { this->m_prevent_snapshots++; } - void allow_snapshots() { this->m_prevent_snapshots--; } + void suppress_snapshots() { m_prevent_snapshots++; } + void allow_snapshots() { m_prevent_snapshots--; } void process_validation_warning(const std::string& warning) const; @@ -1722,7 +1723,7 @@ struct Plater::priv bool can_split(bool to_objects) const; void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); - void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background); + ThumbnailsList generate_thumbnails(const ThumbnailsParams& params); void bring_instance_forward() const; @@ -1798,15 +1799,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); background_process.set_gcode_result(&gcode_result); - background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) - { - std::packaged_task task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) { - generate_thumbnails(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); - }); - std::future result = task.get_future(); - wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, show_bed, transparent_background); }); - result.wait(); - }); + background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params); }); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); background_process.set_export_began_event(EVT_EXPORT_BEGAN); @@ -3850,17 +3843,17 @@ void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsig view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background); } -void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) +ThumbnailsList Plater::priv::generate_thumbnails(const ThumbnailsParams& params) { - thumbnails.clear(); - for (const Vec2d& size : sizes) - { + ThumbnailsList thumbnails; + for (const Vec2d& size : params.sizes) { thumbnails.push_back(ThumbnailData()); Point isize(size); // round to ints - generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, show_bed, transparent_background); + generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), params.printable_only, params.parts_only, params.show_bed, params.transparent_background); if (!thumbnails.back().is_valid()) thumbnails.pop_back(); } + return thumbnails; } wxString Plater::priv::get_project_filename(const wxString& extension) const @@ -4244,9 +4237,9 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::take_snapshot(const std::string& snapshot_name) { - if (this->m_prevent_snapshots > 0) + if (m_prevent_snapshots > 0) return; - assert(this->m_prevent_snapshots >= 0); + assert(m_prevent_snapshots >= 0); UndoRedo::SnapshotData snapshot_data; snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) @@ -5850,7 +5843,7 @@ wxString Plater::get_project_filename(const wxString& extension) const void Plater::set_project_filename(const wxString& filename) { - return p->set_project_filename(filename); + p->set_project_filename(filename); } bool Plater::is_export_gcode_scheduled() const diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index c6a3006b71..7630c44bbc 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -134,7 +134,7 @@ void PresetComboBox::OnSelect(wxCommandEvent& evt) auto marker = reinterpret_cast(this->GetClientData(selected_item)); if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) - this->SetSelection(this->m_last_selected); + this->SetSelection(m_last_selected); else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty())) { m_last_selected = selected_item; on_selection_changed(selected_item); @@ -698,7 +698,7 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) auto marker = reinterpret_cast(this->GetClientData(selected_item)); if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { - this->SetSelection(this->m_last_selected); + this->SetSelection(m_last_selected); evt.StopPropagation(); if (marker == LABEL_ITEM_MARKER) return; @@ -715,8 +715,8 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) } return; } - else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty()) - this->m_last_selected = selected_item; + else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || m_last_selected != selected_item || m_collection->current_is_dirty()) + m_last_selected = selected_item; evt.Skip(); } @@ -973,7 +973,7 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt) auto marker = reinterpret_cast(this->GetClientData(selected_item)); if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) { - this->SetSelection(this->m_last_selected); + this->SetSelection(m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) wxTheApp->CallAfter([this]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b239c29756..5920ab5e39 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -940,7 +940,7 @@ void Tab::update_visibility() page->update_visibility(m_mode, page.get() == m_active_page); rebuild_page_tree(); - if (this->m_type == Preset::TYPE_SLA_PRINT) + if (m_type == Preset::TYPE_SLA_PRINT) update_description_lines(); Layout(); @@ -3150,8 +3150,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (preset_name.empty()) { if (delete_current) { // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); + const std::deque &presets = m_presets->get_presets(); + size_t idx_current = m_presets->get_idx_selected(); // Find the next visible preset. size_t idx_new = idx_current + 1; if (idx_new < presets.size()) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 697c1209de..6d6753ea29 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -755,7 +755,7 @@ namespace UndoRedo { template std::shared_ptr& ImmutableObjectHistory::shared_ptr(StackImpl &stack) { - if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) { + if (m_shared_object.get() == nullptr && ! m_serialized.empty()) { // Deserialize the object. std::istringstream iss(m_serialized); { @@ -897,7 +897,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU this->load_mutable_object(gizmos.id(), gizmos); // Sort the volumes so that we may use binary search. std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); - this->m_active_snapshot_time = timestamp; + m_active_snapshot_time = timestamp; assert(this->valid()); } diff --git a/t/perimeters.t b/t/perimeters.t index 3d3fd3819c..d3f96f122d 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -394,7 +394,7 @@ use Slic3r::Test; }); return scalar keys %z_with_bridges; }; - ok $test->(Slic3r::Test::init_print('V', config => $config)) == 2, + ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, 'no overhangs printed with bridge speed'; # except for the two internal solid layers above void ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 2, 'overhangs printed with bridge speed'; diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 222e94d990..8e311282e0 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -456,7 +456,7 @@ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacin }); // Shrink the initial expolygon a bit, this simulates the infill / perimeter overlap that we usually apply. - ExPolygons uncovered = diff_ex(offset(expolygon, - float(0.2 * scale_(flow_spacing))), grown_paths, true); + ExPolygons uncovered = diff_ex(offset(expolygon, - float(0.2 * scale_(flow_spacing))), grown_paths, ApplySafetyOffset::Yes); // ignore very small dots const double scaled_flow_spacing = std::pow(scale_(flow_spacing), 2); diff --git a/tests/libslic3r/test_elephant_foot_compensation.cpp b/tests/libslic3r/test_elephant_foot_compensation.cpp index a1c23f4a97..09ad33f418 100644 --- a/tests/libslic3r/test_elephant_foot_compensation.cpp +++ b/tests/libslic3r/test_elephant_foot_compensation.cpp @@ -15,22 +15,6 @@ using namespace Slic3r; namespace Slic3r { ClipperLib::Path mittered_offset_path_scaled(const Points& contour, const std::vector& deltas, double miter_limit); - -#if 0 - static Points mittered_offset_path_scaled_points(const Points& contour, const std::vector& deltas, double miter_limit) - { - Points out; - ClipperLib::Path scaled = mittered_offset_path_scaled(contour, deltas, miter_limit); - for (ClipperLib::IntPoint& pt : scaled) { - pt.X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; - pt.Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; - pt.X >>= CLIPPER_OFFSET_POWER_OF_2; - pt.Y >>= CLIPPER_OFFSET_POWER_OF_2; - out.emplace_back(coord_t(pt.x()), coord_t(pt.y())); - } - return out; - } -#endif } static ExPolygon spirograph_gear_1mm() diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index d45e37fb18..f2c78cacec 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -5,6 +5,112 @@ using namespace Slic3r; +SCENARIO("Converted Perl tests", "[Polygon]") { + GIVEN("ccw_square") { + Polygon ccw_square{ { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } }; + Polygon cw_square(ccw_square); + cw_square.reverse(); + + THEN("ccw_square is valid") { + REQUIRE(ccw_square.is_valid()); + } + THEN("cw_square is valid") { + REQUIRE(cw_square.is_valid()); + } + THEN("ccw_square.area") { + REQUIRE(ccw_square.area() == 100 * 100); + } + THEN("cw_square.area") { + REQUIRE(cw_square.area() == - 100 * 100); + } + THEN("ccw_square.centroid") { + REQUIRE(ccw_square.centroid() == Point { 150, 150 }); + } + THEN("cw_square.centroid") { + REQUIRE(cw_square.centroid() == Point { 150, 150 }); + } + THEN("ccw_square.contains_point(150, 150)") { + REQUIRE(ccw_square.contains({ 150, 150 })); + } + THEN("cw_square.contains_point(150, 150)") { + REQUIRE(cw_square.contains({ 150, 150 })); + } + THEN("conversion to lines") { + REQUIRE(ccw_square.lines() == Lines{ + { { 100, 100 }, { 200, 100 } }, + { { 200, 100 }, { 200, 200 } }, + { { 200, 200 }, { 100, 200 } }, + { { 100, 200 }, { 100, 100 } } }); + } + THEN("split_at_first_point") { + REQUIRE(ccw_square.split_at_first_point() == Polyline { ccw_square[0], ccw_square[1], ccw_square[2], ccw_square[3], ccw_square[0] }); + } + THEN("split_at_index(2)") { + REQUIRE(ccw_square.split_at_index(2) == Polyline { ccw_square[2], ccw_square[3], ccw_square[0], ccw_square[1], ccw_square[2] }); + } + THEN("split_at_vertex(ccw_square[2])") { + REQUIRE(ccw_square.split_at_vertex(ccw_square[2]) == Polyline { ccw_square[2], ccw_square[3], ccw_square[0], ccw_square[1], ccw_square[2] }); + } + THEN("is_counter_clockwise") { + REQUIRE(ccw_square.is_counter_clockwise()); + } + THEN("! is_counter_clockwise") { + REQUIRE(! cw_square.is_counter_clockwise()); + } + THEN("make_counter_clockwise") { + cw_square.make_counter_clockwise(); + REQUIRE(cw_square.is_counter_clockwise()); + } + THEN("make_counter_clockwise^2") { + cw_square.make_counter_clockwise(); + cw_square.make_counter_clockwise(); + REQUIRE(cw_square.is_counter_clockwise()); + } + THEN("first_point") { + REQUIRE(&ccw_square.first_point() == &ccw_square.points.front()); + } + } + GIVEN("Triangulating hexagon") { + Polygon hexagon{ { 100, 0 } }; + for (size_t i = 1; i < 6; ++ i) { + Point p = hexagon.points.front(); + p.rotate(PI / 3 * i); + hexagon.points.emplace_back(p); + } + Polygons triangles; + hexagon.triangulate_convex(&triangles); + THEN("right number of triangles") { + REQUIRE(triangles.size() == 4); + } + THEN("all triangles are ccw") { + auto it = std::find_if(triangles.begin(), triangles.end(), [](const Polygon &tri) { return tri.is_clockwise(); }); + REQUIRE(it == triangles.end()); + } + } + GIVEN("General triangle") { + Polygon polygon { { 50000000, 100000000 }, { 300000000, 102000000 }, { 50000000, 104000000 } }; + Line line { { 175992032, 102000000 }, { 47983964, 102000000 } }; + Point intersection; + bool has_intersection = polygon.intersection(line, &intersection); + THEN("Intersection with line") { + REQUIRE(has_intersection); + REQUIRE(intersection == Point { 50000000, 102000000 }); + } + } +} + +TEST_CASE("Centroid of Trapezoid must be inside", "[Polygon][Utils]") +{ + Slic3r::Polygon trapezoid { + { 4702134, 1124765853 }, + { -4702134, 1124765853 }, + { -9404268, 1049531706 }, + { 9404268, 1049531706 }, + }; + Point centroid = trapezoid.centroid(); + CHECK(trapezoid.contains(centroid)); +} + // This test currently only covers remove_collinear_points. // All remaining tests are to be ported from xs/t/06_polygon.t diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t index 779c7deeca..7bbcd5356b 100644 --- a/xs/t/06_polygon.t +++ b/xs/t/06_polygon.t @@ -3,11 +3,8 @@ use strict; use warnings; -use List::Util qw(first); use Slic3r::XS; -use Test::More tests => 21; - -use constant PI => 4 * atan2(1, 1); +use Test::More tests => 3; my $square = [ # ccw [100, 100], @@ -17,81 +14,8 @@ my $square = [ # ccw ]; my $polygon = Slic3r::Polygon->new(@$square); -my $cw_polygon = $polygon->clone; -$cw_polygon->reverse; - -ok $polygon->is_valid, 'is_valid'; -is_deeply $polygon->pp, $square, 'polygon roundtrip'; - is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed'; isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed'; - -my $lines = $polygon->lines; -is_deeply [ map $_->pp, @$lines ], [ - [ [100, 100], [200, 100] ], - [ [200, 100], [200, 200] ], - [ [200, 200], [100, 200] ], - [ [100, 200], [100, 100] ], -], 'polygon lines'; - -is_deeply $polygon->split_at_first_point->pp, [ @$square[0,1,2,3,0] ], 'split_at_first_point'; -is_deeply $polygon->split_at_index(2)->pp, [ @$square[2,3,0,1,2] ], 'split_at_index'; -is_deeply $polygon->split_at_vertex(Slic3r::Point->new(@{$square->[2]}))->pp, [ @$square[2,3,0,1,2] ], 'split_at'; -is $polygon->area, 100*100, 'area'; - -ok $polygon->is_counter_clockwise, 'is_counter_clockwise'; -ok !$cw_polygon->is_counter_clockwise, 'is_counter_clockwise'; -{ - my $clone = $polygon->clone; - $clone->reverse; - ok !$clone->is_counter_clockwise, 'is_counter_clockwise'; - $clone->make_counter_clockwise; - ok $clone->is_counter_clockwise, 'make_counter_clockwise'; - $clone->make_counter_clockwise; - ok $clone->is_counter_clockwise, 'make_counter_clockwise'; -} - ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point'; -ok $polygon->contains_point(Slic3r::Point->new(150,150)), 'ccw contains_point'; -ok $cw_polygon->contains_point(Slic3r::Point->new(150,150)), 'cw contains_point'; - -{ - my @points = (Slic3r::Point->new(100,0)); - foreach my $i (1..5) { - my $point = $points[0]->clone; - $point->rotate(PI/3*$i, [0,0]); - push @points, $point; - } - my $hexagon = Slic3r::Polygon->new(@points); - my $triangles = $hexagon->triangulate_convex; - is scalar(@$triangles), 4, 'right number of triangles'; - ok !(defined first { $_->is_clockwise } @$triangles), 'all triangles are ccw'; -} - -{ - is_deeply $polygon->centroid->pp, [150,150], 'centroid'; -} - -{ - my $polygon = Slic3r::Polygon->new( - [50000000, 100000000], - [300000000, 102000000], - [50000000, 104000000], - ); - my $line = Slic3r::Line->new([175992032,102000000], [47983964,102000000]); - my $intersection = $polygon->intersection($line); - is_deeply $intersection->pp, [50000000, 102000000], 'polygon-line intersection'; -} - -# this is not a test: this just demonstrates bad usage, where $polygon->clone gets -# DESTROY'ed before the derived object ($point), causing bad memory access -if (0) { - my $point; - { - $point = $polygon->clone->[0]; - } - $point->scale(2); -} - __END__ diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index c4640dcf4f..bae6a103e6 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -41,18 +41,6 @@ offset_ex(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = OUTPUT: RETVAL -Polygons -offset2(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta1 - const float delta2 - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset2(polygons, delta1, delta2, joinType, miterLimit); - OUTPUT: - RETVAL - ExPolygons offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons @@ -71,7 +59,7 @@ diff(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - RETVAL = diff(subject, clip, safety_offset); + RETVAL = diff(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); OUTPUT: RETVAL @@ -81,7 +69,7 @@ diff_ex(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - RETVAL = diff_ex(subject, clip, safety_offset); + RETVAL = diff_ex(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); OUTPUT: RETVAL @@ -100,7 +88,7 @@ intersection(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - RETVAL = intersection(subject, clip, safety_offset); + RETVAL = intersection(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); OUTPUT: RETVAL @@ -110,7 +98,7 @@ intersection_ex(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - RETVAL = intersection_ex(subject, clip, safety_offset); + RETVAL = intersection_ex(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); OUTPUT: RETVAL @@ -128,7 +116,7 @@ union(subject, safety_offset = false) Polygons subject bool safety_offset CODE: - RETVAL = union_(subject, safety_offset); + RETVAL = safety_offset ? union_safety_offset(subject) : union_(subject); OUTPUT: RETVAL @@ -137,7 +125,7 @@ union_ex(subject, safety_offset = false) Polygons subject bool safety_offset CODE: - RETVAL = union_ex(subject, safety_offset); + RETVAL = safety_offset ? union_safety_offset_ex(subject) : union_ex(subject); OUTPUT: RETVAL diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index 50ddfd9a19..b97e340bdb 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -10,7 +10,8 @@ // owned by Layer, no constructor/destructor Ref layer(); - Ref region(); + Ref region() + %code%{ RETVAL = &THIS->region(); %}; Ref slices() %code%{ RETVAL = &THIS->slices; %}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 9e632bd534..8aef9e7a3b 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -32,15 +32,11 @@ _constant() Ref config() %code%{ RETVAL = &THIS->config(); %}; - Ref print(); }; %name{Slic3r::Print::Object} class PrintObject { // owned by Print, no constructor/destructor - int region_count() - %code%{ RETVAL = THIS->print()->regions().size(); %}; - Ref print(); Ref model_object(); Ref config() @@ -99,11 +95,7 @@ _constant() %code%{ RETVAL = THIS->objects().size(); %}; PrintRegionPtrs* regions() - %code%{ RETVAL = const_cast(&THIS->regions_mutable()); %}; - Ref get_region(int idx) - %code%{ RETVAL = THIS->regions_mutable()[idx]; %}; - size_t region_count() - %code%{ RETVAL = THIS->regions().size(); %}; + %code%{ RETVAL = const_cast(&THIS->print_regions_mutable()); %}; bool step_done(PrintStep step) %code%{ RETVAL = THIS->is_step_done(step); %}; @@ -113,7 +105,7 @@ _constant() SV* filament_stats() %code%{ HV* hv = newHV(); - for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { + for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { // stringify extruder_id std::ostringstream ss; ss << it->first; @@ -123,7 +115,6 @@ _constant() RETVAL = newRV_noinc((SV*)hv); } %}; - double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); std::string output_filepath(std::string path = "")