Merge branch 'master' into fs_QuadricEdgeCollapse

This commit is contained in:
Filip Sykala 2021-07-07 16:52:10 +02:00
commit ed9152d004
107 changed files with 2066 additions and 808 deletions

View file

@ -1,3 +1,5 @@
min_slic3r_version = 2.3.2-alpha0
0.1.0 Added Ender-7, Sermoon D1, CR-10 SMART
min_slic3r_version = 2.3.1-beta
0.0.17 Updated start g-code. Added specific start g-code for straingauge printers. Improved output filename format. Added filament profile.
0.0.16 Updated CR6-SE start g-code. Added and updated filament profiles.

View file

@ -5,7 +5,7 @@
name = Creality
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 0.0.17
config_version = 0.1.0
# Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/
# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -86,6 +86,15 @@ 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 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:ENDER7]
name = Creality Ender-7
variants = 0.4
technology = FFF
family = ENDER
bed_model = ender7_bed.stl
bed_texture = ender7.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 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
variants = 0.4
@ -131,6 +140,15 @@ 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 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:CR10SMART]
name = Creality CR-10 SMART
variants = 0.4
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 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
variants = 0.4
@ -275,6 +293,15 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @
#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 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:SERMOOND1]
name = Creality Sermoon-D1
variants = 0.4
technology = FFF
family = SERMOON
bed_model = sermoond1_bed.stl
bed_texture = sermoond1.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 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.
@ -432,36 +459,36 @@ top_solid_layers = 4
[print:0.08mm SUPERDETAIL @CREALITY]
inherits = *0.08mm*
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
[print:0.10mm HIGHDETAIL @CREALITY]
inherits = *0.10mm*
renamed_from = "0.10mm HIGHDETAIL @ENDER3"
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
[print:0.12mm DETAIL @CREALITY]
inherits = *0.12mm*
renamed_from = "0.12mm DETAIL @ENDER3"
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
[print:0.16mm OPTIMAL @CREALITY]
inherits = *0.16mm*
renamed_from = "0.15mm OPTIMAL @ENDER3"; "0.15mm OPTIMAL @CREALITY"
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
[print:0.20mm NORMAL @CREALITY]
inherits = *0.20mm*
renamed_from = "0.20mm NORMAL @ENDER3"
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
[print:0.24mm DRAFT @CREALITY]
inherits = *0.24mm*
renamed_from = "0.24mm DRAFT @ENDER3"
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
[print:0.28mm SUPERDRAFT @CREALITY]
inherits = *0.28mm*
compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4
compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4
# When submitting new filaments please print the following temperature tower at 0.1mm layer height:
# https://www.thingiverse.com/thing:2615842
@ -960,6 +987,13 @@ max_print_height = 400
printer_model = ENDER6
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER6\nPRINTER_HAS_BOWDEN
[printer:Creality Ender-7]
inherits = *common*; *descendingz*
bed_shape = 5x5,245x5,245x245,5x245
max_print_height = 300
printer_model = ENDER7
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER7\nPRINTER_HAS_BOWDEN
[printer:Creality Ender-2]
inherits = *common*
renamed_from = "Creality ENDER-2"
@ -998,6 +1032,14 @@ max_print_height = 400
printer_model = CR6MAX
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_CR6MAX\nPRINTER_HAS_BOWDEN
[printer:Creality CR-10 SMART]
inherits = *common*; *straingauge*
retract_length = 6
bed_shape = 5x5,295x5,295x295,5x295
max_print_height = 400
printer_model = CR10SMART
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_CR10SMART\nPRINTER_HAS_BOWDEN
[printer:Creality CR-10 Mini]
inherits = *common*
retract_length = 6
@ -1118,3 +1160,11 @@ printer_notes = Don't remove the following keywords! These keywords are used in
#max_print_height = 400
#printer_model = CRXPRO
#printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_CRXPRO\nPRINTER_HAS_BOWDEN
[printer:Creality Sermoon-D1]
inherits = *common*; *descendingz*
retract_length = 1
bed_shape = 5x5,275x5,275x255,5x255
max_print_height = 310
printer_model = SERMOOND1
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_SERMOOND1\nPRINTER_HAS_BOWDEN

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="240mm" height="240mm" version="1.1" viewBox="0 0 240 240" xmlns="http://www.w3.org/2000/svg">
<rect x=".25" y=".25" width="239.5" height="239.5" fill="none" stroke="#fff" stroke-width=".5"/>
</svg>

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="270mm" height="250mm" version="1.1" viewBox="0 0 270 250" xmlns="http://www.w3.org/2000/svg">
<rect x=".25" y=".25" width="269.5" height="249.5" fill="none" stroke="#fff" stroke-width=".5"/>
</svg>

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

View file

@ -1,4 +1,6 @@
min_slic3r_version = 2.4.0-alpha0
1.4.0-alpha4 Decreased Area Fill (SL1S).
1.4.0-alpha3 Updated SL1S tilt times.
1.4.0-alpha2 Updated Prusa MINI machine limits.
1.4.0-alpha1 Added new SL1S resin profiles.
1.4.0-alpha0 Bumped up config version.

View file

@ -5,7 +5,7 @@
name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.4.0-alpha2
config_version = 1.4.0-alpha4
# Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/
changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -5440,20 +5440,6 @@ initial_exposure_time = 25
material_type = Tough
material_vendor = 3DM
[sla_material:Peopoly Clear Tough @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Tough
material_vendor = Peopoly
[sla_material:Peopoly White Tough @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Tough
material_vendor = Peopoly
[sla_material:Peopoly Deft White @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
@ -5568,20 +5554,6 @@ initial_exposure_time = 25
material_type = Tough
material_vendor = 3DM
[sla_material:Peopoly Clear Tough @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 25
material_type = Tough
material_vendor = Peopoly
[sla_material:Peopoly White Tough @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 25
material_type = Tough
material_vendor = Peopoly
[sla_material:Peopoly Deft White @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
@ -5696,20 +5668,6 @@ initial_exposure_time = 25
material_type = Tough
material_vendor = 3DM
[sla_material:Peopoly Clear Tough @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 25
material_type = Tough
material_vendor = Peopoly
[sla_material:Peopoly White Tough @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 25
material_type = Tough
material_vendor = Peopoly
[sla_material:Peopoly Deft White @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
@ -6346,7 +6304,7 @@ thumbnails = 16x16,220x124
bed_shape = 0x0,180x0,180x180,0x180
default_filament_profile = "Prusament PLA"
default_print_profile = 0.15mm QUALITY @MINI
gcode_flavor = marlinfirmware
gcode_flavor = marlin2
machine_max_acceleration_e = 5000
machine_max_acceleration_extruding = 1250
machine_max_acceleration_retracting = 1250
@ -6458,8 +6416,8 @@ display_pixels_y = 1620
display_width = 128
elefant_foot_compensation = 0.2
elefant_foot_min_width = 0.2
fast_tilt_time = 4
slow_tilt_time = 5.7
fast_tilt_time = 2.5
slow_tilt_time = 5
gamma_correction = 1
max_print_height = 150
min_exposure_time = 1
@ -6468,7 +6426,7 @@ min_initial_exposure_time = 1
max_initial_exposure_time = 300
printer_correction = 1,1,1
relative_correction = 1,1
area_fill = 50
area_fill = 45
# The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer.
[obsolete_presets]

View file

@ -1,64 +1,54 @@
<svg xmlns="http://www.w3.org/2000/svg" width="127mm" height="80mm" viewBox="0 0 360 226.8">
<rect width="360" height="226.77" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 1.41732283464567px"/>
<polyline points="283.5 206.2 283.5 283.5 0 283.5 0 0 283.5 0 283.5 154.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="206.2" x2="283.5" y2="206.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="180.4" x2="206.2" y2="180.4" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="335" y1="181.6" x2="541.2" y2="181.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="154.6" x2="283.5" y2="154.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="128.8" x2="283.5" y2="128.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="103.1" x2="283.5" y2="103.1" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="77.3" x2="283.5" y2="77.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="51.5" x2="283.5" y2="51.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line y1="25.8" x2="283.5" y2="25.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="257.7" y1="206.2" x2="257.7" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="257.7" x2="257.7" y2="154.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="231.9" y1="206.2" x2="231.9" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="231.9" x2="231.9" y2="154.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="206.2" y1="206.2" x2="206.2" y2="231.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="206.2" y1="180.4" x2="206.2" y2="206.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="206.2" x2="206.2" y2="180.4" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="180.4" y1="206.2" x2="180.4" y2="231.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="180.4" x2="180.4" y2="206.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="154.6" x2="154.6" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="128.8" x2="128.8" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="103.1" x2="103.1" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="77.3" x2="77.3" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="51.5" x2="51.5" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="25.8" x2="25.8" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<polyline points="283.5 154.6 283.5 0 566.9 0 566.9 283.5 283.5 283.5 283.5 206.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="206.2" x2="566.9" y2="206.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="154.6" x2="566.9" y2="154.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="128.8" x2="566.9" y2="128.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="103.1" x2="566.9" y2="103.1" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="77.3" x2="566.9" y2="77.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="51.5" x2="566.9" y2="51.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="283.5" y1="25.8" x2="566.9" y2="25.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="335" y1="180.4" x2="335" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="335" x2="335" y2="180.4" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="309.2" y1="206.2" x2="309.2" y2="283.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<line x1="309.2" x2="309.2" y2="154.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.283464566929134px"/>
<path d="M212.8,173.1h6.3a5.1,5.1,0,0,1,2.4.4,4.2,4.2,0,0,1,1.5,1.1,6.1,6.1,0,0,1,.7,1.4,7.7,7.7,0,0,1,.2,1.5,6.8,6.8,0,0,1-.2,1.4,6.1,6.1,0,0,1-.7,1.4,3.3,3.3,0,0,1-1.5,1.1,5.1,5.1,0,0,1-2.4.4h-3.8v5.4h-2.5Zm2.5,6.7h3.6l.8-.2a1.9,1.9,0,0,0,.8-.3,1.6,1.6,0,0,0,.6-.8,1.4,1.4,0,0,0,.2-1.1,1.5,1.5,0,0,0-.2-1.1,1.2,1.2,0,0,0-.5-.7l-.8-.4h-4.5Z" style="fill: #fff"/>
<path d="M226,173.1h6.7a5,5,0,0,1,3.5,1,3.3,3.3,0,0,1,1.1,2.8,5.1,5.1,0,0,1-.3,1.7l-.7,1.1-.8.5-.5.3h0l.6.2a1.8,1.8,0,0,1,.7.5c.3.2.4.6.6.9a5.9,5.9,0,0,1,.2,1.4,16.2,16.2,0,0,0,.1,2.2,3.5,3.5,0,0,0,.6,1.4h-2.6a1.7,1.7,0,0,1-.3-1V185a8.8,8.8,0,0,0-.2-1.6,2.8,2.8,0,0,0-.4-1.2,1.5,1.5,0,0,0-.9-.6,3.2,3.2,0,0,0-1.4-.3h-3.6v5.9H226Zm2.5,6.4h4a2.8,2.8,0,0,0,1.8-.6,1.8,1.8,0,0,0,.6-1.7,1.5,1.5,0,0,0-.2-1.1,1.5,1.5,0,0,0-.5-.6l-.8-.4h-4.9Z" style="fill: #fff"/>
<path d="M239.8,173.1h2.5v9.6a5,5,0,0,0,.4,1.3,2.4,2.4,0,0,0,1,1,4.1,4.1,0,0,0,1.9.4,4.3,4.3,0,0,0,1.9-.4,2.2,2.2,0,0,0,.9-1,2,2,0,0,0,.4-1.3,6.6,6.6,0,0,0,.1-1.4v-8.2h2.5v9a6.7,6.7,0,0,1-.4,2.4,6.6,6.6,0,0,1-1.2,1.7,6.4,6.4,0,0,1-1.8,1,9.8,9.8,0,0,1-4.8,0,5.2,5.2,0,0,1-1.8-1,4.5,4.5,0,0,1-1.1-1.7,5.1,5.1,0,0,1-.4-2.4Z" style="fill: #fff"/>
<path d="M255.8,182.5a3.9,3.9,0,0,0,.3,1.4,2.8,2.8,0,0,0,.7.9l1.2.5,1.4.2,1.3-.2,1-.5a1.8,1.8,0,0,0,.5-.7,1.7,1.7,0,0,0,.1-.7,1.7,1.7,0,0,0-1.1-1.8l-1.8-.5-2.4-.6-1.6-.7-.9-.8a4.7,4.7,0,0,1-.5-1,5.2,5.2,0,0,1-.2-1.1,4.1,4.1,0,0,1,.5-1.9,4.9,4.9,0,0,1,1.2-1.3,4.6,4.6,0,0,1,1.6-.7,6.3,6.3,0,0,1,1.9-.3l2.1.3a5.6,5.6,0,0,1,1.7.9,4.2,4.2,0,0,1,1.6,3.3h-2.5a2.3,2.3,0,0,0-.9-1.9,3.9,3.9,0,0,0-2.1-.6H258l-.8.3a.9.9,0,0,0-.6.6,1.6,1.6,0,0,0,.1,2l1.3.7h.6l1.2.4,1.3.3.9.2,1.2.6a3.8,3.8,0,0,1,.9.9l.5,1a5,5,0,0,1,.2,1.2,4.6,4.6,0,0,1-.5,2,4,4,0,0,1-1.3,1.3,3.7,3.7,0,0,1-1.8.8,6.4,6.4,0,0,1-2,.3,9.1,9.1,0,0,1-2.3-.3,6.1,6.1,0,0,1-1.8-.9,4,4,0,0,1-1.3-1.6,5.9,5.9,0,0,1-.5-2.2Z" style="fill: #fff"/>
<path d="M270.7,173.1h2.7l5.4,14.1h-2.6l-1.4-3.8h-5.6l-1.3,3.8h-2.6Zm-.8,8.5h4.3l-2.1-6.1H272Z" style="fill: #fff"/>
<path d="M287.3,182.5a2.4,2.4,0,0,0,.2,1.4,3.4,3.4,0,0,0,.8.9l1.1.5,1.4.2,1.4-.2.9-.5a1.8,1.8,0,0,0,.5-.7,1.9,1.9,0,0,0,.2-.7,1.7,1.7,0,0,0-1.1-1.8l-1.8-.5-2.4-.6a6.9,6.9,0,0,1-1.5-.7,3.9,3.9,0,0,1-1-.8l-.5-1a4.5,4.5,0,0,1-.1-1.1,4.1,4.1,0,0,1,.4-1.9,4.9,4.9,0,0,1,1.2-1.3,5.6,5.6,0,0,1,1.6-.7l1.9-.3,2.1.3a5.6,5.6,0,0,1,1.7.9,3.3,3.3,0,0,1,1.2,1.4,3.1,3.1,0,0,1,.4,1.9h-2.4a2.6,2.6,0,0,0-1-1.9,3.7,3.7,0,0,0-2.1-.6h-.9l-.8.3a1.2,1.2,0,0,0-.6.6,1.4,1.4,0,0,0-.3.9,1.6,1.6,0,0,0,.5,1.1,2.7,2.7,0,0,0,1.2.6l.6.2,1.2.3,1.4.4.9.2,1.2.6a2.4,2.4,0,0,1,.8.9,3.3,3.3,0,0,1,.6,1,4.1,4.1,0,0,1,.1,1.1,2.9,2.9,0,0,1-.5,2,3.3,3.3,0,0,1-1.2,1.4,7.1,7.1,0,0,1-1.8.8,7.5,7.5,0,0,1-2.1.2l-2.2-.2a5,5,0,0,1-1.9-1,4.4,4.4,0,0,1-1.2-1.5,4.6,4.6,0,0,1-.5-2.3Z" style="fill: #fff"/>
<path d="M298.4,173.1h2.5V185h7.2v2.2h-9.7Z" style="fill: #fff"/>
<path d="M309.3,175.6h1.4a2.8,2.8,0,0,0,1.2-.4,1.6,1.6,0,0,0,.9-.7,2.7,2.7,0,0,0,.6-1h1.8v13.8h-2.5v-9.8h-3.4Z" style="fill: #fff"/>
<path d="M217.2,160.5a4.7,4.7,0,0,1,3.5,1.5,5.6,5.6,0,0,1,.9,1.7,4.5,4.5,0,0,1,.4,2,4.8,4.8,0,0,1-.4,2,5.6,5.6,0,0,1-.9,1.7,5.8,5.8,0,0,1-1.5,1.1,5.2,5.2,0,0,1-4,0,5.8,5.8,0,0,1-1.5-1.1,5.6,5.6,0,0,1-.9-1.7,6.9,6.9,0,0,1-.3-2,6.4,6.4,0,0,1,.3-2,5.6,5.6,0,0,1,.9-1.7,4.7,4.7,0,0,1,3.5-1.5Zm0,1.4a3.5,3.5,0,0,0-1.4.3,2.6,2.6,0,0,0-.9.9,6.1,6.1,0,0,0-.5,1.2,6.8,6.8,0,0,0-.2,1.4,7.8,7.8,0,0,0,.2,1.4,6.1,6.1,0,0,0,.5,1.2,3.4,3.4,0,0,0,.9.8,2.5,2.5,0,0,0,1.4.4,2.3,2.3,0,0,0,1.4-.4,2.4,2.4,0,0,0,.9-.8,6.7,6.7,0,0,0,.6-1.2,7.1,7.1,0,0,0,.1-1.4,6.6,6.6,0,0,0-.1-1.4,4.5,4.5,0,0,0-.6-1.2,2,2,0,0,0-.9-.9A3.2,3.2,0,0,0,217.2,161.9Z" style="fill: #fff"/>
<path d="M223.5,160.7h4.8a4,4,0,0,1,2.4.7,2.7,2.7,0,0,1,.9,2,5.2,5.2,0,0,1-.7,1.9l-.6.4h-.4l.5.2.5.3a4.5,4.5,0,0,1,.5,1.6,8.1,8.1,0,0,0,.1,1.6,3.6,3.6,0,0,0,.4,1.1h-1.8a2.6,2.6,0,0,1-.3-.8v-1.9l-.3-.8-.6-.5h-3.7v4.1h-1.7Zm1.8,4.5h2.8a2,2,0,0,0,1.3-.4,1.4,1.4,0,0,0,.4-1.2,2.2,2.2,0,0,0-.1-.8.8.8,0,0,0-.4-.4c-.2-.2-.4-.2-.6-.3h-3.4Z" style="fill: #fff"/>
<path d="M233.4,160.7h1.7v9.9h-1.7Z" style="fill: #fff"/>
<path d="M245.8,170.6h-1.1l-.2-1.1-1.4,1.1a4.1,4.1,0,0,1-1.6.3,4.5,4.5,0,0,1-2-.4,4.2,4.2,0,0,1-1.5-1.1,5.6,5.6,0,0,1-.9-1.7,6.4,6.4,0,0,1-.3-2,6.9,6.9,0,0,1,.3-2,5.6,5.6,0,0,1,.9-1.7,8.3,8.3,0,0,1,1.5-1.1,4.5,4.5,0,0,1,2-.4l1.6.2a4.7,4.7,0,0,1,1.2.7,3.2,3.2,0,0,1,1,1,8.8,8.8,0,0,1,.5,1.5h-1.7l-.3-.8-.6-.7a1.9,1.9,0,0,0-.8-.3l-.9-.2a2.5,2.5,0,0,0-1.4.4,3.4,3.4,0,0,0-.9.8,6.1,6.1,0,0,0-.5,1.2,6.8,6.8,0,0,0-.2,1.4,5.9,5.9,0,0,0,.2,1.4,4.3,4.3,0,0,0,.5,1.2,2.6,2.6,0,0,0,.9.9,3.5,3.5,0,0,0,1.4.3,2.5,2.5,0,0,0,1.2-.2l.9-.5a4.1,4.1,0,0,0,.5-.9,4.3,4.3,0,0,0,.2-1.1h-2.7v-1.3h4.3Z" style="fill: #fff"/>
<path d="M247.7,160.7h1.8v9.9h-1.8Z" style="fill: #fff"/>
<path d="M251.5,160.7h1.9l4.5,7.3h0v-7.3h1.6v9.9h-1.8l-4.5-7.2h0v7.2h-1.7Z" style="fill: #fff"/>
<path d="M264.3,160.7h1.8l3.8,9.9h-1.8l-.9-2.6h-4l-.9,2.6h-1.8Zm-.6,6h3l-1.5-4.3h0Z" style="fill: #fff"/>
<path d="M270.9,160.7h1.7v8.4h5.1v1.5h-6.8Z" style="fill: #fff"/>
<path d="M319.6,182.5a2.5,2.5,0,0,0,.3,1.3,3.9,3.9,0,0,0,.8,1l1.1.5,1.4.2,1.4-.2a1.9,1.9,0,0,0,.9-.5,1.3,1.3,0,0,0,.6-.7,1.9,1.9,0,0,0,.1-.8,2.1,2.1,0,0,0-.3-1.2l-.8-.5-1.8-.5-2.5-.7a3.4,3.4,0,0,1-1.5-.6,2.6,2.6,0,0,1-.9-.9,2.1,2.1,0,0,1-.6-1,3.7,3.7,0,0,1-.1-1.1,3.4,3.4,0,0,1,.4-1.8,3.7,3.7,0,0,1,1.2-1.3,5.3,5.3,0,0,1,1.7-.8l1.9-.2a7.6,7.6,0,0,1,2.1.3,4.4,4.4,0,0,1,2.9,2.2,4.5,4.5,0,0,1,.4,2h-2.5a2.2,2.2,0,0,0-.9-1.9,3.7,3.7,0,0,0-2.1-.6h-.9l-.9.3-.6.6a1.9,1.9,0,0,0-.2.8,1.6,1.6,0,0,0,.4,1.2l1.3.7h.6l1.2.4,1.3.3.9.3a3.1,3.1,0,0,1,1.3.6,3,3,0,0,1,.8.8,3.7,3.7,0,0,1,.5,1.1,3.6,3.6,0,0,1,.2,1.1,4.2,4.2,0,0,1-.5,2,3.5,3.5,0,0,1-1.3,1.4,7.5,7.5,0,0,1-1.8.8l-2,.2a8.5,8.5,0,0,1-2.3-.3,6.6,6.6,0,0,1-1.9-.9,5.1,5.1,0,0,1-1.7-3.8Z" style="fill: #fff"/>
<path d="M210.4,162.2h6.2a5.3,5.3,0,0,1,2.5.4,3.6,3.6,0,0,1,1.4,1.1,2.7,2.7,0,0,1,.7,1.4,7.7,7.7,0,0,1,.2,1.5,7.8,7.8,0,0,1-.2,1.4,2.7,2.7,0,0,1-.7,1.4,4.9,4.9,0,0,1-1.4,1.1,6.9,6.9,0,0,1-2.5.4h-3.7v5.4h-2.5Zm2.5,6.6h4.4l.8-.4.6-.7a3.1,3.1,0,0,0,0-2.2,1.8,1.8,0,0,0-.5-.7l-.8-.4h-4.6Z" style="fill: #fff"/>
<path d="M223.6,162.2h6.7a4.7,4.7,0,0,1,3.4,1,3.6,3.6,0,0,1,1.2,2.8,4.5,4.5,0,0,1-.3,1.6,4.1,4.1,0,0,1-.7,1.1,2.1,2.1,0,0,1-.8.6l-.6.3h0l.7.2a1.2,1.2,0,0,1,.7.5,1.9,1.9,0,0,1,.5.9,3,3,0,0,1,.2,1.3,17.9,17.9,0,0,0,.2,2.3,3.5,3.5,0,0,0,.6,1.4h-2.7a2.9,2.9,0,0,1-.3-1v-1.1a9.7,9.7,0,0,0-.1-1.7,4.9,4.9,0,0,0-.4-1.1l-.9-.7-1.4-.2H226v5.8h-2.4Zm2.4,6.4h4.1a3,3,0,0,0,1.8-.6,2.5,2.5,0,0,0,.6-1.7,4.3,4.3,0,0,0-.2-1.1,1.8,1.8,0,0,0-.5-.7l-.8-.3h-5Z" style="fill: #fff"/>
<path d="M237.3,162.2h2.5v8.2c0,.4.1.9.1,1.4a2.8,2.8,0,0,0,.4,1.3,2.4,2.4,0,0,0,1,1,3.9,3.9,0,0,0,1.8.4,4.1,4.1,0,0,0,1.9-.4,2.4,2.4,0,0,0,1-1,2.8,2.8,0,0,0,.4-1.3c0-.5.1-1,.1-1.4v-8.2h2.4v9a4.5,4.5,0,0,1-.4,2.3,3,3,0,0,1-1.2,1.7,4.2,4.2,0,0,1-1.8,1.1,9.4,9.4,0,0,1-4.7,0,7.2,7.2,0,0,1-1.9-1.1,5.6,5.6,0,0,1-1.1-1.7,5.9,5.9,0,0,1-.4-2.3Z" style="fill: #fff"/>
<path d="M253.4,171.6a2,2,0,0,0,.2,1.3,3.9,3.9,0,0,0,.8,1l1.1.5,1.4.2,1.4-.2.9-.5a1.8,1.8,0,0,0,.5-.7,1.9,1.9,0,0,0,.2-.8,1.5,1.5,0,0,0-.4-1.1,1.6,1.6,0,0,0-.8-.6l-1.7-.5-2.5-.7-1.5-.6a3.9,3.9,0,0,1-1-.8,4.7,4.7,0,0,1-.5-1,4.9,4.9,0,0,1-.1-1.2,3.9,3.9,0,0,1,.4-1.8,3.7,3.7,0,0,1,1.2-1.3l1.7-.8,1.9-.2a6.4,6.4,0,0,1,2,.3,9.8,9.8,0,0,1,1.8.8,4.4,4.4,0,0,1,1.6,3.4h-2.5a2.2,2.2,0,0,0-.9-1.9,4.2,4.2,0,0,0-2.1-.6h-.9l-.9.3-.6.5a1.8,1.8,0,0,0,.2,2.1,3.4,3.4,0,0,0,1.2.6l.6.2,1.3.3,1.3.4.9.2,1.2.6.9.9a3.1,3.1,0,0,1,.5,1,4.5,4.5,0,0,1,.1,1.1,3.9,3.9,0,0,1-.4,2.1,5.3,5.3,0,0,1-1.3,1.3,4.8,4.8,0,0,1-1.8.8l-2.1.3a8.3,8.3,0,0,1-2.2-.3,6.6,6.6,0,0,1-1.9-.9,6.1,6.1,0,0,1-1.2-1.6,5.9,5.9,0,0,1-.5-2.2Z" style="fill: #fff"/>
<path d="M268.3,162.2h2.6l5.5,14.1h-2.7l-1.3-3.8h-5.6l-1.4,3.8h-2.5Zm-.9,8.4h4.3l-2.1-6h0Z" style="fill: #fff"/>
<path d="M284.8,171.6a3.1,3.1,0,0,0,.3,1.3,2.5,2.5,0,0,0,.8,1l1.1.5,1.4.2,1.4-.2.9-.5a1.8,1.8,0,0,0,.5-.7,1.9,1.9,0,0,0,.2-.8,2,2,0,0,0-.3-1.1,2.1,2.1,0,0,0-.8-.6l-1.8-.5-2.5-.7-1.5-.6a3.4,3.4,0,0,1-.9-.8,2.4,2.4,0,0,1-.5-1,5,5,0,0,1-.2-1.2,4.1,4.1,0,0,1,.5-1.8,4.7,4.7,0,0,1,1.1-1.3l1.7-.8,1.9-.2,2.1.3a4.4,4.4,0,0,1,2.9,2.2,4.8,4.8,0,0,1,.4,2H291a2.2,2.2,0,0,0-.9-1.9,3.9,3.9,0,0,0-2.1-.6h-.9l-.8.3a1.3,1.3,0,0,0-.7.6,2,2,0,0,0-.2.9,1.6,1.6,0,0,0,.5,1.1,2.3,2.3,0,0,0,1.2.6l.6.2,1.2.3,1.3.3.9.3a2.3,2.3,0,0,1,1.2.6,2,2,0,0,1,.9.9,3.1,3.1,0,0,1,.5,1,3.6,3.6,0,0,1,.2,1.1,4.6,4.6,0,0,1-.5,2,3.5,3.5,0,0,1-1.3,1.4,4.9,4.9,0,0,1-1.8.8l-2,.2a7.8,7.8,0,0,1-2.2-.3,4.7,4.7,0,0,1-3.2-2.5,5.1,5.1,0,0,1-.5-2.2Z" style="fill: #fff"/>
<path d="M296,162.2h2.5v11.9h7.1v2.2H296Z" style="fill: #fff"/>
<path d="M306.9,164.7h1.4l1.2-.4.9-.7a2.2,2.2,0,0,0,.5-1.1h1.8v13.9h-2.4v-9.8h-3.4Z" style="fill: #fff"/>
<path d="M214.8,149.6a4.4,4.4,0,0,1,3.5,1.5,5.6,5.6,0,0,1,.9,1.7,6.4,6.4,0,0,1,.3,2,7.5,7.5,0,0,1-.3,2,5.1,5.1,0,0,1-.9,1.6,4.8,4.8,0,0,1-1.5,1.1,5.3,5.3,0,0,1-2,.5,4.6,4.6,0,0,1-2-.5,3.3,3.3,0,0,1-1.5-1.1,5.4,5.4,0,0,1-1-1.6,7.5,7.5,0,0,1-.3-2,6.4,6.4,0,0,1,.3-2,5.9,5.9,0,0,1,1-1.7,4.1,4.1,0,0,1,3.5-1.5Zm0,1.4a3.2,3.2,0,0,0-1.4.3,2,2,0,0,0-.9.9,2.3,2.3,0,0,0-.6,1.2,6.1,6.1,0,0,0-.1,1.4,7.1,7.1,0,0,0,.1,1.4,3.4,3.4,0,0,0,.6,1.2,2.4,2.4,0,0,0,.9.8,2.2,2.2,0,0,0,1.4.3,2.1,2.1,0,0,0,1.3-.3,3.9,3.9,0,0,0,1-.8,6.1,6.1,0,0,0,.5-1.2,7.8,7.8,0,0,0,.2-1.4,5.9,5.9,0,0,0-.2-1.4,3.3,3.3,0,0,0-.5-1.2,2.8,2.8,0,0,0-1-.9A3.1,3.1,0,0,0,214.8,151Z" style="fill: #fff"/>
<path d="M221.1,149.8h4.7a3.7,3.7,0,0,1,2.5.7,2.2,2.2,0,0,1,.8,2,4.3,4.3,0,0,1-.2,1.1,3.5,3.5,0,0,1-.5.8l-.5.4h-.4l.5.2.4.3a1.6,1.6,0,0,1,.4.7,1.4,1.4,0,0,1,.1.9,7.8,7.8,0,0,0,.2,1.6,1.8,1.8,0,0,0,.4,1h-1.9a1.4,1.4,0,0,1-.2-.7v-.7a4.9,4.9,0,0,0-.1-1.2,1.1,1.1,0,0,0-.3-.8,1,1,0,0,0-.6-.5h-3.6v4h-1.7Zm1.7,4.5h2.9a1.7,1.7,0,0,0,1.2-.4,1.5,1.5,0,0,0,.5-1.2,2.4,2.4,0,0,0-.2-.8c0-.2-.2-.3-.3-.5l-.6-.2h-3.5Z" style="fill: #fff"/>
<path d="M230.9,149.8h1.8v9.9h-1.8Z" style="fill: #fff"/>
<path d="M243.4,159.7h-1.1l-.2-1.1-1.4,1.1a4.5,4.5,0,0,1-1.6.3,4.5,4.5,0,0,1-2-.4,4.7,4.7,0,0,1-2.5-2.8,6.9,6.9,0,0,1-.3-2,6.4,6.4,0,0,1,.3-2,4.7,4.7,0,0,1,2.5-2.8,4.5,4.5,0,0,1,2-.4l1.5.2,1.3.7a2.2,2.2,0,0,1,.9,1,3,3,0,0,1,.5,1.5h-1.7a1.6,1.6,0,0,0-.3-.9l-.5-.6-.8-.4h-.9a3.2,3.2,0,0,0-1.4.3,2,2,0,0,0-.9.9,3.4,3.4,0,0,0-.6,1.2,9.9,9.9,0,0,0,0,2.8,3.4,3.4,0,0,0,.6,1.2,2,2,0,0,0,.9.9,3.2,3.2,0,0,0,1.4.3l1.2-.2.8-.5a4.7,4.7,0,0,0,.6-.9,4.1,4.1,0,0,0,.1-1.1h-2.6v-1.4h4.2Z" style="fill: #fff"/>
<path d="M245.3,149.8H247v9.9h-1.7Z" style="fill: #fff"/>
<path d="M249.1,149.8H251l4.4,7.3h0v-7.3h1.7v9.9h-1.9l-4.4-7.3h0v7.3h-1.7Z" style="fill: #fff"/>
<path d="M261.8,149.8h1.9l3.8,9.9h-1.9l-.9-2.6h-4l-.9,2.6H258Zm-.6,6h3.1l-1.5-4.3h0Z" style="fill: #fff"/>
<path d="M268.4,149.8h1.8v8.4h5v1.5h-6.8Z" style="fill: #fff"/>
<path d="M317.2,171.5a2.9,2.9,0,0,0,.3,1.4,3.6,3.6,0,0,0,.7,1l1.2.5,1.4.2,1.4-.2a1.9,1.9,0,0,0,.9-.5,1.8,1.8,0,0,0,.5-.7,1.9,1.9,0,0,0,.2-.8,1.6,1.6,0,0,0-.4-1.2l-.8-.5-1.7-.5-2.5-.7a5.2,5.2,0,0,1-1.5-.6,4.5,4.5,0,0,1-1-.9,3.1,3.1,0,0,1-.5-1,3.7,3.7,0,0,1-.1-1.1,3.4,3.4,0,0,1,.4-1.8,3,3,0,0,1,1.2-1.3,4.8,4.8,0,0,1,1.6-.8,6.5,6.5,0,0,1,1.9-.2l2.1.2a4.6,4.6,0,0,1,1.7.9,5.3,5.3,0,0,1,1.2,1.4,4.2,4.2,0,0,1,.5,2h-2.5a2.6,2.6,0,0,0-1-2,4.1,4.1,0,0,0-2.1-.5h-.9a1.3,1.3,0,0,0-.8.3,1.5,1.5,0,0,0-.6.5,1.6,1.6,0,0,0-.3.9,1.7,1.7,0,0,0,.5,1.2,3.5,3.5,0,0,0,1.2.7h.6l1.3.3,1.3.4.9.2,1.2.6a7.6,7.6,0,0,1,.9.9l.5,1a5.2,5.2,0,0,1,.2,1.1,4.6,4.6,0,0,1-.5,2.1,8.6,8.6,0,0,1-1.3,1.4,4.8,4.8,0,0,1-1.8.7l-2.1.3a9.1,9.1,0,0,1-2.3-.3,4.8,4.8,0,0,1-3.1-2.5,5.2,5.2,0,0,1-.5-2.3Z" style="fill: #fff"/>
<g>
<path d="M213.5,197.1a2.2,2.2,0,0,0,.2,1.2,1.1,1.1,0,0,0,.6.7,1.8,1.8,0,0,0,1,.4l1.1.2h.8l.8-.4.6-.5a2.4,2.4,0,0,0,.2-.9,1.4,1.4,0,0,0-.2-.6,1,1,0,0,0-.4-.5l-.6-.3-.7-.2-2-.5-.7-.3-.7-.4a.9.9,0,0,1-.4-.6,2,2,0,0,1-.2-.9,2,2,0,0,1,.1-.7,4.1,4.1,0,0,1,.5-.9,2.4,2.4,0,0,1,1-.6,3.3,3.3,0,0,1,1.5-.3,2.7,2.7,0,0,1,1.3.2l1.1.5a2.4,2.4,0,0,1,.9,2.1h-.8a2,2,0,0,0-.2-.9l-.6-.7a1.9,1.9,0,0,0-.8-.3l-.9-.2h-.8l-.7.3a1,1,0,0,0-.5.6,1.1,1.1,0,0,0-.2.8,1.3,1.3,0,0,0,.1.6.5.5,0,0,0,.3.4l.4.2.5.2,2.2.5.9.3.8.5a1.8,1.8,0,0,1,.5.7,2,2,0,0,1,.2.9c0,.1-.1.2-.1.4a1.3,1.3,0,0,1-.1.6l-.4.5a1.7,1.7,0,0,1-.6.6l-.9.3-1.4.2-1.5-.2a2.4,2.4,0,0,1-1.1-.6,2.7,2.7,0,0,1-.8-1,5.2,5.2,0,0,1-.2-1.4Z" style="fill: #fff"/>
<path d="M221,191.1h4a2,2,0,0,1,1.1.2,1.6,1.6,0,0,1,.9.5,2.2,2.2,0,0,1,.5.8,3.6,3.6,0,0,1,.2,1.1,3,3,0,0,1-.2,1,2.6,2.6,0,0,1-.5.9l-.9.5h-4.2v3.9H221Zm.9,4.4h2.9a2.5,2.5,0,0,0,1.5-.5,1.4,1.4,0,0,0,.5-1.3c0-.7-.1-1.1-.5-1.4a2.5,2.5,0,0,0-1.5-.5h-2.9Z" style="fill: #fff"/>
<path d="M229,191.1h6.2v.7h-5.4v3.3h5.1v.7h-5.1v3.6h5.5v.7H229Z" style="fill: #fff"/>
<path d="M236.5,191.1h6.2v.7h-5.4v3.3h5v.7h-5v3.6h5.5v.7h-6.3Z" style="fill: #fff"/>
<path d="M244,191.1h3.1a4.3,4.3,0,0,1,3.1,1.1,4.9,4.9,0,0,1,1,3.4,4.5,4.5,0,0,1-1,3.3,4,4,0,0,1-3.1,1.2H244Zm.8,8.3h1.8l1.7-.2a3.5,3.5,0,0,0,1.2-.7,2.3,2.3,0,0,0,.6-1.2,4.3,4.3,0,0,0,.3-1.7,4,4,0,0,0-.3-1.7,2.3,2.3,0,0,0-.6-1.2,3.5,3.5,0,0,0-1.2-.7l-1.7-.2h-1.8Z" style="fill: #fff"/>
<path d="M211,186.2a3.7,3.7,0,0,0,.2,1.2,2.3,2.3,0,0,0,.7.7,1.9,1.9,0,0,0,.9.4h1.9a1.1,1.1,0,0,0,.8-.3.9.9,0,0,0,.6-.6,1.1,1.1,0,0,0,.3-.8,1.4,1.4,0,0,0-.2-.7l-.5-.4a.9.9,0,0,0-.6-.3l-.6-.2-2-.5-.8-.3-.6-.4a1.5,1.5,0,0,1-.5-.6,2.2,2.2,0,0,1-.1-.9,2.5,2.5,0,0,1,.1-.8,1.8,1.8,0,0,1,.5-.8,2.2,2.2,0,0,1,.9-.7l1.6-.2h1.2a3.9,3.9,0,0,1,1.1.6,1.6,1.6,0,0,1,.7.9,2.1,2.1,0,0,1,.3,1.2H216a2,2,0,0,0-.2-.9,1.8,1.8,0,0,0-.5-.7l-.8-.4h-1.8l-.7.3a1,1,0,0,0-.5.6,1.9,1.9,0,0,0-.2.8,4.3,4.3,0,0,0,.1.5l.3.4.5.3.5.2,2.2.5.9.3a1.8,1.8,0,0,1,.7.5l.5.6a1.6,1.6,0,0,1,.2,1v.4c0,.2-.1.3-.2.5s-.2.4-.3.6l-.6.5-1,.4-1.3.2-1.5-.2a2.3,2.3,0,0,1-1.2-.6,2.6,2.6,0,0,1-.7-1,2.1,2.1,0,0,1-.2-1.4Z" style="fill: #fff"/>
<path d="M218.6,180.2h5.1l.8.5a2.8,2.8,0,0,1,.6.9,3,3,0,0,1,.2,1,3.6,3.6,0,0,1-.2,1.1,2.4,2.4,0,0,1-.6.8,1.4,1.4,0,0,1-.8.5,2,2,0,0,1-1.1.2h-3.2v3.9h-.8Zm.8,4.4h3a2,2,0,0,0,1.4-.5,1.6,1.6,0,0,0,.6-1.4,1.8,1.8,0,0,0-.6-1.4,2,2,0,0,0-1.4-.4h-3Z" style="fill: #fff"/>
<path d="M226.5,180.2h6.3v.7h-5.4v3.3h5v.7h-5v3.5h5.4v.8h-6.3Z" style="fill: #fff"/>
<path d="M234,180.2h6.2v.7h-5.3v3.3h5v.7h-5v3.5h5.4v.8H234Z" style="fill: #fff"/>
<path d="M241.5,180.2h3.1a4.2,4.2,0,0,1,3.1,1.1,5,5,0,0,1,1.1,3.4,4.5,4.5,0,0,1-1.1,3.3,4.2,4.2,0,0,1-3.1,1.2h-3.1Zm.9,8.2h1.8a4.7,4.7,0,0,0,1.7-.2,2.5,2.5,0,0,0,1.8-1.8,8.8,8.8,0,0,0,.2-1.7,9,9,0,0,0-.2-1.8,3,3,0,0,0-1.8-1.8l-1.7-.2h-1.8Z" style="fill: #fff"/>
</g>
<g>
<rect width="368.5" height="226.77" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="198.4" x2="368.5" y2="198.4" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="170.1" x2="198.4" y2="170.1" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="141.7" x2="368.5" y2="141.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="113.4" x2="368.5" y2="113.4" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="85" x2="368.5" y2="85" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="56.7" x2="368.5" y2="56.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line y1="28.3" x2="368.5" y2="28.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="340.2" x2="340.2" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="311.8" x2="311.8" y2="141.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="283.5" x2="283.5" y2="141.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="311.8" y1="198.4" x2="311.8" y2="340.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="283.5" y1="198.4" x2="283.5" y2="340.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="255.1" x2="255.1" y2="141.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="255.1" y1="198.4" x2="255.1" y2="340.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="226.8" x2="226.8" y2="141.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="226.8" y1="198.4" x2="226.8" y2="340.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="198.4" x2="198.4" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="170.1" x2="170.1" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="141.7" x2="141.7" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="113.4" x2="113.4" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="85" x2="85" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="56.7" x2="56.7" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="28.3" x2="28.3" y2="226.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
<line x1="340.2" y1="170.1" x2="538.6" y2="170.1" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.28346456692913385px"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -118,7 +118,8 @@ int CLI::run(int argc, char **argv)
boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
#endif // _WIN32
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
// load config files supplied via --load
for (auto const &file : load_configs) {
@ -130,13 +131,19 @@ int CLI::run(int argc, char **argv)
return 1;
}
}
DynamicPrintConfig config;
DynamicPrintConfig config;
ConfigSubstitutions config_substitutions;
try {
config.load(file);
config_substitutions = config.load(file, config_substitution_rule);
} catch (std::exception &ex) {
boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl;
boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl;
return 1;
}
if (! config_substitutions.empty()) {
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
for (const ConfigSubstitution &subst : config_substitutions)
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
}
config.normalize_fdm();
PrinterTechnology other_printer_technology = get_printer_technology(config);
if (printer_technology == ptUnknown) {
@ -174,7 +181,9 @@ int CLI::run(int argc, char **argv)
try {
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
DynamicPrintConfig config;
model = Model::read_from_file(file, &config, true);
ConfigSubstitutionContext config_substitutions(config_substitution_rule);
//FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances);
PrinterTechnology other_printer_technology = get_printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
@ -183,6 +192,11 @@ int CLI::run(int argc, char **argv)
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
return 1;
}
if (! config_substitutions.substitutions.empty()) {
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
for (const ConfigSubstitution& subst : config_substitutions.substitutions)
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
}
// config is applied to m_print_config before the current m_config values.
config += std::move(m_print_config);
m_print_config = std::move(config);
@ -362,7 +376,7 @@ int CLI::run(int argc, char **argv)
o->cut(Z, m_config.opt_float("cut"), &out);
}
#else
model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true);
model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
#endif
model.delete_object(size_t(0));
}

View file

@ -501,8 +501,9 @@ inline P _Box<P>::center() const BP2D_NOEXCEPT {
using Coord = TCoord<P>;
P ret = { // No rounding here, we dont know if these are int coords
Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
Coord( (getY(minc) + getY(maxc)) / Coord(2) )
// Doing the division like this increases the max range of x and y coord
getX(minc) / Coord(2) + getX(maxc) / Coord(2),
getY(minc) / Coord(2) + getY(maxc) / Coord(2)
};
return ret;

View file

@ -4,7 +4,7 @@
#include "Exception.hpp"
#include "LocalesUtils.hpp"
#include "Thread.hpp"
#include "format.hpp"
#include <utility>
#include <vector>
@ -18,15 +18,24 @@
#include <boost/property_tree/ptree_fwd.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/format/format_fwd.hpp>
#include <boost/log/trivial.hpp>
//#include <wx/string.h>
//#include "I18N.hpp"
#ifdef WIN32
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
#include <boost/uuid/detail/md5.hpp>
#include <boost/algorithm/hex.hpp>
#endif
namespace Slic3r {
static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
// Because of a crash in PrusaSlicer 2.3.0/2.3.1 when showing an update notification with some locales, we don't want PrusaSlicer 2.3.0/2.3.1
// to show this notification. On the other hand, we would like PrusaSlicer 2.3.2 to show an update notification of the upcoming PrusaSlicer 2.4.0.
// Thus we will let PrusaSlicer 2.3.2 and couple of follow-up versions to download the version number from an alternate file until the PrusaSlicer 2.3.0/2.3.1
// are phased out, then we will revert to the original name.
//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
@ -177,25 +186,114 @@ void AppConfig::set_defaults()
erase("", "object_settings_size");
}
#ifdef WIN32
static std::string appconfig_md5_hash_line(const std::string_view data)
{
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
// return boost::md5(data).hex_str_value();
// boost::uuids::detail::md5 is an internal namespace thus it may change in the future.
// Also this implementation is not the fastest, it was designed for short blocks of text.
using boost::uuids::detail::md5;
md5 md5_hash;
// unsigned int[4], 128 bits
md5::digest_type md5_digest{};
std::string md5_digest_str;
md5_hash.process_bytes(data.data(), data.size());
md5_hash.get_digest(md5_digest);
boost::algorithm::hex(md5_digest, md5_digest + std::size(md5_digest), std::back_inserter(md5_digest_str));
// MD5 hash is 32 HEX digits long.
assert(md5_digest_str.size() == 32);
// This line will be emited at the end of the file.
return "# MD5 checksum " + md5_digest_str + "\n";
};
// Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file.
static bool verify_config_file_checksum(boost::nowide::ifstream &ifs)
{
auto read_whole_config_file = [&ifs]() -> std::string {
std::stringstream ss;
ss << ifs.rdbuf();
return ss.str();
};
ifs.seekg(0, boost::nowide::ifstream::beg);
std::string whole_config = read_whole_config_file();
// The checksum should be on the last line in the config file.
if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) {
// Split read config into two parts, one with checksum, and the second part is part with configuration from the checksum was computed.
// Verify existence and validity of the MD5 checksum line at the end of the file.
// When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum.
// If the checksum is incorrect, then the file was either not saved correctly or modified.
if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos }))
return true;
}
return false;
}
#endif
std::string AppConfig::load()
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(AppConfig::config_path());
boost::nowide::ifstream ifs;
bool recovered = false;
try {
ifs.open(AppConfig::config_path());
#ifdef WIN32
// Verify the checksum of the config file without taking just for debugging purpose.
if (!verify_config_file_checksum(ifs))
BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() <<
" has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit.";
ifs.seekg(0, boost::nowide::ifstream::beg);
#endif
pt::read_ini(ifs, tree);
} catch (pt::ptree_error& ex) {
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
/*
throw Slic3r::RuntimeError(
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
*/
return ex.what();
#ifdef WIN32
// The configuration file is corrupted, try replacing it with the backup configuration.
ifs.close();
std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str();
if (boost::filesystem::exists(backup_path)) {
// Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct.
boost::nowide::ifstream backup_ifs(backup_path);
if (!verify_config_file_checksum(backup_ifs)) {
BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path);
backup_ifs.close();
boost::filesystem::remove(backup_path);
} else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) {
BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message);
backup_ifs.close();
boost::filesystem::remove(backup_path);
} else {
BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path);
// Try parse configuration file after restore from backup.
try {
ifs.open(AppConfig::config_path());
pt::read_ini(ifs, tree);
recovered = true;
} catch (pt::ptree_error& ex) {
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what());
}
}
} else
#endif // WIN32
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what());
if (! recovered) {
// Report the initial error of parsing PrusaSlicer.ini.
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
/*
throw Slic3r::RuntimeError(
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
*/
return ex.what();
}
}
// 2) Parse the property_tree, extract the sections and key / value pairs.
@ -272,22 +370,21 @@ void AppConfig::save()
const auto path = config_path();
std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
boost::nowide::ofstream c;
c.open(path_pid, std::ios::out | std::ios::trunc);
std::stringstream config_ss;
if (m_mode == EAppMode::Editor)
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
config_ss << "# " << Slic3r::header_slic3r_generated() << std::endl;
else
c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
config_ss << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
// Make sure the "no" category is written first.
for (const auto& kvp : m_storage[""])
c << kvp.first << " = " << kvp.second << std::endl;
config_ss << kvp.first << " = " << kvp.second << std::endl;
// Write the other categories.
for (const auto& category : m_storage) {
if (category.first.empty())
continue;
c << std::endl << "[" << category.first << "]" << std::endl;
config_ss << std::endl << "[" << category.first << "]" << std::endl;
for (const auto& kvp : category.second)
c << kvp.first << " = " << kvp.second << std::endl;
config_ss << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
@ -295,17 +392,42 @@ void AppConfig::save()
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
config_ss << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
for (const auto &model : vendor.second) {
if (model.second.size() == 0) { continue; }
if (model.second.empty()) { continue; }
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
config_ss << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
}
}
c.close();
// One empty line before the MD5 sum.
config_ss << std::endl;
std::string config_str = config_ss.str();
boost::nowide::ofstream c;
c.open(path_pid, std::ios::out | std::ios::trunc);
c << config_str;
#ifdef WIN32
// WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API
// provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
// we save the config file into a backup first before moving it to the final destination.
c << appconfig_md5_hash_line(config_str);
#endif
c.close();
#ifdef WIN32
// Make a backup of the configuration file before copying it to the final destination.
std::string error_message;
std::string backup_path = (boost::format("%1%.bak") % path).str();
// Copy configuration file with PID suffix into the configuration file with "bak" suffix.
if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS)
BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration.";
#endif
// Rename the config atomically.
// On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile.
// To cope with that, we already made a backup of the config on Windows.
rename_file(path_pid, path);
m_dirty = false;
}

View file

@ -37,7 +37,7 @@ public:
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
// return error string or empty strinf
std::string load();
std::string load();
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
void save();
@ -63,12 +63,12 @@ public:
{ std::string value; this->get("", key, value); return value; }
void set(const std::string &section, const std::string &key, const std::string &value)
{
#ifndef _NDEBUG
#ifndef NDEBUG
std::string key_trimmed = key;
boost::trim_all(key_trimmed);
assert(key_trimmed == key);
assert(! key_trimmed.empty());
#endif // _NDEBUG
#endif // NDEBUG
std::string &old = m_storage[section][key];
if (old != value) {
old = value;

View file

@ -33,6 +33,7 @@ add_library(libslic3r STATIC
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonCollection.cpp

View file

@ -23,6 +23,10 @@
#include <boost/format.hpp>
#include <string.h>
//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion)
// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy().
#include "PrintConfig.hpp"
namespace Slic3r {
// Escape \n, \r and backslash
@ -211,6 +215,10 @@ std::string escape_ampersand(const std::string& str)
return std::string(out.data(), outptr - out.data());
}
void ConfigOptionDeleter::operator()(ConfigOption* p) {
delete p;
}
std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
{
std::vector<std::string> args;
@ -361,7 +369,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
// right: option description
std::string descr = def.tooltip;
if (show_defaults && def.default_value && def.type != coBool
bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility";
if (show_defaults_this && def.default_value && def.type != coBool
&& (def.type != coString || !def.default_value->serialize().empty())) {
descr += " (";
if (!def.sidetext.empty()) {
@ -469,7 +478,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create)
}
}
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{
t_config_option_key opt_key = opt_key_src;
std::string value = value_src;
@ -479,29 +488,29 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
if (opt_key.empty())
// Ignore the option.
return true;
return this->set_deserialize_raw(opt_key, value, append);
return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append);
}
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{
if (! this->set_deserialize_nothrow(opt_key_src, value_src, append))
if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
}
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
{
for (const SetDeserializeItem &item : items)
this->set_deserialize(item.opt_key, item.opt_value, item.append);
this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append);
}
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append)
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{
t_config_option_key opt_key = opt_key_src;
t_config_option_key opt_key = opt_key_src;
// Try to deserialize the option by its name.
const ConfigDef *def = this->def();
const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr) {
// If we didn't find an option, look for any other option having this as an alias.
for (const auto &opt : def->options) {
@ -523,14 +532,35 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
// Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers".
for (const t_config_option_key &shortcut : optdef->shortcut)
// Recursive call.
if (! this->set_deserialize_raw(shortcut, value, append))
if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append))
return false;
return true;
}
ConfigOption *opt = this->option(opt_key, true);
assert(opt != nullptr);
return opt->deserialize(value, append);
bool success = opt->deserialize(value, append);
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
(optdef->type == coEnum || optdef->type == coBool))
{
// Deserialize failed, try to substitute with a default value.
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
opt->set(optdef->default_value.get());
if (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable) {
// Log the substitution.
ConfigSubstitution config_substitution;
config_substitution.opt_def = optdef;
config_substitution.old_value = value;//std::unique_ptr<ConfigOption>(opt);
config_substitution.new_value = ConfigOptionUniquePtr(this->option(opt_key, true)->clone());
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
}
return true;
}
return success;
}
// Return an absolute value of a possibly relative config variable.
@ -589,36 +619,37 @@ void ConfigBase::setenv_() const
}
}
void ConfigBase::load(const std::string &file)
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
if (is_gcode_file(file))
this->load_from_gcode_file(file);
else
this->load_from_ini(file);
return is_gcode_file(file) ?
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
this->load_from_ini(file, compatibility_rule);
}
void ConfigBase::load_from_ini(const std::string &file)
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree);
this->load(tree);
return this->load(tree, compatibility_rule);
}
void ConfigBase::load(const boost::property_tree::ptree &tree)
ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
for (const boost::property_tree::ptree::value_type &v : tree) {
try {
t_config_option_key opt_key = v.first;
this->set_deserialize(opt_key, v.second.get_value<std::string>());
this->set_deserialize(opt_key, v.second.get_value<std::string>(), substitutions_ctxt);
} catch (UnknownOptionException & /* e */) {
// ignore
}
}
return std::move(substitutions_ctxt.substitutions);
}
// Load the config keys from the tail of a G-code file.
void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header)
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, bool check_header, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
@ -639,13 +670,15 @@ void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header
ifs.read(data.data(), data_length);
ifs.close();
size_t key_value_pairs = load_from_gcode_string(data.data());
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt);
if (key_value_pairs < 80)
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
return std::move(substitutions_ctxt.substitutions);
}
// Load the config keys from the given string.
size_t ConfigBase::load_from_gcode_string(const char* str)
size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions)
{
if (str == nullptr)
return 0;
@ -690,7 +723,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
if (key == nullptr)
break;
try {
this->set_deserialize(std::string(key, key_end), std::string(value, end));
this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
++num_key_value_pairs;
}
catch (UnknownOptionException & /* e */) {
@ -719,7 +752,7 @@ void ConfigBase::null_nullables()
ConfigOption *opt = this->optptr(opt_key, false);
assert(opt != nullptr);
if (opt->nullable())
opt->deserialize("nil");
opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable);
}
}
@ -883,8 +916,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
// Do not unescape single string values, the unescaping is left to the calling shell.
static_cast<ConfigOptionString*>(opt_base)->value = value;
} else {
// Just bail out if the configuration value is not understood.
ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable);
// Any scalar value of a type different from Bool and String.
if (! this->set_deserialize_nothrow(opt_key, value, false)) {
if (! this->set_deserialize_nothrow(opt_key, value, context, false)) {
boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
return false;
}

View file

@ -16,6 +16,7 @@
#include "Exception.hpp"
#include "Point.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/format/format_fwd.hpp>
#include <boost/functional/hash.hpp>
@ -163,6 +164,41 @@ enum PrinterTechnology : unsigned char
ptAny
};
enum ForwardCompatibilitySubstitutionRule
{
Disable,
Enable,
EnableSilent,
};
class ConfigOption;
class ConfigOptionDef;
// For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter.
struct ConfigOptionDeleter { void operator()(ConfigOption* p); };
using ConfigOptionUniquePtr = std::unique_ptr<ConfigOption, ConfigOptionDeleter>;
// When parsing a configuration value, if the old_value is not understood by this PrusaSlicer version,
// it is being substituted with some default value that this PrusaSlicer could work with.
// This structure serves to inform the user about the substitutions having been done during file import.
struct ConfigSubstitution {
const ConfigOptionDef *opt_def { nullptr };
std::string old_value;
ConfigOptionUniquePtr new_value;
};
using ConfigSubstitutions = std::vector<ConfigSubstitution>;
// Filled in by ConfigBase::set_deserialize_raw(), which based on "rule" either bails out
// or performs substitutions when encountering an unknown configuration value.
struct ConfigSubstitutionContext
{
ConfigSubstitutionContext(ForwardCompatibilitySubstitutionRule rl) : rule(rl) {}
bool empty() const throw() { return substitutions.empty(); }
ForwardCompatibilitySubstitutionRule rule;
ConfigSubstitutions substitutions;
};
// A generic value of a configuration option.
class ConfigOption {
public:
@ -768,7 +804,7 @@ public:
return escape_string_cstyle(this->value);
}
bool deserialize(const std::string &str, bool append = false) override
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
return unescape_string_cstyle(str, this->value);
@ -1272,8 +1308,15 @@ public:
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
this->value = (str.compare("1") == 0);
return true;
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
this->value = true;
return true;
}
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
this->value = false;
return true;
}
return false;
}
private:
@ -1687,6 +1730,14 @@ public:
static const constexpr char *nocli = "~~~noCLI";
};
inline bool operator<(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
return lhs.opt_def->opt_key < rhs.opt_def->opt_key ||
(lhs.opt_def->opt_key == rhs.opt_def->opt_key && lhs.old_value < rhs.old_value);
}
inline bool operator==(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
return lhs.opt_def == rhs.opt_def && lhs.old_value == rhs.old_value;
}
// Map from a config option name to its definition.
// The definition does not carry an actual value of the config option, only its constant default value.
// t_config_option_key is std::string
@ -1765,6 +1816,8 @@ public:
}
};
// An abstract configuration store.
class ConfigBase : public ConfigOptionResolver
{
@ -1853,9 +1906,11 @@ public:
// Set a configuration value from a string, it will call an overridable handle_legacy()
// to resolve renamed and removed configuration keys.
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false);
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions, bool append = false);
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext& config_substitutions, bool append = false);
void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false)
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(opt_key, str, ctxt, append); }
struct SetDeserializeItem {
SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
@ -1870,17 +1925,19 @@ public:
std::string opt_key; std::string opt_value; bool append = false;
};
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(std::initializer_list<SetDeserializeItem> items);
void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
void set_deserialize_strict(std::initializer_list<SetDeserializeItem> items)
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); }
double get_abs_value(const t_config_option_key &opt_key) const;
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
void setenv_() const;
void load(const std::string &file);
void load_from_ini(const std::string &file);
void load_from_gcode_file(const std::string& file, bool check_header = true);
ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &file, bool check_header /* = true */, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Returns number of key/value pairs extracted.
size_t load_from_gcode_string(const char* str);
void load(const boost::property_tree::ptree &tree);
size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions);
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
void save(const std::string &file) const;
// Set all the nullable values to nils.
@ -1888,7 +1945,7 @@ public:
private:
// Set a configuration value from a string.
bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append);
bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
};
// Configuration store with dynamic number of configuration values.

View file

@ -419,7 +419,7 @@ namespace Slic3r {
_3MF_Importer();
~_3MF_Importer();
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version);
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version);
private:
void _destroy_xml_parser();
@ -434,16 +434,16 @@ namespace Slic3r {
XML_ErrorString(XML_GetErrorCode(m_xml_parser));
}
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config);
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
// handlers to parse the .model file
@ -510,7 +510,7 @@ namespace Slic3r {
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_metadata();
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes);
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
// callbacks to parse the .model file
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
@ -539,7 +539,7 @@ namespace Slic3r {
_destroy_xml_parser();
}
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version)
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version)
{
m_version = 0;
m_check_version = check_version;
@ -560,7 +560,7 @@ namespace Slic3r {
m_curr_characters.clear();
clear_errors();
return _load_model_from_file(filename, model, config);
return _load_model_from_file(filename, model, config, config_substitutions);
}
void _3MF_Importer::_destroy_xml_parser()
@ -581,7 +581,7 @@ namespace Slic3r {
XML_StopParser(m_xml_parser, false);
}
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config)
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions)
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
@ -635,7 +635,7 @@ namespace Slic3r {
}
else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) {
// extract slic3r layer config ranges file
_extract_layer_config_ranges_from_archive(archive, stat);
_extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
}
else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) {
// extract sla support points file
@ -647,7 +647,7 @@ namespace Slic3r {
}
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) {
// extract slic3r print config file
_extract_print_config_from_archive(archive, stat, config, filename);
_extract_print_config_from_archive(archive, stat, config, config_substitutions, filename);
}
else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) {
// extract slic3r layer config ranges file
@ -704,7 +704,7 @@ namespace Slic3r {
new_model_object->clear_instances();
new_model_object->add_instance(*model_object->instances.back());
model_object->delete_last_instance();
if (!_generate_volumes(*new_model_object, *geometry, volumes))
if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions))
return false;
}
}
@ -759,7 +759,7 @@ namespace Slic3r {
if (metadata.key == "name")
model_object->name = metadata.value;
else
model_object->config.set_deserialize(metadata.key, metadata.value);
model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
// select object's detected volumes
@ -775,7 +775,7 @@ namespace Slic3r {
volumes_ptr = &volumes;
}
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr))
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions))
return false;
}
@ -867,7 +867,10 @@ namespace Slic3r {
return true;
}
void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename)
void _3MF_Importer::_extract_print_config_from_archive(
mz_zip_archive& archive, const mz_zip_archive_file_stat& stat,
DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions,
const std::string& archive_filename)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
@ -876,7 +879,7 @@ namespace Slic3r {
add_error("Error while reading config data to buffer");
return;
}
config.load_from_gcode_string(buffer.data());
config.load_from_gcode_string(buffer.data(), config_substitutions);
}
}
@ -942,7 +945,7 @@ namespace Slic3r {
}
}
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
@ -987,8 +990,7 @@ namespace Slic3r {
continue;
std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
std::string value = option.second.data();
config.set_deserialize(opt_key, value);
config.set_deserialize(opt_key, value, config_substitutions);
}
config_ranges[{ min_z, max_z }].assign_config(std::move(config));
@ -1827,7 +1829,7 @@ namespace Slic3r {
return true;
}
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes)
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
{
if (!object.volumes.empty()) {
add_error("Found invalid volumes count");
@ -1943,7 +1945,7 @@ namespace Slic3r {
else if (metadata.key == SOURCE_IN_METERS)
volume->source.is_converted_from_meters = metadata.value == "1";
else
volume->config.set_deserialize(metadata.key, metadata.value);
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
}
@ -2953,16 +2955,15 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
return true;
}
bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version)
{
if (path == nullptr || config == nullptr || model == nullptr)
if (path == nullptr || model == nullptr)
return false;
// All import should use "C" locales for number formatting.
CNumericLocalesSetter locales_setter;
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *config, check_version);
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
importer.log_errors();
return res;
}

View file

@ -25,11 +25,12 @@ namespace Slic3r {
};
class Model;
struct ConfigSubstitutionContext;
class DynamicPrintConfig;
struct ThumbnailData;
// Load the content of a 3mf file into the given model and preset bundle.
extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version);
extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version);
// Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices

View file

@ -64,10 +64,11 @@ namespace Slic3r
struct AMFParserContext
{
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) :
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) :
m_parser(parser),
m_model(*model),
m_config(config)
m_config(config),
m_config_substitutions(config_substitutions)
{
m_path.reserve(12);
}
@ -258,6 +259,8 @@ struct AMFParserContext
std::string m_value[5];
// Pointer to config to update if config data are stored inside the amf file
DynamicPrintConfig *m_config { nullptr };
// Config substitution rules and collected config substitution log.
ConfigSubstitutionContext *m_config_substitutions { nullptr };
private:
AMFParserContext& operator=(AMFParserContext&);
@ -702,8 +705,9 @@ void AMFParserContext::endElement(const char * /* name */)
}
case NODE_TYPE_METADATA:
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0)
m_config->load_from_gcode_string(m_value[1].c_str());
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions);
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7;
if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
@ -721,7 +725,7 @@ void AMFParserContext::endElement(const char * /* name */)
config = &it->second;
}
if (config)
config->set_deserialize(opt_key, m_value[1]);
config->set_deserialize(opt_key, m_value[1], *m_config_substitutions);
} else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
// Parse object's layer height profile, a semicolon separated list of floats.
char *p = m_value[1].data();
@ -849,7 +853,7 @@ void AMFParserContext::endDocument()
}
// Load an AMF file into a provided model.
bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model)
{
if ((path == nullptr) || (model == nullptr))
return false;
@ -866,7 +870,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
return false;
}
AMFParserContext ctx(parser, config, model);
AMFParserContext ctx(parser, config, config_substitutions, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
@ -908,7 +912,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
return result;
}
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version)
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
{
if (stat.m_uncomp_size == 0)
{
@ -924,7 +928,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
return false;
}
AMFParserContext ctx(parser, config, model);
AMFParserContext ctx(parser, config, config_substitutions, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
@ -984,7 +988,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
}
// Load an AMF archive into a provided model.
bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
{
if ((path == nullptr) || (model == nullptr))
return false;
@ -1010,7 +1014,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
{
try
{
if (!extract_model_from_archive(archive, stat, config, model, check_version))
if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version))
{
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "Archive does not contain a valid model";
@ -1052,13 +1056,13 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
// Load an AMF file into a provided model.
// If config is not a null pointer, updates it if the amf file/archive contains config data
bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
{
CNumericLocalesSetter locales_setter; // use "C" locales and point as a decimal separator
if (boost::iends_with(path, ".amf.xml"))
// backward compatibility with older slic3r output
return load_amf_file(path, config, model);
return load_amf_file(path, config, config_substitutions, model);
else if (boost::iends_with(path, ".amf"))
{
boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
@ -1069,7 +1073,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
file.read(zip_mask.data(), 2);
file.close();
return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model);
return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model);
}
else
return false;

View file

@ -7,7 +7,7 @@ class Model;
class DynamicPrintConfig;
// Load the content of an amf file into the given model and configuration.
extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version);
extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version);
// Save the given model and the config data into an amf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices

View file

@ -284,11 +284,8 @@ static void extract_model_from_archive(
volume->name = name;
}
// Set the extruder to the volume.
if (extruder_id != (unsigned int)-1) {
char str_extruder[64];
sprintf(str_extruder, "%ud", extruder_id);
volume->config.set_deserialize("extruder", str_extruder);
}
if (extruder_id != (unsigned int)-1)
volume->config.set("extruder", int(extruder_id));
}
// Load a PrusaControl project file into a provided model.

View file

@ -287,13 +287,13 @@ std::vector<ExPolygons> extract_slices_from_sla_archive(
} // namespace
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
{
ArchiveData arch = extract_sla_archive(zipfname, "png");
out.load(arch.profile);
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
}
void import_sla_archive(
ConfigSubstitutions import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
indexed_triangle_set & out,
@ -305,7 +305,7 @@ void import_sla_archive(
windowsize.y() = std::max(2, windowsize.y());
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
profile.load(arch.profile);
ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
RasterParams rstp = get_raster_params(profile);
rstp.win = {windowsize.y(), windowsize.x()};
@ -317,6 +317,8 @@ void import_sla_archive(
if (!slices.empty())
out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
return config_substitutions;
}
using ConfMap = std::map<std::string, std::string>;

View file

@ -38,23 +38,23 @@ public:
}
};
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
void import_sla_archive(
ConfigSubstitutions import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
indexed_triangle_set & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive(
inline ConfigSubstitutions import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
indexed_triangle_set & out,
std::function<bool(int)> progr = [](int) { return true; })
{
DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr);
return import_sla_archive(zipfname, windowsize, out, profile, progr);
}
} // namespace Slic3r::sla

View file

@ -523,7 +523,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Check that there are extrusions on the very first layer.
if (layers_to_print.size() == 1u) {
if (!has_extrusions)
throw Slic3r::SlicingError(_(L("There is an object with no extrusions on the first layer.")));
throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" +
_(L("Object name")) + ": " + object.model_object()->name);
}
// In case there are extrusions on this layer, check there is a layer to lay it on.
@ -541,7 +542,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
_(L("Empty layers detected. Make sure the object is printable.")) + "\n\n" +
_(L("Empty layers detected. Make sure the object is printable.")) + "\n" +
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
"usually caused by negligibly small extrusions or by a faulty model. Try to repair "

View file

@ -1285,7 +1285,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(filename, false);
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
config.load_from_gcode_file(filename, false, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config);
}
else if (m_producer == EProducer::Simplify3D)

View file

@ -19,6 +19,7 @@ CNumericLocalesSetter::CNumericLocalesSetter()
#else // APPLE
m_original_locale = uselocale((locale_t)0);
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_original_locale);
uselocale(m_new_locale);
#endif
}

View file

@ -96,13 +96,17 @@ void Model::update_links_bottom_up_recursive()
}
}
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version)
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
{
Model model;
DynamicPrintConfig temp_config;
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
if (config == nullptr)
config = &temp_config;
if (config_substitutions == nullptr)
config_substitutions = &temp_config_substitutions_context;
bool result = false;
if (boost::algorithm::iends_with(input_file, ".stl"))
@ -110,9 +114,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
else if (boost::algorithm::iends_with(input_file, ".obj"))
result = load_obj(input_file.c_str(), &model);
else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
result = load_amf(input_file.c_str(), config, &model, check_version);
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
else if (boost::algorithm::iends_with(input_file, ".3mf"))
result = load_3mf(input_file.c_str(), config, &model, false);
//FIXME options & LoadAttribute::CheckVersion ?
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
else if (boost::algorithm::iends_with(input_file, ".prusa"))
result = load_prus(input_file.c_str(), &model);
else
@ -127,24 +132,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
for (ModelObject *o : model.objects)
o->input_file = input_file;
if (add_default_instances)
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
sort_remove_duplicates(config_substitutions->substitutions);
return model;
}
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version)
// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
{
assert(config != nullptr);
assert(config_substitutions != nullptr);
Model model;
bool result = false;
if (boost::algorithm::iends_with(input_file, ".3mf"))
result = load_3mf(input_file.c_str(), config, &model, check_version);
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion);
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
result = load_amf(input_file.c_str(), config, &model, check_version);
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
else
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
@ -165,7 +175,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
o->input_file = input_file;
}
if (add_default_instances)
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
@ -398,13 +408,12 @@ bool Model::looks_like_multipart_object() const
}
// Generate next extruder ID string, in the range of (1, max_extruders).
static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
{
char str_extruder[64];
sprintf(str_extruder, "%ud", cntr + 1);
if (++ cntr == max_extruders)
int out = ++ cntr;
if (cntr == max_extruders)
cntr = 0;
return str_extruder;
return out;
}
void Model::convert_multipart_object(unsigned int max_extruders)
@ -431,7 +440,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
assert(new_v != nullptr);
new_v->name = o->name + "_" + std::to_string(counter++);
new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
return new_v;
};
if (o->instances.empty()) {
@ -1134,17 +1143,18 @@ bool ModelObject::needed_repair() const
return false;
}
ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes)
{
if (!keep_upper && !keep_lower) { return {}; }
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
return {};
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// Clone the object to duplicate instances, materials etc.
ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr;
ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr;
ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr;
ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr;
if (keep_upper) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
upper->set_model(nullptr);
upper->sla_support_points.clear();
upper->sla_drain_holes.clear();
@ -1153,7 +1163,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
upper->input_file.clear();
}
if (keep_lower) {
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
lower->set_model(nullptr);
lower->sla_support_points.clear();
lower->sla_drain_holes.clear();
@ -1193,8 +1203,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
if (keep_upper) { upper->add_volume(*volume); }
if (keep_lower) { lower->add_volume(*volume); }
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper->add_volume(*volume);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower->add_volume(*volume);
}
else if (! volume->mesh().empty()) {
@ -1214,19 +1226,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
indexed_triangle_set upper_its, lower_its;
mesh.require_shared_vertices();
cut_mesh(mesh.its, float(z), &upper_its, &lower_its);
if (keep_upper) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
upper_mesh = TriangleMesh(upper_its);
upper_mesh.repair();
upper_mesh.reset_repair_stats();
}
if (keep_lower) {
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
lower_mesh = TriangleMesh(lower_its);
lower_mesh.repair();
lower_mesh.reset_repair_stats();
}
}
if (keep_upper && upper_mesh.facets_count() > 0) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper_mesh.facets_count() > 0) {
ModelVolume* vol = upper->add_volume(upper_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
@ -1235,7 +1247,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
}
if (keep_lower && lower_mesh.facets_count() > 0) {
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower_mesh.facets_count() > 0) {
ModelVolume* vol = lower->add_volume(lower_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
@ -1246,7 +1258,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
// Compute the lower part instances' bounding boxes to figure out where to place
// the upper part
if (keep_upper) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
for (size_t i = 0; i < instances.size(); i++) {
lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true));
}
@ -1257,7 +1269,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
ModelObjectPtrs res;
if (keep_upper && upper->volumes.size() > 0) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
upper->invalidate_bounding_box();
upper->center_around_origin();
@ -1277,7 +1289,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
res.push_back(upper);
}
if (keep_lower && lower->volumes.size() > 0) {
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
lower->invalidate_bounding_box();
lower->center_around_origin();
@ -1288,7 +1300,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset);
instance->set_rotation(Vec3d(rotate_lower ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
}
res.push_back(lower);
@ -1628,7 +1640,7 @@ bool ModelVolume::is_splittable() const
{
// the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
if (m_is_splittable == -1)
m_is_splittable = (int)this->mesh().is_splittable();
m_is_splittable = its_is_splittable(this->mesh().its);
return m_is_splittable == 1;
}
@ -1738,7 +1750,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->center_geometry_after_creation();
this->object->volumes[ivolume]->translate(offset);
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
this->object->volumes[ivolume]->m_is_splittable = 0;
delete mesh;
++ idx;

View file

@ -2,6 +2,7 @@
#define slic3r_Model_hpp_
#include "libslic3r.h"
#include "enum_bitmask.hpp"
#include "Geometry.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
@ -12,6 +13,7 @@
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
#include "enum_bitmask.hpp"
#include <map>
#include <memory>
@ -226,6 +228,10 @@ enum class ModelVolumeType : int {
SUPPORT_ENFORCER,
};
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
@ -344,7 +350,7 @@ public:
size_t materials_count() const;
size_t facets_count() const;
bool needed_repair() const;
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs* new_objects);
void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
@ -1031,8 +1037,20 @@ public:
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false);
static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false);
enum class LoadAttribute : int {
AddDefaultInstances,
CheckVersion
};
using LoadAttributes = enum_bitmask<LoadAttribute>;
static Model read_from_file(
const std::string& input_file,
DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
LoadAttributes options = LoadAttribute::AddDefaultInstances);
static Model read_from_archive(
const std::string& input_file,
DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
LoadAttributes options = LoadAttribute::AddDefaultInstances);
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
ModelObject* add_object();
@ -1097,6 +1115,8 @@ private:
}
};
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE

View file

@ -4,20 +4,15 @@
#include "Layer.hpp"
#include "Print.hpp"
#include "VoronoiVisualUtils.hpp"
#include "MutablePolygon.hpp"
#include <utility>
#include <cfloat>
#include <unordered_set>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/segment.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace Slic3r {
struct ColoredLine {
Line line;
@ -89,28 +84,37 @@ struct PaintedLineVisitor
bool operator()(coord_t iy, coord_t ix)
{
// Called with a row and column of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
const Vec2d v1 = line_to_test.vector().cast<double>();
auto cell_data_range = grid.cell_data_range(iy, ix);
const Vec2d v1 = line_to_test.vector().cast<double>();
const double v1_sqr_norm = v1.squaredNorm();
const double heuristic_thr_part = line_to_test.length() + append_threshold;
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
Line grid_line = grid.line(*it_contour_and_segment);
const Vec2d v2 = grid_line.vector().cast<double>();
Line grid_line = grid.line(*it_contour_and_segment);
const Vec2d v2 = grid_line.vector().cast<double>();
double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length());
// An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other.
// This helps filter out cases when the following expensive calculations are useless.
if ((grid_line.a - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
(grid_line.b - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
(grid_line.a - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr ||
(grid_line.b - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr)
continue;
// When lines have too different length, it is necessary to normalize them
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1.squaredNorm() * v2.squaredNorm()) {
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) {
// The two vectors are nearly collinear (their mutual angle is lower than 30 degrees)
if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) {
double dist_1 = grid_line.distance_to(line_to_test.a);
double dist_2 = grid_line.distance_to(line_to_test.b);
double dist_3 = line_to_test.distance_to(grid_line.a);
double dist_4 = line_to_test.distance_to(grid_line.b);
double total_dist = std::min(std::min(dist_1, dist_2), std::min(dist_3, dist_4));
if (total_dist < 50 * SCALED_EPSILON) {
if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 ||
grid_line.distance_to_squared(line_to_test.b) < append_threshold2 ||
line_to_test.distance_to_squared(grid_line.a) < append_threshold2 ||
line_to_test.distance_to_squared(grid_line.b) < append_threshold2) {
Line line_to_test_projected;
project_line_on_line(grid_line, line_to_test, &line_to_test_projected);
if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) {
if ((line_to_test_projected.a - grid_line.a).cast<double>().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast<double>().squaredNorm())
line_to_test_projected.reverse();
}
painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color});
painted_lines_set.insert(*it_contour_and_segment);
}
@ -125,9 +129,11 @@ struct PaintedLineVisitor
std::vector<PaintedLine> &painted_lines;
Line line_to_test;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> painted_lines_set;
int color = -1;
int color = -1;
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
static inline const double append_threshold = 50 * SCALED_EPSILON;
static inline const double append_threshold2 = Slic3r::sqr(append_threshold);
};
static std::vector<ColoredLine> to_colored_lines(const Polygon &polygon, int color)
@ -154,6 +160,7 @@ static Polygon colored_points_to_polygon(const std::vector<ColoredLine> &lines)
static Polygons colored_points_to_polygon(const std::vector<std::vector<ColoredLine>> &lines)
{
Polygons out;
out.reserve(lines.size());
for (const std::vector<ColoredLine> &l : lines)
out.emplace_back(colored_points_to_polygon(l));
return out;
@ -484,6 +491,12 @@ static std::vector<std::vector<ColoredLine>> colorize_polygons(const Polygons &p
using boost::polygon::voronoi_diagram;
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
struct MMU_Graph
{
enum class ARC_TYPE { BORDER, NON_BORDER };
@ -616,24 +629,63 @@ struct MMU_Graph
{
return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1());
}
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges.
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) {
bbox.offset(SCALED_EPSILON);
struct CPoint
{
CPoint() = delete;
CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {}
const Point m_point;
size_t m_point_idx;
size_t m_contour_idx;
[[nodiscard]] const Point &point() const { return m_point; }
bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
};
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType;
CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON));
CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
for (const Polygon &polygon : color_poly_tmp)
for (const Point &pt : polygon.points)
closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
vertex.color(-1);
Point vertex_point = mk_point(vertex);
const Point &first_point = this->nodes[this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
const Point &second_point = this->nodes[this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
if (vertex_equal_to_point(&vertex, first_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
} else if (vertex_equal_to_point(&vertex, second_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) {
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) {
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) {
closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count()));
vertex.color(this->nodes_count());
this->nodes.push_back({vertex_point});
} else {
vertex.color(voronoi_pt->m_point_idx);
}
}
}
}
};
namespace bg = boost::geometry;
namespace bgm = boost::geometry::model;
namespace bgi = boost::geometry::index;
// float is needed because for coord_t bgi::intersects throws "bad numeric conversion: positive overflow"
using rtree_point_t = bgm::point<float, 2, boost::geometry::cs::cartesian>;
using rtree_t = bgi::rtree<std::pair<rtree_point_t, size_t>, bgi::rstar<16, 4>>;
static inline rtree_point_t mk_rtree_point(const Point &pt) { return rtree_point_t(float(pt.x()), float(pt.y())); }
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
static inline void mark_processed(const voronoi_diagram<double>::const_edge_iterator &edge_iterator)
{
edge_iterator->color(true);
@ -695,7 +747,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
{
Geometry::VoronoiDiagram vd;
std::vector<ColoredLine> lines_colored = to_lines(color_poly);
Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
const Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
const Points points = to_points(color_poly_tmp);
const Lines lines = to_lines(color_poly_tmp);
@ -719,6 +771,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd);
MMU_Graph graph;
graph.nodes.reserve(points.size() + vd.vertices().size());
for (const Point &point : points)
graph.nodes.push_back({point});
@ -726,66 +779,8 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
init_polygon_indices(graph, color_poly, lines_colored);
assert(graph.nodes.size() == lines_colored.size());
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch Eliminates issues with intersection edges.
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
auto append_voronoi_vertices_to_graph = [&graph, &color_poly_tmp, &vd]() -> void {
auto is_equal_points = [](const Point &p1, const Point &p2) { return p1 == p2 || (p1 - p2).cast<double>().norm() <= 3 * SCALED_EPSILON; };
BoundingBox bbox = get_extents(color_poly_tmp);
bbox.offset(SCALED_EPSILON);
// EdgeGrid is used for vertices near to contour and rtree for other vertices
// FIXME Lukas H.: Get rid of EdgeGrid and rtree. Use only one structure for both cases.
EdgeGrid::Grid grid;
grid.set_bbox(bbox);
grid.create(color_poly_tmp, coord_t(scale_(10.)));
rtree_t rtree;
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
vertex.color(-1);
Point vertex_point = mk_point(vertex);
const Point &first_point = graph.nodes[graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
const Point &second_point = graph.nodes[graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
if (vertex_equal_to_point(&vertex, first_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
} else if (vertex_equal_to_point(&vertex, second_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) {
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(vertex_point, coord_t(3 * SCALED_EPSILON));
if (cp.valid()) {
size_t global_idx = graph.get_global_index(cp.contour_idx, cp.start_point_idx);
size_t global_idx_next = graph.get_global_index(cp.contour_idx, (cp.start_point_idx + 1) % color_poly_tmp[cp.contour_idx].points.size());
vertex.color(is_equal_points(vertex_point, graph.nodes[global_idx].point) ? global_idx : global_idx_next);
} else {
if (rtree.empty()) {
rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count()));
vertex.color(graph.nodes_count());
graph.nodes.push_back({vertex_point});
} else {
std::vector<std::pair<rtree_point_t, size_t>> closest;
rtree.query(bgi::nearest(mk_rtree_point(vertex_point), 1), std::back_inserter(closest));
assert(!closest.empty());
rtree_point_t r_point = closest.front().first;
Point closest_p(bg::get<0>(r_point), bg::get<1>(r_point));
if (Line(vertex_point, closest_p).length() > 3 * SCALED_EPSILON) {
rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count()));
vertex.color(graph.nodes_count());
graph.nodes.push_back({vertex_point});
} else {
vertex.color(closest.front().second);
}
}
}
}
}
};
append_voronoi_vertices_to_graph();
BoundingBox bbox = get_extents(color_poly_tmp);
graph.append_voronoi_vertices(vd, color_poly_tmp, bbox);
auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram<double>::const_edge_iterator &edge_it) -> ColoredLine {
size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx;
@ -803,7 +798,6 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
return lines_colored[contour_next_idx];
};
BoundingBox bbox = get_extents(color_poly_tmp);
bbox.offset(scale_(10.));
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
@ -1428,7 +1422,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
// All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON
// to ensure that very close polygons will be merged.
ex_polygons = union_ex(ex_polygons);
// Remove all expolygons and holes with an area less than 0.01mm^2
// Remove all expolygons and holes with an area less than 0.1mm^2
remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f)));
// Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams
// and consequently with the extraction of colored segments by function extract_colored_segments.
@ -1437,19 +1431,19 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
// Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices.
// This consequently leads to issues with the extraction of colored segments by function extract_colored_segments.
// Calling expolygons_simplify fixed these issues.
input_expolygons[layer_idx] = simplify_polygons_ex(to_polygons(expolygons_simplify(offset_ex(ex_polygons, float(-10 * SCALED_EPSILON)), 5 * SCALED_EPSILON)));
input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]);
input_expolygons[layer_idx] = smooth_outward(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), 10 * coord_t(SCALED_EPSILON));
input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]);
}
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end";
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
throw_on_cancel_callback();
BoundingBox bbox(get_extents(input_expolygons[layer_idx]));
BoundingBox bbox(get_extents(input_polygons[layer_idx]));
// Projected triangles may slightly exceed the input polygons.
bbox.offset(20 * SCALED_EPSILON);
edge_grids[layer_idx].set_bbox(bbox);
edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.)));
edge_grids[layer_idx].create(input_polygons[layer_idx], coord_t(scale_(10.)));
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin";
@ -1498,7 +1492,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
// [P0, P2] a [P0, P1]
float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z());
line_end_f = facet[0] + t1 * (facet[1] - facet[0]);
} else if (facet[1].z() <= layer->slice_z) {
} else {
// [P0, P2] a [P1, P2]
float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z());
line_end_f = facet[1] + t2 * (facet[2] - facet[1]);

View file

@ -166,7 +166,7 @@ static bool clip_narrow_corner(
assert(orient1 > 0 == blocked);
assert(orient2 > 0 == blocked);
}
#endif // _NDEBUG
#endif // NDEBUG
if (polygon.size() < 3 || (forward == Far && backward == Far)) {
polygon.clear();
} else {

View file

@ -3,6 +3,7 @@
#include "Point.hpp"
#include "Polygon.hpp"
#include "ExPolygon.hpp"
namespace Slic3r {
@ -330,6 +331,24 @@ inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled)
return polygons;
}
inline ExPolygons smooth_outward(ExPolygons expolygons, coord_t clip_dist_scaled)
{
MutablePolygon mp;
for (ExPolygon &expolygon : expolygons) {
mp.assign(expolygon.contour, expolygon.contour.size() * 2);
smooth_outward(mp, clip_dist_scaled);
mp.polygon(expolygon.contour);
for (Polygon &hole : expolygon.holes) {
mp.assign(hole, hole.size() * 2);
smooth_outward(mp, clip_dist_scaled);
mp.polygon(hole);
}
expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end());
}
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end());
return expolygons;
}
}
#endif // slic3r_MutablePolygon_hpp_

View file

@ -666,7 +666,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
void PresetCollection::load_presets(
const std::string &dir_path, const std::string &subdir,
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
{
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
@ -693,7 +695,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
// Load the preset file, apply preset values on top of defaults.
try {
DynamicPrintConfig config;
config.load_from_ini(preset.file);
ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
if (! config_substitutions.empty())
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
const Preset &default_preset = this->default_preset_for(config);
preset.config = default_preset.config;
@ -1580,7 +1584,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::str
// Load all printers found in dir_path.
// Throws an exception on error.
void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir)
void PhysicalPrinterCollection::load_printers(
const std::string& dir_path, const std::string& subdir,
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
{
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
@ -1606,7 +1612,9 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
// Load the preset file, apply preset values on top of defaults.
try {
DynamicPrintConfig config;
config.load_from_ini(printer.file);
ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule);
if (! config_substitutions.empty())
substitutions.push_back({ name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) });
printer.update_from_config(config);
printer.loaded = true;
}

View file

@ -113,6 +113,9 @@ public:
TYPE_SLA_MATERIAL,
TYPE_PRINTER,
TYPE_COUNT,
// This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class,
// PhysicalPrinter class is used instead.
TYPE_PHYSICAL_PRINTER,
};
Type type = TYPE_INVALID;
@ -251,6 +254,27 @@ enum class PresetSelectCompatibleType {
Always
};
// Substitutions having been performed during parsing a single configuration file.
struct PresetConfigSubstitutions {
// User readable preset name.
std::string preset_name;
// Type of the preset (Print / Filament / Printer ...)
Preset::Type preset_type;
enum class Source {
UserFile,
ConfigBundle,
};
Source preset_source;
// Source of the preset. It may be empty in case of a ConfigBundle being loaded.
std::string preset_file;
// What config value has been substituted with what.
ConfigSubstitutions substitutions;
};
// Substitutions having been performed during parsing a set of configuration files, for example when starting up
// PrusaSlicer and reading the user Print / Filament / Printer profiles.
using PresetsConfigSubstitutions = std::vector<PresetConfigSubstitutions>;
// Collections of presets of the same type (one of the Print, Filament or Printer type).
class PresetCollection
{
@ -280,7 +304,7 @@ public:
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
// Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir);
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
@ -692,7 +716,7 @@ public:
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
// Load ini files of the particular type from the provided directory path.
void load_printers(const std::string& dir_path, const std::string& subdir);
void load_printers(const std::string& dir_path, const std::string& subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
void load_printers_from_presets(PrinterPresetCollection &printer_presets);
// Load printer from the loaded configuration
void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false);

View file

@ -187,7 +187,7 @@ void PresetBundle::setup_directories()
}
}
void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id)
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
{
// First load the vendor specific system presets.
std::string errors_cummulative = this->load_system_presets();
@ -200,33 +200,35 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
#endif
;
PresetsConfigSubstitutions substitutions;
try {
this->prints.load_presets(dir_user_presets, "print");
this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->sla_prints.load_presets(dir_user_presets, "sla_print");
this->sla_prints.load_presets(dir_user_presets, "sla_print", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->filaments.load_presets(dir_user_presets, "filament");
this->filaments.load_presets(dir_user_presets, "filament", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->sla_materials.load_presets(dir_user_presets, "sla_material");
this->sla_materials.load_presets(dir_user_presets, "sla_material", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->printers.load_presets(dir_user_presets, "printer");
this->printers.load_presets(dir_user_presets, "printer", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
try {
this->physical_printers.load_printers(dir_user_presets, "physical_printer");
this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
}
@ -236,6 +238,8 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
throw Slic3r::RuntimeError(errors_cummulative);
this->load_selections(config, preferred_model_id);
return substitutions;
}
// Load system presets into this PresetBundle.
@ -255,13 +259,13 @@ std::string PresetBundle::load_system_presets()
// Load the config bundle, flatten it.
if (first) {
// Reset this PresetBundle and load the first vendor config.
this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
first = false;
} else {
// Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles.
PresetBundle other;
other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
if (! duplicates.empty()) {
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
@ -690,15 +694,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
void PresetBundle::load_config_file(const std::string &path)
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
if (is_gcode_file(path)) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(path);
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return;
return config_substitutions;
}
// 1) Try to load the config file into a boost property tree.
@ -717,6 +721,7 @@ void PresetBundle::load_config_file(const std::string &path)
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
ConfigSubstitutions config_substitutions;
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
@ -727,15 +732,18 @@ void PresetBundle::load_config_file(const std::string &path)
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load(tree);
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
break;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
load_config_file_config_bundle(path, tree);
break;
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree);
}
// This shall never happen. Suppres compiler warnings.
assert(false);
return ConfigSubstitutions{};
}
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
@ -907,16 +915,25 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
}
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
{
// 1) Load the config bundle into a temp data.
PresetBundle tmp_bundle;
// Load the config bundle, don't save the loaded presets to user profile directory.
tmp_bundle.load_configbundle(path, 0);
// Load the config bundle, but don't save the loaded presets to user profile directory, as only the presets marked as active in the loaded preset bundle
// will be loaded into the master PresetBundle and activated.
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {});
UNUSED(presets_imported);
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
auto load_one = [&path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
ConfigSubstitutions config_substitutions;
auto load_one = [&path, &bundle_name, &presets_substitutions = presets_substitutions, &config_substitutions](
PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
// If there are substitutions reported for this preset, move them to config_substitutions.
if (auto it = std::find_if(presets_substitutions.begin(), presets_substitutions.end(), [&preset_name_src](const PresetConfigSubstitutions& subs){ return subs.preset_name == preset_name_src; });
it != presets_substitutions.end() && ! it->substitutions.empty())
append(config_substitutions, std::move(it->substitutions));
Preset *preset_src = collection_src.find_preset(preset_name_src, false);
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
assert(preset_src != nullptr);
@ -970,6 +987,9 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
this->update_compatible(PresetSelectCompatibleType::Never);
sort_remove_duplicates(config_substitutions);
return config_substitutions;
}
// Process the Config Bundle loaded as a Boost property tree.
@ -1114,11 +1134,20 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
{
if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM))
// Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE.
this->reset(flags & LOAD_CFGBNDLE_SAVE);
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
ConfigSubstitutionContext substitution_context {
flags.has(LoadConfigBundleAttribute::LoadSystem) ?
ForwardCompatibilitySubstitutionRule::Disable :
ForwardCompatibilitySubstitutionRule::Enable
};
PresetsConfigSubstitutions substitutions;
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
// Reset this bundle, delete user profile files if SaveImported.
this->reset(flags.has(LoadConfigBundleAttribute::SaveImported));
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
@ -1131,25 +1160,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
}
const VendorProfile *vendor_profile = nullptr;
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) {
if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
auto vp = VendorProfile::from_ini(tree, path);
if (vp.models.size() == 0) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
return 0;
return std::make_pair(PresetsConfigSubstitutions{}, 0);
} else if (vp.num_variants() == 0) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
return 0;
return std::make_pair(PresetsConfigSubstitutions{}, 0);
}
vendor_profile = &this->vendors.insert({vp.id, vp}).first->second;
}
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) {
return 0;
}
if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly))
return std::make_pair(PresetsConfigSubstitutions{}, 0);
// 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
// If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact.
flatten_configbundle_hierarchy(tree, ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) ? this : nullptr);
flatten_configbundle_hierarchy(tree, flags.has(LoadConfigBundleAttribute::LoadSystem) ? nullptr : this);
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
// Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
@ -1246,7 +1274,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
DynamicPrintConfig config;
std::string alias_name;
std::vector<std::string> renamed_from;
auto parse_config_section = [&section, &alias_name, &renamed_from, &path](DynamicPrintConfig &config) {
auto parse_config_section = [&section, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
substitution_context.substitutions.clear();
for (auto &kvp : section.second) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
@ -1256,7 +1285,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
config.set_deserialize(kvp.first, kvp.second.data());
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
}
};
if (presets == &this->printers) {
@ -1277,7 +1307,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
if (! incorrect_keys.empty())
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) {
if (flags.has(LoadConfigBundleAttribute::LoadSystem) && presets == &printers) {
// Filter out printer presets, which are not mentioned in the vendor profile.
// These presets are considered not installed.
auto printer_model = config.opt_string("printer_model");
@ -1312,7 +1342,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
section.first << "\" has already been loaded from another Confing Bundle.";
continue;
}
} else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
} else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
// This is a user config bundle.
const Preset *existing = presets->find_preset(preset_name, false);
if (existing != nullptr) {
@ -1341,9 +1371,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
/ presets->section_name() / file_name).make_preferred();
// Load the preset into the list of presets, save it to disk.
Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false);
if (flags & LOAD_CFGBNDLE_SAVE)
if (flags.has(LoadConfigBundleAttribute::SaveImported))
loaded.save();
if (flags & LOAD_CFGBNDLE_SYSTEM) {
if (flags.has(LoadConfigBundleAttribute::LoadSystem)) {
loaded.is_system = true;
loaded.vendor = vendor_profile;
}
@ -1364,7 +1394,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
else
loaded.alias = std::move(alias_name);
loaded.renamed_from = std::move(renamed_from);
if (! substitution_context.empty())
substitutions.push_back({
preset_name, presets->type(), PresetConfigSubstitutions::Source::ConfigBundle,
std::string(), std::move(substitution_context.substitutions) });
++ presets_loaded;
}
@ -1373,8 +1406,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
const DynamicPrintConfig& default_config = ph_printers->default_config();
DynamicPrintConfig config = default_config;
substitution_context.substitutions.clear();
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data());
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
@ -1400,14 +1434,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
#endif
/ "physical_printer" / file_name).make_preferred();
// Load the preset into the list of presets, save it to disk.
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE);
++ph_printers_loaded;
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported));
if (! substitution_context.empty())
substitutions.push_back({
ph_printer_name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::ConfigBundle,
std::string(), std::move(substitution_context.substitutions) });
++ ph_printers_loaded;
}
}
// 3) Activate the presets and physical printer if any exists.
if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
if (! active_print.empty())
prints.select_preset_by_name(active_print, true);
if (! active_sla_print.empty())
@ -1427,7 +1464,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
this->update_compatible(PresetSelectCompatibleType::Never);
}
return presets_loaded + ph_printers_loaded;
return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded);
}
void PresetBundle::update_multi_material_filament_presets()

View file

@ -3,6 +3,7 @@
#include "Preset.hpp"
#include "AppConfig.hpp"
#include "enum_bitmask.hpp"
#include <memory>
#include <unordered_map>
@ -26,7 +27,7 @@ public:
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini
void load_presets(AppConfig &config, const std::string &preferred_model_id = std::string());
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string());
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
@ -82,24 +83,26 @@ public:
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
void load_config_file(const std::string &path);
ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
// Load settings into the provided settings instance.
// Activate the presets stored in the config bundle.
// Returns the number of presets loaded successfully.
enum {
enum LoadConfigBundleAttribute {
// Save the profiles, which have been loaded.
LOAD_CFGBNDLE_SAVE = 1,
SaveImported,
// Delete all old config profiles before loading.
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
ResetUserProfile,
// Load a system config bundle.
LOAD_CFGBNDLE_SYSTEM = 4,
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
LoadSystem,
LoadVendorOnly,
};
// Load the config bundle, store it to the user profile directory by default.
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>;
// Load the config bundle based on the flags.
// Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise.
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(const std::string &path, LoadConfigBundleAttributes flags);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
@ -155,12 +158,14 @@ private:
// and the external config is just referenced, not stored into user profile directory.
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;
};
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View file

@ -448,9 +448,9 @@ public:
// Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps().
CANCELED_INTERNAL = 2
};
CancelStatus cancel_status() const { return m_cancel_status; }
CancelStatus cancel_status() const { return m_cancel_status.load(std::memory_order_acquire); }
// Has the calculation been canceled?
bool canceled() const { return m_cancel_status != NOT_CANCELED; }
bool canceled() const { return m_cancel_status.load(std::memory_order_acquire) != NOT_CANCELED; }
// Cancel the running computation. Stop execution of all the background threads.
void cancel() { m_cancel_status = CANCELED_BY_USER; }
void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; }
@ -481,7 +481,7 @@ protected:
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
void throw_if_canceled() const { if (m_cancel_status.load(std::memory_order_acquire)) throw CanceledException(); }
// Wrapper around this->throw_if_canceled(), so that throw_if_canceled() may be passed to a function without making throw_if_canceled() public.
PrintTryCancel make_try_cancel() const { return PrintTryCancel(this); }

View file

@ -50,7 +50,7 @@ static t_config_enum_values s_keys_map_GCodeFlavor {
{ "teacup", gcfTeacup },
{ "makerware", gcfMakerWare },
{ "marlin", gcfMarlinLegacy },
{ "marlinfirmware", gcfMarlinFirmware },
{ "marlin2", gcfMarlinFirmware },
{ "sailfish", gcfSailfish },
{ "smoothie", gcfSmoothie },
{ "mach3", gcfMach3 },
@ -67,6 +67,7 @@ static t_config_enum_values s_keys_map_MachineLimitsUsage {
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage)
static t_config_enum_values s_keys_map_PrintHostType {
{ "prusalink", htPrusaLink },
{ "octoprint", htOctoPrint },
{ "duet", htDuet },
{ "flashair", htFlashAir },
@ -172,6 +173,13 @@ static const t_config_enum_values s_keys_map_BrimType = {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = {
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
{ "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
{
for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
@ -1245,7 +1253,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("teacup");
def->enum_values.push_back("makerware");
def->enum_values.push_back("marlin");
def->enum_values.push_back("marlinfirmware");
def->enum_values.push_back("marlin2");
def->enum_values.push_back("sailfish");
def->enum_values.push_back("mach3");
def->enum_values.push_back("machinekit");
@ -1772,11 +1780,13 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
"the kind of the host.");
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
def->enum_values.push_back("prusalink");
def->enum_values.push_back("octoprint");
def->enum_values.push_back("duet");
def->enum_values.push_back("flashair");
def->enum_values.push_back("astrobox");
def->enum_values.push_back("repetier");
def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
@ -3608,8 +3618,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} catch (boost::bad_lexical_cast &) {
value = "0";
}
} else if (opt_key == "gcode_flavor" && value == "makerbot") {
value = "makerware";
} else if (opt_key == "gcode_flavor") {
if (value == "makerbot")
value = "makerware";
else if (value == "marlinfirmware")
// the "new" marlin firmware flavor used to be called "marlinfirmware" for some time during PrusaSlicer 2.4.0-alpha development.
value = "marlin2";
} else if (opt_key == "fill_density" && value.find("%") == std::string::npos) {
try {
// fill_density was turned into a percent value
@ -3622,7 +3636,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "bed_size" && !value.empty()) {
opt_key = "bed_shape";
ConfigOptionPoint p;
p.deserialize(value);
p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable);
std::ostringstream oss;
oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1);
value = oss.str();
@ -4171,6 +4185,20 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->label = L("Ignore non-existent config files");
def->tooltip = L("Do not fail if a file supplied to --load does not exist.");
def = this->add("config_compatibility", coEnum);
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF).");
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. "
"For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to "
"bail out or to substitute an unknown value with a default silently or verbosely.");
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();
def->enum_values.push_back("disable");
def->enum_values.push_back("enable");
def->enum_values.push_back("enable_silent");
def->enum_labels.push_back(L("Bail out on unknown configuration values"));
def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults."));
def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults."));
def->set_default_value(new ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>(ForwardCompatibilitySubstitutionRule::Enable));
def = this->add("load", coStrings);
def->label = L("Load config file");
def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files.");

View file

@ -44,7 +44,7 @@ enum class MachineLimitsUsage {
};
enum PrintHostType {
htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
};
enum AuthorizationType {
@ -141,6 +141,7 @@ 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)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@ -1086,8 +1087,8 @@ public:
bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; }
template<typename T>
void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); }
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false)
{ m_data.set_deserialize(opt_key, str, append); this->touch(); }
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false)
{ m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); }
bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; }
// Getters are thread safe.

View file

@ -10,11 +10,11 @@
#include <condition_variable>
#include <mutex>
#include <thread>
#include <tbb/global_control.h>
#include <tbb/parallel_for.h>
#include <tbb/task_arena.h>
#include "Thread.hpp"
#include "Utils.hpp"
namespace Slic3r {
@ -199,16 +199,14 @@ void name_tbb_thread_pool_threads()
// TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency().
// const size_t nthreads_hw = std::thread::hardware_concurrency();
const size_t nthreads_hw = tbb::this_task_arena::max_concurrency();
size_t nthreads = nthreads_hw;
size_t nthreads = nthreads_hw;
#ifdef SLIC3R_PROFILE
// Shiny profiler is not thread safe, thus disable parallelization.
disable_multi_threading();
nthreads = 1;
#endif
if (nthreads != nthreads_hw)
tbb::global_control(tbb::global_control::max_allowed_parallelism, nthreads);
std::atomic<size_t> nthreads_running(0);
std::condition_variable cv;
std::mutex cv_m;

View file

@ -3,9 +3,9 @@
#include <boost/container/small_vector.hpp>
#ifndef _NDEBUG
#ifndef NDEBUG
#define EXPENSIVE_DEBUG_CHECKS
#endif // _NDEBUG
#endif // NDEBUG
namespace Slic3r {
@ -19,7 +19,7 @@ static inline Vec3i root_neighbors(const TriangleMesh &mesh, int triangle_id)
return neighbors;
}
#ifndef _NDEBUG
#ifndef NDEBUG
bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const
{
for (int i = 0; i < 3; ++ i) {
@ -57,7 +57,7 @@ bool TriangleSelector::verify_triangle_neighbors(const Triangle &tr, const Vec3i
}
return true;
}
#endif // _NDEBUG
#endif // NDEBUG
// sides_to_split==-1 : just restore previous split
void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx)
@ -308,12 +308,12 @@ int TriangleSelector::triangle_midpoint_or_allocate(int itriangle, int vertexi,
}
assert(m_vertices[midpoint].ref_cnt == 0);
} else {
#ifndef _NDEBUG
#ifndef NDEBUG
Vec3f c1 = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v);
Vec3f c2 = m_vertices[midpoint].v;
float d = (c2 - c1).norm();
assert(std::abs(d) < EPSILON);
#endif // _NDEBUG
#endif // NDEBUG
assert(m_vertices[midpoint].ref_cnt > 0);
}
return midpoint;
@ -816,13 +816,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo
assert(tr.is_split());
// indices of triangle vertices
#ifdef _NDEBUG
#ifdef NDEBUG
boost::container::small_vector<int, 6> verts_idxs;
#else // _NDEBUG
#else // NDEBUG
// For easier debugging.
std::vector<int> verts_idxs;
verts_idxs.reserve(6);
#endif // _NDEBUG
#endif // NDEBUG
for (int j=0, idx = tr.special_side(); j<3; ++j, idx = next_idx_modulo(idx, 3))
verts_idxs.push_back(tr.verts_idxs[idx]);
@ -861,13 +861,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo
break;
}
#ifndef _NDEBUG
#ifndef NDEBUG
assert(this->verify_triangle_neighbors(tr, neighbors));
for (int i = 0; i <= tr.number_of_split_sides(); ++i) {
Vec3i n = this->child_neighbors(tr, neighbors, i);
assert(this->verify_triangle_neighbors(m_triangles[tr.children[i]], n));
}
#endif // _NDEBUG
#endif // NDEBUG
}
bool TriangleSelector::has_facets(EnforcerBlockerType state) const

View file

@ -204,10 +204,10 @@ private:
int triangle_midpoint(int itriangle, int vertexi, int vertexj) const;
int triangle_midpoint_or_allocate(int itriangle, int vertexi, int vertexj);
#ifndef _NDEBUG
#ifndef NDEBUG
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
bool verify_triangle_midpoints(const Triangle& tr) const;
#endif // _NDEBUG
#endif // NDEBUG
void get_facets_strict_recursive(
const Triangle &tr,

View file

@ -0,0 +1,80 @@
#ifndef slic3r_enum_bitmask_hpp_
#define slic3r_enum_bitmask_hpp_
// enum_bitmask for passing a set of attributes to a function in a type safe way.
// Adapted from https://gpfault.net/posts/typesafe-bitmasks.txt.html
// with hints from https://www.strikerx3.dev/cpp/2019/02/27/typesafe-enum-class-bitmasks-in-cpp.html
#include <type_traits>
namespace Slic3r {
// enum_bitmasks can only be used with enums.
template<class option_type, typename = typename std::enable_if<std::is_enum<option_type>::value>::type>
class enum_bitmask {
// The type we'll use for storing the value of our bitmask should be the same as the enum's underlying type.
using underlying_type = typename std::underlying_type<option_type>::type;
// This method helps us avoid having to explicitly set enum values to powers of two.
static constexpr underlying_type mask_value(option_type o) { return 1 << static_cast<underlying_type>(o); }
// Private ctor to be used internally.
explicit constexpr enum_bitmask(underlying_type o) : m_bits(o) {}
public:
// Default ctor creates a bitmask with no options selected.
constexpr enum_bitmask() : m_bits(0) {}
// Creates a enum_bitmask with just one bit set.
// This ctor is intentionally non-explicit, to allow passing an options to a function:
// FunctionExpectingBitmask(Options::Opt1)
constexpr enum_bitmask(option_type o) : m_bits(mask_value(o)) {}
// Set the bit corresponding to the given option.
constexpr enum_bitmask operator|(option_type t) { return enum_bitmask(m_bits | mask_value(t)); }
// Combine with another enum_bitmask of the same type.
constexpr enum_bitmask operator|(enum_bitmask<option_type> t) { return enum_bitmask(m_bits | t.m_bits); }
// Get the value of the bit corresponding to the given option.
constexpr bool operator&(option_type t) { return m_bits & mask_value(t); }
constexpr bool has(option_type t) { return m_bits & mask_value(t); }
private:
underlying_type m_bits = 0;
};
// For enabling free functions producing enum_bitmask<> type from bit operations on enums.
template<typename Enum> struct is_enum_bitmask_type { static const bool enable = false; };
#define ENABLE_ENUM_BITMASK_OPERATORS(x) template<> struct is_enum_bitmask_type<x> { static const bool enable = true; };
template<class Enum> inline constexpr bool is_enum_bitmask_type_v = is_enum_bitmask_type<Enum>::enable;
// Creates an enum_bitmask from two options, convenient for passing of options to a function:
// FunctionExpectingBitmask(Options::Opt1 | Options::Opt2 | Options::Opt3)
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, option_type rhs) {
static_assert(std::is_enum_v<option_type>);
return enum_bitmask<option_type>{lhs} | rhs;
}
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, enum_bitmask<option_type> rhs) {
static_assert(std::is_enum_v<option_type>);
return enum_bitmask<option_type>{lhs} | rhs;
}
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, option_type opt) {
static_assert(std::is_enum_v<option_type>);
return condition ? enum_bitmask<option_type>{opt} : enum_bitmask<option_type>{};
}
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, enum_bitmask<option_type> opt) {
static_assert(std::is_enum_v<option_type>);
return condition ? opt : enum_bitmask<option_type>{};
}
} // namespace Slic3r
#endif // slic3r_enum_bitmask_hpp_

View file

@ -115,6 +115,7 @@
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Config.hpp"
#include "enum_bitmask.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "MultiPoint.hpp"

View file

@ -8,6 +8,7 @@
#include "Platform.hpp"
#include "Time.hpp"
#include "libslic3r.h"
#ifdef WIN32
#include <windows.h>
@ -43,7 +44,13 @@
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <tbb/global_control.h>
// We are using quite an old TBB 2017 U7, which does not support global control API officially.
// Before we update our build servers, let's use the old API, which is deprecated in up to date TBB.
#ifdef TBB_HAS_GLOBAL_CONTROL
#include <tbb/global_control.h>
#else
#include <tbb/task_scheduler_init.h>
#endif
#if defined(__linux__) || defined(__GNUC__ )
#include <strings.h>
@ -118,7 +125,12 @@ void trace(unsigned int level, const char *message)
void disable_multi_threading()
{
// Disable parallelization so the Shiny profiler works
#ifdef TBB_HAS_GLOBAL_CONTROL
tbb::global_control(tbb::global_control::max_allowed_parallelism, 1);
#else // TBB_HAS_GLOBAL_CONTROL
static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(1);
UNUSED(tbb_init);
#endif // TBB_HAS_GLOBAL_CONTROL
}
static std::string g_var_dir;

View file

@ -181,6 +181,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Mouse3DController.hpp
GUI/DoubleSlider.cpp
GUI/DoubleSlider.hpp
GUI/Notebook.cpp
GUI/Notebook.hpp
GUI/ObjectDataViewModel.cpp
GUI/ObjectDataViewModel.hpp
GUI/InstanceCheck.cpp

View file

@ -413,7 +413,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly);
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;

View file

@ -255,7 +255,6 @@ private:
std::shared_ptr<UITask> m_ui_task;
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
mutable tbb::mutex m_step_state_mutex;
bool set_step_started(BackgroundSlicingProcessStep step);
void set_step_done(BackgroundSlicingProcessStep step);
bool is_step_done(BackgroundSlicingProcessStep step) const;

View file

@ -64,7 +64,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
this->is_prusa_bundle = ais_prusa_bundle;
std::string path_string = source_path.string();
size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM);
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem);
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
assert(config_substitutions.empty());
auto first_vendor = preset_bundle->vendors.begin();
if (first_vendor == preset_bundle->vendors.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
@ -2592,7 +2594,10 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
}
}
preset_bundle->load_presets(*app_config, preferred_model);
// Reloading the configs after some modifications were done to PrusaSlicer.ini.
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
// and the Wizard shall not create any new values that would require substitution.
PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model);
if (page_custom->custom_wanted()) {
page_firmware->apply_custom_config(*custom_config);

View file

@ -2010,11 +2010,11 @@ void Control::show_cog_icon_context_menu()
[]() { return true; }, [this]() { return m_extra_style == 0; }, GUI::wxGetApp().plater());
append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show object height"), _L("Show object height on the ruler"),
[this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style &= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu,
[this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style ^= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu,
[]() { return true; }, [this]() { return m_extra_style & wxSL_AUTOTICKS; }, GUI::wxGetApp().plater());
append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show estimated print time"), _L("Show estimated print time on the ruler"),
[this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style &= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu,
[this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style ^= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu,
[]() { return true; }, [this]() { return m_extra_style & wxSL_VALUE_LABEL; }, GUI::wxGetApp().plater());
append_submenu(&menu, ruler_mode_menu, wxID_ANY, _L("Ruler mode"), _L("Set ruler mode"), "",

View file

@ -1141,6 +1141,10 @@ void Choice::set_value(const boost::any& value, bool change_event)
}
case coEnum: {
int val = boost::any_cast<int>(value);
if (m_opt_id.compare("host_type") == 0 && val != 0 &&
m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
val--;
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
{
std::string key;
@ -1197,7 +1201,7 @@ void Choice::set_values(const wxArrayString &values)
auto ww = dynamic_cast<choice_ctrl*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
// ww->Append("");
for (const auto &el : values)
ww->Append(el);
ww->SetValue(value);
@ -1219,7 +1223,10 @@ boost::any& Choice::get_value()
if (m_opt.type == coEnum)
{
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") {
if (m_opt_id.compare("host_type") == 0 && m_opt.enum_values.size() > field->GetCount()) {
// for case, when PrusaLink isn't used as a HostType
m_value = field->GetSelection()+1;
} else if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") {
const std::string& key = m_opt.enum_values[field->GetSelection()];
m_value = int(ConfigOptionEnum<InfillPattern>::get_enum_values().at(key));
}

View file

@ -10,7 +10,6 @@ namespace boost::filesystem { class path; }
class wxWindow;
class wxMenuBar;
class wxNotebook;
class wxComboCtrl;
class wxFileDialog;
class wxTopLevelWindow;

View file

@ -72,6 +72,7 @@
#include "DesktopIntegrationDialog.hpp"
#include "BitmapCache.hpp"
#include "Notebook.hpp"
#ifdef __WXMSW__
#include <dbt.h>
@ -624,6 +625,13 @@ void GUI_App::post_init()
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
}
else {
if (! this->init_params->preset_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
@ -652,6 +660,24 @@ void GUI_App::post_init()
if (! this->init_params->extra_config.empty())
this->mainframe->load_config(this->init_params->extra_config);
}
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
if (this->preset_updater) {
this->check_updates(false);
CallAfter([this] {
this->config_wizard_startup();
this->preset_updater->slic3r_update_notify();
this->preset_updater->sync(preset_bundle);
});
}
#ifdef _WIN32
// Sets window property to mainframe so other instances can indentify it.
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
}
IMPLEMENT_APP(GUI_App)
@ -885,7 +911,7 @@ bool GUI_App::on_init_inner()
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
try {
preset_bundle->load_presets(*app_config);
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
} catch (const std::exception &ex) {
show_error(nullptr, ex.what());
}
@ -904,8 +930,6 @@ bool GUI_App::on_init_inner()
if (scrn && is_editor())
scrn->SetText(_L("Preparing settings tabs") + dots);
m_tabs_as_menu = dark_mode() || app_config->get("tabs_as_menu") == "1";
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor())
@ -948,7 +972,6 @@ bool GUI_App::on_init_inner()
if (! plater_)
return;
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();
@ -969,33 +992,6 @@ bool GUI_App::on_init_inner()
#endif
this->post_init();
}
// Preset updating & Configwizard are done after the above initializations,
// and after MainFrame is created & shown.
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
static bool once = true;
if (once) {
once = false;
if (preset_updater != nullptr) {
check_updates(false);
CallAfter([this] {
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
}
#ifdef _WIN32
//sets window property to mainframe so other instances can indentify it
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
}
});
m_initialized = true;
@ -1046,6 +1042,7 @@ void GUI_App::init_label_colours()
m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT);
m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216);
#else
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
#endif
@ -1093,24 +1090,22 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju
btn->Bind(wxEVT_LEAVE_WINDOW, [focus_button](wxMouseEvent& event) { focus_button(false); event.Skip(); });
}
}
else if (dark_mode()) {
if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(window)) {
if (text->GetBorder() != wxBORDER_SIMPLE)
text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE);
}
else if (wxCheckListBox* list = dynamic_cast<wxCheckListBox*>(window)) {
list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE);
list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
for (size_t i = 0; i < list->GetCount(); i++)
if (wxOwnerDrawn* item = list->GetItem(i)) {
item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
item->SetTextColour(m_color_label_default);
}
return;
}
else if (dynamic_cast<wxListBox*>(window))
window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE);
else if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(window)) {
if (text->GetBorder() != wxBORDER_SIMPLE)
text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE);
}
else if (wxCheckListBox* list = dynamic_cast<wxCheckListBox*>(window)) {
list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE);
list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
for (size_t i = 0; i < list->GetCount(); i++)
if (wxOwnerDrawn* item = list->GetItem(i)) {
item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
item->SetTextColour(m_color_label_default);
}
return;
}
else if (dynamic_cast<wxListBox*>(window))
window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE);
if (!just_font)
window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
@ -1141,8 +1136,6 @@ void GUI_App::UpdateDlgDarkUI(wxDialog* dlg)
void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
{
#ifdef _WIN32
if (!dark_mode())
return;
UpdateDarkUI(dvc, highlited);
wxItemAttr attr(dark_mode() ? m_color_highlight_default : m_color_label_default,
m_color_window_default,
@ -1158,8 +1151,6 @@ void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent)
{
#ifdef _WIN32
if (!dark_mode())
return;
wxGetApp().UpdateDarkUI(parent);
auto children = parent->GetChildren();
@ -1225,6 +1216,11 @@ void GUI_App::set_label_clr_sys(const wxColour& clr)
app_config->save();
}
bool GUI_App::tabs_as_menu() const
{
return app_config->get("tabs_as_menu") == "1"; // || dark_mode();
}
wxSize GUI_App::get_min_size() const
{
return wxSize(76*m_em_unit, 49 * m_em_unit);
@ -1369,6 +1365,14 @@ void fatal_error(wxWindow* parent)
// exit 1; // #ys_FIXME
}
#ifdef _WIN32
void GUI_App::force_colors_update()
{
NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
m_force_colors_update = true;
}
#endif
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
void GUI_App::update_ui_from_settings()
@ -1376,13 +1380,13 @@ void GUI_App::update_ui_from_settings()
update_label_colours();
mainframe->update_ui_from_settings();
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Use to force dark colors for SystemLightMode
if (m_force_sys_colors_update) {
m_force_sys_colors_update = false;
mainframe->force_sys_color_changed();
mainframe->diff_dialog.force_sys_color_changed();
#ifdef _WIN32
if (m_force_colors_update) {
m_force_colors_update = false;
mainframe->force_color_changed();
mainframe->diff_dialog.force_color_changed();
if (m_wizard)
m_wizard->force_sys_color_changed();
m_wizard->force_color_changed();
}
#endif
}
@ -1766,6 +1770,11 @@ void GUI_App::update_mode()
{
sidebar().update_mode();
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(mainframe->m_tabpanel)->UpdateMode();
#endif
for (auto tab : tabs_list)
tab->update_mode();
@ -1872,7 +1881,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
try {
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config);
if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
! all_substitutions.empty()) {
// TODO:
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
} catch (std::exception &ex) {
@ -1899,13 +1914,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
this->plater_->refresh_print();
if (dlg.recreate_GUI()) {
#ifdef _MSW_DARK_MODE
if (dlg.color_mode_changed()) {
NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
init_label_colours();
}
#endif
m_tabs_as_menu = dark_mode() || app_config->get("tabs_as_menu") == "1";
recreate_GUI(_L("Restart application") + dots);
return;
}

View file

@ -124,11 +124,10 @@ private:
wxColour m_color_highlight_label_default;
wxColour m_color_hovered_btn_label;
wxColour m_color_highlight_default;
//bool m_force_sys_colors_update { false }; // #ysDarkMSW - Use to force dark colors for SystemLightMode
wxColour m_color_selected_btn_bg;
bool m_force_colors_update { false };
#endif
bool m_tabs_as_menu{ false };
wxFont m_small_font;
wxFont m_bold_font;
wxFont m_normal_font;
@ -202,7 +201,9 @@ public:
#ifdef _WIN32
const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; }
const wxColour& get_highlight_default_clr() { return m_color_highlight_default; }
// void force_sys_colors_update() { m_force_sys_colors_update = true; } // #ysDarkMSW - Use to force dark colors for SystemLightMode
const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; }
const wxColour& get_color_selected_btn_bg() { return m_color_selected_btn_bg; }
void force_colors_update();
#endif
const wxFont& small_font() { return m_small_font; }
@ -210,7 +211,7 @@ public:
const wxFont& normal_font() { return m_normal_font; }
const wxFont& code_font() { return m_code_font; }
int em_unit() const { return m_em_unit; }
bool tabs_as_menu() const { return m_tabs_as_menu;}
bool tabs_as_menu() const;
wxSize get_min_size() const;
float toolbar_icon_scale(const bool is_limited = false) const;
void set_auto_toolbar_icon_scale(float scale) const;
@ -273,7 +274,7 @@ public:
NotificationManager* notification_manager();
// Parameters extracted from the command line to be passed to GUI after initialization.
const GUI_InitParams* init_params { nullptr };
GUI_InitParams* init_params { nullptr };
AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr };
@ -281,7 +282,7 @@ public:
MainFrame* mainframe{ nullptr };
Plater* plater_{ nullptr };
PresetUpdater* get_preset_updater() { return preset_updater; }
PresetUpdater* get_preset_updater() { return preset_updater; }
wxBookCtrlBase* tab_panel() const ;
int extruders_cnt() const;

View file

@ -1115,5 +1115,20 @@ void MenuFactory::sys_color_changed()
}
}
void MenuFactory::sys_color_changed(wxMenuBar* menubar)
{
for (size_t id = 0; id < menubar->GetMenuCount(); id++) {
wxMenu* menu = menubar->GetMenu(id);
msw_rescale_menu(menu);
#ifdef _WIN32
// but under MSW we have to update item's bachground color
for (wxMenuItem* item : menu->GetMenuItems())
update_menu_item_def_colors(item);
#endif
}
menubar->Refresh();
}
} //namespace GUI
} //namespace Slic3r

View file

@ -44,6 +44,8 @@ public:
void msw_rescale();
void sys_color_changed();
static void sys_color_changed(wxMenuBar* menu_bar);
wxMenu* default_menu();
wxMenu* object_menu();
wxMenu* sla_object_menu();

View file

@ -50,39 +50,8 @@ int GUI_Run(GUI_InitParams &params)
// gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui);
gui->init_params = &params;
/*
gui->CallAfter([gui, this, &load_configs, params.start_as_gcodeviewer] {
if (!gui->initialized()) {
return;
}
if (params.start_as_gcodeviewer) {
if (!m_input_files.empty())
gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str()));
} else {
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
gui->mainframe->load_config(m_print_config);
#endif
if (!load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
gui->mainframe->load_config_file(load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (!m_input_files.empty())
gui->plater()->load_files(m_input_files, true, true);
if (!m_extra_config.empty())
gui->mainframe->load_config(m_extra_config);
}
});
*/
int result = wxEntry(params.argc, params.argv);
return result;
return wxEntry(params.argc, params.argv);
} catch (const Slic3r::Exception &ex) {
boost::nowide::cerr << ex.what() << std::endl;
wxMessageBox(boost::nowide::widen(ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP);

View file

@ -1,6 +1,7 @@
#ifndef slic3r_GUI_Init_hpp_
#define slic3r_GUI_Init_hpp_
#include <libslic3r/Preset.hpp>
#include <libslic3r/PrintConfig.hpp>
namespace Slic3r {
@ -12,6 +13,9 @@ struct GUI_InitParams
int argc;
char **argv;
// Substitutions of unknown configuration values done during loading of user presets.
PresetsConfigSubstitutions preset_substitutions;
std::vector<std::string> load_configs;
DynamicPrintConfig extra_config;
std::vector<std::string> input_files;

View file

@ -9,7 +9,6 @@
#include <string>
#include "libslic3r/GCode/GCodeProcessor.hpp"
class wxNotebook;
class wxGLCanvas;
class wxBoxSizer;
class wxStaticText;

View file

@ -1,15 +1,15 @@
#include "GUI_Utils.hpp"
#include "GUI_App.hpp"
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#ifdef _WIN32
#include <Windows.h>
#include "GUI_App.hpp"
#include "libslic3r/AppConfig.hpp"
#include <wx/msw/registry.h>
#endif
#include <Windows.h>
#include "libslic3r/AppConfig.hpp"
#include <wx/msw/registry.h>
#endif // _WIN32
#include <wx/toplevel.h>
#include <wx/sizer.h>
@ -174,7 +174,7 @@ bool check_dark_mode() {
#ifdef _WIN32
void update_dark_ui(wxWindow* window)
{
bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode();
bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1";// ? true : check_dark_mode();// #ysDarkMSW - Allow it when we deside to support the sustem colors for application
window->SetBackgroundColour(is_dark ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
window->SetForegroundColour(is_dark ? wxColour(250, 250, 250) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}

View file

@ -175,8 +175,8 @@ public:
const wxFont& normal_font() const { return m_normal_font; }
void enable_force_rescale() { m_force_rescale = true; }
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Use to force dark colors for SystemLightMode
void force_sys_color_changed()
#ifdef _WIN32
void force_color_changed()
{
update_dark_ui(this);
on_sys_color_changed();

View file

@ -15,6 +15,7 @@
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
namespace Slic3r {
@ -231,7 +232,10 @@ void GLGizmoCut::perform_cut(const Selection& selection)
coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
if (object_cut_z > 0.)
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower);
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
else {
// the object is SLA-elevated and the plane is under it.
}

View file

@ -21,6 +21,7 @@ void GLGizmoFdmSupports::on_shutdown()
{
m_angle_threshold_deg = 0.f;
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}

View file

@ -8,6 +8,7 @@
#include "slic3r/GUI/BitmapCache.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Model.hpp"
@ -16,9 +17,26 @@
namespace Slic3r::GUI {
static inline void show_notification_extruders_limit_exceeded()
{
wxGetApp()
.plater()
->get_notification_manager()
->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification,
GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the "
"first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT));
}
void GLGizmoMmuSegmentation::on_opening()
{
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
show_notification_extruders_limit_exceeded();
}
void GLGizmoMmuSegmentation::on_shutdown()
{
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
std::string GLGizmoMmuSegmentation::on_get_name() const
@ -131,6 +149,9 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
ModelObject *model_object = m_c->selection_info()->model_object();
int prev_extruders_count = int(m_original_extruders_colors.size());
if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
show_notification_extruders_limit_exceeded();
this->init_extruders_data();
// Reinitialize triangle selectors because of change of extruder count need also change the size of GLIndexedVertexArray
if (prev_extruders_count != wxGetApp().extruders_edited_cnt())
@ -157,7 +178,7 @@ static void render_extruders_combo(const std::string &labe
ImGui::BeginGroup();
ImVec2 combo_pos = ImGui::GetCursorScreenPos();
if (ImGui::BeginCombo(label.c_str(), "")) {
for (size_t extruder_idx = 0; extruder_idx < extruders.size(); ++extruder_idx) {
for (size_t extruder_idx = 0; extruder_idx < std::min(extruders.size(), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT); ++extruder_idx) {
ImGui::PushID(int(extruder_idx));
ImVec2 start_position = ImGui::GetCursorScreenPos();

View file

@ -36,6 +36,12 @@ public:
void set_painter_gizmo_data(const Selection& selection) override;
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
static const constexpr size_t EXTRUDERS_LIMIT = 16;
protected:
std::array<float, 4> get_cursor_sphere_left_button_color() const override;
std::array<float, 4> get_cursor_sphere_right_button_color() const override;
@ -63,7 +69,7 @@ private:
void update_model_object() const override;
void update_from_model_object() override;
void on_opening() override {}
void on_opening() override;
void on_shutdown() override;
PainterGizmoType get_painter_type() const override;

View file

@ -16,6 +16,13 @@ namespace Slic3r::GUI {
void GLGizmoSeam::on_shutdown()
{
m_parent.toggle_model_objects_visibility(true);
}
bool GLGizmoSeam::on_init()
{
m_shortcut_key = WXK_CONTROL_P;

View file

@ -27,7 +27,7 @@ private:
void update_from_model_object() override;
void on_opening() override {}
void on_shutdown() override {}
void on_shutdown() override;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.

View file

@ -140,22 +140,27 @@ void SLAImportJob::process()
if (p->path.empty()) return;
std::string path = p->path.ToUTF8().data();
ConfigSubstitutions config_substitutions;
try {
switch (p->sel) {
case Sel::modelAndProfile:
import_sla_archive(path, p->win, p->mesh, p->profile, progr);
config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
break;
case Sel::modelOnly:
import_sla_archive(path, p->win, p->mesh, progr);
config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
break;
case Sel::profileOnly:
import_sla_archive(path, p->profile);
config_substitutions = import_sla_archive(path, p->profile);
break;
}
} catch (std::exception &ex) {
p->err = ex.what();
}
if (! config_substitutions.empty()) {
//FIXME Add reporting here "Loading profiles found following incompatibilities."
}
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
_(L("Importing done.")));

View file

@ -3,13 +3,13 @@
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "Notebook.hpp"
#include <wx/scrolwin.h>
#include <wx/display.h>
#include "GUI_App.hpp"
#include "wxExtensions.hpp"
#include "MainFrame.hpp"
#include <wx/notebook.h>
#include <wx/listbook.h>
namespace Slic3r {
namespace GUI {
@ -18,8 +18,6 @@ KBShortcutsDialog::KBShortcutsDialog()
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, wxString(wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + " - " + _L("Keyboard Shortcuts"),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
// SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
// fonts
const wxFont& font = wxGetApp().normal_font();
const wxFont& bold_font = wxGetApp().bold_font();
@ -31,13 +29,10 @@ KBShortcutsDialog::KBShortcutsDialog()
#ifdef _MSW_DARK_MODE
wxBookCtrlBase* book;
if (wxGetApp().dark_mode()) {
book = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
wxGetApp().UpdateDarkUI(book);
wxGetApp().UpdateDarkUI(dynamic_cast<wxListbook*>(book)->GetListView());
}
else
book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
// if (wxGetApp().dark_mode())
book = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
/* else
book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);*/
#else
wxNotebook* book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
#endif

View file

@ -41,6 +41,8 @@
#include "GUI_App.hpp"
#include "UnsavedChangesDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
#include "GUI_Factories.hpp"
#ifdef _WIN32
#include <dbt.h>
@ -427,8 +429,13 @@ void MainFrame::update_layout()
case ESettingsLayout::Old:
{
m_plater->Reparent(m_tabpanel);
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"));
else
#endif
m_tabpanel->InsertPage(0, m_plater, _L("Plater"));
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 1);
m_plater->Show();
m_tabpanel->Show();
// update Tabs
@ -447,6 +454,11 @@ void MainFrame::update_layout()
m_tabpanel->Hide();
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
m_plater_page = new wxPanel(m_tabpanel);
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"));
else
#endif
m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */
m_plater->Show();
break;
@ -455,7 +467,7 @@ void MainFrame::update_layout()
{
m_main_sizer->Add(m_plater, 1, wxEXPAND);
m_tabpanel->Reparent(&m_settings_dialog);
m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND);
m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 2);
m_tabpanel->Show();
m_plater->Show();
@ -476,6 +488,11 @@ void MainFrame::update_layout()
}
}
#ifdef _MSW_DARK_MODE
// Sizer with buttons for mode changing
m_plater->sidebar().show_mode_sizer(wxGetApp().tabs_as_menu() || m_layout != ESettingsLayout::Old);
#endif
#ifdef __WXMSW__
if (update_scaling_state != State::noUpdate)
{
@ -640,7 +657,7 @@ void MainFrame::init_tabpanel()
wxGetApp().UpdateDarkUI(m_tabpanel);
}
else
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
m_tabpanel = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true);
#else
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
#endif
@ -652,7 +669,7 @@ void MainFrame::init_tabpanel()
m_settings_dialog.set_tabpanel(m_tabpanel);
#ifdef __WXMSW__
m_tabpanel->Bind(/*wxEVT_LISTBOOK_PAGE_CHANGED*/wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
#else
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
#endif
@ -763,20 +780,25 @@ void MainFrame::register_win32_callbacks()
void MainFrame::create_preset_tabs()
{
wxGetApp().update_label_colours_from_appconfig();
add_created_tab(new TabPrint(m_tabpanel));
add_created_tab(new TabFilament(m_tabpanel));
add_created_tab(new TabSLAPrint(m_tabpanel));
add_created_tab(new TabSLAMaterial(m_tabpanel));
add_created_tab(new TabPrinter(m_tabpanel));
add_created_tab(new TabPrint(m_tabpanel), "cog");
add_created_tab(new TabFilament(m_tabpanel), "spool");
add_created_tab(new TabSLAPrint(m_tabpanel), "cog");
add_created_tab(new TabSLAMaterial(m_tabpanel), "resin");
add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer");
}
void MainFrame::add_created_tab(Tab* panel)
void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/)
{
panel->create_preset_tab();
const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
if (panel->supports_printer_technology(printer_tech))
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->AddPage(panel, panel->title(), bmp_name);
else
#endif
m_tabpanel->AddPage(panel, panel->title());
}
@ -960,6 +982,12 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
wxGetApp().update_fonts(this);
this->SetFont(this->normal_font());
#ifdef _MSW_DARK_MODE
// update common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
#endif
// update Plater
wxGetApp().plater()->msw_rescale();
@ -968,9 +996,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
for (auto tab : wxGetApp().tabs_list)
tab->msw_rescale();
wxMenuBar* menu_bar = this->GetMenuBar();
for (size_t id = 0; id < menu_bar->GetMenuCount(); id++)
msw_rescale_menu(menu_bar->GetMenu(id));
for (size_t id = 0; id < m_menubar->GetMenuCount(); id++)
msw_rescale_menu(m_menubar->GetMenu(id));
// Workarounds for correct Window rendering after rescale
@ -1003,6 +1030,11 @@ void MainFrame::on_sys_color_changed()
#ifdef __WXMSW__
wxGetApp().UpdateDarkUI(m_tabpanel);
m_statusbar->update_dark_ui();
#ifdef _MSW_DARK_MODE
// update common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
#endif
#endif
// update Plater
@ -1012,10 +1044,7 @@ void MainFrame::on_sys_color_changed()
for (auto tab : wxGetApp().tabs_list)
tab->sys_color_changed();
// msw_rescale_menu updates just icons, so use it
wxMenuBar* menu_bar = this->GetMenuBar();
for (size_t id = 0; id < menu_bar->GetMenuCount(); id++)
msw_rescale_menu(menu_bar->GetMenu(id));
MenuFactory::sys_color_changed(m_menubar);
this->Refresh();
}
@ -1518,6 +1547,7 @@ void MainFrame::update_menubar()
m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_menu_bitmap(is_fff ? "printer" : "sla_printer"));
}
#if 0
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
void MainFrame::quick_slice(const int qs)
{
@ -1643,6 +1673,7 @@ void MainFrame::quick_slice(const int qs)
// };
// Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); });
}
#endif
void MainFrame::reslice_now()
{
@ -1729,7 +1760,13 @@ void MainFrame::load_config_file()
bool MainFrame::load_config_file(const std::string &path)
{
try {
wxGetApp().preset_bundle->load_config_file(path);
ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable);
if (! config_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
} catch (const std::exception &ex) {
show_error(this, ex.what());
return false;
@ -1793,14 +1830,22 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
wxGetApp().app_config->update_config_dir(get_dir_name(file));
auto presets_imported = 0;
size_t presets_imported = 0;
PresetsConfigSubstitutions config_substitutions;
try {
presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data());
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported);
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;
}
if (! config_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
// Load the currently selected preset into the GUI, update the preset selection box.
wxGetApp().load_current_presets();
@ -2087,8 +2132,8 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
#else
this->SetFont(wxGetApp().normal_font());
#endif // __WXMSW__
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif // __WXMSW__
// Load the icon either from the exe, or from the ico file.
#if _WIN32
@ -2169,6 +2214,12 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
const int& em = em_unit();
const wxSize& size = wxSize(85 * em, 50 * em);
#ifdef _MSW_DARK_MODE
// update common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
#endif
// update Tabs
for (auto tab : wxGetApp().tabs_list)
tab->msw_rescale();

View file

@ -18,7 +18,6 @@
#include "Event.hpp"
#include "UnsavedChangesDialog.hpp"
class wxNotebook;
class wxBookCtrlBase;
class wxProgressDialog;
@ -154,7 +153,7 @@ public:
void init_tabpanel();
void create_preset_tabs();
void add_created_tab(Tab* panel);
void add_created_tab(Tab* panel, const std::string& bmp_name = "");
bool is_active_and_shown_tab(Tab* tab);
// Register Win32 RawInput callbacks (3DConnexion) and removable media insert / remove callbacks.
// Called from wxEVT_ACTIVATE, as wxEVT_CREATE was not reliable (bug in wxWidgets?).
@ -170,7 +169,7 @@ public:
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
bool is_dlg_layout() const { return m_layout == ESettingsLayout::Dlg; }
void quick_slice(const int qs = qsUndef);
// void quick_slice(const int qs = qsUndef);
void reslice_now();
void repair_stl();
void export_config();

View file

@ -17,6 +17,7 @@
#include "I18N.hpp"
#include "ConfigWizard.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "GUI_App.hpp"
namespace Slic3r {
@ -24,7 +25,7 @@ namespace GUI {
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id, wxBitmap bitmap)
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
: wxDialog(parent ? parent : dynamic_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, boldfont(wxGetApp().normal_font()/*wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)*/)
, content_sizer(new wxBoxSizer(wxVERTICAL))
, btn_sizer(new wxBoxSizer(wxHORIZONTAL))
@ -32,6 +33,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
this->SetFont(wxGetApp().normal_font());
this->CenterOnParent();
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
@ -135,6 +137,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit()));
Fit();
this->CenterOnParent();
}
// WarningDialog
@ -156,9 +159,10 @@ WarningDialog::WarningDialog(wxWindow *parent,
wxGetApp().UpdateDlgDarkUI(this);
Fit();
this->CenterOnParent();
}
#ifdef _WIN32
// MessageDialog
MessageDialog::MessageDialog(wxWindow* parent,
@ -180,7 +184,9 @@ MessageDialog::MessageDialog(wxWindow* parent,
wxGetApp().UpdateDlgDarkUI(this);
Fit();
this->CenterOnParent();
}
#endif
}
}

View file

@ -7,6 +7,7 @@
#include <wx/dialog.h>
#include <wx/font.h>
#include <wx/bitmap.h>
#include <wx/msgdlg.h>
class wxBoxSizer;
class wxCheckBox;
@ -83,7 +84,7 @@ public:
virtual ~WarningDialog() = default;
};
#ifdef _WIN32
// Generic message dialog, used intead of wxMessageDialog
class MessageDialog : public MsgDialog
{
@ -98,6 +99,19 @@ public:
MessageDialog &operator=(const MessageDialog&) = delete;
virtual ~MessageDialog() = default;
};
#else
// just a wrapper to wxMessageBox to use the same code on all platforms
class MessageDialog : public wxMessageDialog
{
public:
MessageDialog(wxWindow* parent,
const wxString& message,
const wxString& caption = wxEmptyString,
long style = wxOK)
: wxMessageDialog(parent, message, caption, style) {}
~MessageDialog() {}
};
#endif
}

134
src/slic3r/GUI/Notebook.cpp Normal file
View file

@ -0,0 +1,134 @@
#include "Notebook.hpp"
#ifdef _WIN32
#include "GUI_App.hpp"
#include "wxExtensions.hpp"
#include <wx/button.h>
#include <wx/sizer.h>
wxDEFINE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent);
ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = false*/) :
wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL)
{
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
m_sizer = new wxBoxSizer(wxHORIZONTAL);
this->SetSizer(m_sizer);
if (add_mode_buttons) {
m_mode_sizer = new ModeSizer(this, int(0.5 * em_unit(this)));
m_sizer->AddStretchSpacer(20);
m_sizer->Add(m_mode_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
}
this->Bind(wxEVT_PAINT, &ButtonsListCtrl::OnPaint, this);
}
void ButtonsListCtrl::OnPaint(wxPaintEvent&)
{
Slic3r::GUI::wxGetApp().UpdateDarkUI(this);
const wxSize sz = GetSize();
wxPaintDC dc(this);
if (m_selection < 0 || m_selection >= (int)m_pageButtons.size())
return;
// highlight selected button
const wxColour& selected_btn_bg = Slic3r::GUI::wxGetApp().get_color_selected_btn_bg();
const wxColour& default_btn_bg = Slic3r::GUI::wxGetApp().get_highlight_default_clr();
const wxColour& btn_marker_color = Slic3r::GUI::wxGetApp().get_color_hovered_btn_label();
for (int idx = 0; idx < m_pageButtons.size(); idx++) {
wxButton* btn = m_pageButtons[idx];
btn->SetBackgroundColour(idx == m_selection ? selected_btn_bg : default_btn_bg);
wxPoint pos = btn->GetPosition();
wxSize size = btn->GetSize();
const wxColour& clr = idx == m_selection ? btn_marker_color : default_btn_bg;
dc.SetPen(clr);
dc.SetBrush(clr);
dc.DrawRectangle(pos.x, sz.y - 3, size.x, 3);
}
dc.SetPen(btn_marker_color);
dc.SetBrush(btn_marker_color);
dc.DrawRectangle(1, sz.y - 1, sz.x, 1);
}
void ButtonsListCtrl::UpdateMode()
{
m_mode_sizer->SetMode(Slic3r::GUI::wxGetApp().get_mode());
}
void ButtonsListCtrl::Rescale()
{
m_mode_sizer->msw_rescale();
for (ScalableButton* btn : m_pageButtons)
btn->msw_rescale();
}
void ButtonsListCtrl::SetSelection(int sel)
{
if (m_selection == sel)
return;
m_selection = sel;
Refresh();
}
bool ButtonsListCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/* = false*/, const std::string& bmp_name/* = ""*/)
{
ScalableButton* btn = new ScalableButton(this, wxID_ANY, bmp_name, text, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | (bmp_name.empty() ? 0 : wxBU_LEFT));
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
m_selection = it - m_pageButtons.begin();
wxCommandEvent evt = wxCommandEvent(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED);
evt.SetId(m_selection);
wxPostEvent(this->GetParent(), evt);
Refresh();
}
});
Slic3r::GUI::wxGetApp().UpdateDarkUI(btn);
m_pageButtons.insert(m_pageButtons.begin() + n, btn);
m_sizer->Insert(n, new wxSizerItem(btn, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3));
m_sizer->Layout();
return true;
}
void ButtonsListCtrl::RemovePage(size_t n)
{
ScalableButton* btn = m_pageButtons[n];
m_pageButtons.erase(m_pageButtons.begin() + n);
m_sizer->Remove(n);
btn->Reparent(nullptr);
btn->Destroy();
m_sizer->Layout();
}
bool ButtonsListCtrl::SetPageImage(size_t n, const std::string& bmp_name) const
{
if (n >= m_pageButtons.size())
return false;
return m_pageButtons[n]->SetBitmap_(bmp_name);
}
void ButtonsListCtrl::SetPageText(size_t n, const wxString& strText)
{
ScalableButton* btn = m_pageButtons[n];
btn->SetLabel(strText);
}
wxString ButtonsListCtrl::GetPageText(size_t n) const
{
ScalableButton* btn = m_pageButtons[n];
return btn->GetLabel();
}
#endif // _WIN32

305
src/slic3r/GUI/Notebook.hpp Normal file
View file

@ -0,0 +1,305 @@
#ifndef slic3r_Notebook_hpp_
#define slic3r_Notebook_hpp_
#ifdef _WIN32
#include <wx/bookctrl.h>
class ModeSizer;
class ScalableButton;
// custom message the ButtonsListCtrl sends to its parent (Notebook) to notify a selection change:
wxDECLARE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent);
class ButtonsListCtrl : public wxControl
{
public:
ButtonsListCtrl(wxWindow* parent, bool add_mode_buttons = false);
~ButtonsListCtrl() {}
void OnPaint(wxPaintEvent&);
void SetSelection(int sel);
void UpdateMode();
void Rescale();
bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = "");
void RemovePage(size_t n);
bool SetPageImage(size_t n, const std::string& bmp_name) const;
void SetPageText(size_t n, const wxString& strText);
wxString GetPageText(size_t n) const;
private:
wxWindow* m_parent;
wxBoxSizer* m_sizer;
std::vector<ScalableButton*> m_pageButtons;
int m_selection {-1};
ModeSizer* m_mode_sizer {nullptr};
};
class Notebook: public wxBookCtrlBase
{
public:
Notebook(wxWindow * parent,
wxWindowID winid = wxID_ANY,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = 0,
bool add_mode_buttons = false)
{
Init();
Create(parent, winid, pos, size, style, add_mode_buttons);
}
bool Create(wxWindow * parent,
wxWindowID winid = wxID_ANY,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = 0,
bool add_mode_buttons = false)
{
if (!wxBookCtrlBase::Create(parent, winid, pos, size, style | wxBK_TOP))
return false;
m_bookctrl = new ButtonsListCtrl(this, add_mode_buttons);
wxSizer* mainSizer = new wxBoxSizer(IsVertical() ? wxVERTICAL : wxHORIZONTAL);
if (style & wxBK_RIGHT || style & wxBK_BOTTOM)
mainSizer->Add(0, 0, 1, wxEXPAND, 0);
m_controlSizer = new wxBoxSizer(IsVertical() ? wxHORIZONTAL : wxVERTICAL);
m_controlSizer->Add(m_bookctrl, wxSizerFlags(1).Expand());
wxSizerFlags flags;
if (IsVertical())
flags.Expand();
else
flags.CentreVertical();
mainSizer->Add(m_controlSizer, flags.Border(wxALL, m_controlMargin));
SetSizer(mainSizer);
this->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
{
if (int page_idx = evt.GetId(); page_idx >= 0)
SetSelection(page_idx);
});
return true;
}
// Methods specific to this class.
// A method allowing to add a new page without any label (which is unused
// by this control) and show it immediately.
bool ShowNewPage(wxWindow * page)
{
return AddPage(page, wxString(), ""/*true *//* select it */);
}
// Set effect to use for showing/hiding pages.
void SetEffects(wxShowEffect showEffect, wxShowEffect hideEffect)
{
m_showEffect = showEffect;
m_hideEffect = hideEffect;
}
// Or the same effect for both of them.
void SetEffect(wxShowEffect effect)
{
SetEffects(effect, effect);
}
// And the same for time outs.
void SetEffectsTimeouts(unsigned showTimeout, unsigned hideTimeout)
{
m_showTimeout = showTimeout;
m_hideTimeout = hideTimeout;
}
void SetEffectTimeout(unsigned timeout)
{
SetEffectsTimeouts(timeout, timeout);
}
// Implement base class pure virtual methods.
// adds a new page to the control
bool AddPage(wxWindow* page,
const wxString& text,
const std::string& bmp_name,
bool bSelect = false)
{
DoInvalidateBestSize();
return InsertPage(GetPageCount(), page, text, bmp_name, bSelect);
}
// Page management
virtual bool InsertPage(size_t n,
wxWindow * page,
const wxString & text,
bool bSelect = false,
int imageId = NO_IMAGE) override
{
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
return false;
GetBtnsListCtrl()->InsertPage(n, text, bSelect);
if (!DoSetSelectionAfterInsertion(n, bSelect))
page->Hide();
return true;
}
bool InsertPage(size_t n,
wxWindow * page,
const wxString & text,
const std::string& bmp_name = "",
bool bSelect = false)
{
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
return false;
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name);
if (!DoSetSelectionAfterInsertion(n, bSelect))
page->Hide();
return true;
}
virtual int SetSelection(size_t n) override
{
GetBtnsListCtrl()->SetSelection(n);
return DoSetSelection(n, SetSelection_SendEvent);
}
virtual int ChangeSelection(size_t n) override
{
GetBtnsListCtrl()->SetSelection(n);
return DoSetSelection(n);
}
// Neither labels nor images are supported but we still store the labels
// just in case the user code attaches some importance to them.
virtual bool SetPageText(size_t n, const wxString & strText) override
{
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
GetBtnsListCtrl()->SetPageText(n, strText);
return true;
}
virtual wxString GetPageText(size_t n) const override
{
wxCHECK_MSG(n < GetPageCount(), wxString(), wxS("Invalid page"));
return GetBtnsListCtrl()->GetPageText(n);
}
virtual bool SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId)) override
{
return false;
}
virtual int GetPageImage(size_t WXUNUSED(n)) const override
{
return NO_IMAGE;
}
bool SetPageImage(size_t n, const std::string& bmp_name)
{
return GetBtnsListCtrl()->SetPageImage(n, bmp_name);
}
// Override some wxWindow methods too.
virtual void SetFocus() override
{
wxWindow* const page = GetCurrentPage();
if (page)
page->SetFocus();
}
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(m_bookctrl); }
void UpdateMode()
{
GetBtnsListCtrl()->UpdateMode();
}
void Rescale()
{
GetBtnsListCtrl()->Rescale();
}
protected:
virtual void UpdateSelectedPage(size_t WXUNUSED(newsel)) override
{
// Nothing to do here, but must be overridden to avoid the assert in
// the base class version.
}
virtual wxBookCtrlEvent * CreatePageChangingEvent() const override
{
return new wxBookCtrlEvent(wxEVT_BOOKCTRL_PAGE_CHANGING,
GetId());
}
virtual void MakeChangedEvent(wxBookCtrlEvent & event) override
{
event.SetEventType(wxEVT_BOOKCTRL_PAGE_CHANGED);
}
virtual wxWindow * DoRemovePage(size_t page) override
{
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
if (win)
{
GetBtnsListCtrl()->RemovePage(page);
DoSetSelectionAfterRemoval(page);
}
return win;
}
virtual void DoSize() override
{
wxWindow* const page = GetCurrentPage();
if (page)
page->SetSize(GetPageRect());
}
virtual void DoShowPage(wxWindow * page, bool show) override
{
if (show)
page->ShowWithEffect(m_showEffect, m_showTimeout);
else
page->HideWithEffect(m_hideEffect, m_hideTimeout);
}
private:
void Init()
{
// We don't need any border as we don't have anything to separate the
// page contents from.
SetInternalBorder(0);
// No effects by default.
m_showEffect =
m_hideEffect = wxSHOW_EFFECT_NONE;
m_showTimeout =
m_hideTimeout = 0;
}
wxShowEffect m_showEffect,
m_hideEffect;
unsigned m_showTimeout,
m_hideTimeout;
ButtonsListCtrl* m_ctrl{ nullptr };
};
#endif // _WIN32
#endif // slic3r_Notebook_hpp_

View file

@ -272,27 +272,28 @@ void NotificationManager::PopNotification::count_lines()
// find next suitable endline
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) {
// more than one line till end
size_t next_space = text.find_first_of(' ', last_end);
if (next_space > 0) {
size_t next_space_candidate = text.find_first_of(' ', next_space + 1);
int next_space = text.find_first_of(' ', last_end);
if (next_space > 0 && next_space < text.length()) {
int next_space_candidate = text.find_first_of(' ', next_space + 1);
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
next_space = next_space_candidate;
next_space_candidate = text.find_first_of(' ', next_space + 1);
}
// when one word longer than line.
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) {
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
letter_count++;
}
m_endlines.push_back(last_end + letter_count);
last_end += letter_count;
}
else {
m_endlines.push_back(next_space);
last_end = next_space + 1;
} else {
next_space = text.length();
}
// when one word longer than line.
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) {
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
letter_count++;
}
m_endlines.push_back(last_end + letter_count);
last_end += letter_count;
} else {
m_endlines.push_back(next_space);
last_end = next_space + 1;
}
}
else {

View file

@ -87,7 +87,9 @@ enum class NotificationType
DesktopIntegrationSuccess,
DesktopIntegrationFail,
UndoDesktopIntegrationSuccess,
UndoDesktopIntegrationFail
UndoDesktopIntegrationFail,
// Notification that a printer has more extruders than are supported by MM Gizmo/segmentation.
MmSegmentationExceededExtrudersLimit
};

View file

@ -68,7 +68,8 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str
// update Print Host upload from the selected preset
m_parent->get_printer()->update_from_preset(*preset);
// update values in parent (PhysicalPrinterDialog)
m_parent->update();
m_parent->update(true);
}
// update PrinterTechnology if it was changed
@ -154,7 +155,8 @@ void PresetForPrinter::msw_rescale()
PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_name) :
DPIDialog(parent, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
m_printer("", wxGetApp().preset_bundle->physical_printers.default_config())
m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()),
had_all_mk3(!printer_name.empty())
{
SetFont(wxGetApp().normal_font());
#ifndef _WIN32
@ -455,7 +457,7 @@ void PhysicalPrinterDialog::update_printhost_buttons()
m_printhost_browse_btn->Enable(host->has_auto_discovery());
}
void PhysicalPrinterDialog::update()
void PhysicalPrinterDialog::update(bool printer_change)
{
m_optgroup->reload_config();
@ -463,13 +465,24 @@ void PhysicalPrinterDialog::update()
// Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment)
bool supports_multiple_printers = false;
if (tech == ptFFF) {
m_optgroup->show_field("host_type");
m_optgroup->hide_field("printhost_authorization_type");
m_optgroup->show_field("printhost_apikey", true);
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
m_optgroup->hide_field(opt_key);
update_host_type(printer_change);
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
supports_multiple_printers = opt && opt->value == htRepetier;
m_optgroup->show_field("host_type");
if (opt->value == htPrusaLink)
{
m_optgroup->show_field("printhost_authorization_type");
AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value;
m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword);
for (const char* opt_key : { "printhost_user", "printhost_password" })
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
} else {
m_optgroup->hide_field("printhost_authorization_type");
m_optgroup->show_field("printhost_apikey", true);
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
m_optgroup->hide_field(opt_key);
supports_multiple_printers = opt && opt->value == htRepetier;
}
}
else {
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
@ -493,6 +506,57 @@ void PhysicalPrinterDialog::update()
this->Layout();
}
void PhysicalPrinterDialog::update_host_type(bool printer_change)
{
if (m_presets.empty())
return;
bool all_presets_are_from_mk3_family = true;
for (PresetForPrinter* prstft : m_presets) {
std::string preset_name = prstft->get_preset_name();
if (Preset* preset = wxGetApp().preset_bundle->printers.find_preset(preset_name)) {
std::string model_id = preset->config.opt_string("printer_model");
if (preset->vendor && preset->vendor->name == "Prusa Research") {
const std::vector<VendorProfile::PrinterModel>& models = preset->vendor->models;
auto it = std::find_if(models.begin(), models.end(),
[model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; });
if (it != models.end() && it->family == "MK3")
continue;
} else if (!preset->vendor && model_id.rfind("MK3", 0) == 0) {
continue;
}
}
all_presets_are_from_mk3_family = false;
break;
}
Field* ht = m_optgroup->get_field("host_type");
wxArrayString types;
// Append localized enum_labels
assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size());
for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) {
if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family)
continue;
types.Add(_(ht->m_opt.enum_labels[i]));
}
Choice* choice = dynamic_cast<Choice*>(ht);
choice->set_values(types);
auto set_to_choice_and_config = [this, choice](PrintHostType type) {
choice->set_value(static_cast<int>(type));
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(type));
};
if ((printer_change && all_presets_are_from_mk3_family) || (!had_all_mk3 && all_presets_are_from_mk3_family))
set_to_choice_and_config(htPrusaLink);
else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value == htPrusaLink))
set_to_choice_and_config(htOctoPrint);
else
choice->set_value(m_config->option("host_type")->getInt());
had_all_mk3 = all_presets_are_from_mk3_family;
}
wxString PhysicalPrinterDialog::get_printer_name()
{
@ -628,8 +692,9 @@ void PhysicalPrinterDialog::AddPreset(wxEvent& event)
m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W);
update_full_printer_names();
this->Fit();
update_host_type(true);
}
void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer)
@ -657,7 +722,8 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer)
this->Layout();
this->Fit();
update_host_type(true);
}
}} // namespace Slic3r::GUI

View file

@ -85,7 +85,8 @@ public:
PhysicalPrinterDialog(wxWindow* parent, wxString printer_name);
~PhysicalPrinterDialog();
void update();
void update(bool printer_change = false);
void update_host_type(bool printer_change);
void update_printhost_buttons();
void update_printers();
wxString get_printer_name();
@ -95,10 +96,11 @@ public:
PrinterTechnology get_printer_technology();
void DeletePreset(PresetForPrinter* preset_for_printer);
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override {};
bool had_all_mk3;
};

View file

@ -583,7 +583,7 @@ struct Sidebar::priv
wxScrolledWindow *scrolled;
wxPanel* presets_panel; // Used for MSW better layouts
ModeSizer *mode_sizer;
ModeSizer *mode_sizer {nullptr};
wxFlexGridSizer *sizer_presets;
PlaterPresetComboBox *combo_print;
std::vector<PlaterPresetComboBox*> combos_filament;
@ -754,7 +754,8 @@ Sidebar::Sidebar(Plater *parent)
p->sliced_info = new SlicedInfo(p->scrolled);
// Sizer in the scrolled area
scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/);
if (p->mode_sizer)
scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL);
is_msw ?
scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) :
scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5);
@ -944,13 +945,16 @@ void Sidebar::update_presets(Preset::Type preset_type)
void Sidebar::update_mode_sizer() const
{
p->mode_sizer->SetMode(m_mode);
if (p->mode_sizer)
p->mode_sizer->SetMode(m_mode);
}
void Sidebar::change_top_border_for_mode_sizer(bool increase_border)
{
p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0);
p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0);
if (p->mode_sizer) {
p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0);
p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0);
}
}
void Sidebar::update_reslice_btn_tooltip() const
@ -965,7 +969,8 @@ void Sidebar::msw_rescale()
{
SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1));
p->mode_sizer->msw_rescale();
if (p->mode_sizer)
p->mode_sizer->msw_rescale();
for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*> { p->combo_print,
p->combo_sla_print,
@ -1009,7 +1014,8 @@ void Sidebar::sys_color_changed()
for (wxWindow* btn : std::vector<wxWindow*>{ p->btn_reslice, p->btn_export_gcode })
wxGetApp().UpdateDarkUI(btn, true);
p->mode_sizer->msw_rescale();
if (p->mode_sizer)
p->mode_sizer->msw_rescale();
p->frequently_changed_parameters->sys_color_changed();
p->object_settings->sys_color_changed();
#endif
@ -1394,6 +1400,12 @@ void Sidebar::collapse(bool collapse)
wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0");
}
#ifdef _MSW_DARK_MODE
void Sidebar::show_mode_sizer(bool show)
{
p->mode_sizer->Show(show);
}
#endif
void Sidebar::update_ui_from_settings()
{
@ -2237,7 +2249,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
DynamicPrintConfig config;
{
DynamicPrintConfig config_loaded;
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config);
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion));
if (load_config && !config_loaded.empty()) {
// Based on the printer technology field found in the loaded config, select the base for the config,
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
@ -2261,6 +2274,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// and place the loaded config over the base.
config += std::move(config_loaded);
}
if (! config_substitutions.empty()) {
// TODO:
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
}
@ -2330,7 +2349,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
}
else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config);
model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
for (auto obj : model.objects)
if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string();
@ -3215,7 +3234,7 @@ void Plater::priv::replace_with_stl()
Model new_model;
try {
new_model = Model::read_from_file(path, nullptr, true, false);
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
for (ModelObject* model_object : new_model.objects) {
model_object->center_around_origin();
model_object->ensure_on_bed();
@ -3388,7 +3407,7 @@ void Plater::priv::reload_from_disk()
Model new_model;
try
{
new_model = Model::read_from_file(path, nullptr, true, false);
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
for (ModelObject* model_object : new_model.objects) {
model_object->center_around_origin();
model_object->ensure_on_bed();
@ -4599,7 +4618,9 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
// Switch to the other printer technology. Switch to the last printer active for that particular technology.
AppConfig *app_config = wxGetApp().app_config;
app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
wxGetApp().preset_bundle->load_presets(*app_config);
//FIXME Why are we reloading the whole preset bundle here? Please document. This is fishy and it is unnecessarily expensive.
// Anyways, don't report any config value substitutions, they have been already reported to the user at application start up.
wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
// load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(),
// but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first.
this->sidebar->obj_list()->unselect_objects();
@ -5306,21 +5327,20 @@ void Plater::toggle_layers_editing(bool enable)
wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
}
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes)
{
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto *object = p->model.objects[obj_idx];
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
if (!keep_upper && !keep_lower) {
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
return;
}
Plater::TakeSnapshot snapshot(this, _L("Cut by Plane"));
wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
const auto new_objects = object->cut(instance_idx, z, attributes);
remove(obj_idx);
p->load_model_objects(new_objects);
@ -5328,9 +5348,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
{
selection.add_object((unsigned int)(last_id - i), i == 0);
}
}
void Plater::export_gcode(bool prefer_removable)

View file

@ -9,6 +9,7 @@
#include "Selection.hpp"
#include "libslic3r/enum_bitmask.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
@ -24,6 +25,8 @@ namespace Slic3r {
class Model;
class ModelObject;
enum class ModelObjectCutAttribute : int;
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
class ModelInstance;
class Print;
class SLAPrint;
@ -109,6 +112,10 @@ public:
void update_searcher();
void update_ui_from_settings();
#ifdef _MSW_DARK_MODE
void show_mode_sizer(bool show);
#endif
std::vector<PlaterPresetComboBox*>& combos_filament();
Search::OptionsSearcher& get_searcher();
std::string& get_search_line();
@ -204,7 +211,7 @@ public:
void convert_unit(ConversionType conv_type);
void toggle_layers_editing(bool enable);
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false);
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes);
void export_gcode(bool prefer_removable);
void export_stl(bool extended = false, bool selection_only = false);

View file

@ -6,7 +6,7 @@
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include <wx/notebook.h>
#include <wx/listbook.h>
#include "Notebook.hpp"
namespace Slic3r {
namespace GUI {
@ -58,15 +58,12 @@ void PreferencesDialog::build()
#ifdef _MSW_DARK_MODE
wxBookCtrlBase* tabs;
if (wxGetApp().dark_mode()) {
tabs = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNO_BORDER);
wxGetApp().UpdateDarkUI(tabs);
wxGetApp().UpdateDarkUI(dynamic_cast<wxListbook*>(tabs)->GetListView());
}
else {
// if (wxGetApp().dark_mode())
tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT);
/* else {
tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT);
tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
}
}*/
#else
wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT );
tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@ -345,7 +342,6 @@ void PreferencesDialog::build()
def.set_default_value(new ConfigOptionBool{ app_config->get("dark_color_mode") == "1" });
option = Option(def, "dark_color_mode");
m_optgroup_gui->append_single_option_line(option);
#endif
def.label = L("Set settings tabs as menu items (experimental)");
def.type = coBool;
@ -354,6 +350,7 @@ void PreferencesDialog::build()
def.set_default_value(new ConfigOptionBool{ app_config->get("tabs_as_menu") == "1" });
option = Option(def, "tabs_as_menu");
m_optgroup_gui->append_single_option_line(option);
#endif
def.label = L("Use custom size for toolbar icons");
def.type = coBool;
@ -414,11 +411,7 @@ void PreferencesDialog::accept()
// if (m_values.find("no_defaults") != m_values.end()
// warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME));
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu"
#ifdef _MSW_DARK_MODE
,"dark_color_mode"
#endif
};
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu" };
for (const std::string& option : options_to_recreate_GUI) {
if (m_values.find(option) != m_values.end()) {
@ -432,9 +425,6 @@ void PreferencesDialog::accept()
wxICON_QUESTION | wxYES | wxNO);
if (dialog.ShowModal() == wxID_YES) {
m_recreate_GUI = true;
#ifdef _MSW_DARK_MODE
m_color_mode_changed = m_values.find("dark_color_mode") != m_values.end();
#endif
}
else {
for (const std::string& option : options_to_recreate_GUI)
@ -495,6 +485,11 @@ void PreferencesDialog::accept()
EndModal(wxID_OK);
#ifdef _MSW_DARK_MODE
if (m_values.find("dark_color_mode") != m_values.end())
wxGetApp().force_colors_update();
#endif
if (m_settings_layout_changed)
;// application will be recreated after Preference dialog will be destroyed
else
@ -585,7 +580,9 @@ void PreferencesDialog::create_icon_size_slider()
void PreferencesDialog::create_settings_mode_widget()
{
bool dark_mode = wxGetApp().dark_mode();
#ifdef _MSW_DARK_MODE
bool disable_new_layout = wxGetApp().tabs_as_menu();
#endif
std::vector<wxString> choices = { _L("Old regular layout with the tab bar"),
_L("New layout, access via settings button in the top menu"),
_L("Settings in non-modal window") };
@ -596,7 +593,7 @@ void PreferencesDialog::create_settings_mode_widget()
app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
#ifdef _MSW_DARK_MODE
if (dark_mode) {
if (disable_new_layout) {
choices = { _L("Old regular layout with the tab bar"),
_L("Settings in non-modal window") };
selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0;
@ -621,14 +618,18 @@ void PreferencesDialog::create_settings_mode_widget()
int dlg_id = 2;
#ifdef _MSW_DARK_MODE
if (dark_mode)
if (disable_new_layout)
dlg_id = 1;
#endif
btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id, dark_mode](wxCommandEvent& ) {
btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id
#ifdef _MSW_DARK_MODE
, disable_new_layout
#endif
](wxCommandEvent& ) {
m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0";
#ifdef _MSW_DARK_MODE
if (!dark_mode)
if (!disable_new_layout)
m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0";
#endif
m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0";
@ -637,7 +638,7 @@ void PreferencesDialog::create_settings_mode_widget()
}
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(/*m_layout_mode_box*/stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
}

View file

@ -25,7 +25,6 @@ class PreferencesDialog : public DPIDialog
std::shared_ptr<ConfigOptionsGroup> m_optgroup_render;
#endif // ENABLE_ENVIRONMENT_MAP
wxSizer* m_icon_size_sizer;
wxRadioBox* m_layout_mode_box;
wxColourPickerCtrl* m_sys_colour {nullptr};
wxColourPickerCtrl* m_mod_colour {nullptr};
bool isOSX {false};
@ -35,9 +34,7 @@ class PreferencesDialog : public DPIDialog
bool m_seq_top_gcode_indices_changed{ false };
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
bool m_recreate_GUI{false};
#ifdef _MSW_DARK_MODE
bool m_color_mode_changed {false};
#endif
public:
explicit PreferencesDialog(wxWindow* parent);
~PreferencesDialog() = default;
@ -48,9 +45,6 @@ public:
bool seq_seq_top_gcode_indices_changed() const { return m_seq_top_gcode_indices_changed; }
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
bool recreate_GUI() const { return m_recreate_GUI; }
#ifdef _MSW_DARK_MODE
bool color_mode_changed() const { return m_color_mode_changed; }
#endif
void build();
void accept();

View file

@ -208,12 +208,13 @@ SavePresetDialog::~SavePresetDialog()
void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix)
{
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#if defined(__WXMSW__)
// ys_FIXME! temporary workaround for correct font scaling
// Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts,
// From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT
this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
#else
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif // __WXMSW__
if (suffix.empty())
@ -240,6 +241,10 @@ void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix
topSizer->SetSizeHints(this);
this->CenterOnScreen();
#ifdef _WIN32
wxGetApp().UpdateDlgDarkUI(this);
#endif
}
void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix)

View file

@ -44,6 +44,7 @@
#include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
#ifdef WIN32
#include <commctrl.h>
@ -255,8 +256,11 @@ void Tab::create_preset_tab()
m_modified_label_clr = wxGetApp().get_label_clr_modified();
m_default_text_clr = wxGetApp().get_label_clr_default();
#ifdef _MSW_DARK_MODE
// Sizer with buttons for mode changing
m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this)));
if (wxGetApp().tabs_as_menu())
#endif
m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this)));
const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor();
m_hsizer = new wxBoxSizer(wxHORIZONTAL);
@ -285,9 +289,11 @@ void Tab::create_preset_tab()
// m_hsizer->AddStretchSpacer(32);
// StretchSpacer has a strange behavior under OSX, so
// There is used just additional sizer for m_mode_sizer with right alignment
auto mode_sizer = new wxBoxSizer(wxVERTICAL);
mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT);
m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10);
if (m_mode_sizer) {
auto mode_sizer = new wxBoxSizer(wxVERTICAL);
mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT);
m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10);
}
//Horizontal sizer to hold the tree and the selected page.
m_hsizer = new wxBoxSizer(wxHORIZONTAL);
@ -490,7 +496,8 @@ void Tab::OnActivate()
// Workaroud for Menu instead of NoteBook
#ifdef _MSW_DARK_MODE
if (wxGetApp().tabs_as_menu()) {
// if (wxGetApp().tabs_as_menu())
{
wxSize sz = m_presets_choice->GetSize();
wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y+1);
if (sz != ok_sz) {
@ -943,7 +950,8 @@ void Tab::update_mode()
m_mode = wxGetApp().get_mode();
// update mode for ModeSizer
m_mode_sizer->SetMode(m_mode);
if (m_mode_sizer)
m_mode_sizer->SetMode(m_mode);
update_visibility();
@ -969,7 +977,8 @@ void Tab::msw_rescale()
{
m_em_unit = em_unit(m_parent);
m_mode_sizer->msw_rescale();
if (m_mode_sizer)
m_mode_sizer->msw_rescale();
m_presets_choice->msw_rescale();
m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1));
@ -1026,7 +1035,8 @@ void Tab::sys_color_changed()
update_label_colours();
#ifdef _WIN32
wxWindowUpdateLocker noUpdates(this);
m_mode_sizer->msw_rescale();
if (m_mode_sizer)
m_mode_sizer->msw_rescale();
wxGetApp().UpdateDarkUI(this);
wxGetApp().UpdateDarkUI(m_treectrl);
#endif
@ -3085,7 +3095,15 @@ void Tab::load_current_preset()
}
if (tab->supports_printer_technology(printer_technology))
{
wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title());
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu()) {
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "resin" : "cog";
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
}
else
#endif
wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title());
#ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563)
int page_id = wxGetApp().tab_panel()->FindPage(tab);
wxGetApp().tab_panel()->GetPage(page_id)->Show(true);
@ -3099,6 +3117,10 @@ void Tab::load_current_preset()
}
static_cast<TabPrinter*>(this)->m_printer_technology = printer_technology;
m_active_page = tmp_page;
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer");
#endif
}
on_presets_changed();
if (printer_technology == ptFFF) {

View file

@ -129,7 +129,7 @@ protected:
wxScrolledWindow* m_page_view {nullptr};
wxBoxSizer* m_page_sizer {nullptr};
ModeSizer* m_mode_sizer;
ModeSizer* m_mode_sizer {nullptr};
struct PresetDependencies {
Preset::Type type = Preset::TYPE_INVALID;

View file

@ -838,9 +838,13 @@ ScalableButton::ScalableButton( wxWindow * parent,
Create(parent, id, label, pos, size, style);
Slic3r::GUI::wxGetApp().UpdateDarkUI(this);
SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt));
if (m_use_default_disabled_bitmap)
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
if (!icon_name.empty()) {
SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt));
if (m_use_default_disabled_bitmap)
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
if (!label.empty())
SetBitmapMargins(int(0.5* em_unit(parent)), 0);
}
if (size != wxDefaultSize)
{
@ -873,6 +877,20 @@ void ScalableButton::SetBitmap_(const ScalableBitmap& bmp)
m_current_icon_name = bmp.name();
}
bool ScalableButton::SetBitmap_(const std::string& bmp_name)
{
m_current_icon_name = bmp_name;
if (m_current_icon_name.empty())
return false;
wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt);
SetBitmap(bmp);
SetBitmapCurrent(bmp);
if (m_use_default_disabled_bitmap)
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
return true;
}
void ScalableButton::SetBitmapDisabled_(const ScalableBitmap& bmp)
{
SetBitmapDisabled(bmp.bmp());
@ -898,11 +916,13 @@ void ScalableButton::msw_rescale()
{
Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border);
SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt));
if (!m_disabled_icon_name.empty())
SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt));
else if (m_use_default_disabled_bitmap)
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
if (!m_current_icon_name.empty()) {
SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt));
if (!m_disabled_icon_name.empty())
SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt));
else if (m_use_default_disabled_bitmap)
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
}
if (m_width > 0 || m_height>0)
{

View file

@ -231,6 +231,7 @@ public:
~ScalableButton() {}
void SetBitmap_(const ScalableBitmap& bmp);
bool SetBitmap_(const std::string& bmp_name);
void SetBitmapDisabled_(const ScalableBitmap &bmp);
int GetBitmapHeight();
void UseDefaultBitmapDisabled();

View file

@ -379,7 +379,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
// PresetBundle bundle;
on_progress(L("Loading repaired model"), 80);
DynamicPrintConfig config;
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false);
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent };
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false);
boost::filesystem::remove(path_dst);
if (! loaded)
throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed"));

View file

@ -213,4 +213,48 @@ void SL1Host::set_auth(Http &http) const
}
}
// PrusaLink
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
OctoPrint(config),
authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
username(config->opt_string("printhost_user")),
password(config->opt_string("printhost_password"))
{
}
const char* PrusaLink::get_name() const { return "PrusaLink"; }
wxString PrusaLink::get_test_ok_msg() const
{
return _(L("Connection to PrusaLink works correctly."));
}
wxString PrusaLink::get_test_failed_msg(wxString& msg) const
{
return GUI::from_u8((boost::format("%s: %s")
% _utf8(L("Could not connect to PrusaLink"))
% std::string(msg.ToUTF8())).str());
}
bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const
{
return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false;
}
void PrusaLink::set_auth(Http& http) const
{
switch (authorization_type) {
case atKeyPassword:
http.header("X-Api-Key", get_apikey());
break;
case atUserPassword:
http.auth_digest(username, password);
break;
}
if (!get_cafile().empty()) {
http.ca_file(get_cafile());
}
}
}

View file

@ -70,6 +70,31 @@ private:
std::string password;
};
class PrusaLink : public OctoPrint
{
public:
PrusaLink(DynamicPrintConfig* config);
~PrusaLink() override = default;
const char* get_name() const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString& msg) const override;
bool can_start_print() const override { return true; }
protected:
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
private:
void set_auth(Http& http) const override;
// Host authorization type.
AuthorizationType authorization_type;
// username and password for HTTP Digest Authentization (RFC RFC2617)
std::string username;
std::string password;
};
}
#endif

View file

@ -56,16 +56,15 @@ static const char *TMP_EXTENSION = ".download";
void copy_file_fix(const fs::path &source, const fs::path &target)
{
static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target);
// Make sure the file has correct permission both before and after we copy over it
if (fs::exists(target)) {
fs::permissions(target, perms);
std::string error_message;
CopyFileResult cfr = copy_file(source.string(), target.string(), error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "Copying failed(" << cfr << "): " << error_message;
throw Slic3r::CriticalException(GUI::format(
_L("Copying of file %1% to %2% failed: %3%"),
source, target, error_message));
}
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
fs::permissions(target, perms);
}
struct Update
@ -612,7 +611,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
update.install();
PresetBundle bundle;
bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem);
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
@ -710,6 +709,17 @@ void PresetUpdater::slic3r_update_notify()
}
}
static void reload_configs_update_gui()
{
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();
}
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
{
if (! p->enabled_config_update) { return R_NOOP; }
@ -767,7 +777,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
}
//forced update
if(incompatible_version)
if (incompatible_version)
{
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size());
@ -782,14 +792,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
p->perform_updates(std::move(updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();
reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
@ -814,11 +818,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
@ -871,11 +871,7 @@ void PresetUpdater::on_update_notification_confirm()
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(p->waiting_updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
reload_configs_update_gui();
p->has_waiting_updates = false;
//return R_UPDATE_INSTALLED;
}

View file

@ -50,6 +50,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
case htFlashAir: return new FlashAir(config);
case htAstroBox: return new AstroBox(config);
case htRepetier: return new Repetier(config);
case htPrusaLink: return new PrusaLink(config);
default: return nullptr;
}
} else {

View file

@ -200,14 +200,14 @@ void init_print(std::initializer_list<TriangleMesh> input_meshes, Slic3r::Print
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments)
{
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize(config_items);
config.set_deserialize_strict(config_items);
init_print(meshes, print, model, config, comments);
}
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments)
{
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize(config_items);
config.set_deserialize_strict(config_items);
init_print(meshes, print, model, config, comments);
}

View file

@ -19,7 +19,7 @@ SCENARIO("Extrusion width specifics", "[Flow]") {
GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
// this is a sharedptr
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
config.set_deserialize_strict({
{ "brim_width", 2 },
{ "skirts", 1 },
{ "perimeters", 3 },

View file

@ -10,7 +10,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr
GIVEN("A config from a file and a single extruder.") {
GCodeWriter writer;
GCodeConfig &config = writer.config;
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini");
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable);
std::vector<unsigned int> extruder_ids {0};
writer.set_extruders(extruder_ids);

View file

@ -50,7 +50,7 @@ SCENARIO("Print: Skirt generation", "[Print]") {
SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") {
GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
config.set_deserialize_strict({
{ "top_solid_layers", 2 },
{ "bottom_solid_layers", 1 },
{ "layer_height", 0.25 }, // get a known number of layers

View file

@ -224,7 +224,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_num_extruders(4);
config.set_deserialize({
config.set_deserialize_strict({
{ "start_gcode", "; Extruder [current_extruder]" },
{ "infill_extruder", 2 },
{ "solid_infill_extruder", 2 },

View file

@ -31,7 +31,7 @@ static int get_brim_tool(const std::string &gcode)
TEST_CASE("Skirt height is honored", "[Skirt]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 1 },
{ "skirt_height", 5 },
{ "perimeters", 0 },
@ -64,7 +64,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
GIVEN("A default configuration") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_num_extruders(4);
config.set_deserialize({
config.set_deserialize_strict({
{ "support_material_speed", 99 },
{ "first_layer_height", 0.3 },
{ "gcode_comments", true },
@ -78,7 +78,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
});
WHEN("Brim width is set to 5") {
config.set_deserialize({
config.set_deserialize_strict({
{ "perimeters", 0 },
{ "skirts", 0 },
{ "brim_width", 5 }
@ -100,7 +100,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("Skirt area is smaller than the brim") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 1 },
{ "brim_width", 10}
});
@ -110,7 +110,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("Skirt height is 0 and skirts > 0") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 2 },
{ "skirt_height", 0 }
});
@ -123,7 +123,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
// This is a real error! One shall print the brim with the external perimeter extruder!
WHEN("Perimeter extruder = 2 and support extruders = 3") {
THEN("Brim is printed with the extruder used for the perimeters of first object") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "brim_width", 5 },
{ "perimeter_extruder", 2 },
@ -137,7 +137,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
THEN("brim is printed with same extruder as skirt") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "brim_width", 5 },
{ "perimeter_extruder", 2 },
@ -153,7 +153,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#endif
WHEN("brim width to 1 with layer_width of 0.5") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 }
@ -167,7 +167,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#if 0
WHEN("brim ears on a square") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 },
@ -182,7 +182,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("brim ears on a square but with a too small max angle") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 },
@ -198,7 +198,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#endif
WHEN("Object is plated with overhang support and a brim") {
config.set_deserialize({
config.set_deserialize_strict({
{ "layer_height", 0.4 },
{ "first_layer_height", 0.4 },
{ "skirts", 1 },

Some files were not shown because too many files have changed in this diff Show more