Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--create_release.py2
-rw-r--r--resources/icons/info.svg71
-rw-r--r--resources/icons/resin_cog.svg21
-rw-r--r--resources/icons/sla_printer_cog.svg25
-rw-r--r--resources/icons/white/info.svg71
m---------resources/profiles0
-rw-r--r--resources/ui_layout/print.ui12
-rw-r--r--src/PrusaSlicer.cpp24
-rw-r--r--src/libslic3r/AppConfig.cpp148
-rw-r--r--src/libslic3r/AppConfig.hpp2
-rw-r--r--src/libslic3r/CMakeLists.txt1
-rw-r--r--src/libslic3r/ClipperUtils.cpp3
-rw-r--r--src/libslic3r/Config.cpp143
-rw-r--r--src/libslic3r/Config.hpp259
-rw-r--r--src/libslic3r/Extruder.cpp8
-rw-r--r--src/libslic3r/Extruder.hpp8
-rw-r--r--src/libslic3r/ExtrusionEntity.cpp9
-rw-r--r--src/libslic3r/ExtrusionEntity.hpp19
-rw-r--r--src/libslic3r/ExtrusionEntityCollection.cpp11
-rw-r--r--src/libslic3r/ExtrusionEntityCollection.hpp1
-rw-r--r--src/libslic3r/Fill/Fill.cpp13
-rw-r--r--src/libslic3r/Fill/FillSmooth.cpp7
-rw-r--r--src/libslic3r/Fill/FillSmooth.hpp18
-rw-r--r--src/libslic3r/Format/3mf.cpp53
-rw-r--r--src/libslic3r/Format/3mf.hpp3
-rw-r--r--src/libslic3r/Format/AMF.cpp32
-rw-r--r--src/libslic3r/Format/AMF.hpp2
-rw-r--r--src/libslic3r/Format/PRUS.cpp7
-rw-r--r--src/libslic3r/Format/SL1.cpp14
-rw-r--r--src/libslic3r/Format/SL1.hpp8
-rw-r--r--src/libslic3r/GCode.cpp292
-rw-r--r--src/libslic3r/GCode.hpp3
-rw-r--r--src/libslic3r/GCode/GCodeProcessor.cpp5
-rw-r--r--src/libslic3r/GCodeWriter.cpp72
-rw-r--r--src/libslic3r/GCodeWriter.hpp35
-rw-r--r--src/libslic3r/Layer.hpp2
-rw-r--r--src/libslic3r/LayerRegion.cpp2
-rw-r--r--src/libslic3r/Model.cpp39
-rw-r--r--src/libslic3r/Model.hpp19
-rw-r--r--src/libslic3r/Optimize/Optimizer.hpp1
-rw-r--r--src/libslic3r/PerimeterGenerator.cpp352
-rw-r--r--src/libslic3r/PerimeterGenerator.hpp9
-rw-r--r--src/libslic3r/Preset.cpp151
-rw-r--r--src/libslic3r/Preset.hpp67
-rw-r--r--src/libslic3r/PresetBundle.cpp296
-rw-r--r--src/libslic3r/PresetBundle.hpp29
-rw-r--r--src/libslic3r/Print.cpp111
-rw-r--r--src/libslic3r/Print.hpp5
-rw-r--r--src/libslic3r/PrintConfig.cpp128
-rw-r--r--src/libslic3r/PrintConfig.hpp25
-rw-r--r--src/libslic3r/PrintObject.cpp169
-rw-r--r--src/libslic3r/PrintRegion.cpp46
-rw-r--r--src/libslic3r/ShortestPath.cpp17
-rw-r--r--src/libslic3r/Utils.hpp2
-rw-r--r--src/libslic3r/enum_bitmask.hpp80
-rw-r--r--src/libslic3r/pchheader.hpp1
-rw-r--r--src/libslic3r/utils.cpp69
-rw-r--r--src/slic3r/Config/Snapshot.cpp69
-rw-r--r--src/slic3r/Config/Snapshot.hpp7
-rw-r--r--src/slic3r/GUI/CalibrationBedDialog.cpp6
-rw-r--r--src/slic3r/GUI/CalibrationBridgeDialog.cpp6
-rw-r--r--src/slic3r/GUI/CalibrationCubeDialog.cpp4
-rw-r--r--src/slic3r/GUI/CalibrationFlowDialog.cpp6
-rw-r--r--src/slic3r/GUI/CalibrationOverBridgeDialog.cpp4
-rw-r--r--src/slic3r/GUI/CalibrationRetractionDialog.cpp16
-rw-r--r--src/slic3r/GUI/CalibrationTempDialog.cpp8
-rw-r--r--src/slic3r/GUI/ConfigManipulation.cpp4
-rw-r--r--src/slic3r/GUI/ConfigWizard.cpp27
-rw-r--r--src/slic3r/GUI/ConfigWizard_private.hpp2
-rw-r--r--src/slic3r/GUI/ExtraRenderers.cpp2
-rw-r--r--src/slic3r/GUI/Field.cpp15
-rw-r--r--src/slic3r/GUI/FirmwareDialog.cpp13
-rw-r--r--src/slic3r/GUI/GLCanvas3D.cpp2
-rw-r--r--src/slic3r/GUI/GUI.cpp123
-rw-r--r--src/slic3r/GUI/GUI.hpp3
-rw-r--r--src/slic3r/GUI/GUI_App.cpp114
-rw-r--r--src/slic3r/GUI/GUI_App.hpp9
-rw-r--r--src/slic3r/GUI/GUI_Init.cpp33
-rw-r--r--src/slic3r/GUI/GUI_Init.hpp4
-rw-r--r--src/slic3r/GUI/GUI_ObjectList.cpp4
-rw-r--r--src/slic3r/GUI/Jobs/ArrangeJob.cpp27
-rw-r--r--src/slic3r/GUI/Jobs/SLAImportJob.cpp31
-rw-r--r--src/slic3r/GUI/MainFrame.cpp171
-rw-r--r--src/slic3r/GUI/MainFrame.hpp14
-rw-r--r--src/slic3r/GUI/MsgDialog.cpp54
-rw-r--r--src/slic3r/GUI/MsgDialog.hpp16
-rw-r--r--src/slic3r/GUI/NotificationManager.cpp78
-rw-r--r--src/slic3r/GUI/NotificationManager.hpp46
-rw-r--r--src/slic3r/GUI/OptionsGroup.cpp2
-rw-r--r--src/slic3r/GUI/PhysicalPrinterDialog.cpp91
-rw-r--r--src/slic3r/GUI/PhysicalPrinterDialog.hpp6
-rw-r--r--src/slic3r/GUI/Plater.cpp47
-rw-r--r--src/slic3r/GUI/PresetComboBoxes.cpp32
-rw-r--r--src/slic3r/GUI/PresetHints.cpp93
-rw-r--r--src/slic3r/GUI/Search.cpp4
-rw-r--r--src/slic3r/GUI/Tab.cpp85
-rw-r--r--src/slic3r/GUI/Tab.hpp4
-rw-r--r--src/slic3r/GUI/UnsavedChangesDialog.cpp6
-rw-r--r--src/slic3r/GUI/UpdateDialogs.cpp21
-rw-r--r--src/slic3r/GUI/UpdateDialogs.hpp3
-rw-r--r--src/slic3r/Utils/FixModelByWin10.cpp3
-rw-r--r--src/slic3r/Utils/HexFile.cpp1
-rw-r--r--src/slic3r/Utils/HexFile.hpp1
-rw-r--r--src/slic3r/Utils/OctoPrint.cpp46
-rw-r--r--src/slic3r/Utils/OctoPrint.hpp25
-rw-r--r--src/slic3r/Utils/PresetUpdater.cpp136
-rw-r--r--src/slic3r/Utils/PresetUpdater.hpp14
-rw-r--r--src/slic3r/Utils/PrintHost.cpp1
-rw-r--r--tests/data/cpp/test_data.cpp4
-rw-r--r--tests/fff_print/test_flow.cpp2
-rw-r--r--tests/fff_print/test_gcodewriter.cpp2
-rw-r--r--tests/fff_print/test_print.cpp2
-rw-r--r--tests/fff_print/test_printgcode.cpp2
-rw-r--r--tests/fff_print/test_skirt_brim.cpp22
-rw-r--r--tests/libslic3r/test_3mf.cpp8
-rw-r--r--tests/libslic3r/test_config.cpp14
-rw-r--r--tests/libslic3r/test_placeholder_parser.cpp2
-rw-r--r--xs/src/perlglue.cpp5
-rw-r--r--xs/xsp/Config.xsp4
-rw-r--r--xs/xsp/Flow.xsp2
-rw-r--r--xs/xsp/Layer.xsp2
-rw-r--r--xs/xsp/Model.xsp2
122 files changed, 3298 insertions, 1336 deletions
diff --git a/create_release.py b/create_release.py
index 8145b3ee3..118ee3495 100644
--- a/create_release.py
+++ b/create_release.py
@@ -17,7 +17,7 @@ import subprocess
repo = "supermerill/SuperSlicer"
program_name = "SuperSlicer"
path_7zip = r"C:\Program Files\7-Zip\7z.exe"
-github_auth_token = "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"
+github_auth_token = "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"
def get_version():
settings_stream = open("./version.inc", mode="r", encoding="utf-8");
diff --git a/resources/icons/info.svg b/resources/icons/info.svg
new file mode 100644
index 000000000..276b26061
--- /dev/null
+++ b/resources/icons/info.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ id="error"
+ x="0px"
+ y="0px"
+ viewBox="0 0 200 200"
+ enable-background="new 0 0 100 100"
+ xml:space="preserve"
+ sodipodi:docname="notification_error.svg"
+ width="200"
+ height="200"
+ inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata
+ id="metadata19"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs17" /><sodipodi:namedview
+ inkscape:document-rotation="0"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="2560"
+ inkscape:window-height="1377"
+ id="namedview15"
+ showgrid="false"
+ inkscape:zoom="5.04"
+ inkscape:cx="117.17146"
+ inkscape:cy="98.609664"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="error" />
+<g
+ id="g4"
+ transform="matrix(2.52,0,0,2.52,-26,-26)">
+ <path
+ fill="#808080"
+ d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
+ id="path2" />
+</g>
+<g
+ id="g8"
+ transform="matrix(2.52,0,0,2.52,-26,-26)">
+ <circle
+ fill="#808080"
+ cx="50"
+ cy="65"
+ r="5"
+ id="circle6" />
+</g>
+<g
+ id="g12"
+ transform="matrix(2.52,0,0,2.52,-26,-26)">
+ <path
+ fill="#808080"
+ d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z"
+ id="path10" />
+</g>
+</svg>
diff --git a/resources/icons/resin_cog.svg b/resources/icons/resin_cog.svg
new file mode 100644
index 000000000..fbe4d9c74
--- /dev/null
+++ b/resources/icons/resin_cog.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
+<g id="resin" transform="translate(2)">
+ <rect x="4" y="7" fill="#2172eb" width="8" height="8"/>
+ <path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6
+ c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/>
+</g>
+<g
+ id="machine_x2B_cog"
+ transform="matrix(0.83360586,0,0,0.83360586,-0.53099627,-0.47101916)"><path
+ style="fill:#808080;stroke:none;stroke-opacity:1"
+ inkscape:connector-curvature="0"
+ d="M 13.77,6.39 C 13.64,5.92 13.45,5.47 13.22,5.06 l 0.43,-1.3 -1.41,-1.41 -1.3,0.43 C 10.52,2.55 10.08,2.36 9.61,2.23 L 9,1 H 7 L 6.39,2.23 C 5.92,2.36 5.47,2.54 5.06,2.78 L 3.76,2.35 2.34,3.76 2.77,5.06 C 2.54,5.47 2.36,5.92 2.23,6.39 L 1,7 v 2 l 1.23,0.61 c 0.13,0.47 0.32,0.92 0.55,1.33 l -0.43,1.3 1.41,1.41 1.3,-0.43 c 0.42,0.23 0.86,0.42 1.33,0.55 L 7,15 h 2 l 0.61,-1.23 c 0.47,-0.13 0.92,-0.32 1.33,-0.55 l 1.3,0.43 1.41,-1.41 -0.43,-1.3 c 0.23,-0.42 0.42,-0.86 0.55,-1.33 L 15,9 V 7 Z M 8,13 C 5.24,13 3,10.76 3,8 3,5.24 5.24,3 8,3 c 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z"
+ id="path2" /><path
+ style="fill:#2172eb"
+ inkscape:connector-curvature="0"
+ d="M 11.3,7.08 C 11.23,6.81 11.12,6.56 10.99,6.32 L 11.24,5.58 10.43,4.77 9.68,5.01 C 9.45,4.88 9.19,4.78 8.92,4.7 L 8.57,4 H 7.43 L 7.08,4.7 C 6.81,4.78 6.55,4.88 6.32,5.01 L 5.58,4.77 4.77,5.58 5.02,6.32 C 4.88,6.55 4.78,6.81 4.7,7.08 L 4,7.43 v 1.14 l 0.7,0.35 c 0.07,0.27 0.18,0.52 0.31,0.76 l -0.25,0.74 0.81,0.81 0.74,-0.25 c 0.24,0.13 0.49,0.24 0.76,0.31 L 7.43,12 h 1.14 l 0.35,-0.7 c 0.27,-0.07 0.52,-0.18 0.76,-0.31 l 0.74,0.25 0.81,-0.81 -0.25,-0.74 C 11.11,9.45 11.22,9.2 11.29,8.93 L 12,8.57 V 7.43 Z M 8,10.86 C 6.42,10.86 5.14,9.58 5.14,8 5.14,6.42 6.42,5.14 8,5.14 c 1.58,0 2.86,1.28 2.86,2.86 0,1.58 -1.28,2.86 -2.86,2.86 z"
+ id="path4" /></g>
+</svg>
diff --git a/resources/icons/sla_printer_cog.svg b/resources/icons/sla_printer_cog.svg
new file mode 100644
index 000000000..1fb5e5a12
--- /dev/null
+++ b/resources/icons/sla_printer_cog.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
+<g id="sla" transform="matrix(0.90508475,0,0,0.90508475,2.4542373,1.4237288)">
+ <rect x="3" y="11" fill="#808080" width="1" height="4"/>
+ <rect x="12" y="11" fill="#808080" width="1" height="4"/>
+ <rect x="7.5" y="6.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 19.5 3.5)" fill="#808080" width="1" height="10"/>
+ <rect x="7.5" y="9.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22.5 6.5)" fill="#808080" width="1" height="10"/>
+ <rect x="10.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 24.5 1.5)" fill="#808080" width="2" height="3"/>
+ <rect x="3.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 17.5 8.5)" fill="#808080" width="2" height="3"/>
+ <rect x="3" y="1" fill="#2172eb" width="10" height="10"/>
+</g>
+<g
+ id="machine_x2B_cog"
+ transform="matrix(0.83360586,0,0,0.83360586,-0.53099627,-0.47101916)"><path
+ style="fill:#808080;stroke:none;stroke-opacity:1"
+ inkscape:connector-curvature="0"
+ d="M 13.77,6.39 C 13.64,5.92 13.45,5.47 13.22,5.06 l 0.43,-1.3 -1.41,-1.41 -1.3,0.43 C 10.52,2.55 10.08,2.36 9.61,2.23 L 9,1 H 7 L 6.39,2.23 C 5.92,2.36 5.47,2.54 5.06,2.78 L 3.76,2.35 2.34,3.76 2.77,5.06 C 2.54,5.47 2.36,5.92 2.23,6.39 L 1,7 v 2 l 1.23,0.61 c 0.13,0.47 0.32,0.92 0.55,1.33 l -0.43,1.3 1.41,1.41 1.3,-0.43 c 0.42,0.23 0.86,0.42 1.33,0.55 L 7,15 h 2 l 0.61,-1.23 c 0.47,-0.13 0.92,-0.32 1.33,-0.55 l 1.3,0.43 1.41,-1.41 -0.43,-1.3 c 0.23,-0.42 0.42,-0.86 0.55,-1.33 L 15,9 V 7 Z M 8,13 C 5.24,13 3,10.76 3,8 3,5.24 5.24,3 8,3 c 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z"
+ id="path2" /><path
+ style="fill:#808080"
+ inkscape:connector-curvature="0"
+ d="M 11.3,7.08 C 11.23,6.81 11.12,6.56 10.99,6.32 L 11.24,5.58 10.43,4.77 9.68,5.01 C 9.45,4.88 9.19,4.78 8.92,4.7 L 8.57,4 H 7.43 L 7.08,4.7 C 6.81,4.78 6.55,4.88 6.32,5.01 L 5.58,4.77 4.77,5.58 5.02,6.32 C 4.88,6.55 4.78,6.81 4.7,7.08 L 4,7.43 v 1.14 l 0.7,0.35 c 0.07,0.27 0.18,0.52 0.31,0.76 l -0.25,0.74 0.81,0.81 0.74,-0.25 c 0.24,0.13 0.49,0.24 0.76,0.31 L 7.43,12 h 1.14 l 0.35,-0.7 c 0.27,-0.07 0.52,-0.18 0.76,-0.31 l 0.74,0.25 0.81,-0.81 -0.25,-0.74 C 11.11,9.45 11.22,9.2 11.29,8.93 L 12,8.57 V 7.43 Z M 8,10.86 C 6.42,10.86 5.14,9.58 5.14,8 5.14,6.42 6.42,5.14 8,5.14 c 1.58,0 2.86,1.28 2.86,2.86 0,1.58 -1.28,2.86 -2.86,2.86 z"
+ id="path4" /></g>
+</svg>
diff --git a/resources/icons/white/info.svg b/resources/icons/white/info.svg
new file mode 100644
index 000000000..db227aa32
--- /dev/null
+++ b/resources/icons/white/info.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ id="error"
+ x="0px"
+ y="0px"
+ viewBox="0 0 200 200"
+ enable-background="new 0 0 100 100"
+ xml:space="preserve"
+ sodipodi:docname="notification_error.svg"
+ width="200"
+ height="200"
+ inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata
+ id="metadata19"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs17" /><sodipodi:namedview
+ inkscape:document-rotation="0"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="2560"
+ inkscape:window-height="1377"
+ id="namedview15"
+ showgrid="false"
+ inkscape:zoom="5.04"
+ inkscape:cx="117.17146"
+ inkscape:cy="98.609664"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="error" />
+<g
+ id="g4"
+ transform="matrix(2.52,0,0,2.52,-26,-26)">
+ <path
+ fill="#FFFFFF"
+ d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
+ id="path2" />
+</g>
+<g
+ id="g8"
+ transform="matrix(2.52,0,0,2.52,-26,-26)">
+ <circle
+ fill="#FFFFFF"
+ cx="50"
+ cy="65"
+ r="5"
+ id="circle6" />
+</g>
+<g
+ id="g12"
+ transform="matrix(2.52,0,0,2.52,-26,-26)">
+ <path
+ fill="#FFFFFF"
+ d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z"
+ id="path10" />
+</g>
+</svg>
diff --git a/resources/profiles b/resources/profiles
-Subproject 118aa919c16837eb2ff6ba97e2934fa4144ef80
+Subproject b2309c4a532bd2bbe26c5d9f1bff8bb2bee707f
diff --git a/resources/ui_layout/print.ui b/resources/ui_layout/print.ui
index 991ac342a..f5e1bd710 100644
--- a/resources/ui_layout/print.ui
+++ b/resources/ui_layout/print.ui
@@ -14,6 +14,7 @@ group:Horizontal shells
setting:bottom_solid_min_thickness
end_line
top_bottom_shell_thickness_explanation
+ setting:solid_over_perimeters
setting:enforce_full_fill_volume
group:Quality
line:Only one perimeter on Top surfaces
@@ -92,7 +93,10 @@ group:Modifying slices
line:XY compensation
setting:width$6:xy_size_compensation
setting:width$6:xy_inner_size_compensation
+ end_line
+ line:XY First layer compensation
setting:width$6:first_layer_size_compensation
+ setting:width$6:first_layer_size_compensation_layers
end_line
line:Vertical Hole shrinking compensation
setting:width$6:hole_size_compensation
@@ -225,6 +229,9 @@ group:label_width$8:sidetext_width$7:Speed for print moves
setting:width$4:solid_infill_speed
setting:width$4:top_solid_infill_speed
end_line
+ line:Ironing speed
+ setting:label$_:width$4:ironing_speed
+ end_line
line:Support speed
setting:width$4:support_material_speed
setting:width$4:support_material_interface_speed
@@ -238,7 +245,6 @@ group:label_width$8:sidetext_width$7:Speed for print moves
setting:width$4:gap_fill_speed
setting:width$4:thin_walls_speed
end_line
- setting:label$Ironing post-process speed:label_width$30:width$4:ironing_speed
group:Speed for non-print moves
line:Travel speed
setting:label$xy:travel_speed
@@ -254,11 +260,12 @@ group:sidetext_width$7:Modifiers
setting:label_width$8:width$4:small_perimeter_max_length
setting:label_width$8:width$4:small_perimeter_speed
group:Acceleration control (advanced)
+ setting:default_acceleration
setting:perimeter_acceleration
setting:infill_acceleration
setting:bridge_acceleration
setting:first_layer_acceleration
- setting:default_acceleration
+ setting:travel_acceleration
group:Autospeed (advanced)
setting:label$Volumetric speed for Autospeed:max_volumetric_speed
setting:max_print_speed
@@ -348,6 +355,7 @@ group:Plater
group:Sequential printing
setting:complete_objects
setting:complete_objects_one_skirt
+ setting:complete_objects_one_brim
setting:complete_objects_sort
line:Extruder clearance (mm)
setting:width$6:extruder_clearance_radius
diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp
index 17811f3c9..ff951e6b4 100644
--- a/src/PrusaSlicer.cpp
+++ b/src/PrusaSlicer.cpp
@@ -110,7 +110,8 @@ int CLI::run(int argc, char **argv)
boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), GCODEVIEWER_APP_CMD);
#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) {
@@ -122,13 +123,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 = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) {
@@ -166,7 +173,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 = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
@@ -175,6 +184,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);
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index 9504b13bb..3824fb749 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -3,6 +3,7 @@
#include "AppConfig.hpp"
#include "Exception.hpp"
#include "Thread.hpp"
+#include "format.hpp"
#include <utility>
#include <vector>
@@ -16,9 +17,13 @@
#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 {
@@ -263,15 +268,103 @@ 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) {
+#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
@@ -283,6 +376,7 @@ std::string AppConfig::load()
*/
return ex.what();
}
+ }
// 2) Parse the property_tree, extract the sections and key / value pairs.
for (const auto &section : tree) {
@@ -358,22 +452,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 std::pair<std::string, std::string> &kvp : m_storage[""])
- c << kvp.first << " = " << kvp.second << std::endl;
+ for (const auto& kvp : m_storage[""])
+ 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;
- for (const std::pair<std::string, std::string> &kvp : category.second)
- c << kvp.first << " = " << kvp.second << std::endl;
+ config_ss << std::endl << "[" << category.first << "]" << std::endl;
+ for (const auto& kvp : category.second)
+ config_ss << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
@@ -381,17 +474,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;
}
}
+ // 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;
}
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index c8ccd18cd..0a53a5330 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -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();
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 33e79aa88..1678d2e2f 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -34,6 +34,7 @@ add_library(libslic3r STATIC
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
+ enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonCollection.cpp
diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp
index db5dd463c..0528c137a 100644
--- a/src/libslic3r/ClipperUtils.cpp
+++ b/src/libslic3r/ClipperUtils.cpp
@@ -1228,8 +1228,9 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<s
holes.reserve(expoly.holes.size());
for (const Polygon& hole : expoly.holes)
append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, false));
+ //tiny holes can be reduced to giberish, get rid of them.
for (auto it = holes.begin(); it != holes.end();)
- if (ClipperLib::Area(*it) < 0.) {
+ if (ClipperLib::Area(*it) < CLIPPER_OFFSET_SCALE*CLIPPER_OFFSET_SCALE) {
it = holes.erase(it);
}
else ++it;
diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp
index b4e9b4281..24f2a44af 100644
--- a/src/libslic3r/Config.cpp
+++ b/src/libslic3r/Config.cpp
@@ -21,6 +21,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"
+
#define L(s) (s)
namespace Slic3r {
@@ -246,6 +250,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;
@@ -272,7 +280,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coPercents: return new ConfigOptionPercentsNullable();
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
case coBools: return new ConfigOptionBoolsNullable();
- default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
+ default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
}
} else {
switch (this->type) {
@@ -293,7 +301,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coBool: return new ConfigOptionBool();
case coBools: return new ConfigOptionBools();
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
- default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
+ default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
}
}
}
@@ -396,7 +404,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()) {
@@ -528,7 +537,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;
@@ -538,23 +547,22 @@ 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)) {
- throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
+ if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
+ throw BadOptionValueException(format("Invalid value provided for parameter %1%: %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;
// Try to deserialize the option by its name.
@@ -583,7 +591,7 @@ 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;
}
@@ -591,10 +599,56 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
ConfigOption *opt = this->option(opt_key, true);
if (opt == nullptr)
throw new UnknownOptionException(opt_key);
+ bool success = true;
+ if (!optdef->can_phony || !value.empty()) {
+ success = true;
+ bool substituted = false;
+ if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
+ //FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
+ bool nullable = opt->nullable();
+ ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
+ if (optdef->default_value) {
+ // Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
+ assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
+ auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
+ if (values.size() == 1 && values.front() == 1)
+ default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
+ }
+ auto result = nullable ?
+ static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
+ static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
+ success = result != ConfigHelpers::DeserializationResult::Failed;
+ substituted = result == ConfigHelpers::DeserializationResult::Substituted;
+ } else {
+ 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) && ConfigHelpers::looks_like_enum_value(value)) {
+ // Deserialize failed, try to substitute with a default value.
+ //assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
+ if (optdef->type == coEnum && opt_key == "gcode_flavor" && (value == "marlin2" || value == "marlinfirmware"))
+ static_cast<ConfigOptionEnum<GCodeFlavor>*>(opt)->value = gcfMarlin;
+ else if (optdef->type == coBool)
+ static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
+ else
+ // Just use the default of the option.
+ opt->set(optdef->default_value.get());
+ success = true;
+ substituted = true;
+ }
+ }
- bool ok = true;
- if (!optdef->can_phony || !value.empty())
- ok = opt->deserialize(value, append);
+ if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
+ substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
+ // Log the substitution.
+ ConfigSubstitution config_substitution;
+ config_substitution.opt_def = optdef;
+ config_substitution.old_value = value;
+ config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
+ substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
+ }
+ }
//set phony status
if (optdef->can_phony)
if(value.empty())
@@ -603,8 +657,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
opt->set_phony(false);
else
opt->set_phony(false);
-
- return ok;
+ return success;
}
// Return an absolute value of a possibly relative config variable.
@@ -665,7 +718,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
cast_opt->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
std::stringstream ss; ss << "ConfigBase::get_abs_value(): "<< opt_key<<" has not a valid option type for get_abs_value()";
- throw Slic3r::RuntimeError(ss.str());
+ throw ConfigurationError(ss.str());
}
// Return an absolute value of a possibly relative config variable.
@@ -676,7 +729,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
- throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
+ throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
}
@@ -699,37 +752,43 @@ 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, 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)
{
+ try {
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);
+ } catch (const ConfigurationError &e) {
+ throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
+}
}
-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)
+ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
+ try {
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
{
@@ -743,7 +802,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
strncmp(slic3rpp_gcode_header, firstline.c_str(), strlen(slic3rpp_gcode_header)) != 0 &&
strncmp(superslicer_gcode_header, firstline.c_str(), strlen(superslicer_gcode_header)) != 0 &&
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
- throw Slic3r::RuntimeError("Not a g-code recognized for configuration import.");
+ throw ConfigurationError("Not a g-code recognized for configuration import.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
@@ -753,13 +812,18 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
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));
+ throw ConfigurationError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
+ return std::move(substitutions_ctxt.substitutions);
+ } catch (const ConfigurationError &e) {
+ throw ConfigurationError(format("Failed loading configuration from G-code \"%1%\": %2%", file, e.what()));
+}
}
// 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;
@@ -804,8 +868,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
if (key == nullptr)
break;
try {
- //change it from set_deserialize to set_deserialize_nothrow to allow bad/old config to swtch to default value.
- if(this->set_deserialize_nothrow(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 */) {
@@ -842,7 +905,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);
}
}
@@ -893,7 +956,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
-// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
+// throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = optdef->create_default_option();
@@ -1013,8 +1076,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;
}
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index f7cb5536c..736b24400 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -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/property_tree/ptree_fwd.hpp>
@@ -75,23 +76,59 @@ enum OptionCategory : int
};
std::string toString(OptionCategory opt);
-/// Specialization of std::exception to indicate that an unknown config option has been encountered.
-class UnknownOptionException : public Slic3r::RuntimeError {
+namespace ConfigHelpers {
+ inline bool looks_like_enum_value(std::string value)
+ {
+ boost::trim(value);
+ if (value.empty() || value.size() > 64 || ! isalpha(value.front()))
+ return false;
+ for (const char c : value)
+ if (! (isalnum(c) || c == '_' || c == '-'))
+ return false;
+ return true;
+ }
+
+ inline bool enum_looks_like_true_value(std::string value) {
+ boost::trim(value);
+ return boost::iequals(value, "enabled") || boost::iequals(value, "on");
+ }
+
+ enum class DeserializationSubstitution {
+ Disabled,
+ DefaultsToFalse,
+ DefaultsToTrue
+ };
+
+ enum class DeserializationResult {
+ Loaded,
+ Substituted,
+ Failed,
+ };
+};
+
+// Base for all exceptions thrown by the configuration layer.
+class ConfigurationError : public Slic3r::RuntimeError {
+public:
+ using RuntimeError::RuntimeError;
+};
+
+// Specialization of std::exception to indicate that an unknown config option has been encountered.
+class UnknownOptionException : public ConfigurationError {
public:
UnknownOptionException() :
- Slic3r::RuntimeError("Unknown option exception") {}
+ ConfigurationError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
- Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
+ ConfigurationError(std::string("Unknown option exception: ") + opt_key) {}
};
-/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
-class NoDefinitionException : public Slic3r::RuntimeError
+// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
+class NoDefinitionException : public ConfigurationError
{
public:
NoDefinitionException() :
- Slic3r::RuntimeError("No definition exception") {}
+ ConfigurationError("No definition exception") {}
NoDefinitionException(const std::string &opt_key) :
- Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
+ ConfigurationError(std::string("No definition exception: ") + opt_key) {}
};
// a bit more specific than a runtime_error
class ConfigurationException : public std::runtime_error
@@ -103,13 +140,22 @@ public:
std::runtime_error(std::string("Configuration exception: ") + opt_key) {}
};
-/// Indicate that an unsupported accessor was called on a config option.
-class BadOptionTypeException : public Slic3r::RuntimeError
+// Indicate that an unsupported accessor was called on a config option.
+class BadOptionTypeException : public ConfigurationError
{
public:
- BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
- BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
- BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
+ BadOptionTypeException() : ConfigurationError("Bad option type exception") {}
+ BadOptionTypeException(const std::string &message) : ConfigurationError(message) {}
+ BadOptionTypeException(const char* message) : ConfigurationError(message) {}
+};
+
+// Indicate that an option has been deserialized from an invalid value.
+class BadOptionValueException : public ConfigurationError
+{
+public:
+ BadOptionValueException() : ConfigurationError("Bad option value exception") {}
+ BadOptionValueException(const std::string &message) : ConfigurationError(message) {}
+ BadOptionValueException(const char* message) : ConfigurationError(message) {}
};
// Type of a configuration value.
@@ -208,8 +254,47 @@ inline OutputFormat operator&=(OutputFormat& a, OutputFormat b) {
a = a & b; return a;
}
+enum ForwardCompatibilitySubstitutionRule
+{
+ // Disable susbtitution, throw exception if an option value is not recognized.
+ Disable,
+ // Enable substitution of an unknown option value with default. Log the substitution.
+ Enable,
+ // Enable substitution of an unknown option value with default. Don't log the substitution.
+ EnableSilent,
+ // Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles.
+ EnableSystemSilent,
+ // Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile.
+ EnableSilentDisableSystem,
+};
+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 {
@@ -277,7 +362,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
this->phony = rhs->phony;
@@ -286,7 +371,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
}
@@ -352,7 +437,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
this->phony = rhs->phony;
@@ -370,12 +455,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
if (other->values.empty())
- throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
+ throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
else
- throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
@@ -394,12 +479,12 @@ public:
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
- throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
+ throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
- throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
const T& get_at(size_t i) const
@@ -426,7 +511,7 @@ public:
if (opt_default == nullptr)
this->values.resize(n, this->default_value);
if (opt_default->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
+ throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type.");
if(static_cast<const ConfigOptionVector<T>*>(opt_default)->values.empty())
this->values.resize(n, this->default_value);
else
@@ -446,7 +531,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionVector: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
}
@@ -458,9 +543,9 @@ public:
// An option overrides another option if it is not nil and not equal.
bool overriden_by(const ConfigOption *rhs) const override {
if (this->nullable())
- throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
+ throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
+ throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable())
// Overridding a non-nullable object with another non-nullable object.
@@ -478,9 +563,9 @@ public:
// Apply an override option, possibly a nullable one.
bool apply_override(const ConfigOption *rhs) override {
if (this->nullable())
- throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
+ throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
+ throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable()) {
// Overridding a non-nullable object with another non-nullable object.
@@ -571,7 +656,7 @@ public:
bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
}
@@ -618,7 +703,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
- throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+ throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@@ -643,9 +728,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
- throw Slic3r::RuntimeError("Serializing NaN");
+ throw ConfigurationError("Serializing NaN");
} else
- throw Slic3r::RuntimeError("Serializing invalid number");
+ throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
if (NULLABLE) {
@@ -764,7 +849,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
- throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+ throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int32_t value;
@@ -781,7 +866,7 @@ private:
if (NULLABLE)
ss << "nil";
else
- throw Slic3r::RuntimeError("Serializing NaN");
+ throw ConfigurationError("Serializing NaN");
} else
ss << v;
}
@@ -811,7 +896,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);
@@ -970,7 +1055,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
}
@@ -981,7 +1066,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
- throw ConfigurationException("ConfigOptionFloatOrPercent: Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
}
@@ -1046,7 +1131,7 @@ public:
bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
}
@@ -1093,7 +1178,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
- throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+ throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
bool percent = item_str.find_first_of("%") != std::string::npos;
std::istringstream iss(item_str);
@@ -1121,9 +1206,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
- throw Slic3r::RuntimeError("Serializing NaN");
+ throw ConfigurationError("Serializing NaN");
} else
- throw Slic3r::RuntimeError("Serializing invalid number");
+ throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
if (NULLABLE) {
@@ -1312,8 +1397,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") {
+ this->value = true;
+ return true;
+ }
+ if (str == "0") {
+ this->value = false;
+ return true;
+ }
+ return false;
}
private:
@@ -1374,24 +1466,39 @@ public:
}
return vv;
}
-
- bool deserialize(const std::string &str, bool append = false) override
+
+ ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution)
{
if (! append)
this->values.clear();
std::istringstream is(str);
std::string item_str;
+ bool substituted = false;
while (std::getline(is, item_str, ',')) {
boost::trim(item_str);
+ unsigned char new_value = 0;
if (item_str == "nil") {
if (NULLABLE)
- this->values.push_back(nil_value());
+ new_value = nil_value();
else
- throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+ throw ConfigurationError("Deserializing nil into a non-nullable object");
+ } else if (item_str == "1") {
+ new_value = true;
+ } else if (item_str == "0") {
+ new_value = false;
+ } else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) {
+ new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
+ substituted = true;
} else
- this->values.push_back(item_str.compare("1") == 0);
+ return ConfigHelpers::DeserializationResult::Failed;
+ this->values.push_back(new_value);
}
- return true;
+ return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded;
}
protected:
@@ -1400,7 +1507,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
- throw Slic3r::RuntimeError("Serializing NaN");
+ throw ConfigurationError("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@@ -1436,14 +1543,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = (T)rhs->getInt();
this->phony = rhs->phony;
@@ -1522,14 +1629,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
+ throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
- throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
+ throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = rhs->getInt();
this->phony = rhs->phony;
@@ -1585,7 +1692,7 @@ public:
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
- default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
+ default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@@ -1604,7 +1711,7 @@ public:
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
- default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
+ default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
}
}
}
@@ -1616,7 +1723,7 @@ public:
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
- default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
+ default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@@ -1635,7 +1742,7 @@ public:
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
- default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
+ default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
}
}
// Make the compiler happy, shut up the warnings.
@@ -1731,6 +1838,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
@@ -1758,7 +1873,7 @@ public:
return out;
}
- /// Iterate through all of the CLI options and write them to a stream.
+ // Iterate through all of the CLI options and write them to a stream.
std::ostream& print_cli_help(
std::ostream& out, bool show_defaults,
std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
@@ -1809,6 +1924,8 @@ public:
}
};
+
+
// An abstract configuration store.
class ConfigBase : public ConfigOptionResolver
{
@@ -1905,9 +2022,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) {}
@@ -1922,17 +2041,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);
+ 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, 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, bool to_prusa = false) const;
// Set all the nullable values to nils.
@@ -1940,7 +2061,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.
@@ -2110,9 +2231,9 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(options); }
};
-/// Configuration store with a static definition of configuration values.
-/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
-/// because the configuration values could be accessed directly.
+// Configuration store with a static definition of configuration values.
+// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
+// because the configuration values could be accessed directly.
class StaticConfig : public virtual ConfigBase
{
public:
diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp
index 2f37a62f2..e9783a75d 100644
--- a/src/libslic3r/Extruder.cpp
+++ b/src/libslic3r/Extruder.cpp
@@ -147,12 +147,12 @@ double Tool::retract_restart_extra_toolchange() const
return 0;
}
-int Tool::temp_offset() const
+int16_t Tool::temp_offset() const
{
return 0;
}
-int Tool::fan_offset() const
+int8_t Tool::fan_offset() const
{
return 0;
}
@@ -219,12 +219,12 @@ double Extruder::retract_restart_extra_toolchange() const
return m_config->retract_restart_extra_toolchange.get_at(m_id);
}
-int Extruder::temp_offset() const
+int16_t Extruder::temp_offset() const
{
return m_config->extruder_temperature_offset.get_at(m_id);
}
-int Extruder::fan_offset() const
+int8_t Extruder::fan_offset() const
{
return m_config->extruder_fan_offset.get_at(m_id);
}
diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp
index c9c38138a..1d8917090 100644
--- a/src/libslic3r/Extruder.hpp
+++ b/src/libslic3r/Extruder.hpp
@@ -48,8 +48,8 @@ public:
virtual double retract_restart_extra() const;
virtual double retract_length_toolchange() const;
virtual double retract_restart_extra_toolchange() const;
- virtual int temp_offset() const;
- virtual int fan_offset() const;
+ virtual int16_t temp_offset() const;
+ virtual int8_t fan_offset() const;
protected:
// Private constructor to create a key for a search in std::set.
@@ -104,8 +104,8 @@ public:
double retract_restart_extra() const override;
double retract_length_toolchange() const override;
double retract_restart_extra_toolchange() const override;
- int temp_offset() const override;
- int fan_offset() const override;
+ int16_t temp_offset() const override;
+ int8_t fan_offset() const override;
protected:
// Private constructor to create a key for a search in std::set.
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
index 1cbb9e00f..5c825f5c7 100644
--- a/src/libslic3r/ExtrusionEntity.cpp
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -255,15 +255,6 @@ void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scale
path.polygons_covered_by_spacing(out, scaled_epsilon);
}
-double ExtrusionLoop::min_mm3_per_mm() const
-{
- double min_mm3_per_mm = std::numeric_limits<double>::max();
- for (const ExtrusionPath &path : this->paths)
- if (path.role() != erGapFill && path.role() != erThinWall && path.role() != erMilling)
- min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
- return min_mm3_per_mm;
-}
-
std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
{
switch (role) {
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
index 0d9069050..c062456de 100644
--- a/src/libslic3r/ExtrusionEntity.hpp
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -201,8 +201,6 @@ public:
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
virtual Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
- // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
- virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0;
virtual void collect_polylines(Polylines &dst) const = 0;
virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; }
@@ -269,8 +267,6 @@ public:
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
virtual Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
- // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
- double min_mm3_per_mm() const override { return this->mm3_per_mm; }
Polyline as_polyline() const override { return this->polyline; }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
@@ -362,15 +358,6 @@ public:
entity.polygons_covered_by_spacing(out, scaled_epsilon);
}
- // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
- double min_mm3_per_mm() const override {
- double min_mm3_per_mm = std::numeric_limits<double>::max();
- for (const THING &entity : this->paths)
- if (entity.role() != erGapFill && entity.role() != erThinWall && entity.role() != erMilling)
- min_mm3_per_mm = std::min(min_mm3_per_mm, entity.min_mm3_per_mm());
- return min_mm3_per_mm;
- }
-
Polyline as_polyline() const override {
Polyline out;
if (!paths.empty()) {
@@ -444,8 +431,8 @@ public:
ExtrusionPaths paths;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
- ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
- ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
+ ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) { assert(this->first_point() == this->paths.back().polyline.points.back()); }
+ ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) { assert(this->first_point() == this->paths.back().polyline.points.back()); }
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.push_back(path); }
ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
@@ -481,8 +468,6 @@ public:
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
- // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
- double min_mm3_per_mm() const override;
Polyline as_polyline() const override { return this->polygon().split_at_first_point(); }
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume = 0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp
index a7c338e02..c80028780 100644
--- a/src/libslic3r/ExtrusionEntityCollection.cpp
+++ b/src/libslic3r/ExtrusionEntityCollection.cpp
@@ -170,15 +170,4 @@ FlatenEntities::flatten(const ExtrusionEntityCollection &to_flatten) && {
return std::move(to_fill);
}
-
-double
-ExtrusionEntityCollection::min_mm3_per_mm() const
-{
- double min_mm3_per_mm = std::numeric_limits<double>::max();
- for (const ExtrusionEntity *entity : this->entities)
- if(entity->role() != erGapFill && entity->role() != erThinWall && entity->role() != erMilling)
- min_mm3_per_mm = std::min(min_mm3_per_mm, entity->min_mm3_per_mm());
- return min_mm3_per_mm;
-}
-
}
diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp
index 7c8920cb4..439dddab2 100644
--- a/src/libslic3r/ExtrusionEntityCollection.hpp
+++ b/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -115,7 +115,6 @@ public:
/// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy).
/// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False).
ExtrusionEntityCollection flatten(bool preserve_ordering = false) const;
- double min_mm3_per_mm() const override;
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
// Following methods shall never be called on an ExtrusionEntityCollection.
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
index 313bf9692..26952ae46 100644
--- a/src/libslic3r/Fill/Fill.cpp
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -112,7 +112,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
FlowRole extrusion_role = surface.has_pos_top() ? frTopSolidInfill : (surface.has_fill_solid() ? frSolidInfill : frInfill);
bool is_bridge = layer.id() > 0 && surface.has_mod_bridge();
bool is_denser = false;
- params.extruder = layerm.region()->extruder(extrusion_role);
+ params.extruder = layerm.region()->extruder(extrusion_role, *layer.object());
params.pattern = region_config.fill_pattern.value;
params.density = float(region_config.fill_density) / 100.f;
params.dont_adjust = false;
@@ -324,7 +324,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
}
if (internal_solid_fill == nullptr) {
// Produce another solid fill.
- params.extruder = layerm.region()->extruder(frSolidInfill);
+ params.extruder = layerm.region()->extruder(frSolidInfill, *layer.object());
params.pattern = layerm.region()->config().solid_fill_pattern.value;
params.density = 100.f;
params.role = erInternalInfill;
@@ -465,9 +465,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
}
//init the surface with the current polygon
- surface_fill.surface.expolygon = std::move(expoly);
- //make fill
- f->fill_surface_extrusion(&surface_fill.surface, surface_fill.params, m_regions[surface_fill.region_id]->fills.entities);
+ if (!expoly.contour.empty()) {
+ surface_fill.surface.expolygon = std::move(expoly);
+
+ //make fill
+ f->fill_surface_extrusion(&surface_fill.surface, surface_fill.params, m_regions[surface_fill.region_id]->fills.entities);
+ }
}
}
diff --git a/src/libslic3r/Fill/FillSmooth.cpp b/src/libslic3r/Fill/FillSmooth.cpp
index 1591081b9..8c4e89bdc 100644
--- a/src/libslic3r/Fill/FillSmooth.cpp
+++ b/src/libslic3r/Fill/FillSmooth.cpp
@@ -36,6 +36,9 @@ namespace Slic3r {
if (params.config != NULL && idx > 0) params_modifided.flow_mult *= (float)params.config->fill_smooth_distribution.get_abs_value(1);
else if (params.config != NULL && idx == 0) params_modifided.flow_mult *= (1.f - (float)params.config->fill_smooth_distribution.get_abs_value(1));
else params_modifided.flow_mult *= (float)percentFlow[idx];
+ //set role
+ if (rolePass[idx] != erNone)
+ params_modifided.role = rolePass[idx];
//choose if we are going to extrude with or without overlap
if ((params.flow.bridge && idx == 0) || has_overlap[idx] || this->no_overlap_expolygons.empty()){
@@ -115,8 +118,8 @@ namespace Slic3r {
// first infill
FillParams first_pass_params = params;
- if(first_pass_params.role != ExtrusionRole::erSupportMaterial && first_pass_params.role != ExtrusionRole::erSupportMaterialInterface)
- first_pass_params.role = ExtrusionRole::erSolidInfill;
+ //if(first_pass_params.role != ExtrusionRole::erSupportMaterial && first_pass_params.role != ExtrusionRole::erSupportMaterialInterface)
+ //s first_pass_params.role = ExtrusionRole::erSolidInfill;
perform_single_fill(0, *eecroot, *surface, first_pass_params, volume_to_occupy);
//use monotonic for ironing pass
diff --git a/src/libslic3r/Fill/FillSmooth.hpp b/src/libslic3r/Fill/FillSmooth.hpp
index 789978002..fa7a0b4b6 100644
--- a/src/libslic3r/Fill/FillSmooth.hpp
+++ b/src/libslic3r/Fill/FillSmooth.hpp
@@ -18,9 +18,9 @@ public:
fillPattern[0] = InfillPattern::ipRectilinearWGapFill;
fillPattern[1] = InfillPattern::ipRectilinear;
fillPattern[2] = InfillPattern::ipRectilinear;
- rolePass[0] = erSolidInfill;
- rolePass[1] = erTopSolidInfill;
- rolePass[2] = erTopSolidInfill;
+ rolePass[0] = erNone;// erTopSolidInfill;
+ rolePass[1] = erIroning;
+ rolePass[2] = erIroning;
percentWidth[0] = 1;
percentWidth[1] = 2;
percentWidth[2] = 1.0;
@@ -73,9 +73,9 @@ public:
fillPattern[0] = InfillPattern::ipHilbertCurve; //ipRectilinear
fillPattern[1] = InfillPattern::ipConcentric;
fillPattern[2] = InfillPattern::ipRectilinear;
- rolePass[0] = erTopSolidInfill;//erSolidInfill
- rolePass[1] = erSolidInfill;
- rolePass[2] = erTopSolidInfill;
+ rolePass[0] = erSolidInfill;//erSolidInfill
+ rolePass[1] = erTopSolidInfill;
+ rolePass[2] = erIroning;
percentWidth[0] = 1; //0.8
percentWidth[1] = 1.5;
percentWidth[2] = 2.8;
@@ -106,9 +106,9 @@ public:
fillPattern[0] = InfillPattern::ipHilbertCurve; //ipHilbertCurve
fillPattern[1] = InfillPattern::ipHilbertCurve;
fillPattern[2] = InfillPattern::ipRectilinear;
- rolePass[0] = erSolidInfill;
- rolePass[1] = erTopSolidInfill;
- rolePass[2] = erTopSolidInfill;
+ rolePass[0] = erTopSolidInfill;
+ rolePass[1] = erIroning;
+ rolePass[2] = erIroning;
percentWidth[0] = 1;
percentWidth[1] = 1.5;
percentWidth[2] = 1.0;
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index f8b4f0970..fe98c0518 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -427,7 +427,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();
@@ -442,16 +442,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
@@ -518,7 +518,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);
@@ -547,7 +547,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;
@@ -568,7 +568,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()
@@ -590,7 +590,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);
@@ -653,7 +653,7 @@ namespace Slic3r {
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
@@ -665,7 +665,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);
print_config_parsed = true;
}
if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE))
@@ -687,7 +687,8 @@ namespace Slic3r {
}
//parsed superslicer/prusa files if slic3r not found
//note that is we successfully read one of the config file, then the other ones should also have the same name
- auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename](const std::string &print_config_name, const std::string& model_config_name) -> bool {
+ auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename, &config_substitutions]
+ (const std::string &print_config_name, const std::string& model_config_name) -> bool {
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
@@ -699,7 +700,7 @@ namespace Slic3r {
if (boost::algorithm::iequals(name, print_config_name))
{
// 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);
print_config_parsed = true;
} else if (boost::algorithm::iequals(name, model_config_name))
{
@@ -774,7 +775,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
@@ -791,7 +792,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;
}
@@ -868,7 +869,12 @@ 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)
{
@@ -879,7 +885,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);
}
}
@@ -954,7 +960,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)
{
@@ -1003,8 +1009,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));
@@ -1895,7 +1900,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())
{
@@ -1997,7 +2002,7 @@ namespace Slic3r {
else if (metadata.key == SOURCE_IN_INCHES)
volume->source.is_converted_from_inches = metadata.value == "1";
else
- volume->config.set_deserialize(metadata.key, metadata.value);
+ volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
}
@@ -2971,13 +2976,13 @@ 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;
_3MF_Importer importer;
- bool res = importer.load_model_from_file(path, *model, *config, check_version);
+ bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
importer.log_errors();
return res;
}
diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp
index ccfd9356d..553b31898 100644
--- a/src/libslic3r/Format/3mf.hpp
+++ b/src/libslic3r/Format/3mf.hpp
@@ -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
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
index 72c6af310..3fe538b36 100644
--- a/src/libslic3r/Format/AMF.cpp
+++ b/src/libslic3r/Format/AMF.cpp
@@ -62,10 +62,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);
}
@@ -256,6 +257,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&);
@@ -704,8 +707,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()) {
@@ -723,7 +727,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();
@@ -848,7 +852,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;
@@ -865,7 +869,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);
@@ -909,7 +913,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)
{
@@ -925,7 +929,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);
@@ -985,7 +989,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;
@@ -1011,7 +1015,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);
printf("Archive does not contain a valid model");
@@ -1053,11 +1057,11 @@ 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)
{
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);
@@ -1068,7 +1072,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;
diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp
index 138195cd6..e834104e7 100644
--- a/src/libslic3r/Format/AMF.hpp
+++ b/src/libslic3r/Format/AMF.hpp
@@ -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
diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp
index e2c38d957..6f86a203f 100644
--- a/src/libslic3r/Format/PRUS.cpp
+++ b/src/libslic3r/Format/PRUS.cpp
@@ -286,11 +286,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.
diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp
index eedf19a0d..5f26c463b 100644
--- a/src/libslic3r/Format/SL1.cpp
+++ b/src/libslic3r/Format/SL1.cpp
@@ -203,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
- throw Slic3r::FileIOError("Invalid SL1 file");
+ throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
RasterParams rstp;
@@ -229,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
- throw Slic3r::FileIOError("Invalid SL1 file");
+ throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}
@@ -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,
Vec2i32 windowsize,
TriangleMesh & 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_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
+
+ return config_substitutions;
}
using ConfMap = std::map<std::string, std::string>;
diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp
index 71c9959b6..f214e7c07 100644
--- a/src/libslic3r/Format/SL1.hpp
+++ b/src/libslic3r/Format/SL1.hpp
@@ -25,23 +25,23 @@ public:
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override;
};
-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,
Vec2i32 windowsize,
TriangleMesh & 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,
Vec2i32 windowsize,
TriangleMesh & 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
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index d4e1502ea..48a60af38 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -273,10 +273,11 @@ static inline void set_extra_lift(const Layer& layer, const Print& print, GCodeW
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
- gcode += gcodegen.travel_to(
+ Polyline polyline = gcodegen.travel_to(
+ gcode,
wipe_tower_point_to_object_point(gcodegen, start_pos),
- erMixed,
- "Travel to a Wipe Tower");
+ erMixed);
+ gcodegen.write_travel_to(gcode, polyline, "Travel to a Wipe Tower");
gcode += gcodegen.unretract();
}
@@ -727,6 +728,81 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re
// free functions called by GCode::_do_export()
namespace DoExport {
+
+ class ExtrusionMinMM : public ExtrusionVisitorConst {
+ double min = std::numeric_limits<double>::max();
+ std::unordered_set<ExtrusionRole> excluded;
+ public:
+ ExtrusionMinMM(const ConfigBase* config) {
+ excluded.insert(erIroning);
+ excluded.insert(erMilling);
+ excluded.insert(erCustom);
+ excluded.insert(erMixed);
+ excluded.insert(erNone);
+ excluded.insert(erWipeTower);
+ if (config->get_abs_value("perimeter_speed") != 0 && config->get_abs_value("small_perimeter_speed") != 0) {
+ excluded.insert(erPerimeter);
+ excluded.insert(erSkirt);
+ }
+ if (config->get_abs_value("external_perimeter_speed") != 0 && config->get_abs_value("small_perimeter_speed") != 0)
+ excluded.insert(erExternalPerimeter);
+ if (config->get_abs_value("overhangs_speed") != 0 && config->get_abs_value("small_perimeter_speed") != 0)
+ excluded.insert(erOverhangPerimeter);
+ if (config->get_abs_value("gap_fill_speed") != 0)
+ excluded.insert(erGapFill);
+ if (config->get_abs_value("thin_walls_speed") != 0)
+ excluded.insert(erThinWall);
+ if (config->get_abs_value("infill_speed") != 0)
+ excluded.insert(erInternalInfill);
+ if (config->get_abs_value("solid_infill_speed") != 0)
+ excluded.insert(erSolidInfill);
+ if (config->get_abs_value("top_solid_infill_speed") != 0)
+ excluded.insert(erTopSolidInfill);
+ if (config->get_abs_value("bridge_speed") != 0)
+ excluded.insert(erBridgeInfill);
+ if (config->get_abs_value("bridge_speed_internal") != 0)
+ excluded.insert(erInternalBridgeInfill);
+ if (config->get_abs_value("support_material_speed") != 0)
+ excluded.insert(erSupportMaterial);
+ if (config->get_abs_value("support_material_interface_speed") != 0)
+ excluded.insert(erSupportMaterialInterface);
+ }
+ virtual void use(const ExtrusionPath& path) override {
+ if (excluded.find(path.role()) == excluded.end())
+ min = std::min(min, path.mm3_per_mm);
+ }
+ virtual void use(const ExtrusionPath3D& path3D) override {
+ if (excluded.find(path3D.role()) == excluded.end())
+ min = std::min(min, path3D.mm3_per_mm);
+ }
+ virtual void use(const ExtrusionMultiPath& multipath) override {
+ for (const ExtrusionPath& path : multipath.paths)
+ use(path);
+ }
+ virtual void use(const ExtrusionMultiPath3D& multipath) override {
+ for (const ExtrusionPath& path : multipath.paths)
+ use(path);
+ }
+ virtual void use(const ExtrusionLoop& loop) override {
+ for (const ExtrusionPath& path : loop.paths)
+ use(path);
+ }
+ virtual void use(const ExtrusionEntityCollection& collection) override {
+ for (const ExtrusionEntity* entity : collection.entities)
+ entity->visit(*this);
+ }
+ double reset_use_get(const ExtrusionEntityCollection entity) { reset(); use(entity); return get(); }
+ double get() { return min; }
+ void reset() { min = std::numeric_limits<double>::max(); }
+ //test if at least a ExtrusionRole from tests is used for min computation
+ bool is_compatible(std::initializer_list<ExtrusionRole> tests) {
+ for (ExtrusionRole test : tests)
+ if (excluded.find(test) == excluded.end())
+ return true;
+ return false;
+ }
+ };
+
static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
{
silent_time_estimator_enabled = (config.gcode_flavor.value == gcfMarlin) && config.silent_mode;
@@ -737,6 +813,7 @@ namespace DoExport {
static double autospeed_volumetric_limit(const Print &print)
{
+ ExtrusionMinMM compute_min_mm3_per_mm{ &print.full_print_config() };
// get the minimum cross-section used in the print
std::vector<double> mm3_per_mm;
for (auto object : print.objects()) {
@@ -744,37 +821,20 @@ namespace DoExport {
const PrintRegion* region = print.regions()[region_id];
for (auto layer : object->layers()) {
const LayerRegion* layerm = layer->regions()[region_id];
- if (region->config().get_abs_value("perimeter_speed") == 0 ||
- region->config().get_abs_value("small_perimeter_speed") == 0 ||
- region->config().get_abs_value("external_perimeter_speed") == 0 ||
- region->config().get_abs_value("overhangs_speed") == 0)
- mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
- if (region->config().get_abs_value("infill_speed") == 0 ||
- region->config().get_abs_value("solid_infill_speed") == 0 ||
- region->config().get_abs_value("top_solid_infill_speed") == 0 ||
- region->config().get_abs_value("bridge_speed") == 0 ||
- region->config().get_abs_value("bridge_speed_internal") == 0)
- {
- // Minimal volumetric flow should not be calculated over ironing extrusions.
- // Use following lambda instead of the built-it method.
- // https://github.com/prusa3d/PrusaSlicer/issues/5082
- auto min_mm3_per_mm_no_ironing = [](const ExtrusionEntityCollection& eec) -> double {
- double min = std::numeric_limits<double>::max();
- for (const ExtrusionEntity* ee : eec.entities)
- if (ee->role() != erIroning)
- min = std::min(min, ee->min_mm3_per_mm());
- return min;
- };
-
- mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills));
- }
+ if (compute_min_mm3_per_mm.is_compatible({ erPerimeter, erExternalPerimeter, erOverhangPerimeter }))
+ mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(layerm->perimeters));
+ if (compute_min_mm3_per_mm.is_compatible({ erInternalInfill, erSolidInfill, erTopSolidInfill,erBridgeInfill,erInternalBridgeInfill }))
+ mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(layerm->fills));
}
}
- if (object->config().get_abs_value("support_material_speed") == 0 ||
- object->config().get_abs_value("support_material_interface_speed") == 0)
+ if (compute_min_mm3_per_mm.is_compatible({ erSupportMaterial, erSupportMaterialInterface }))
for (auto layer : object->support_layers())
- mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
+ mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(layer->support_fills));
}
+ if (compute_min_mm3_per_mm.is_compatible({ erSkirt })) {
+ mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(print.skirt()));
+ mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(print.brim()));
+ }
// filter out 0-width segments
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
double volumetric_speed = 0.;
@@ -1333,7 +1393,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Disable fan.
if ((initial_extruder_id != (uint16_t)-1) && !this->config().start_gcode_manual && print.config().disable_fan_first_layers.get_at(initial_extruder_id))
- _write(file, m_writer.set_fan(0, true, initial_extruder_id));
+ _write(file, m_writer.set_fan(uint8_t(0), true, initial_extruder_id));
//ensure fan is at the right speed
print.throw_if_canceled();
@@ -1427,7 +1487,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters.use_external_mp_once();
_write(file, this->retract());
- _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
+ std::string gcode;
+ Polyline polyline = this->travel_to(gcode, Point(0, 0), erNone);
+ this->write_travel_to(gcode, polyline, "move to origin position for next object");
+ _write(file, gcode);
m_enable_cooling_markers = true;
// Disable motion planner when traveling to first object point.
m_avoid_crossing_perimeters.disable_once();
@@ -1535,7 +1598,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_add_object_change_labels(gcode);
_write(file, gcode);
}
- _write(file, m_writer.set_fan(false));
+ _write(file, m_writer.set_fan(uint8_t(0)));
// adds tag for processor
_write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str());
@@ -3107,9 +3170,10 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s
double min_length = scale_(this->m_config.small_perimeter_min_length.get_abs_value(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0)));
double max_length = scale_(this->m_config.small_perimeter_max_length.get_abs_value(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0)));
if (loop.length() <= min_length) {
- speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
+ speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
} else {
- speed = -(loop.length() - min_length) / (max_length - min_length);
+ //set speed between -1 and 0 you have to multiply the real peed by the opposite of that, and add the other part as small_perimeter_speed
+ speed = (min_length - loop.length()) / (max_length - min_length);
}
}
@@ -3662,39 +3726,77 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string
std::string gcode;
std::string description{ description_in };
- // go to first point of extrusion path
- if (!m_last_pos_defined || m_last_pos != path.first_point()) {
- gcode += this->travel_to(
- path.first_point(),
- path.role(),
- "move to first " + description + " point"
- );
- }
-
- //if needed, write the gcode_label_objects_end then gcode_label_objects_start
- //should be already done by travel_to, but just in case
- _add_object_change_labels(gcode);
-
- // compensate retraction
- gcode += this->unretract();
- // adjust acceleration
+ // adjust acceleration, inside the travel to set the deceleration
+ double acceleration = get_default_acceleration(m_config);
+ double travel_acceleration = m_writer.get_acceleration();
{
- double acceleration = get_default_acceleration(m_config);
if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) {
acceleration = m_config.first_layer_acceleration.get_abs_value(acceleration);
} else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) {
acceleration = m_config.perimeter_acceleration.get_abs_value(acceleration);
- } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())
- && path.role() != erOverhangPerimeter ) {
+ } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())
+ && path.role() != erOverhangPerimeter) {
acceleration = m_config.bridge_acceleration.get_abs_value(acceleration);
} else if (m_config.infill_acceleration.value > 0 && is_infill(path.role())) {
acceleration = m_config.infill_acceleration.get_abs_value(acceleration);
}
- //travel acceleration should be already set at startup via special gcode, and so it's automatically used by G0.
- m_writer.set_acceleration((uint16_t)floor(acceleration + 0.5));
+ if (m_config.travel_acceleration.value > 0)
+ travel_acceleration = m_config.travel_acceleration.get_abs_value(acceleration);
}
+ if (travel_acceleration == acceleration) {
+ m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5));
+ // go to first point of extrusion path (stop at midpoint to let us set the decel speed)
+ if (!m_last_pos_defined || m_last_pos != path.first_point()) {
+ Polyline polyline = this->travel_to(gcode, path.first_point(), path.role());
+ this->write_travel_to(gcode, polyline, "move to first " + description + " point (" + std::to_string(acceleration) +" == "+ std::to_string(travel_acceleration)+")");
+ }
+ } else {
+ // go to midpoint to let us set the decel speed)
+ if (!m_last_pos_defined || m_last_pos != path.first_point()) {
+ Polyline poly_start = this->travel_to(gcode, path.first_point(), path.role());
+ coordf_t length = poly_start.length();
+ if (length > SCALED_EPSILON) {
+ Polyline poly_end;
+ coordf_t min_length = scale_(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.5)) * 20;
+ if (poly_start.size() > 2 && length > min_length * 3) {
+ //if complex travel, try to deccelerate only at the end, unless it's less than ~ 20 nozzle
+ if (poly_start.lines().back().length() < min_length) {
+ poly_end = poly_start;
+ poly_start.clip_end(min_length);
+ poly_end.clip_start(length - min_length);
+ } else {
+ poly_end.points.push_back(poly_start.points.back());
+ poly_start.points.pop_back();
+ poly_end.points.push_back(poly_start.points.back());
+ poly_end.reverse();
+ }
+ } else {
+ poly_end = poly_start;
+ poly_start.clip_end(length / 2);
+ poly_end.clip_start(length / 2);
+ }
+ m_writer.set_acceleration((uint32_t)floor(travel_acceleration + 0.5));
+ this->write_travel_to(gcode, poly_start, "move to first " + description + " point (acceleration)");
+ //travel acceleration should be already set at startup via special gcode, and so it's automatically used by G0.
+ m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5));
+ this->write_travel_to(gcode, poly_end, "move to first " + description + " point (deceleration)");
+ } else {
+ m_writer.set_acceleration((uint32_t)floor(travel_acceleration + 0.5));
+ this->write_travel_to(gcode, poly_start, "move to first " + description + " point (acceleration)");
+ }
+ } else {
+ m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5));
+ }
+ }
+
+ //if needed, write the gcode_label_objects_end then gcode_label_objects_start
+ //should be already done by travel_to, but just in case
+ _add_object_change_labels(gcode);
+
+ // compensate retraction
+ gcode += this->unretract();
// set speed
if (speed < 0) {
@@ -3734,40 +3836,53 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string
if (factor < 1 && !(is_bridge(path.role()))) {
float small_speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
//apply factor between feature speed and small speed
- speed = speed * factor + (1.f - factor) * small_speed;
- }
+ speed = (speed * factor) + double((1.f - factor) * small_speed);
+ }
}
if (m_volumetric_speed != 0. && speed == 0) {
//if m_volumetric_speed, use the max size for thinwall & gapfill, to avoid variations
- speed = m_volumetric_speed / path.mm3_per_mm;
- if (speed > m_config.max_print_speed.value)
- speed = m_config.max_print_speed.value;
+ double vol_speed = m_volumetric_speed / path.mm3_per_mm;
+ if (vol_speed > m_config.max_print_speed.value)
+ vol_speed = m_config.max_print_speed.value;
+ // if using a % of an auto speed, use the % over the volumetric speed.
+ if (path.role() == erExternalPerimeter) {
+ speed = m_config.get_abs_value("external_perimeter_speed", vol_speed);
+ } else if (path.role() == erInternalBridgeInfill) {
+ speed = m_config.get_abs_value("bridge_speed_internal", vol_speed);
+ } else if (path.role() == erOverhangPerimeter) {
+ speed = m_config.get_abs_value("overhangs_speed", vol_speed);
+ } else if (path.role() == erSolidInfill) {
+ speed = m_config.get_abs_value("solid_infill_speed", vol_speed);
+ } else if (path.role() == erTopSolidInfill) {
+ speed = m_config.get_abs_value("top_solid_infill_speed", vol_speed);
+ }
+ if(speed == 0){
+ speed = vol_speed;
+ }
}
if (speed == 0) // this code shouldn't trigger as if it's 0, you have to get a m_volumetric_speed
speed = m_config.max_print_speed.value;
if (this->on_first_layer())
- if (path.role() == erInternalInfill || path.role() == erSolidInfill)
- speed = std::min(m_config.get_abs_value("first_layer_infill_speed", speed), speed);
- else
- speed = std::min(m_config.get_abs_value("first_layer_speed", speed), speed);
- if (m_config.max_volumetric_speed.value > 0) {
- // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
- speed = std::min(
- speed,
- m_config.max_volumetric_speed.value / path.mm3_per_mm
- );
- }
- if (EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_volumetric_speed, 0) > 0) {
- // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
- speed = std::min(
- speed,
- EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_volumetric_speed, speed) / path.mm3_per_mm
- );
+ if (path.role() == erInternalInfill || path.role() == erSolidInfill) {
+ double first_layer_infill_speed = m_config.get_abs_value("first_layer_infill_speed", speed);
+ if(first_layer_infill_speed > 0)
+ speed = std::min(first_layer_infill_speed, speed);
+ } else {
+ double first_layer_speed = m_config.get_abs_value("first_layer_speed", speed);
+ if (first_layer_speed > 0)
+ speed = std::min(first_layer_speed, speed);
+ }
+ // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
+ if (m_config.max_volumetric_speed.value > 0 && path.mm3_per_mm > 0) {
+ speed = std::min(m_config.max_volumetric_speed.value / path.mm3_per_mm, speed);
+ }
+ double filament_max_volumetric_speed = EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_volumetric_speed, 0);
+ if (filament_max_volumetric_speed > 0) {
+ speed = std::min(filament_max_volumetric_speed / path.mm3_per_mm, speed);
}
- if (EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_speed, 0) > 0) {
- speed = std::min(
- speed,
- EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_speed, speed));
+ double filament_max_speed = EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_speed, 0);
+ if (filament_max_speed > 0) {
+ speed = std::min(filament_max_speed, speed);
}
double F = speed * 60; // convert mm/sec to mm/min
@@ -3881,7 +3996,7 @@ void GCode::_add_object_change_labels(std::string& gcode) {
}
// This method accepts &point in print coordinates.
-std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment)
+Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole role)
{
/* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by
@@ -3910,7 +4025,6 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
m_avoid_crossing_perimeters.reset_once_modifiers();
// generate G-code for the travel move
- std::string gcode;
if (needs_retraction) {
if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
m_wipe.reset_path();
@@ -3931,15 +4045,19 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
//if needed, write the gcode_label_objects_end then gcode_label_objects_start
_add_object_change_labels(gcode);
+ return travel;
+}
+
+
+void GCode::write_travel_to(std::string &gcode, const Polyline& travel, std::string comment)
+{
// use G1 because we rely on paths being straight (G0 may make round paths)
if (travel.size() >= 2) {
- for (size_t i = 1; i < travel.size(); ++ i)
+ for (size_t i = 1; i < travel.size(); ++i)
gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
this->set_last_pos(travel.points.back());
}
- return gcode;
}
-
bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
{
if (travel.length() < scale_(EXTRUDER_CONFIG_WITH_DEFAULT(retract_before_travel, 0))) {
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 9559a8a6b..a116d5dfe 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -315,7 +315,8 @@ private:
std::string extrude_ironing(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region);
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
- std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
+ Polyline travel_to(std::string& gcode, const Point &point, ExtrusionRole role);
+ void write_travel_to(std::string& gcode, const Polyline& travel, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone);
std::string retract(bool toolchange = false);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
index e84380de9..51e6fbb7a 100644
--- a/src/libslic3r/GCode/GCodeProcessor.cpp
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -878,7 +878,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::SuperSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
- config.load_from_gcode_file(filename);
+ // 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, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config);
}
}
diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp
index 3c0f743df..e9ababa33 100644
--- a/src/libslic3r/GCodeWriter.cpp
+++ b/src/libslic3r/GCodeWriter.cpp
@@ -9,7 +9,7 @@
#define FLAVOR_IS(val) this->config.gcode_flavor.value == val
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor.value != val
#define COMMENT(comment) if (this->config.gcode_comments.value && !comment.empty()) gcode << " ; " << comment;
-#define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val)
+#define PRECISION(val, precision) to_string_nozero(val, precision)
#define XYZ_NUM(val) PRECISION(val, this->config.gcode_precision_xyz.value)
#define FLOAT_PRECISION(val, precision) std::defaultfloat << std::setprecision(precision) << (val)
#define F_NUM(val) FLOAT_PRECISION(val, 8)
@@ -17,6 +17,34 @@
namespace Slic3r {
+std::string to_string_nozero(double value, int32_t max_precision) {
+ double intpart;
+ if (modf(value, &intpart) == 0.0) {
+ //shortcut for int
+ return boost::lexical_cast<std::string>(intpart);
+ } else {
+ std::stringstream ss;
+ //first, get the int part, to see how many digit it takes
+ int long10 = 0;
+ if (intpart > 9)
+ long10 = std::floor(std::log10(std::abs(intpart)));
+ //set the usable precision: there is only 15-16 decimal digit in a double
+ ss << std::fixed << std::setprecision(int(std::min(15 - long10, int(max_precision)))) << value;
+ std::string ret = ss.str();
+ uint8_t nb_del = 0;
+ for (uint8_t i = uint8_t(ss.tellp()) - 1; i > 0; i--) {
+ if (ret[i] == '0')
+ nb_del++;
+ else
+ break;
+ }
+ if (nb_del > 0)
+ return ret.substr(0, ret.size() - nb_del);
+ else
+ return ret;
+ }
+}
+
std::string GCodeWriter::PausePrintCode = "M601";
void GCodeWriter::apply_print_config(const PrintConfig &print_config)
@@ -129,19 +157,19 @@ std::string GCodeWriter::postamble() const
return gcode.str();
}
-std::string GCodeWriter::set_temperature(const unsigned int temperature, bool wait, int tool)
+std::string GCodeWriter::set_temperature(const int16_t temperature, bool wait, int tool)
{
//use m_tool if tool isn't set
if (tool < 0 && m_tool != nullptr)
tool = m_tool->id();
//add offset
- int16_t temp_w_offset = int16_t(temperature);
+ int16_t temp_w_offset = temperature;
temp_w_offset += int16_t(get_tool(tool)->temp_offset());
temp_w_offset = std::max(int16_t(0), std::min(int16_t(2000), temp_w_offset));
// temp_w_offset has an effective minimum value of 0, so this cast is safe.
- if (m_last_temperature_with_offset == static_cast<uint16_t>(temp_w_offset) && !wait)
+ if (m_last_temperature_with_offset == temp_w_offset && !wait)
return "";
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return "";
@@ -184,7 +212,7 @@ std::string GCodeWriter::set_temperature(const unsigned int temperature, bool wa
return gcode.str();
}
-std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
+std::string GCodeWriter::set_bed_temperature(uint32_t temperature, bool wait)
{
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
return std::string();
@@ -220,25 +248,25 @@ std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait
return gcode.str();
}
-std::string GCodeWriter::set_fan(const unsigned int speed, bool dont_save, uint16_t default_tool)
+std::string GCodeWriter::set_fan(const uint8_t speed, bool dont_save, uint16_t default_tool)
{
std::ostringstream gcode;
const Tool *tool = m_tool == nullptr ? get_tool(default_tool) : m_tool;
//add fan_offset
- int16_t fan_speed = int16_t(speed);
+ int8_t fan_speed = int8_t(std::min(uint8_t(100), speed));
if (tool != nullptr)
- fan_speed += int8_t(tool->fan_offset());
- fan_speed = std::max(int16_t(0), std::min(int16_t(100), fan_speed));
+ fan_speed += tool->fan_offset();
+ fan_speed = std::max(int8_t(0), std::min(int8_t(100), fan_speed));
const auto fan_baseline = (this->config.fan_percentage.value ? 100.0 : 255.0);
// fan_speed has an effective minimum value of 0, so this cast is safe.
//test if it's useful to write it
- if (m_last_fan_speed_with_offset != static_cast<uint16_t>(fan_speed) || dont_save) {
+ if (m_last_fan_speed_with_offset != fan_speed || dont_save) {
//save new current value
if (!dont_save) {
m_last_fan_speed = speed;
- m_last_fan_speed_with_offset = fan_speed;
+ m_last_fan_speed_with_offset = uint8_t(fan_speed);
}
// write it
@@ -271,7 +299,7 @@ std::string GCodeWriter::set_fan(const unsigned int speed, bool dont_save, uint1
return gcode.str();
}
-void GCodeWriter::set_acceleration(unsigned int acceleration)
+void GCodeWriter::set_acceleration(uint32_t acceleration)
{
// Clamp the acceleration to the allowed maximum.
if (m_max_acceleration > 0 && acceleration > m_max_acceleration)
@@ -283,6 +311,11 @@ void GCodeWriter::set_acceleration(unsigned int acceleration)
m_current_acceleration = acceleration;
}
+uint32_t GCodeWriter::get_acceleration() const
+{
+ return m_current_acceleration;
+}
+
std::string GCodeWriter::write_acceleration(){
if (m_current_acceleration == m_last_acceleration || m_current_acceleration == 0)
return "";
@@ -294,9 +327,12 @@ std::string GCodeWriter::write_acceleration(){
if (FLAVOR_IS(gcfRepetier)) {
// M201: Set max printing acceleration
gcode << "M201 X" << m_current_acceleration << " Y" << m_current_acceleration;
- } else if(FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfLerdge) || FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfSprinter)){
+ } else if(FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfLerdge) || FLAVOR_IS(gcfSprinter)){
// M204: Set printing acceleration
gcode << "M204 P" << m_current_acceleration;
+ } else if (FLAVOR_IS(gcfRepRap)) {
+ // M204: Set printing & travel acceleration
+ gcode << "M204 P" << m_current_acceleration <<" T" << m_current_acceleration;
} else {
// M204: Set default acceleration
gcode << "M204 S" << m_current_acceleration;
@@ -331,16 +367,16 @@ std::string GCodeWriter::reset_e(bool force)
}
}
-std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
+std::string GCodeWriter::update_progress(uint32_t num, uint32_t tot, bool allow_100) const
{
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
return "";
- unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5);
- if (!allow_100) percent = std::min(percent, (unsigned int)99);
+ uint8_t percent = (uint32_t)floor(100.0 * num / tot + 0.5);
+ if (!allow_100) percent = std::min(percent, (uint8_t)99);
std::ostringstream gcode;
- gcode << "M73 P" << percent;
+ gcode << "M73 P" << int(percent);
if (this->config.gcode_comments) gcode << " ; update progress";
gcode << "\n";
return gcode.str();
@@ -354,7 +390,7 @@ std::string GCodeWriter::toolchange_prefix() const
"T";
}
-std::string GCodeWriter::toolchange(unsigned int tool_id)
+std::string GCodeWriter::toolchange(uint16_t tool_id)
{
// set the new extruder
/*auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [tool_id](const Extruder &e) { return e.id() < tool_id; });
diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp
index 0d8c7b42c..084b8e4e1 100644
--- a/src/libslic3r/GCodeWriter.hpp
+++ b/src/libslic3r/GCodeWriter.hpp
@@ -44,24 +44,25 @@ public:
const Tool* get_tool(uint16_t id) const;
std::string preamble();
std::string postamble() const;
- std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1);
- std::string set_bed_temperature(unsigned int temperature, bool wait = false);
- unsigned int get_fan() { return m_last_fan_speed; }
+ std::string set_temperature(int16_t temperature, bool wait = false, int tool = -1);
+ std::string set_bed_temperature(uint32_t temperature, bool wait = false);
+ uint8_t get_fan() { return m_last_fan_speed; }
/// set fan at speed. Save it as current fan speed if !dont_save, and use tool default_tool if the internal m_tool is null (no toolchange done yet).
- std::string set_fan(unsigned int speed, bool dont_save = false, uint16_t default_tool = 0);
- void set_acceleration(unsigned int acceleration);
+ std::string set_fan(uint8_t speed, bool dont_save = false, uint16_t default_tool = 0);
+ void set_acceleration(uint32_t acceleration);
+ uint32_t get_acceleration() const;
std::string write_acceleration();
std::string reset_e(bool force = false);
- std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const;
+ std::string update_progress(uint32_t num, uint32_t tot, bool allow_100 = false) const;
// return false if this extruder was already selected
- bool need_toolchange(unsigned int tool_id) const
+ bool need_toolchange(uint16_t tool_id) const
{ return m_tool == nullptr || m_tool->id() != tool_id; }
- std::string set_tool(unsigned int tool_id)
+ std::string set_tool(uint16_t tool_id)
{ return this->need_toolchange(tool_id) ? this->toolchange(tool_id) : ""; }
// Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
// printed with the same extruder.
std::string toolchange_prefix() const;
- std::string toolchange(unsigned int tool_id);
+ std::string toolchange(uint16_t tool_id);
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
@@ -84,16 +85,16 @@ private:
std::string m_extrusion_axis;
bool m_single_extruder_multi_material;
Tool* m_tool;
- unsigned int m_last_acceleration;
- unsigned int m_current_acceleration;
+ uint32_t m_last_acceleration;
+ uint32_t m_current_acceleration;
// Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware.
// If set to zero, the limit is not in action.
- unsigned int m_max_acceleration;
- unsigned int m_last_fan_speed;
- unsigned int m_last_fan_speed_with_offset;
- unsigned int m_last_temperature;
- unsigned int m_last_temperature_with_offset;
- unsigned int m_last_bed_temperature;
+ uint32_t m_max_acceleration;
+ uint8_t m_last_fan_speed;
+ uint8_t m_last_fan_speed_with_offset;
+ int16_t m_last_temperature;
+ int16_t m_last_temperature_with_offset;
+ int16_t m_last_bed_temperature;
bool m_last_bed_temperature_reached;
double m_lifted;
Vec3d m_pos = Vec3d::Zero();
diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp
index 49d75e5e0..ed0c70071 100644
--- a/src/libslic3r/Layer.hpp
+++ b/src/libslic3r/Layer.hpp
@@ -49,7 +49,7 @@ public:
// collection of expolygons representing the bridged areas (thus not
// needing support material)
- Polygons bridged;
+// Polygons bridged;
// collection of polylines representing the unsupported bridge edges
Polylines unsupported_bridge_edges;
diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp
index 214bd6e87..3a5bcfd5b 100644
--- a/src/libslic3r/LayerRegion.cpp
+++ b/src/libslic3r/LayerRegion.cpp
@@ -327,7 +327,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
}else if (bd.detect_angle(custom_angle)) {
bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->config().support_material) {
- polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial)));
+ //polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial)));
append(this->unsupported_bridge_edges, bd.unsupported_edges());
}
} else {
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 7246dde30..11098c972 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -121,13 +121,17 @@ bool Model::equals(const Model& rhs) const {
return true;
}
-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"))
@@ -135,9 +139,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
@@ -152,24 +157,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.");
@@ -191,7 +201,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);
@@ -424,13 +434,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)
@@ -457,7 +466,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()) {
@@ -1766,7 +1775,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));
delete mesh;
++ idx;
}
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index b1b86f4a6..2190080c8 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -12,6 +12,7 @@
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
+#include "enum_bitmask.hpp"
#include <map>
#include <memory>
@@ -996,8 +997,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);
bool equals(const Model& rhs) const;
@@ -1062,6 +1075,8 @@ private:
}
};
+ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
+
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp
index 05191eba2..8ae55c61c 100644
--- a/src/libslic3r/Optimize/Optimizer.hpp
+++ b/src/libslic3r/Optimize/Optimizer.hpp
@@ -8,6 +8,7 @@
#include <functional>
#include <limits>
#include <cassert>
+#include <optional>
namespace Slic3r { namespace opt {
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index 33e675082..ece14427c 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -99,16 +99,54 @@ void PerimeterGenerator::process()
coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) );
// prepare grown lower layer slices for overhang detection
- if (this->lower_slices != NULL && this->config->overhangs_width.value > 0) {
+ if (this->lower_slices != NULL && (this->config->overhangs_width.value > 0 || this->config->overhangs_width_speed.value > 0)) {
// We consider overhang any part where the entire nozzle diameter is not supported by the
// lower layer, so we take lower slices and offset them by overhangs_width of the nozzle diameter used
// in the current layer
- double offset_val = double(scale_(config->overhangs_width.get_abs_value(nozzle_diameter))) - (float)(ext_perimeter_width / 2);
- this->_lower_slices_bridge_flow = offset(*this->lower_slices, offset_val);
- }
- if (this->lower_slices != NULL && this->config->overhangs_width_speed.value > 0) {
- double offset_val = double(scale_(config->overhangs_width_speed.get_abs_value(nozzle_diameter))) - (float)(ext_perimeter_width / 2);
- this->_lower_slices_bridge_speed = offset(*this->lower_slices, offset_val);
+
+ //we use a range to avoid threshold issues.
+ coord_t overhangs_width_flow = scale_(config->overhangs_width.get_abs_value(nozzle_diameter));
+ coord_t overhangs_width_speed = scale_(config->overhangs_width_speed.get_abs_value(nozzle_diameter));
+ coord_t min_feature = std::min(overhangs_width_flow, overhangs_width_speed) / 2;
+ coord_t overhangs_width_flow_90 = coord_t(overhangs_width_flow * 0.99);
+ coord_t overhangs_width_flow_110 = coord_t(overhangs_width_flow * 1.15);
+ coord_t overhangs_width_speed_90 = coord_t(overhangs_width_speed * 0.99);
+ coord_t overhangs_width_speed_110 = coord_t(overhangs_width_speed * 1.15);
+
+ //flow offset should be greater than speed offset because the flow apply also the speed.
+ //check if overhangs_width_speed is low enough to be relevant (if flow is activated)
+ if (overhangs_width_flow > 0 && overhangs_width_speed + nozzle_diameter * 0.01 > overhangs_width_flow) {
+ overhangs_width_speed = 0;
+ overhangs_width_speed_90 = 0;
+ overhangs_width_speed_110 = 0;
+ }
+ if (overhangs_width_flow > 0) {
+ if (overhangs_width_flow_90 < overhangs_width_speed_110) {
+ overhangs_width_speed_110 = overhangs_width_flow_90 = (overhangs_width_flow + overhangs_width_speed) / 2;
+ }
+ }
+
+ if (overhangs_width_speed > 0 || overhangs_width_flow > 0) {
+ ExPolygons simplified;
+ //simplify the lower slices if too high (means low number) resolution (we can be very aggressive here)
+ if (this->print_config->resolution < min_feature / 2) {
+ for (const ExPolygon& expoly : *lower_slices) {
+ expoly.simplify(min_feature, &simplified);
+ }
+ }
+
+ if (overhangs_width_speed > 0) {
+ this->_lower_slices_bridge_speed_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_90 - (coordf_t)(ext_perimeter_width / 2));
+ this->_lower_slices_bridge_speed_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_110 - (coordf_t)(ext_perimeter_width / 2));
+ }
+ if (overhangs_width_flow > 0) {
+ if (overhangs_width_speed_110 == overhangs_width_flow_90)
+ this->_lower_slices_bridge_flow_small = this->_lower_slices_bridge_speed_big;
+ else
+ this->_lower_slices_bridge_flow_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_90 - (coordf_t)(ext_perimeter_width / 2));
+ this->_lower_slices_bridge_flow_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_110 - (coordf_t)(ext_perimeter_width / 2));
+ }
+ }
}
// have to grown the perimeters if mill post-process
@@ -337,6 +375,7 @@ void PerimeterGenerator::process()
// simplification already done at slicing
//ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
ExPolygons last = union_ex(surface.expolygon);
+ double last_area = -1;
if (loop_number >= 0) {
@@ -525,6 +564,8 @@ void PerimeterGenerator::process()
(float)(ext_min_spacing / 4),
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
(round_peri ? min_round_spacing : 3)));
+
+ next_onion = intersection_ex(next_onion, last);
}
}
if (m_spiral_vase && next_onion.size() > 1) {
@@ -547,11 +588,23 @@ void PerimeterGenerator::process()
+(float)(min_spacing / 2 - 1),
(round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter),
(round_peri ? min_round_spacing : 3));
-
+ // now try with different min spacing if we fear some hysteresis
+ //TODO, do that for each polygon from last, instead to do for all of them in one go.
ExPolygons no_thin_onion = offset_ex(last, double(-good_spacing));
+ if (last_area < 0) {
+ last_area = 0;
+ for (const ExPolygon& expoly : last) {
+ last_area += expoly.area();
+ }
+ }
+ double new_area = 0;
+ for (const ExPolygon& expoly : next_onion) {
+ new_area += expoly.area();
+ }
+
std::vector<float> divs { 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2
size_t idx_div = 0;
- while (next_onion.size() > no_thin_onion.size() && idx_div < divs.size()) {
+ while ((next_onion.size() > no_thin_onion.size() || (new_area != 0 && last_area > new_area * 100)) && idx_div < divs.size()) {
float div = divs[idx_div];
//use a sightly bigger spacing to try to drastically improve the split, that can lead to very thick gapfill
ExPolygons next_onion_secondTry = offset2_ex(
@@ -559,10 +612,21 @@ void PerimeterGenerator::process()
-(float)(good_spacing + (min_spacing / div) - 1),
+(float)((min_spacing / div) - 1));
if (next_onion.size() > next_onion_secondTry.size() * 1.2 && next_onion.size() > next_onion_secondTry.size() + 2) {
+ // don't get it if it creates too many
next_onion = next_onion_secondTry;
+ } else if (next_onion.size() > next_onion_secondTry.size() || last_area > new_area * 100) {
+ // don't get it if it's too small
+ double area_new = 0;
+ for (const ExPolygon& expoly : next_onion_secondTry) {
+ area_new += expoly.area();
+ }
+ if (last_area > area_new * 100 || new_area == 0) {
+ next_onion = next_onion_secondTry;
+ }
}
idx_div++;
}
+ last_area = new_area;
} else {
// If "overlapping_perimeters" is enabled, this paths will be entered, which
@@ -1073,92 +1137,238 @@ void PerimeterGenerator::process()
} // for each island
}
-template<typename LINE>
-ExtrusionPaths PerimeterGenerator::create_overhangs(LINE loop_polygons, ExtrusionRole role, bool is_external) const {
+
+ExtrusionPaths PerimeterGenerator::create_overhangs(const Polyline& loop_polygons, ExtrusionRole role, bool is_external) const {
ExtrusionPaths paths;
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1);
if (this->config->overhangs_width.get_abs_value(nozzle_diameter) == 0 && 0 == this->config->overhangs_width_speed.get_abs_value(nozzle_diameter)) {
//error
+ ExtrusionPath path(role);
+ path.polyline = loop_polygons;
+ path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
+ path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
+ path.height = (float)this->layer->height;
+ assert(path.mm3_per_mm == path.mm3_per_mm);
+ assert(path.width == path.width);
+ assert(path.height == path.height);
+ paths.push_back(path);
return paths;
- } else if (this->config->overhangs_width.get_abs_value(nozzle_diameter) > this->config->overhangs_width_speed.get_abs_value(nozzle_diameter)) {
- //set the fan & speed before the flow
+ }
+ //set the fan & speed before the flow
+ Polylines ok_polylines = { loop_polygons };
+
+ Polylines small_speed;
+ Polylines big_speed;
+ bool no_small_flow = _lower_slices_bridge_speed_big == _lower_slices_bridge_flow_small;
+ Polylines small_flow;
+ Polylines big_flow;
+
+ Polylines* previous = &ok_polylines;
+ if (this->config->overhangs_width_speed.value > 0) {
+ if (!this->_lower_slices_bridge_speed_small.empty()) {
+ small_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_small);
+ if (!small_speed.empty()) {
+ *previous = intersection_pl(*previous, this->_lower_slices_bridge_speed_small);
+ previous = &small_speed;
+ }
+ }
+ if (!this->_lower_slices_bridge_speed_big.empty()) {
+ big_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_big);
+ if (!big_speed.empty()) {
+ *previous = intersection_pl(*previous, this->_lower_slices_bridge_speed_big);
+ previous = &big_speed;
+ }
+ }
+ }
+ if (this->config->overhangs_width.value > 0) {
+ if (!this->_lower_slices_bridge_flow_small.empty()) {
+ small_flow = diff_pl(*previous, this->_lower_slices_bridge_flow_small);
+ if (!small_flow.empty()) {
+ *previous = intersection_pl(*previous, this->_lower_slices_bridge_flow_small);
+ previous = &small_flow;
+ }
+ }
+ if (!this->_lower_slices_bridge_flow_big.empty()) {
+ big_flow = diff_pl(*previous, this->_lower_slices_bridge_flow_big);
+ if (!big_flow.empty()) {
+ *previous = intersection_pl(*previous, this->_lower_slices_bridge_flow_big);
+ previous = &big_flow;
+ }
+ }
+ }
- // get non-overhang paths by intersecting this loop with the grown lower slices
+ //note: layer height is used to identify the path type
+ if (!ok_polylines.empty()) {
extrusion_paths_append(
paths,
- (this->config->overhangs_width_speed.value > 0 ?
- intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed) :
- intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow)),
+ ok_polylines,
role,
is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
- (float)this->layer->height);
-
- // get overhang paths by checking what parts of this loop fall
- // outside the grown lower slices
- Polylines poly_speed =
- (this->config->overhangs_width_speed.value > 0 ?
- diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed) :
- Polylines{});
- if(this->config->overhangs_width_speed.value > 0)
- extrusion_paths_append(
- paths,
- intersection_pl(poly_speed, this->_lower_slices_bridge_flow),
- erOverhangPerimeter,
- is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
- is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
- (float)this->layer->height);
-
+ 0);
+ }
+ if (!small_speed.empty()) {
extrusion_paths_append(
paths,
- (this->config->overhangs_width_speed.value > 0 ?
- diff_pl(poly_speed, this->_lower_slices_bridge_flow):
- diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow)),
+ small_speed,
erOverhangPerimeter,
- this->_mm3_per_mm_overhang,
- this->overhang_flow.width,
- this->overhang_flow.height);
-
- } else {
- //can't set flow without fan & speed
-
- // get non-overhang paths by intersecting this loop with the grown lower slices
+ is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
+ is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
+ no_small_flow ? 2 : 1);
+ }
+ if (!big_speed.empty()) {
extrusion_paths_append(
paths,
- (this->config->overhangs_width.value > 0 ?
- intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow) :
- intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed)),
- role,
+ big_speed,
+ erOverhangPerimeter,
is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
- (float)this->layer->height);
-
- // get overhang paths by checking what parts of this loop fall
- // outside the grown lower slices
- if (this->config->overhangs_width.value > 0) {
- extrusion_paths_append(
- paths,
- diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow),
- erOverhangPerimeter,
- this->_mm3_per_mm_overhang,
- this->overhang_flow.width,
- this->overhang_flow.height);
- } else {
- extrusion_paths_append(
- paths,
- diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed),
- erOverhangPerimeter,
- is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
- is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
- (float)this->layer->height);
- }
+ no_small_flow ? 3 : 2);
+ }
+ if (!small_flow.empty()) {
+ extrusion_paths_append(
+ paths,
+ small_flow,
+ erOverhangPerimeter,
+ this->_mm3_per_mm_overhang,
+ this->overhang_flow.width,
+ 3);
+ }
+ if (!big_flow.empty()) {
+ extrusion_paths_append(
+ paths,
+ big_flow,
+ erOverhangPerimeter,
+ this->_mm3_per_mm_overhang,
+ this->overhang_flow.width,
+ 4);
}
// reapply the nearest point search for starting point
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
if(!paths.empty())
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
+
+ bool has_normal = !ok_polylines.empty();
+ bool has_speed = !small_speed.empty() || !big_speed.empty();
+ bool has_flow = !small_flow.empty() || !big_flow.empty();
+
+ std::function<void(ExtrusionPaths&, const std::function<bool(ExtrusionPath&, ExtrusionPath&, ExtrusionPath&)>&)> foreach = [](ExtrusionPaths &paths, const std::function<bool(ExtrusionPath&, ExtrusionPath&, ExtrusionPath&)>& doforeach) {
+ if (paths.size() > 2)
+ for (int i = 1; i < paths.size() - 1; i++) {
+ if (doforeach(paths[i - 1], paths[i], paths[i + 1])) {
+ paths.erase(paths.begin() + i);
+ i--;
+ if (paths[i].height == paths[i + 1].height) {
+ paths[i].polyline.points.insert(paths[i].polyline.points.end(), paths[i + 1].polyline.points.begin() + 1, paths[i + 1].polyline.points.end());
+ paths.erase(paths.begin() + i + 1);
+ }
+ }
+ }
+ if (paths.size() > 2)
+ if (doforeach(paths[paths.size() - 2], paths.back(), paths.front())) {
+ paths.erase(paths.end() - 1);
+ if (paths.back().height == paths.front().height) {
+ paths.front().polyline.points.insert(paths.front().polyline.points.begin(), paths.back().polyline.points.begin(), paths.back().polyline.points.end() - 1);
+ paths.erase(paths.end() - 1);
+ }
+ }
+ if (paths.size() > 2)
+ if (doforeach(paths.back(), paths.front(), paths[1])) {
+ paths.erase(paths.begin());
+ if (paths.back().height == paths.front().height) {
+ paths.front().polyline.points.insert(paths.front().polyline.points.begin(), paths.back().polyline.points.begin(), paths.back().polyline.points.end() - 1);
+ paths.erase(paths.end() - 1);
+ }
+ }
+ };
+
+ if (paths.size() > 2) {
+ double min_length = this->perimeter_flow.scaled_width() * 2;
+ double ok_length = this->perimeter_flow.scaled_width() * 20;
+
+ foreach(paths, [min_length, ok_length](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) {
+ if (curr.length() < min_length) {
+ float diff_height = std::abs(prev.height - curr.height) - std::abs(next.height - curr.height);
+ //have to choose the rigth path
+ if (diff_height < 0 || (diff_height == 0 && prev.length() > next.length())) {
+ //merge to previous
+ assert(prev.last_point() == curr.first_point());
+ assert(curr.polyline.points.size() > 1);
+ prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
+ } else {
+ //merge to next
+ assert(curr.last_point() == next.first_point());
+ assert(curr.polyline.points.size() > 1);
+ next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
+ }
+ return true;
+ } else if(((int)curr.height) % 2 == 1 && curr.length() > ok_length){
+ curr.height++;
+ if (prev.height == curr.height) {
+ prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
+ return true;
+ } else if (next.height == curr.height) {
+ next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
+ return true;
+ }
+ }
+ return false;
+ });
+
+ foreach(paths, [](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) {
+ if (curr.height == 3) {
+ //have to choose the rigth path
+ if (prev.height == 4 || (prev.height == 2 && next.height < 2)) {
+ //merge to previous
+ assert(prev.last_point() == curr.first_point());
+ assert(curr.polyline.points.size() > 1);
+ prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
+ } else {
+ //merge to next
+ assert(curr.last_point() == next.first_point());
+ assert(curr.polyline.points.size() > 1);
+ next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
+ }
+ return true;
+ }
+ return false;
+ });
+ foreach(paths, [](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) {
+ if (curr.height == 1) {
+ //have to choose the rigth path
+ if (prev.height == 2 || (prev.height == 0 && next.height > 2)) {
+ //merge to previous
+ assert(prev.last_point() == curr.first_point());
+ assert(curr.polyline.points.size() > 1);
+ prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end());
+ } else {
+ //merge to next
+ assert(curr.last_point() == next.first_point());
+ assert(curr.polyline.points.size() > 1);
+ next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1);
+ }
+ return true;
+ }
+ return false;
+ });
+ }
+ if(paths.size() == 2){
+ double min_length = this->perimeter_flow.scaled_width() * 2;
+ if (paths.front().length() < min_length) {
+ paths.back().polyline.points.insert(paths.back().polyline.points.begin(), paths.front().polyline.points.begin(), paths.front().polyline.points.end() - 1);
+ paths.erase(paths.begin());
+ }else if (paths.back().length() < min_length) {
+ paths.front().polyline.points.insert(paths.front().polyline.points.end(), paths.back().polyline.points.begin() + 1, paths.back().polyline.points.end());
+ paths.erase(paths.begin() + 1);
+ }
+ }
+ //set correct height
+ for (ExtrusionPath& path : paths) {
+ path.height = path.height < 3 ? (float)this->layer->height : this->overhang_flow.height;
+ }
+
return paths;
}
@@ -1196,7 +1406,7 @@ ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
ExtrusionPaths paths;
if ( this->config->overhangs_width_speed.value > 0 && this->layer->id() > 0
&& !(this->object_config->support_material && this->object_config->support_material_contact_distance_type.value == zdNone)) {
- paths = this->create_overhangs(loop.polygon, role, is_external);
+ paths = this->create_overhangs(loop.polygon.split_at_first_point(), role, is_external);
} else {
ExtrusionPath path(role);
path.polyline = loop.polygon.split_at_first_point();
diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp
index 7d6916df3..8a8f976f9 100644
--- a/src/libslic3r/PerimeterGenerator.hpp
+++ b/src/libslic3r/PerimeterGenerator.hpp
@@ -99,11 +99,12 @@ private:
double _ext_mm3_per_mm;
double _mm3_per_mm;
double _mm3_per_mm_overhang;
- Polygons _lower_slices_bridge_flow;
- Polygons _lower_slices_bridge_speed;
+ Polygons _lower_slices_bridge_flow_small;
+ Polygons _lower_slices_bridge_flow_big;
+ Polygons _lower_slices_bridge_speed_small;
+ Polygons _lower_slices_bridge_speed_big;
- template<typename LINE>
- ExtrusionPaths create_overhangs(LINE loop_polygons, ExtrusionRole role, bool is_external) const;
+ ExtrusionPaths create_overhangs(const Polyline& loop_polygons, ExtrusionRole role, bool is_external) const;
// transform loops into ExtrusionEntityCollection, adding also thin walls into it.
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const;
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index 00d48462f..be21b8b89 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -420,8 +420,8 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
if (model.empty() || variant.empty())
return;
is_visible = app_config.get_variant(vendor->id, model, variant);
- } else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) {
- const std::string &section_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
+ } else if (type == TYPE_FFF_FILAMENT || type == TYPE_SLA_MATERIAL) {
+ const std::string &section_name = (type == TYPE_FFF_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS;
if (app_config.has_section(section_name)) {
// Check whether this profile is marked as "installed" in PrusaSlicer.ini,
// or whether a profile is marked as "installed", which this profile may have been renamed from.
@@ -446,6 +446,8 @@ const std::vector<std::string>& Preset::print_options()
"top_solid_min_thickness",
"bottom_solid_layers",
"bottom_solid_min_thickness",
+ "solid_over_perimeters",
+ "duplicate_distance",
"extra_perimeters",
"extra_perimeters_odd_layers",
"extra_perimeters_overhangs",
@@ -456,26 +458,28 @@ const std::vector<std::string>& Preset::print_options()
"avoid_crossing_perimeters",
"avoid_crossing_not_first_layer",
"thin_perimeters", "thin_perimeters_all",
- "thin_walls",
"overhangs_speed",
"overhangs_width",
"overhangs_width_speed",
"overhangs_reverse",
"overhangs_reverse_threshold",
- "seam_position",
+ "seam_position",
+ // external_perimeters
"external_perimeters_first",
"external_perimeters_vase",
"external_perimeters_nothole",
"external_perimeters_hole",
- "fill_density"
- , "fill_pattern"
- , "fill_top_flow_ratio"
- , "fill_smooth_width"
- , "fill_smooth_distribution"
- , "top_fill_pattern"
- , "bottom_fill_pattern"
- , "solid_fill_pattern",
+ // fill pattern
+ "fill_density",
+ "fill_pattern",
+ "fill_top_flow_ratio",
+ "fill_smooth_width",
+ "fill_smooth_distribution",
+ "top_fill_pattern",
+ "bottom_fill_pattern",
+ "solid_fill_pattern",
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers",
+ // ironing
"ironing",
"ironing_type",
"ironing_flowrate",
@@ -493,25 +497,40 @@ const std::vector<std::string>& Preset::print_options()
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
#endif /* HAS_PRESSURE_EQUALIZER */
"min_width_top_surface",
+ "bridge_speed",
+ "bridge_speed_internal",
+ // speeds
+ "external_perimeter_speed",
+ "first_layer_speed",
+ "infill_speed",
"perimeter_speed",
"small_perimeter_speed",
- "small_perimeter_min_length",
"small_perimeter_max_length",
- "external_perimeter_speed", "infill_speed", "solid_infill_speed",
- "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
- "bridge_speed",
- "bridge_speed_internal",
+ "small_perimeter_min_length",
+ "solid_infill_speed",
+ "support_material_interface_speed",
+ "support_material_speed",
+ "support_material_xy_spacing",
+ "top_solid_infill_speed",
+ "travel_speed", "travel_speed_z",
+ // gapfill
"gap_fill",
"gap_fill_min_area",
"gap_fill_overlap",
"gap_fill_speed",
- "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration",
- "bridge_acceleration", "first_layer_acceleration", "default_acceleration",
- "duplicate_distance",
+ // acceleration
+ "bridge_acceleration",
+ "default_acceleration",
+ "first_layer_acceleration",
+ "infill_acceleration",
+ "perimeter_acceleration",
+ "travel_acceleration",
+ // skirt
"skirts", "skirt_distance", "skirt_height",
"skirt_extrusion_width",
"min_skirt_length",
"draft_shield",
+ // brim
"brim_inside_holes",
"brim_width",
"brim_width_interior",
@@ -520,39 +539,43 @@ const std::vector<std::string>& Preset::print_options()
"brim_ears_max_angle",
"brim_ears_pattern",
"brim_offset",
- "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
- "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
+ // support
+ "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
+ "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_interface_pattern",
- "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
- "support_material_interface_spacing", "support_material_interface_contact_loops"
- , "support_material_contact_distance_type"
- , "support_material_contact_distance_top"
- , "support_material_contact_distance_bottom"
- , "support_material_buildplate_only", "dont_support_bridges", "notes",
+ "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
+ "support_material_interface_spacing", "support_material_interface_contact_loops",
+ "support_material_contact_distance_type",
+ "support_material_contact_distance_top",
+ "support_material_contact_distance_bottom",
+ "support_material_buildplate_only", "dont_support_bridges", "notes",
"complete_objects",
"complete_objects_one_skirt",
+ "complete_objects_one_brim",
"complete_objects_sort",
"extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells",
+ // width & spacing
"extrusion_spacing",
"extrusion_width",
"first_layer_extrusion_spacing",
"first_layer_extrusion_width",
"perimeter_round_corners",
- "perimeter_extrusion_spacing",
- "perimeter_extrusion_width",
- "external_perimeter_extrusion_spacing",
- "external_perimeter_extrusion_width",
- "infill_extrusion_spacing",
- "infill_extrusion_width",
- "solid_infill_extrusion_spacing",
- "solid_infill_extrusion_width",
- "top_infill_extrusion_spacing",
- "top_infill_extrusion_width",
- "support_material_extrusion_width",
- "infill_overlap", "bridge_flow_ratio",
+ "perimeter_extrusion_spacing",
+ "perimeter_extrusion_width",
+ "external_perimeter_extrusion_spacing",
+ "external_perimeter_extrusion_width",
+ "infill_extrusion_spacing",
+ "infill_extrusion_width",
+ "solid_infill_extrusion_spacing",
+ "solid_infill_extrusion_width",
+ "top_infill_extrusion_spacing",
+ "top_infill_extrusion_width",
+ "support_material_extrusion_width",
+ // overlap, ratios
+ "infill_overlap", "bridge_flow_ratio",
"infill_anchor",
"infill_anchor_max",
"clip_multipart_objects",
@@ -560,14 +583,17 @@ const std::vector<std::string>& Preset::print_options()
"bridge_overlap",
"first_layer_flow_ratio",
"clip_multipart_objects", "enforce_full_fill_volume", "external_infill_margin", "bridged_infill_margin",
+ // compensation
"first_layer_size_compensation",
+ "first_layer_size_compensation_layers",
"xy_size_compensation",
"xy_inner_size_compensation",
"hole_size_compensation",
"hole_size_threshold",
"hole_to_polyhole",
"hole_to_polyhole_threshold",
- "threads", "resolution",
+ "threads",
+ // wipe tower
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging",
"wipe_tower_brim",
"single_extruder_multi_material_priming",
@@ -583,14 +609,19 @@ const std::vector<std::string>& Preset::print_options()
"seam_travel_cost",
"infill_connection", "infill_connection_solid", "infill_connection_top", "infill_connection_bottom",
"first_layer_infill_speed",
+ // thin wall
+ "thin_walls",
"thin_walls_min_width",
"thin_walls_overlap",
- "thin_walls_speed"
- , "model_precision"
- , "curve_smoothing_precision"
- , "curve_smoothing_cutoff_dist"
- , "curve_smoothing_angle_convex"
- , "curve_smoothing_angle_concave",
+ "thin_walls_speed",
+ "thin_walls_merge",
+ //precision, spoothign
+ "model_precision",
+ "resolution",
+ "curve_smoothing_precision",
+ "curve_smoothing_cutoff_dist",
+ "curve_smoothing_angle_convex",
+ "curve_smoothing_angle_concave",
"print_extrusion_multiplier",
"print_retract_length",
"print_temperature",
@@ -599,11 +630,11 @@ const std::vector<std::string>& Preset::print_options()
"external_perimeter_overlap",
"perimeter_bonding",
"perimeter_overlap",
+ //milling
"milling_after_z",
"milling_post_process",
"milling_extra_size",
"milling_speed",
- "thin_walls_merge",
};
return s_opts;
}
@@ -889,7 +920,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
@@ -916,7 +949,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;
@@ -1551,8 +1586,8 @@ void PresetCollection::update_map_system_profile_renamed()
std::string PresetCollection::name() const
{
switch (this->type()) {
- case Preset::TYPE_PRINT: return L("print");
- case Preset::TYPE_FILAMENT: return L("filament");
+ case Preset::TYPE_FFF_PRINT: return L("print");
+ case Preset::TYPE_FFF_FILAMENT: return L("filament");
case Preset::TYPE_SLA_PRINT: return L("SLA print");
case Preset::TYPE_SLA_MATERIAL: return L("SLA material");
case Preset::TYPE_PRINTER: return L("printer");
@@ -1563,8 +1598,8 @@ std::string PresetCollection::name() const
std::string PresetCollection::section_name() const
{
switch (this->type()) {
- case Preset::TYPE_PRINT: return "print";
- case Preset::TYPE_FILAMENT: return "filament";
+ case Preset::TYPE_FFF_PRINT: return "print";
+ case Preset::TYPE_FFF_FILAMENT: return "filament";
case Preset::TYPE_SLA_PRINT: return "sla_print";
case Preset::TYPE_SLA_MATERIAL: return "sla_material";
case Preset::TYPE_PRINTER: return "printer";
@@ -1800,7 +1835,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
@@ -1826,7 +1863,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::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) });
printer.update_from_config(config);
printer.loaded = true;
}
diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp
index 415831a5a..28b64bbbd 100644
--- a/src/libslic3r/Preset.hpp
+++ b/src/libslic3r/Preset.hpp
@@ -110,15 +110,24 @@ typedef std::map<std::string, VendorProfile> VendorMap;
class Preset
{
public:
- enum Type
+ enum Type : uint8_t
{
- TYPE_INVALID,
- TYPE_PRINT,
- TYPE_SLA_PRINT,
- TYPE_FILAMENT,
- TYPE_SLA_MATERIAL,
- TYPE_PRINTER,
- TYPE_COUNT,
+ TYPE_INVALID = 0,
+ TYPE_PRINT1 = 1 << 0,
+ TYPE_MATERIAL = 1 << 1,
+ TYPE_PRINTER = 1 << 2,
+ TYPE_TAB = TYPE_PRINT1 | TYPE_MATERIAL | TYPE_PRINTER,
+ TYPE_FFF = 1 << 3,
+ TYPE_FFF_PRINT = TYPE_FFF | TYPE_PRINT1,
+ TYPE_FFF_FILAMENT = TYPE_FFF | TYPE_MATERIAL,
+ TYPE_SLA = 1 << 4,
+ TYPE_SLA_PRINT = TYPE_SLA | TYPE_PRINT1,
+ TYPE_SLA_MATERIAL = TYPE_SLA | TYPE_MATERIAL,
+ TYPE_TECHNOLOGY = TYPE_FFF | TYPE_SLA,
+
+ // 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 = 1 << 5,
};
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
@@ -184,7 +193,7 @@ public:
// Returns the "compatible_prints_condition".
static std::string& compatible_prints_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_prints_condition", true)->value; }
std::string& compatible_prints_condition() {
- assert(this->type == TYPE_FILAMENT || this->type == TYPE_SLA_MATERIAL);
+ assert(this->type == TYPE_FFF_FILAMENT || this->type == TYPE_SLA_MATERIAL);
return Preset::compatible_prints_condition(this->config);
}
const std::string& compatible_prints_condition() const { return const_cast<Preset*>(this)->compatible_prints_condition(); }
@@ -192,7 +201,7 @@ public:
// Returns the "compatible_printers_condition".
static std::string& compatible_printers_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_printers_condition", true)->value; }
std::string& compatible_printers_condition() {
- assert(this->type == TYPE_PRINT || this->type == TYPE_SLA_PRINT || this->type == TYPE_FILAMENT || this->type == TYPE_SLA_MATERIAL);
+ assert(this->type == TYPE_FFF_PRINT || this->type == TYPE_SLA_PRINT || this->type == TYPE_FFF_FILAMENT || this->type == TYPE_SLA_MATERIAL);
return Preset::compatible_printers_condition(this->config);
}
const std::string& compatible_printers_condition() const { return const_cast<Preset*>(this)->compatible_printers_condition(); }
@@ -251,6 +260,19 @@ bool is_compatible_with_print (const PresetWithVendorProfile &preset, const Pre
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
+inline Preset::Type operator|(Preset::Type a, Preset::Type b) {
+ return static_cast<Preset::Type>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
+}
+inline Preset::Type operator&(Preset::Type a, Preset::Type b) {
+ return static_cast<Preset::Type>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b));
+}
+inline Preset::Type operator|=(Preset::Type& a, Preset::Type b) {
+ a = a | b; return a;
+}
+inline Preset::Type operator&=(Preset::Type& a, Preset::Type b) {
+ a = a & b; return a;
+}
+
enum class PresetSelectCompatibleType {
// Never select a compatible preset if the newly selected profile is not compatible.
Never,
@@ -260,6 +282,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
{
@@ -290,7 +333,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.
@@ -663,7 +706,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);
diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp
index f709ae926..82f44251a 100644
--- a/src/libslic3r/PresetBundle.cpp
+++ b/src/libslic3r/PresetBundle.cpp
@@ -38,8 +38,8 @@ static std::vector<std::string> s_project_options {
const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch";
PresetBundle::PresetBundle() :
- prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
- filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
+ prints(Preset::TYPE_FFF_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
+ filaments(Preset::TYPE_FFF_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())),
sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())),
sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())),
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
@@ -162,10 +162,12 @@ 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();
+ PresetsConfigSubstitutions substitutions;
+ std::string errors_cummulative;
+ std::tie(substitutions, errors_cummulative) = this->load_system_presets(substitution_rule);
const std::string dir_user_presets = data_dir()
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
@@ -175,33 +177,34 @@ 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
;
+
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();
}
@@ -211,16 +214,26 @@ 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.
// For each vendor, there will be a single PresetBundle loaded.
-std::string PresetBundle::load_system_presets()
+std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule)
{
+ if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
+ // Loading system presets, don't log substitutions.
+ compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
+ else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
+ // Loading system presets, throw on unknown option value.
+ compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
+
// Here the vendor specific read only Config Bundles are stored.
- boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
- std::string errors_cummulative;
- bool first = true;
+ boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
+ PresetsConfigSubstitutions substitutions;
+ std::string errors_cummulative;
+ bool first = true;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
@@ -230,13 +243,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);
+ append(substitutions, this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
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);
+ append(substitutions, other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
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: ";
@@ -258,7 +271,7 @@ std::string PresetBundle::load_system_presets()
}
this->update_system_maps();
- return errors_cummulative;
+ return std::make_pair(std::move(substitutions), errors_cummulative);
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
@@ -320,9 +333,9 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p
if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
return alias;
- const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints :
+ const PresetCollection& presets = preset_type == Preset::TYPE_FFF_PRINT ? prints :
preset_type == Preset::TYPE_SLA_PRINT ? sla_prints :
- preset_type == Preset::TYPE_FILAMENT ? filaments :
+ preset_type == Preset::TYPE_FFF_FILAMENT ? filaments :
sla_materials;
return presets.get_preset_name_by_alias(alias);
@@ -331,9 +344,9 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p
void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type,
const std::vector<std::string>& unselected_options)
{
- PresetCollection& presets = type == Preset::TYPE_PRINT ? prints :
+ PresetCollection& presets = type == Preset::TYPE_FFF_PRINT ? prints :
type == Preset::TYPE_SLA_PRINT ? sla_prints :
- type == Preset::TYPE_FILAMENT ? filaments :
+ type == Preset::TYPE_FFF_FILAMENT ? filaments :
type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers;
// if we want to save just some from selected options
@@ -348,7 +361,7 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
// If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible.
update_compatible(PresetSelectCompatibleType::Never);
- if (type == Preset::TYPE_FILAMENT) {
+ if (type == Preset::TYPE_FFF_FILAMENT) {
// synchronize the first filament presets.
set_filament_preset(0, filaments.get_selected_preset_name());
}
@@ -666,15 +679,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, 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.
@@ -685,33 +698,41 @@ void PresetBundle::load_config_file(const std::string &path)
} catch (const std::ifstream::failure &err) {
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const boost::property_tree::file_parser_error &err) {
- throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
- % err.filename() % err.message() % err.line()).str());
+ throw Slic3r::RuntimeError(format("Failed loading the Config Bundle \"%1%\": %2% at line %3%",
+ err.filename(), err.message(), err.line()));
} catch (const std::runtime_error &err) {
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
- switch (config_file_type) {
- case CONFIG_FILE_TYPE_UNKNOWN:
- throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
- case CONFIG_FILE_TYPE_APP_CONFIG:
- throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
- case CONFIG_FILE_TYPE_CONFIG:
- {
- // Initialize a config from full defaults.
- DynamicPrintConfig config;
- config.apply(FullPrintConfig::defaults());
- config.load(tree);
- 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;
+ ConfigSubstitutions config_substitutions;
+ try {
+ switch (config_file_type) {
+ case CONFIG_FILE_TYPE_UNKNOWN:
+ throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
+ case CONFIG_FILE_TYPE_APP_CONFIG:
+ throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
+ case CONFIG_FILE_TYPE_CONFIG:
+ {
+ // Initialize a config from full defaults.
+ DynamicPrintConfig config;
+ config.apply(FullPrintConfig::defaults());
+ config_substitutions = config.load(tree, compatibility_rule);
+ Preset::normalize(config);
+ load_config_file_config(path, true, std::move(config));
+ return config_substitutions;
+ }
+ case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
+ return load_config_file_config_bundle(path, tree, compatibility_rule);
+ }
+ } catch (const ConfigurationError &e) {
+ throw Slic3r::RuntimeError(format("Invalid configuration file %1%: %2%", path, e.what()));
}
+
+ // 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.
@@ -883,65 +904,19 @@ 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)
+// Note: only called when using --load from cli. Will load the bundle like with the menu but wihtout saving it.
+ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
+ const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
- // 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);
- 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 = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
- 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);
- std::string preset_name_dst;
- if (preset_dst != nullptr && preset_dst->is_default) {
- // No need to copy a default preset, it always exists in collection_dst.
- if (activate)
- collection_dst.select_preset(0);
- return preset_name_src;
- } else if (preset_dst != nullptr && preset_src->config == preset_dst->config) {
- // Don't save as the config exists in the current bundle and its content is the same.
- return preset_name_src;
- } else {
- // Generate a new unique name.
- preset_name_dst = preset_name_src + bundle_name;
- Preset *preset_dup = nullptr;
- for (size_t i = 1; (preset_dup = collection_dst.find_preset(preset_name_dst, false)) != nullptr; ++ i) {
- if (preset_src->config == preset_dup->config)
- // The preset has been already copied into collection_dst.
- return preset_name_dst;
- // Try to generate another name.
- char buf[64];
- sprintf(buf, " (%d)", (int)i);
- preset_name_dst = preset_name_src + buf + bundle_name;
- }
- }
- assert(! preset_name_dst.empty());
- // Save preset_src->config into collection_dst under preset_name_dst.
- // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway,
- // but some of the alpha versions of Slic3r did.
- ConfigOption *opt_compatible = preset_src->config.optptr("compatible_printers");
- if (opt_compatible != nullptr) {
- assert(opt_compatible->type() == coStrings);
- if (opt_compatible->type() == coStrings)
- static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear();
- }
- collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true;
- return preset_name_dst;
- };
- load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset_name(), true);
- load_one(this->sla_prints, tmp_bundle.sla_prints, tmp_bundle.sla_prints .get_selected_preset_name(), true);
- load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset_name(), true);
- load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset_name(), true);
- load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset_name(), true);
- this->update_multi_material_filament_presets();
- for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i)
- this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
-
+ // Load the config bundle, but don't save the loaded presets to user profile directory
+ // [PresetsConfigSubstitutions, size_t]
+ auto [presets_substitutions, presets_imported] = this->load_configbundle(path, { }, compatibility_rule);
+ ConfigSubstitutions config_substitutions;
this->update_compatible(PresetSelectCompatibleType::Never);
+ for (PresetConfigSubstitutions &sub : presets_substitutions)
+ append(config_substitutions, std::move(sub.substitutions));
+ sort_remove_duplicates(config_substitutions);
+ return std::move(config_substitutions);
}
// Process the Config Bundle loaded as a Boost property tree.
@@ -1086,11 +1061,16 @@ 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, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
- 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 { compatibility_rule };
+ 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;
@@ -1103,25 +1083,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.
@@ -1188,7 +1167,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
active_sla_material = kvp.second.data();
} else if (kvp.first == "printer") {
active_printer = kvp.second.data();
- }else if (kvp.first == "physical_printer") {
+ } else if (kvp.first == "physical_printer") {
active_physical_printer = kvp.second.data();
}
}
@@ -1225,30 +1204,36 @@ 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) {
- for (auto &kvp : section.second) {
- if (kvp.first == "alias")
- alias_name = kvp.second.data();
- else if (kvp.first == "renamed_from") {
- if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
- BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
- section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
- }
- }
- config.set_deserialize(kvp.first, kvp.second.data());
+ try {
+ 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();
+ else if (kvp.first == "renamed_from") {
+ if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
+ BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
+ section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
+ }
+ }
+ // 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) {
+ // Select the default config based on the printer_technology field extracted from kvp.
+ DynamicPrintConfig config_src;
+ parse_config_section(config_src);
+ default_config = &presets->default_preset_for(config_src).config;
+ config = *default_config;
+ config.apply(config_src);
+ } else {
+ default_config = &presets->default_preset().config;
+ config = *default_config;
+ parse_config_section(config);
}
- };
- if (presets == &this->printers) {
- // Select the default config based on the printer_technology field extracted from kvp.
- DynamicPrintConfig config_src;
- parse_config_section(config_src);
- default_config = &presets->default_preset_for(config_src).config;
- config = *default_config;
- config.apply(config_src);
- } else {
- default_config = &presets->default_preset().config;
- config = *default_config;
- parse_config_section(config);
+ } catch (const ConfigurationError &e) {
+ throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
Preset::normalize(config);
// Report configuration fields, which are misplaced into a wrong group.
@@ -1256,7 +1241,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");
@@ -1291,7 +1276,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) {
@@ -1320,9 +1305,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;
}
@@ -1343,7 +1328,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;
}
@@ -1352,8 +1340,13 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
const DynamicPrintConfig& default_config = ph_printers->default_config();
DynamicPrintConfig config = default_config;
- for (auto& kvp : section.second)
- config.set_deserialize(kvp.first, kvp.second.data());
+ substitution_context.substitutions.clear();
+ try {
+ for (auto& kvp : section.second)
+ config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
+ } catch (const ConfigurationError &e) {
+ throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
+ }
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
@@ -1379,18 +1372,21 @@ 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())
- sla_materials.select_preset_by_name(active_sla_print, true);
+ sla_prints.select_preset_by_name(active_sla_print, true);
if (! active_sla_material.empty())
sla_materials.select_preset_by_name(active_sla_material, true);
if (! active_printer.empty())
@@ -1406,7 +1402,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()
diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp
index 5d7cc84ba..cd26a0968 100644
--- a/src/libslic3r/PresetBundle.hpp
+++ b/src/libslic3r/PresetBundle.hpp
@@ -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
// This is done just once on application start up.
- void load_presets(AppConfig &config, const std::string &preferred_model_id = "");
+ PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = "");
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
@@ -82,24 +83,27 @@ 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, ForwardCompatibilitySubstitutionRule compatibility_rule);
// 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);
@@ -136,7 +140,7 @@ public:
static const char *PRUSA_BUNDLE;
private:
- std::string load_system_presets();
+ std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Update renamed_from and alias maps of system profiles.
@@ -155,12 +159,15 @@ 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, ForwardCompatibilitySubstitutionRule compatibility_rule);
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;
};
+ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
+
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 900dae6e7..38883733d 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -172,6 +172,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"toolchange_gcode",
"top_fan_speed",
"threads",
+ "travel_acceleration",
"travel_speed",
"travel_speed_z",
"use_firmware_retraction",
@@ -202,6 +203,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "skirt_distance"
|| opt_key == "min_skirt_length"
|| opt_key == "complete_objects_one_skirt"
+ || opt_key == "complete_objects_one_brim"
|| opt_key == "ooze_prevention"
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
@@ -1275,8 +1277,9 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin
{
Polygons convex_hulls_other;
std::map<ObjectID, Polygon> map_model_object_to_convex_hull;
+ const double dist_grow = PrintConfig::min_object_distance(&print.default_region_config()) * 2;
for (const PrintObject *print_object : print.objects()) {
- double dist_grow = PrintConfig::min_object_distance(&print.full_print_config()) * 2 ;// &print_object->config());
+ const double object_grow = print.config().complete_objects_one_brim ? dist_grow : std::max(dist_grow, print_object->config().brim_width.value);
assert(! print_object->model_object()->instances.empty());
assert(! print_object->instances().empty());
ObjectID model_object_id = print_object->model_object()->id();
@@ -1294,7 +1297,7 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin
Geometry::assemble_transform(Vec3d::Zero(), model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())),
// Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
- float(scale_(0.5 * dist_grow - EPSILON)),
+ float(scale_(0.5 * object_grow - EPSILON)),
jtRound, float(scale_(0.1))).front());
}
// Make a copy, so it may be rotated for instances.
@@ -1515,20 +1518,6 @@ std::pair<PrintBase::PrintValidationError, std::string> Print::validate() const
return L("One or more object were assigned an extruder that the printer does not have.");
#endif
- auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool {
- double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter);
- double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter);
- if (extrusion_width_min == 0) {
- // Default "auto-generated" extrusion width is always valid.
- } else if (extrusion_width_min <= layer_height) {
- err_msg = (boost::format(L("%1%=%2% mm is too low to be printable at a layer height %3% mm")) % opt_key % extrusion_width_min % layer_height).str();
- return false;
- } else if (extrusion_width_max >= max_nozzle_diameter * 4.) {
- err_msg = (boost::format(L("Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm")) % opt_key % extrusion_width_max % max_nozzle_diameter).str();
- return false;
- }
- return true;
- };
for (PrintObject *object : m_objects) {
if (object->config().raft_layers > 0 || object->config().support_material.value) {
if ((object->config().support_material_extruder == 0 || object->config().support_material_interface_extruder == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) {
@@ -1552,40 +1541,62 @@ std::pair<PrintBase::PrintValidationError, std::string> Print::validate() const
}
}
}
-
- // validate first_layer_height
- double first_layer_height = object->config().get_abs_value("first_layer_height", this->m_config.nozzle_diameter.get_at(0));
- double first_layer_min_nozzle_diameter;
- if (object->config().raft_layers > 0) {
- // if we have raft layers, only support material extruder is used on first layer
- size_t first_layer_extruder = object->config().raft_layers == 1
- ? object->config().support_material_interface_extruder-1
- : object->config().support_material_extruder-1;
- first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ?
- min_nozzle_diameter :
- m_config.nozzle_diameter.get_at(first_layer_extruder);
- } else {
- // if we don't have raft layers, any nozzle diameter is potentially used in first layer
- first_layer_min_nozzle_diameter = min_nozzle_diameter;
+
+ // validate layer_height for each region
+ for (size_t region_id = 0; region_id < object->region_volumes.size(); ++region_id) {
+ if (object->region_volumes[region_id].empty()) continue;
+ const PrintRegion* region = this->regions()[region_id];
+ std::vector<uint16_t> object_extruders;
+ PrintRegion::collect_object_printing_extruders(config(), object->config(), region->config(), object_extruders);
+ //object->region_volumes[region_id].front().first.second < object->layers()
+ double layer_height = object->config().layer_height.value;
+ for (uint16_t extruder_id : object_extruders) {
+ double min_layer_height = config().min_layer_height.values[extruder_id];
+ double max_layer_height = config().max_layer_height.values[extruder_id];
+ double nozzle_diameter = config().nozzle_diameter.values[extruder_id];
+ double first_layer_height = object->config().first_layer_height.get_abs_value(nozzle_diameter);
+ if (max_layer_height < EPSILON) max_layer_height = nozzle_diameter * 0.75;
+
+ //check first layer
+ if (object->region_volumes[region_id].front().first.first < first_layer_height) {
+ if (first_layer_height + EPSILON < min_layer_height)
+ return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("First layer height can't be greater than %s")) % "min layer height").str() };
+ for (auto tuple : std::vector<std::pair<double, const char*>>{
+ {nozzle_diameter, "nozzle diameter"},
+ {max_layer_height, "max layer height"},
+ {skirt_flow(extruder_id).width, "skirt extrusion width"},
+ {region->width(FlowRole::frSupportMaterial, true, *object), "support material extrusion width"},
+ {region->width(FlowRole::frPerimeter, true, *object), "perimeter extrusion width"},
+ {region->width(FlowRole::frExternalPerimeter, true, *object), "perimeter extrusion width"},
+ {region->width(FlowRole::frInfill, true, *object), "infill extrusion width"},
+ {region->width(FlowRole::frSolidInfill, true, *object), "solid infill extrusion width"},
+ {region->width(FlowRole::frTopSolidInfill, true, *object), "top solid infill extrusion width"},
+ })
+ if (first_layer_height > tuple.first + EPSILON)
+ return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("First layer height can't be greater than %s")) % tuple.second).str() };
+
+ }
+ //check not-first layer
+ if (object->region_volumes[region_id].front().first.second > layer_height) {
+ if (layer_height + EPSILON < min_layer_height)
+ return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("First layer height can't be greater than %s")) % "min layer height").str() };
+ for (auto tuple : std::vector<std::pair<double, const char*>>{
+ {nozzle_diameter, "nozzle diameter"},
+ {max_layer_height, "max layer height"},
+ {skirt_flow(extruder_id).width, "skirt extrusion width"},
+ {region->width(FlowRole::frSupportMaterial, false, *object), "support material extrusion width"},
+ {region->width(FlowRole::frPerimeter, false, *object), "perimeter extrusion width"},
+ {region->width(FlowRole::frExternalPerimeter, false, *object), "perimeter extrusion width"},
+ {region->width(FlowRole::frInfill, false, *object), "infill extrusion width"},
+ {region->width(FlowRole::frSolidInfill, false, *object), "solid infill extrusion width"},
+ {region->width(FlowRole::frTopSolidInfill, false, *object), "top solid infill extrusion width"},
+ })
+ if (layer_height > tuple.first + EPSILON)
+ return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("Layer height can't be greater than %s")) % tuple.second).str() };
+ }
+ }
}
- if (first_layer_height > first_layer_min_nozzle_diameter)
- return { PrintBase::PrintValidationError::pveWrongSettings,L("First layer height can't be greater than nozzle diameter") };
-
- // validate layer_height
- double layer_height = object->config().layer_height.value;
- if (layer_height > min_nozzle_diameter)
- return { PrintBase::PrintValidationError::pveWrongSettings,L("Layer height can't be greater than nozzle diameter") };
-
- // Validate extrusion widths.
- std::string err_msg;
- if (! validate_extrusion_width(object->config(), "extrusion_width", layer_height, err_msg))
- return { PrintBase::PrintValidationError::pveWrongSettings,err_msg };
- if ((object->config().support_material || object->config().raft_layers > 0) && ! validate_extrusion_width(object->config(), "support_material_extrusion_width", layer_height, err_msg))
- return { PrintBase::PrintValidationError::pveWrongSettings,err_msg };
- for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" })
- for (size_t i = 0; i < object->region_volumes.size(); ++ i)
- if (! object->region_volumes[i].empty() && ! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg))
- return { PrintBase::PrintValidationError::pveWrongSettings, err_msg };
+
}
}
@@ -1803,7 +1814,7 @@ void Print::process()
const PrintObjectConfig &brim_config = obj_group.front()->config();
if (brim_config.brim_width > 0 || brim_config.brim_width_interior > 0) {
this->set_status(88, L("Generating brim"));
- if (config().complete_objects) {
+ if (config().complete_objects && !config().complete_objects_one_brim) {
for (PrintObject *obj : obj_group) {
//get flow
std::vector<uint16_t> set_extruders = this->object_extruders({ obj });
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 840b87f8f..ba79a4db5 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -65,8 +65,9 @@ public:
const Print* print() const { return m_print; }
const PrintRegionConfig& config() const { return m_config; }
// 1-based extruder identifier for this region and role.
- uint16_t extruder(FlowRole role) const;
- Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
+ uint16_t extruder(FlowRole role, const PrintObject& object) const;
+ Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject& object) const;
+ float width(FlowRole role, bool first_layer, const PrintObject& object) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
// Average diameter of nozzles participating on extruding this region.
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 7716b881e..7fc8f40b5 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -74,7 +74,7 @@ void PrintConfigDef::init_common_params()
def = this->add("thumbnails", coPoints);
def->label = L("Thumbnails size");
- def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
+ def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
def->mode = comExpert;
def->min = 0;
def->max = 2048;
@@ -310,6 +310,7 @@ void PrintConfigDef::init_fff_params()
"\nSet zero to disable acceleration control for bridges."
"\nNote that it won't be applied to overhangs, they still use the perimeter acceleration.");
def->sidetext = L("mm/s² or %");
+ def->ratio_over = "default_acceleration";
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(0,false));
@@ -574,6 +575,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
+ def = this->add("complete_objects_one_brim", coBool);
+ def->label = L("Print all brim at startup");
+ def->category = OptionCategory::output;
+ def->tooltip = L("When using 'Complete individual objects', the default behavior is to draw the brim at the beginning of each object."
+ " if you prefer to have more place for you objects, you can print all the brims at the beginning, so ther is less problem with collision.");
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionBool(false));
+
def = this->add("complete_objects_sort", coEnum);
def->label = L("Object sort");
def->category = OptionCategory::output;
@@ -627,6 +636,7 @@ void PrintConfigDef::init_fff_params()
"\nYou can set it as a % of the max of the X/Y machine acceleration limit."
"\nSet zero to prevent resetting acceleration at all.");
def->sidetext = L("mm/s² or %");
+ def->ratio_over = "machine_max_acceleration_X";
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(0,false));
@@ -1695,6 +1705,19 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0));
+ def = this->add("first_layer_size_compensation_layers", coInt);
+ def->label = L("height in layers");
+ def->full_label = L("XY First layer compensation height in layers");
+ def->category = OptionCategory::slicing;
+ def->tooltip = L("The number of layers on which the elephant foot compensation will be active. "
+ "The first layer will be shrunk by the elephant foot compensation value, then "
+ "the next layers will be gradually shrunk less, up to the layer indicated by this value.");
+ def->sidetext = L("layers");
+ def->min = 1;
+ def->max = 30;
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionInt(1));
+
def = this->add("fill_smooth_width", coFloatOrPercent);
def->label = L("Width");
def->full_label = L("Ironing width");
@@ -1730,6 +1753,7 @@ void PrintConfigDef::init_fff_params()
"\nCan be a % of the default acceleration"
"\nSet zero to disable acceleration control for first layer.");
def->sidetext = L("mm/s² or %");
+ def->ratio_over = "default_acceleration";
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
@@ -1880,7 +1904,8 @@ void PrintConfigDef::init_fff_params()
def->full_label = L("Gap fill speed");
def->category = OptionCategory::speed;
def->tooltip = L("Speed for filling small gaps using short zigzag moves. Keep this reasonably low "
- "to avoid too much shaking and resonance issues.");
+ "to avoid too much shaking and resonance issues."
+ "\nGap fill extrusions are ignored from the automatic volumetric speed computation, unless you set it to 0.");
def->sidetext = L("mm/s");
def->min = 0;
def->mode = comAdvanced;
@@ -1972,6 +1997,7 @@ void PrintConfigDef::init_fff_params()
"\nCan be a % of the default acceleration"
"\nSet zero to disable acceleration control for infill.");
def->sidetext = L("mm/s² or %");
+ def->ratio_over = "default_acceleration";
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(0,false));
@@ -2286,14 +2312,17 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.1));
- def = this->add("ironing_speed", coFloat);
+ def = this->add("ironing_speed", coFloatOrPercent);
def->label = L("Ironing");
def->category = OptionCategory::ironing;
- def->tooltip = L("Ironing");
+ def->tooltip = L("Ironing speed. Used for the ironing pass of the ironing infill pattern, and the post-process infill."
+ " Can be defined as mm.s, or a % of the top solid infill speed."
+ "\nIroning extrusions are ignored from the automatic volumetric speed computation.");
def->sidetext = L("mm/s");
- def->min = 0;
+ def->ratio_over = "top_solid_infill_speed";
+ def->min = 0.1;
def->mode = comAdvanced;
- def->set_default_value(new ConfigOptionFloat(15));
+ def->set_default_value(new ConfigOptionFloatOrPercent(15, false));
def = this->add("layer_gcode", coString);
def->label = L("After layer change G-code");
@@ -2693,12 +2722,14 @@ 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_values.push_back("klipper");
+ def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
@@ -2863,6 +2894,8 @@ void PrintConfigDef::init_fff_params()
"\nCan be a % of the default acceleration"
"\nSet zero to disable acceleration control for perimeters.");
def->sidetext = L("mm/s² or %");
+ def->ratio_over = "default_acceleration";
+ def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(0,false));
@@ -3607,6 +3640,20 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
+ def = this->add("solid_over_perimeters", coInt);
+ def->label = L("Max perimeters layer for solid infill");
+ def->category = OptionCategory::perimeter;
+ def->tooltip = L("When you have a medium/hight number of top/bottom solid layers, and a low/medium of perimeters,"
+ " then it have to put some solid infill inside the part to have enough solid layers."
+ "\nBy setting this to somethign higher than 0, you can remove this 'inside filling'."
+ " This number allow to keep some if there is a low number of perimeter over the void."
+ "\nIf this setting is equal or higher than the top/bottom solid layer count, it won't evict anything."
+ "\nIf this setting is set to 1, it will evict all solid fill are are only over perimeters."
+ "\nSet it to 0 to disable.");
+ def->min = 0;
+ def->mode = comAdvanced;
+ def->set_default_value(new ConfigOptionInt(2));
+
def = this->add("support_material", coBool);
def->label = L("Generate support material");
def->category = OptionCategory::support;
@@ -4070,6 +4117,18 @@ void PrintConfigDef::init_fff_params()
def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.));
+ def = this->add("travel_acceleration", coFloatOrPercent);
+ def->label = L("Travel");
+ def->full_label = L("Travel acceleration");
+ def->category = OptionCategory::speed;
+ def->tooltip = L("Acceleration for travel moves (jumps between distant extrusion points)."
+ "\nNote that the deceleration of a travel will use the acceleration value of the extrusion that will be printed after it (if any)");
+ def->sidetext = L("mm/s² or %");
+ def->ratio_over = "default_acceleration";
+ def->min = 0;
+ def->mode = comExpert;
+ def->set_default_value(new ConfigOptionFloatOrPercent(1500, false));
+
def = this->add("travel_speed", coFloat);
def->label = L("Travel");
def->full_label = L("Travel speed");
@@ -5241,7 +5300,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();
@@ -5388,6 +5447,7 @@ void PrintConfigDef::to_prusa(t_config_option_key& opt_key, std::string& value,
"brim_offset",
"chamber_temperature",
"complete_objects_one_skirt",
+"complete_objects_one_brim",
"complete_objects_sort",
"solid_fill_pattern",
"enforce_full_fill_volume",
@@ -5516,6 +5576,8 @@ void PrintConfigDef::to_prusa(t_config_option_key& opt_key, std::string& value,
"start_gcode_manual",
"perimeter_round_corners",
"travel_speed_z",
+"first_layer_size_compensation_layers",
+"travel_acceleration",
};
//looks if it's to be removed, or have to be transformed
if (to_remove_keys.find(opt_key) != to_remove_keys.end()) {
@@ -5542,9 +5604,12 @@ void PrintConfigDef::to_prusa(t_config_option_key& opt_key, std::string& value,
}
} else if ("elephant_foot_min_width" == opt_key) {
opt_key = "elefant_foot_min_width";
- } else if("first_layer_acceleration" == opt_key || "infill_acceleration" == opt_key || "bridge_acceleration" == opt_key || "default_acceleration" == opt_key || "overhangs_speed" == opt_key || "perimeter_acceleration" == opt_key){
- if (value.find("%") != std::string::npos)
- value = "0";
+ } else if("first_layer_acceleration" == opt_key || "infill_acceleration" == opt_key || "bridge_acceleration" == opt_key || "default_acceleration" == opt_key || "perimeter_acceleration" == opt_key
+ || "overhangs_speed" == opt_key || "ironing_speed" == opt_key){
+ // remove '%'
+ if (value.find("%") != std::string::npos) {
+ value = std::to_string(all_conf.get_abs_value(opt_key));
+ }
} else if ("gap_fill_speed" == opt_key && all_conf.has("gap_fill") && !all_conf.option<ConfigOptionBool>("gap_fill")->value) {
value = "0";
} else if ("bridge_flow_ratio" == opt_key && all_conf.has("bridge_flow_ratio")) {
@@ -5664,7 +5729,6 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei
//std::cout << "START min_object_distance =>" << base_dist << "\n";
const ConfigOptionBool* co_opt = config->option<ConfigOptionBool>("complete_objects");
if (co_opt && co_opt->value) {
- double brim_dist = 0;
double skirt_dist = 0;
try {
std::vector<double> vals = dynamic_cast<const ConfigOptionFloats*>(config->option("nozzle_diameter"))->values;
@@ -5679,18 +5743,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei
base_dist = extruder_clearance_radius;
}
- //add brim width
- // note: now brim can be per-object, so you have to get a different min_object_distance per object
- // You should increase/reduce the size of the polygons that have a model-wide setting.
const double first_layer_height = config->get_abs_value("first_layer_height");
- if (ref_height <= first_layer_height && ref_height != 0) {
- if (config->option("brim_width")->getFloat() > 0) {
- brim_dist += config->option("brim_width")->getFloat();
- }
- }
- else //if (config->option("brim_width")->getFloat() + 1 > base_dist) {
- base_dist += config->option("brim_width")->getFloat();
- //}
//add the skirt
if (config->option("skirts")->getInt() > 0 && config->option("skirt_height")->getInt() >= 1 && !config->option("complete_objects_one_skirt")->getBool()) {
if (ref_height == 0) {
@@ -5723,7 +5776,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei
catch (const std::exception & ex) {
boost::nowide::cerr << ex.what() << std::endl;
}
- return base_dist + std::max(skirt_dist, brim_dist);
+ return base_dist + skirt_dist;
}
return base_dist;
}
@@ -5892,7 +5945,13 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
double max_nozzle_diameter = 0;
for (double dmr : nozzle_diameter_option->values)
max_nozzle_diameter = std::max(max_nozzle_diameter, dmr);
- Flow flow = Flow::new_from_spacing(spacing_option->get_abs_value(max_nozzle_diameter), max_nozzle_diameter, layer_height_option->value, false);
+ double spacing_value = spacing_option->get_abs_value(max_nozzle_diameter);
+ Flow flow = Flow::new_from_spacing(spacing_value, max_nozzle_diameter,layer_height_option->value, false);
+ //test for valid height. If too high, revert to round shape
+ if (flow.height > spacing_value / (1 - (1. - 0.25 * PI) * flow.spacing_ratio)) {
+ flow.width = spacing_value / (1 - (1. - 0.25 * PI) * flow.spacing_ratio);
+ flow.height = flow.width;
+ }
if (opt_key == "extrusion_spacing") {
ConfigOptionFloatOrPercent* width_option = this->option<ConfigOptionFloatOrPercent>("extrusion_width");
if (width_option) {
@@ -6002,6 +6061,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow flow = Flow::new_from_config_width(FlowRole::frPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (flow.width < flow.height) flow.height = flow.width;
spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
something_changed = true;
@@ -6013,6 +6073,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow flow = Flow::new_from_config_width(FlowRole::frPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (flow.width < flow.height) flow.height = flow.width;
spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
something_changed = true;
@@ -6025,6 +6086,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow flow = Flow::new_from_config_width(FlowRole::frExternalPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (flow.width < flow.height) flow.height = flow.width;
flow.spacing_ratio = perimeter_overlap_option->get_abs_value(1);
spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
@@ -6038,6 +6100,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow ext_perimeter_flow = Flow::new_from_config_width(FlowRole::frPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (ext_perimeter_flow.width < ext_perimeter_flow.height) ext_perimeter_flow.height = ext_perimeter_flow.width;
ext_perimeter_flow.spacing_ratio = external_perimeter_overlap_option->get_abs_value(0.5);
spacing_option->value = (width_option->percent) ? std::round(100 * ext_perimeter_flow.spacing() / max_nozzle_diameter) : (std::round(ext_perimeter_flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
@@ -6050,6 +6113,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow flow = Flow::new_from_config_width(FlowRole::frInfill, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (flow.width < flow.height) flow.height = flow.width;
spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
something_changed = true;
@@ -6061,6 +6125,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow flow = Flow::new_from_config_width(FlowRole::frSolidInfill, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (flow.width < flow.height) flow.height = flow.width;
spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
something_changed = true;
@@ -6072,6 +6137,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const
width_option->set_phony(false);
spacing_option->set_phony(true);
Flow flow = Flow::new_from_config_width(FlowRole::frTopSolidInfill, *width_option, max_nozzle_diameter, layer_height_option->value, 0);
+ if (flow.width < flow.height) flow.height = flow.width;
spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000);
spacing_option->percent = width_option->percent;
something_changed = true;
@@ -6495,6 +6561,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 Slic3r may not understand configurations produced by newest Slic3r versions. "
+ "For example, newer Slic3r 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("Bail out on unknown configuration values");
+ def->enum_labels.push_back("Enable reading unknown configuration values by verbosely substituting them with defaults.");
+ def->enum_labels.push_back("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.");
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 0bccab020..d6b165be9 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -72,6 +72,7 @@ enum class MachineLimitsUsage : uint8_t {
};
enum PrintHostType {
+ htPrusaLink,
htOctoPrint,
htDuet,
htFlashAir,
@@ -222,6 +223,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<MachineLimitsUsag
template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() {
static t_config_enum_values keys_map = {
+ {"prusalink", htPrusaLink},
{"octoprint", htOctoPrint},
{"duet", htDuet},
{"flashair", htFlashAir},
@@ -369,6 +371,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<ZLiftTop>::get_en
return keys_map;
}
+template<> inline const t_config_enum_values& ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values() {
+ static const t_config_enum_values keys_map = {
+ { "disable", ForwardCompatibilitySubstitutionRule::Disable },
+ { "enable", ForwardCompatibilitySubstitutionRule::Enable },
+ { "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
+ };
+
+ return keys_map;
+}
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
// Does not store the actual values, but defines default values.
class PrintConfigDef : public ConfigDef
@@ -622,6 +633,7 @@ public:
ConfigOptionFloatOrPercent first_layer_height;
ConfigOptionFloatOrPercent first_layer_extrusion_width;
ConfigOptionFloat first_layer_size_compensation;
+ ConfigOptionInt first_layer_size_compensation_layers;
ConfigOptionFloat hole_size_compensation;
ConfigOptionFloat hole_size_threshold;
ConfigOptionBool infill_only_where_needed;
@@ -692,6 +704,7 @@ protected:
OPT_PTR(first_layer_height);
OPT_PTR(first_layer_extrusion_width);
OPT_PTR(first_layer_size_compensation);
+ OPT_PTR(first_layer_size_compensation_layers);
OPT_PTR(infill_only_where_needed);
OPT_PTR(interface_shells);
OPT_PTR(layer_height);
@@ -802,7 +815,7 @@ public:
ConfigOptionEnum<IroningType> ironing_type;
ConfigOptionPercent ironing_flowrate;
ConfigOptionFloat ironing_spacing;
- ConfigOptionFloat ironing_speed;
+ ConfigOptionFloatOrPercent ironing_speed;
// milling options
ConfigOptionFloatOrPercent milling_after_z;
ConfigOptionFloatOrPercent milling_extra_size;
@@ -837,6 +850,7 @@ public:
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
ConfigOptionInt solid_infill_every_layers;
ConfigOptionFloatOrPercent solid_infill_speed;
+ ConfigOptionInt solid_over_perimeters;
ConfigOptionInt print_temperature;
ConfigOptionBool thin_perimeters;
ConfigOptionBool thin_perimeters_all;
@@ -947,6 +961,7 @@ protected:
OPT_PTR(solid_infill_extrusion_width);
OPT_PTR(solid_infill_every_layers);
OPT_PTR(solid_infill_speed);
+ OPT_PTR(solid_over_perimeters);
OPT_PTR(print_temperature);
OPT_PTR(thin_perimeters);
OPT_PTR(thin_perimeters_all);
@@ -1262,6 +1277,7 @@ public:
ConfigOptionInts chamber_temperature;
ConfigOptionBool complete_objects;
ConfigOptionBool complete_objects_one_skirt;
+ ConfigOptionBool complete_objects_one_brim;
ConfigOptionEnum<CompleteObjectSort> complete_objects_sort;
ConfigOptionFloats colorprint_heights;
ConfigOptionBools cooling;
@@ -1327,6 +1343,7 @@ public:
ConfigOptionBool thumbnails_with_bed;
ConfigOptionPercent time_estimation_compensation;
ConfigOptionInts top_fan_speed;
+ ConfigOptionFloatOrPercent travel_acceleration;
ConfigOptionBools wipe;
ConfigOptionBool wipe_tower;
ConfigOptionFloatOrPercent wipe_tower_brim;
@@ -1357,6 +1374,7 @@ protected:
OPT_PTR(chamber_temperature);
OPT_PTR(complete_objects);
OPT_PTR(complete_objects_one_skirt);
+ OPT_PTR(complete_objects_one_brim);
OPT_PTR(complete_objects_sort);
OPT_PTR(colorprint_heights);
OPT_PTR(cooling);
@@ -1422,6 +1440,7 @@ protected:
OPT_PTR(thumbnails_with_bed);
OPT_PTR(time_estimation_compensation);
OPT_PTR(top_fan_speed);
+ OPT_PTR(travel_acceleration);
OPT_PTR(wipe);
OPT_PTR(wipe_tower);
OPT_PTR(wipe_tower_brim);
@@ -1894,8 +1913,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.
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index 100366ba1..02c3d5057 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -710,6 +710,7 @@ namespace Slic3r {
|| opt_key == "slice_closing_radius"
|| opt_key == "clip_multipart_objects"
|| opt_key == "first_layer_size_compensation"
+ || opt_key == "first_layer_size_compensation_layers"
|| opt_key == "elephant_foot_min_width"
|| opt_key == "dont_support_bridges"
|| opt_key == "support_material_contact_distance_type"
@@ -777,6 +778,7 @@ namespace Slic3r {
|| opt_key == "solid_infill_below_area"
|| opt_key == "solid_infill_extruder"
|| opt_key == "solid_infill_every_layers"
+ || opt_key == "solid_over_perimeters"
|| opt_key == "top_solid_layers"
|| opt_key == "top_solid_min_thickness") {
steps.emplace_back(posPrepareInfill);
@@ -1478,7 +1480,11 @@ namespace Slic3r {
{
// Collected polygons, offsetted
Polygons top_surfaces;
+ Polygons top_fill_surfaces;
+ Polygons top_perimeter_surfaces;
Polygons bottom_surfaces;
+ Polygons bottom_fill_surfaces;
+ Polygons bottom_perimeter_surfaces;
Polygons holes;
};
bool spiral_vase = this->print()->config().spiral_vase.value;
@@ -1537,10 +1543,14 @@ namespace Slic3r {
// Top surfaces.
append(cache.top_surfaces, offset(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing));
append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing));
+ append(cache.top_fill_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing));
+ append(cache.top_perimeter_surfaces, to_polygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)));
// Bottom surfaces.
const SurfaceType surfaces_bottom[2] = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge };
append(cache.bottom_surfaces, offset(to_expolygons(layerm.slices().filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
+ append(cache.bottom_fill_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
+ append(cache.bottom_perimeter_surfaces, to_polygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)));
// Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
// First find the maxium number of perimeters per region slice.
unsigned int perimeters = 0;
@@ -1595,13 +1605,16 @@ namespace Slic3r {
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
+ //solid_over_perimeters value, to remove solid fill where there's only perimeters on multiple layers
+ int nb_perimeter_layers_for_solid_fill = region.config().solid_over_perimeters.value;
+
if (!top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
- [this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
+ [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill](const tbb::blocked_range<size_t>& range) {
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
m_print->throw_if_canceled();
Layer& layer = *m_layers[idx_layer];
@@ -1611,10 +1624,18 @@ namespace Slic3r {
auto& cache = cache_top_botom_regions[idx_layer];
cache.top_surfaces = offset(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing);
append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing));
+ if (nb_perimeter_layers_for_solid_fill != 0) {
+ cache.top_fill_surfaces = offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing);
+ cache.top_perimeter_surfaces = to_polygons(layerm.slices().filter_by_type(stPosTop | stDensSolid));
+ }
// Bottom surfaces.
const SurfaceType surfaces_bottom[2] = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge };
cache.bottom_surfaces = offset(to_expolygons(layerm.slices().filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing);
append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
+ if (nb_perimeter_layers_for_solid_fill != 0) {
+ cache.bottom_fill_surfaces = offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing);
+ cache.bottom_perimeter_surfaces = to_polygons(layerm.slices().filter_by_types(surfaces_bottom, 2));
+ }
// Holes over all regions. Only collect them once, they are valid for all idx_region iterations.
if (cache.holes.empty()) {
for (size_t idx_region = 0; idx_region < layer.regions().size(); ++idx_region)
@@ -1629,7 +1650,7 @@ namespace Slic3r {
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
- [this, idx_region, &cache_top_botom_regions]
+ [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
@@ -1653,6 +1674,8 @@ namespace Slic3r {
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
Polygons shell;
+ Polygons fill_shell;
+ Polygons max_perimeter_shell; // for nb_perimeter_layers_for_solid_fill
Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
ExPolygons shell_ex;
@@ -1700,6 +1723,16 @@ namespace Slic3r {
// than running the union_ all at once.
shell = union_(shell, false);
}
+ if (nb_perimeter_layers_for_solid_fill != 0) {
+ if (!cache.top_fill_surfaces.empty()) {
+ polygons_append(fill_shell, cache.top_fill_surfaces);
+ fill_shell = union_(fill_shell, false);
+ }
+ if (nb_perimeter_layers_for_solid_fill > 1 && i - idx_layer < nb_perimeter_layers_for_solid_fill) {
+ polygons_append(max_perimeter_shell, cache.top_perimeter_surfaces);
+ max_perimeter_shell = union_(max_perimeter_shell, false);
+ }
+ }
}
}
if (int n_bottom_layers = region_config.bottom_solid_layers.value; n_bottom_layers > 0) {
@@ -1719,6 +1752,16 @@ namespace Slic3r {
// than running the union_ all at once.
shell = union_(shell, false);
}
+ if (nb_perimeter_layers_for_solid_fill != 0) {
+ if (!cache.bottom_fill_surfaces.empty()) {
+ polygons_append(fill_shell, cache.bottom_fill_surfaces);
+ fill_shell = union_(fill_shell, false);
+ }
+ if (nb_perimeter_layers_for_solid_fill > 1 && idx_layer - i < nb_perimeter_layers_for_solid_fill) {
+ polygons_append(max_perimeter_shell, cache.bottom_perimeter_surfaces);
+ max_perimeter_shell = union_(max_perimeter_shell, false);
+ }
+ }
}
}
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
@@ -1779,11 +1822,27 @@ namespace Slic3r {
svg.Close();
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
-
// Trim the shells region by the internal & internal void surfaces.
const SurfaceType surfaceTypesInternal[] = { stPosInternal | stDensSparse, stPosInternal | stDensVoid, stPosInternal | stDensSolid };
const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3));
- shell = intersection(shell, polygonsInternal, true);
+ {
+ Polygons shell_internal = intersection(shell, polygonsInternal, true);
+ //check if a polygon is only over perimeter, in this case evict it (depends from nb_perimeter_layers_for_solid_fill value)
+ if (nb_perimeter_layers_for_solid_fill != 0) {
+ for (int i = 0; i < shell_internal.size(); i++) {
+ if (intersection({ shell_internal[i] }, fill_shell, false).empty()) {
+ if (nb_perimeter_layers_for_solid_fill < 2 || intersection({ shell_internal[i] }, max_perimeter_shell, false).empty()) {
+ shell_internal.erase(shell_internal.begin() + i);
+ i--;
+ }
+ }
+ }
+ if (shell_internal.empty())
+ continue;
+ }
+
+ shell = std::move(shell_internal);
+ }
polygons_append(shell, diff(polygonsInternal, holes));
if (shell.empty())
continue;
@@ -2112,7 +2171,7 @@ namespace Slic3r {
static void apply_to_print_region_config(PrintRegionConfig& out, const DynamicPrintConfig& in)
{
- // 1) Copy the "extruder key to infill_extruder and perimeter_extruder.
+ // 1) Copy the "extruder" key to infill_extruder and perimeter_extruder.
std::string sextruder = "extruder";
auto* opt_extruder = in.opt<ConfigOptionInt>(sextruder);
if (opt_extruder) {
@@ -2322,7 +2381,7 @@ namespace Slic3r {
}
std::vector<ExPolygons> expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode, slicing_mode_normal_below_layer, SlicingMode::Regular);
//scale for shrinkage
- const size_t extruder_id = this->print()->regions()[region_id]->extruder(FlowRole::frPerimeter) - 1;
+ const size_t extruder_id = this->print()->regions()[region_id]->extruder(FlowRole::frPerimeter, *this) - 1;
double scale = print()->config().filament_shrink.get_abs_value(extruder_id, 1);
if (scale != 1) {
scale = 1 / scale;
@@ -2375,7 +2434,7 @@ namespace Slic3r {
}
//scale for shrinkage
for (SlicedVolume& sv : sliced_volumes) {
- double scale = print()->config().filament_shrink.get_abs_value(this->print()->regions()[sv.region_id]->extruder(FlowRole::frPerimeter) - 1, 1);
+ double scale = print()->config().filament_shrink.get_abs_value(this->print()->regions()[sv.region_id]->extruder(FlowRole::frPerimeter, *this) - 1, 1);
if (scale != 1) {
scale = 1 / scale;
for (ExPolygons& polys : sv.expolygons_by_layer)
@@ -2500,9 +2559,13 @@ namespace Slic3r {
float hole_delta = inner_delta + float(scale_(m_config.hole_size_compensation.value));
//FIXME only apply the compensation if no raft is enabled.
float first_layer_compensation = 0.f;
- if (layer_id == 0 && m_config.raft_layers == 0 && m_config.first_layer_size_compensation.value != 0) {
+ int first_layers = m_config.first_layer_size_compensation_layers.value;
+ if (layer_id < first_layers && m_config.raft_layers == 0 && m_config.first_layer_size_compensation.value != 0) {
// Only enable Elephant foot compensation if printing directly on the print bed.
first_layer_compensation = float(scale_(m_config.first_layer_size_compensation.value));
+ // reduce first_layer_compensation for every layer over the first one.
+ first_layer_compensation = (first_layers - layer_id + 1) * first_layer_compensation / float(first_layers);
+ // simplify compensations if possible
if (first_layer_compensation > 0) {
outter_delta += first_layer_compensation;
inner_delta += first_layer_compensation;
@@ -2537,7 +2600,7 @@ namespace Slic3r {
expolygons = _shrink_contour_holes(std::max(0.f, outter_delta), std::max(0.f, inner_delta), std::max(0.f, hole_delta), expolygons);
}
// Apply the elephant foot compensation.
- if (layer_id == 0 && first_layer_compensation != 0.f) {
+ if (layer_id < first_layers && first_layer_compensation != 0.f) {
expolygons = union_ex(Slic3r::elephant_foot_compensation(expolygons, layerm->flow(frExternalPerimeter),
unscale<double>(-first_layer_compensation)));
}
@@ -2591,7 +2654,7 @@ namespace Slic3r {
// Apply the negative XY compensation. (the ones that is <0)
ExPolygons trimming;
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
- if (layer_id == 0 && first_layer_compensation < 0.f) {
+ if (layer_id < first_layers && first_layer_compensation < 0.f) {
ExPolygons expolygons_first_layer = offset_ex(layer->merged(eps), -eps);
trimming = Slic3r::elephant_foot_compensation(expolygons_first_layer,
layer->regions().front()->flow(frExternalPerimeter), unscale<double>(-first_layer_compensation));
@@ -2929,6 +2992,16 @@ namespace Slic3r {
return this->slice_volumes(zs, SlicingMode::Regular, volumes);
}
+//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
+static void fix_mesh_connectivity(TriangleMesh &mesh)
+{
+ auto nr_degenerated = mesh.stl.stats.degenerate_facets;
+ stl_check_facets_exact(&mesh.stl);
+ if (nr_degenerated != mesh.stl.stats.degenerate_facets)
+ // stl_check_facets_exact() removed some newly degenerated faces. Some faces could become degenerate after some mesh transformation.
+ stl_generate_shared_vertices(&mesh.stl, mesh.its);
+}
+
std::vector<ExPolygons> PrintObject::slice_volumes(
const std::vector<float>& z,
SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below,
@@ -2941,10 +3014,8 @@ namespace Slic3r {
TriangleMesh mesh(volumes.front()->mesh());
mesh.transform(volumes.front()->get_matrix(), true);
assert(mesh.repaired);
- if (volumes.size() == 1 && mesh.repaired) {
- //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
- stl_check_facets_exact(&mesh.stl);
- }
+ if (volumes.size() == 1 && mesh.repaired)
+ fix_mesh_connectivity(mesh);
for (size_t idx_volume = 1; idx_volume < volumes.size(); ++idx_volume) {
const ModelVolume& model_volume = *volumes[idx_volume];
TriangleMesh vol_mesh(model_volume.mesh());
@@ -2977,10 +3048,8 @@ namespace Slic3r {
//FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them.
TriangleMesh mesh(volume.mesh());
mesh.transform(volume.get_matrix(), true);
- if (mesh.repaired) {
- //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
- stl_check_facets_exact(&mesh.stl);
- }
+ if (mesh.repaired)
+ fix_mesh_connectivity(mesh);
if (mesh.stl.stats.number_of_facets > 0) {
mesh.transform(m_trafo, true);
// apply XY shift
@@ -3253,10 +3322,12 @@ namespace Slic3r {
coordf_t print_z = layer->print_z;
coordf_t bottom_z = layer->bottom_z();
+ // 0: topSolid, 1: botSolid, 2: boSolidBridged
for (size_t idx_surface_type = 0; idx_surface_type < 3; ++idx_surface_type) {
m_print->throw_if_canceled();
SurfaceType type = (idx_surface_type == 0) ? (stPosTop | stDensSolid) :
- ((idx_surface_type == 1) ? (stPosBottom | stDensSolid) : (stPosBottom | stDensSolid | stModBridge));
+ ((idx_surface_type == 1) ? (stPosBottom | stDensSolid) :
+ (stPosBottom | stDensSolid | stModBridge));
int num_solid_layers = ((type & stPosTop) == stPosTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value;
if (num_solid_layers == 0)
continue;
@@ -3273,16 +3344,17 @@ namespace Slic3r {
// Surfaces including the area of perimeters. Everything, that is visible from the top / bottom
// (not covered by a layer above / below).
// This does not contain the areas covered by perimeters!
- Polygons solid;
+ ExPolygons solid;
for (const Surface& surface : layerm->slices().surfaces)
if (surface.surface_type == type)
- polygons_append(solid, to_polygons(surface.expolygon));
+ solid.push_back(surface.expolygon);
// Infill areas (slices without the perimeters).
for (const Surface& surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == type)
- polygons_append(solid, to_polygons(surface.expolygon));
+ solid.push_back(surface.expolygon);
if (solid.empty())
continue;
+ solid = union_ex(solid);
// Slic3r::debugf "Layer %d has %s surfaces\n", $i, (($type & stTop) != 0) ? 'top' : 'bottom';
// Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking.
@@ -3308,13 +3380,14 @@ namespace Slic3r {
// narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
// upper perimeter as an obstacle and shell will not be propagated to more upper layers
//FIXME How does it work for stInternalBRIDGE? This is set for sparse infill. Likely this does not work.
- Polygons new_internal_solid;
+ ExPolygons new_internal_solid;
{
- Polygons internal;
+ ExPolygons internal;
for (const Surface& surface : neighbor_layerm->fill_surfaces.surfaces)
if (surface.has_pos_internal() && (surface.has_fill_sparse() || surface.has_fill_solid()))
- polygons_append(internal, to_polygons(surface.expolygon));
- new_internal_solid = intersection(solid, internal, true);
+ internal.push_back(surface.expolygon);
+ internal = union_ex(internal);
+ new_internal_solid = intersection_ex(solid, internal, true);
}
if (new_internal_solid.empty()) {
// No internal solid needed on this layer. In order to decide whether to continue
@@ -3339,18 +3412,23 @@ namespace Slic3r {
// and it's not wanted in a hollow print even if it would make sense when
// obeying the solid shell count option strictly (DWIM!)
float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
- Polygons too_narrow = diff(
+ ExPolygons too_narrow = diff_ex(
new_internal_solid,
- offset2(new_internal_solid, -margin, +margin, jtMiter, 5),
+ offset2_ex(new_internal_solid, -margin, +margin, jtMiter, 5),
true);
// Trim the regularized region by the original region.
if (!too_narrow.empty())
- new_internal_solid = solid = diff(new_internal_solid, too_narrow);
+ if (!too_narrow.empty()) {
+ solid = new_internal_solid = diff_ex(new_internal_solid, too_narrow);
+ }
}
+
+ //merill: this is creating artifacts, and i can't recreate the issue it wants to fix.
+
// make sure the new internal solid is wide enough, as it might get collapsed
// when spacing is added in Fill.pm
- {
+ if(false){
//FIXME Vojtech: Disable this and you will be sorry.
// https://github.com/prusa3d/PrusaSlicer/issues/26 bottom
float margin = 3.f * layerm->flow(frSolidInfill).scaled_width(); // require at least this size
@@ -3359,28 +3437,28 @@ namespace Slic3r {
// get a triangle in $too_narrow; if we grow it below then the shell
// would have a different shape from the external surface and we'd still
// have the same angle, so the next shell would be grown even more and so on.
- Polygons too_narrow = diff(
+ ExPolygons too_narrow = diff_ex(
new_internal_solid,
- offset2(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5),
+ offset2_ex(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5),
true);
if (!too_narrow.empty()) {
// grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this
// additional area in the next shell too
// make sure our grown surfaces don't exceed the fill area
- Polygons internal;
+ ExPolygons internal;
for (const Surface& surface : neighbor_layerm->fill_surfaces.surfaces)
if (surface.has_pos_internal() && !surface.has_mod_bridge())
- polygons_append(internal, to_polygons(surface.expolygon));
- polygons_append(new_internal_solid,
- intersection(
- offset(too_narrow, +margin),
+ internal.push_back(surface.expolygon);
+ expolygons_append(new_internal_solid,
+ intersection_ex(
+ offset_ex(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we can't
// remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge
// is grown, and that little space is an internal solid shell so
// it triggers this too_narrow logic.)
- internal));
+ union_ex(internal)));
// see https://github.com/prusa3d/PrusaSlicer/pull/3426
// solid = new_internal_solid;
}
@@ -3389,30 +3467,31 @@ namespace Slic3r {
// internal-solid are the union of the existing internal-solid surfaces
// and new ones
SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces);
- polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stPosInternal | stDensSolid)));
+ expolygons_append(new_internal_solid, to_expolygons(backup.filter_by_type(stPosInternal | stDensSolid)));
ExPolygons internal_solid = union_ex(new_internal_solid, false);
// assign new internal-solid surfaces to layer
neighbor_layerm->fill_surfaces.set(internal_solid, stPosInternal | stDensSolid);
// subtract intersections from layer surfaces to get resulting internal surfaces
- Polygons polygons_internal = to_polygons(std::move(internal_solid));
+ //ExPolygons polygons_internal = to_polygons(std::move(internal_solid));
ExPolygons internal = diff_ex(
- to_polygons(backup.filter_by_type(stPosInternal | stDensSparse)),
- polygons_internal,
+ to_expolygons(backup.filter_by_type(stPosInternal | stDensSparse)),
+ internal_solid,
true);
// assign resulting internal surfaces to layer
neighbor_layerm->fill_surfaces.append(internal, stPosInternal | stDensSparse);
- polygons_append(polygons_internal, to_polygons(std::move(internal)));
+ expolygons_append(internal_solid, internal);
// assign top and bottom surfaces to layer
SurfaceType surface_types_solid[] = { stPosTop | stDensSolid, stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge };
backup.keep_types(surface_types_solid, 3);
//backup.keep_types_flag(stPosTop | stPosBottom);
std::vector<SurfacesPtr> top_bottom_groups;
backup.group(&top_bottom_groups);
- for (SurfacesPtr& group : top_bottom_groups)
+ for (SurfacesPtr& group : top_bottom_groups) {
neighbor_layerm->fill_surfaces.append(
- diff_ex(to_polygons(group), polygons_internal),
+ diff_ex(to_expolygons(group), union_ex(internal_solid)),
// Use an existing surface as a template, it carries the bridge angle etc.
*group.front());
+ }
}
EXTERNAL:;
} // foreach type (stTop, stBottom, stBottomBridge)
diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp
index 21f5d5586..e62b34ea6 100644
--- a/src/libslic3r/PrintRegion.cpp
+++ b/src/libslic3r/PrintRegion.cpp
@@ -4,7 +4,7 @@
namespace Slic3r {
// 1-based extruder identifier for this region and role.
-uint16_t PrintRegion::extruder(FlowRole role) const
+uint16_t PrintRegion::extruder(FlowRole role, const PrintObject& object) const
{
size_t extruder = 0;
if (role == frPerimeter || role == frExternalPerimeter)
@@ -13,6 +13,10 @@ uint16_t PrintRegion::extruder(FlowRole role) const
extruder = m_config.infill_extruder;
else if (role == frSolidInfill || role == frTopSolidInfill)
extruder = m_config.solid_infill_extruder;
+ else if (role == frSupportMaterial)
+ extruder = object.config().support_material_extruder;
+ else if (role == frSupportMaterialInterface)
+ extruder = object.config().support_material_interface_extruder;
else
throw Slic3r::InvalidArgument("Unknown role");
return extruder;
@@ -50,10 +54,48 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
- double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1);
+ double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role, object) - 1);
return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio.get_abs_value(1) : 0.0f);
}
+float PrintRegion::width(FlowRole role, bool first_layer, const PrintObject& object) const
+{
+ const ConfigOptionFloatOrPercent* config_width = nullptr;
+ // otherwise, get extrusion width from configuration
+ // (might be an absolute value, or a percent value, or zero for auto)
+ if (first_layer && object.config().first_layer_extrusion_width.value > 0) {
+ config_width = &object.config().first_layer_extrusion_width;
+ } else if (role == frExternalPerimeter) {
+ config_width = &m_config.external_perimeter_extrusion_width;
+ } else if (role == frPerimeter) {
+ config_width = &m_config.perimeter_extrusion_width;
+ } else if (role == frInfill) {
+ config_width = &m_config.infill_extrusion_width;
+ } else if (role == frSolidInfill) {
+ config_width = &m_config.solid_infill_extrusion_width;
+ } else if (role == frTopSolidInfill) {
+ config_width = &m_config.top_infill_extrusion_width;
+ } else if (role == frSupportMaterial || role == frSupportMaterialInterface) {
+ config_width = &object.config().support_material_extrusion_width;
+ } else {
+ throw Slic3r::InvalidArgument("Unknown role");
+ }
+
+ if (!config_width || config_width->value == 0)
+ config_width = &object.config().extrusion_width;
+
+ // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
+ // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
+ double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role, object) - 1);
+ if (config_width->value <= 0.) {
+ // If user left option to 0, calculate a sane default width.
+ return Flow::auto_extrusion_width(role, nozzle_diameter);
+ } else {
+ // If user set a manual value, use it.
+ return float(config_width->get_abs_value(nozzle_diameter));
+ }
+}
+
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
{
return (print_config.nozzle_diameter.get_at(m_config.perimeter_extruder.value - 1) +
diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp
index 6eff2ada0..f7e7d5bca 100644
--- a/src/libslic3r/ShortestPath.cpp
+++ b/src/libslic3r/ShortestPath.cpp
@@ -1547,6 +1547,11 @@ static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vect
assert(edges_in.size() == edges_out.size());
}
+// Worst time complexity: O(min(n, 100) * (n * log n + n^2)
+// Expected time complexity: O(min(n, 100) * (n * log n + k * n)
+// where n is the number of edges and k is the number of connection_lengths candidates after the first one
+// is found that improves the total cost.
+//FIXME there are likley better heuristics to lower the time complexity.
static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
{
if (edges.size() < 2)
@@ -1556,7 +1561,8 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl
std::vector<FlipEdge> edges_tmp(edges);
std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0));
std::vector<char> connection_tried(edges.size(), false);
- for (size_t iter = 0; iter < edges.size(); ++ iter) {
+ const size_t max_iterations = std::min(edges.size(), size_t(100));
+ for (size_t iter = 0; iter < max_iterations; ++ iter) {
// Initialize connection costs and connection lengths.
for (size_t i = 1; i < edges.size(); ++ i) {
const FlipEdge &e1 = edges[i - 1];
@@ -1621,6 +1627,8 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl
}
}
+#if 0
+// Currently not used, too slow.
static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
{
if (edges.size() < 3) {
@@ -1703,6 +1711,7 @@ static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<
}
}
}
+#endif
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign> Matrixd;
@@ -1774,6 +1783,8 @@ static inline std::pair<double, size_t> minimum_crossover_cost(
return std::make_pair(cost_min, flip_min);
}
+#if 0
+// Currently not used, too slow.
static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector<FlipEdge> &edges)
{
if (edges.size() < 3) {
@@ -1870,8 +1881,11 @@ static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector
}
}
}
+#endif
// Flip the sequences of polylines to lower the total length of connecting lines.
+// Used by the infill generator if the infill is not connected with perimeter lines
+// and to order the brim lines.
static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polylines &polylines, bool fixed_start)
{
#ifndef NDEBUG
@@ -1923,6 +1937,7 @@ static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polyl
#endif /* NDEBUG */
}
+// Used to optimize order of infill lines and brim lines.
Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
{
#ifdef DEBUG_SVG_OUTPUT
diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp
index f9be1f494..665b930ba 100644
--- a/src/libslic3r/Utils.hpp
+++ b/src/libslic3r/Utils.hpp
@@ -226,7 +226,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER
return container[next_idx_modulo(idx, container.size())];
}
-extern std::string xml_escape(std::string text);
+extern std::string xml_escape(std::string text, bool is_marked = false);
#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__
diff --git a/src/libslic3r/enum_bitmask.hpp b/src/libslic3r/enum_bitmask.hpp
new file mode 100644
index 000000000..4c2076313
--- /dev/null
+++ b/src/libslic3r/enum_bitmask.hpp
@@ -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_
diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp
index a1d6da5fe..0135fd712 100644
--- a/src/libslic3r/pchheader.hpp
+++ b/src/libslic3r/pchheader.hpp
@@ -111,6 +111,7 @@
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Config.hpp"
+#include "enum_bitmask.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "MultiPoint.hpp"
diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp
index 0d03de17b..ef77f2263 100644
--- a/src/libslic3r/utils.cpp
+++ b/src/libslic3r/utils.cpp
@@ -424,6 +424,53 @@ std::error_code rename_file(const std::string &from, const std::string &to)
}
#ifdef __linux__
+// Copied from boost::filesystem.
+// Called by copy_file_linux() in case linux sendfile() API is not supported.
+int copy_file_linux_read_write(int infile, int outfile, uintmax_t file_size)
+{
+ std::vector<char> buf(
+ // Prefer the buffer to be larger than the file size so that we don't have
+ // to perform an extra read if the file fits in the buffer exactly.
+ std::clamp<size_t>(file_size + (file_size < ~static_cast<uintmax_t >(0u)),
+ // Min and max buffer sizes are selected to minimize the overhead from system calls.
+ // The values are picked based on coreutils cp(1) benchmarking data described here:
+ // https://github.com/coreutils/coreutils/blob/d1b0257077c0b0f0ee25087efd46270345d1dd1f/src/ioblksize.h#L23-L72
+ 8u * 1024u, 256u * 1024u),
+ 0);
+
+#if defined(POSIX_FADV_SEQUENTIAL)
+ ::posix_fadvise(infile, 0, 0, POSIX_FADV_SEQUENTIAL);
+#endif
+
+ // Don't use file size to limit the amount of data to copy since some filesystems, like procfs or sysfs,
+ // provide files with generated content and indicate that their size is zero or 4096. Just copy as much data
+ // as we can read from the input file.
+ while (true) {
+ ssize_t sz_read = ::read(infile, buf.data(), buf.size());
+ if (sz_read == 0)
+ break;
+ if (sz_read < 0) {
+ int err = errno;
+ if (err == EINTR)
+ continue;
+ return err;
+ }
+ // Allow for partial writes - see Advanced Unix Programming (2nd Ed.),
+ // Marc Rochkind, Addison-Wesley, 2004, page 94
+ for (ssize_t sz_wrote = 0; sz_wrote < sz_read;) {
+ ssize_t sz = ::write(outfile, buf.data() + sz_wrote, static_cast<std::size_t>(sz_read - sz_wrote));
+ if (sz < 0) {
+ int err = errno;
+ if (err == EINTR)
+ continue;
+ return err;
+ }
+ sz_wrote += sz;
+ }
+ }
+ return 0;
+}
+
// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes,
// for example ChromeOS Linux integration or FlashAIR WebDAV.
// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept,
@@ -465,7 +512,7 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste
err = errno;
goto fail;
}
-
+
const mode_t from_mode = from_stat.st_mode;
if (!S_ISREG(from_mode)) {
err = ENOSYS;
@@ -520,6 +567,19 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste
ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy);
if (sz < 0) {
err = errno;
+ if (offset == 0u) {
+ // sendfile may fail with EINVAL if the underlying filesystem does not support it.
+ // See https://patchwork.kernel.org/project/linux-nfs/patch/20190411183418.4510-1-olga.kornievskaia@gmail.com/
+ // https://bugzilla.redhat.com/show_bug.cgi?id=1783554.
+ // https://github.com/boostorg/filesystem/commit/4b9052f1e0b2acf625e8247582f44acdcc78a4ce
+ if (err == EINVAL || err == EOPNOTSUPP) {
+ err = copy_file_linux_read_write(infile.fd, outfile.fd, from_stat.st_size);
+ if (err < 0)
+ goto fail;
+ // Succeeded.
+ break;
+ }
+ }
if (err == EINTR)
continue;
if (err == 0)
@@ -529,7 +589,6 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste
offset += sz;
}
}
-
// If we created a new file with an explicitly added S_IWUSR permission,
// we may need to update its mode bits to match the source file.
if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) {
@@ -781,7 +840,7 @@ unsigned get_current_pid()
#endif
}
-std::string xml_escape(std::string text)
+std::string xml_escape(std::string text, bool is_marked/* = false*/)
{
std::string::size_type pos = 0;
for (;;)
@@ -796,8 +855,8 @@ std::string xml_escape(std::string text)
case '\"': replacement = "&quot;"; break;
case '\'': replacement = "&apos;"; break;
case '&': replacement = "&amp;"; break;
- case '<': replacement = "&lt;"; break;
- case '>': replacement = "&gt;"; break;
+ case '<': replacement = is_marked ? "<" :"&lt;"; break;
+ case '>': replacement = is_marked ? ">" :"&gt;"; break;
default: break;
}
diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp
index 47b8384ec..0ded575c8 100644
--- a/src/slic3r/Config/Snapshot.cpp
+++ b/src/slic3r/Config/Snapshot.cpp
@@ -8,14 +8,23 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include <boost/filesystem/operations.hpp>
+#include <boost/log/trivial.hpp>
#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/format.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Time.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/FileParserError.hpp"
#include "libslic3r/Utils.hpp"
+#include "../GUI/GUI.hpp"
+#include "../GUI/GUI_App.hpp"
+#include "../GUI/I18N.hpp"
+#include "../GUI/MainFrame.hpp"
+
+#include <wx/richmsgdlg.h>
+
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
@@ -362,7 +371,8 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (Slic3r::is_ini_file(dir_entry))
- boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
+ if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
+ throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
}
static void delete_existing_ini_files(const boost::filesystem::path &path)
@@ -413,7 +423,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, ForwardCompatibilitySubstitutionRule::EnableSilent);
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
@@ -433,14 +443,27 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
}
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
- boost::filesystem::create_directory(snapshot_dir);
-
- // Backup the presets.
- for (const char *subdir : snapshot_subdirs)
- copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
- snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
- assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
- m_snapshots.emplace_back(std::move(snapshot));
+
+ try {
+ boost::filesystem::create_directory(snapshot_dir);
+
+ // Backup the presets.
+ for (const char *subdir : snapshot_subdirs)
+ copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
+ snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
+ assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
+ m_snapshots.emplace_back(std::move(snapshot));
+ } catch (...) {
+ if (boost::filesystem::is_directory(snapshot_dir)) {
+ try {
+ // Clean up partially copied snapshot.
+ boost::filesystem::remove_all(snapshot_dir);
+ } catch (...) {
+ BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
+ }
+ }
+ throw;
+ }
return m_snapshots.back();
}
@@ -551,6 +574,32 @@ SnapshotDB& SnapshotDB::singleton()
return instance;
}
+const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
+{
+ try {
+ return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
+ } catch (std::exception &err) {
+ show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
+ _L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
+ return nullptr;
+ }
+}
+
+bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
+{
+ try {
+ SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
+ return true;
+ } catch (std::exception &err) {
+ wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
+ _L("SuperSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
+ _L("SuperSlicer error"),
+ wxYES_NO);
+ dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
+ return dlg.ShowModal() == wxID_YES;
+ }
+}
+
} // namespace Config
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/Config/Snapshot.hpp b/src/slic3r/Config/Snapshot.hpp
index 48add8a1a..f45300633 100644
--- a/src/slic3r/Config/Snapshot.hpp
+++ b/src/slic3r/Config/Snapshot.hpp
@@ -127,6 +127,13 @@ private:
std::vector<Snapshot> m_snapshots;
};
+// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr.
+const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
+
+// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond.
+// Return true on success and on "Continue" to continue with the process (for example installation of presets).
+bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message);
+
} // namespace Config
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/CalibrationBedDialog.cpp b/src/slic3r/GUI/CalibrationBedDialog.cpp
index 781ad2bb6..9d3a2efe8 100644
--- a/src/slic3r/GUI/CalibrationBedDialog.cpp
+++ b/src/slic3r/GUI/CalibrationBedDialog.cpp
@@ -51,7 +51,7 @@ void CalibrationBedDialog::create_geometry(wxCommandEvent& event_args) {
Slic3r::resources_dir()+"/calibration/bed_leveling/patch.amf"}, true, false, false);
assert(objs_idx.size() == 5);
- const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
+ const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
/// --- scale ---
@@ -136,10 +136,10 @@ void CalibrationBedDialog::create_geometry(wxCommandEvent& event_args) {
//update plater
GLCanvas3D::set_warning_freeze(false);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config);
plat->on_config_change(new_print_config);
plat->changed_objects(objs_idx);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty();
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty();
//update everything, easier to code.
this->gui_app->obj_list()->update_after_undo_redo();
//if(!plat->is_background_process_update_scheduled())
diff --git a/src/slic3r/GUI/CalibrationBridgeDialog.cpp b/src/slic3r/GUI/CalibrationBridgeDialog.cpp
index 0689c8bb3..97e13a0a2 100644
--- a/src/slic3r/GUI/CalibrationBridgeDialog.cpp
+++ b/src/slic3r/GUI/CalibrationBridgeDialog.cpp
@@ -79,7 +79,7 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool
std::vector<size_t> objs_idx = plat->load_files(items, true, false, false);
assert(objs_idx.size() == nb_items);
- const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
+ const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
/// --- scale ---
@@ -146,10 +146,10 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool
//update plater
GLCanvas3D::set_warning_freeze(false);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config);
plat->on_config_change(new_print_config);
plat->changed_objects(objs_idx);
- //this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty();
+ //this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty();
//update everything, easier to code.
ObjectList* obj = this->gui_app->obj_list();
obj->update_after_undo_redo();
diff --git a/src/slic3r/GUI/CalibrationCubeDialog.cpp b/src/slic3r/GUI/CalibrationCubeDialog.cpp
index b5b737527..24c3243cf 100644
--- a/src/slic3r/GUI/CalibrationCubeDialog.cpp
+++ b/src/slic3r/GUI/CalibrationCubeDialog.cpp
@@ -66,8 +66,8 @@ void CalibrationCubeDialog::create_geometry(std::string calibration_path) {
Slic3r::resources_dir()+"/calibration/cube/"+ calibration_path}, true, false, false);
assert(objs_idx.size() == 1);
- const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
- const DynamicPrintConfig* filamentConfig = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config();
+ const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
+ const DynamicPrintConfig* filamentConfig = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config();
const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
/// --- scale ---
diff --git a/src/slic3r/GUI/CalibrationFlowDialog.cpp b/src/slic3r/GUI/CalibrationFlowDialog.cpp
index 9cfe37de9..74858ac0d 100644
--- a/src/slic3r/GUI/CalibrationFlowDialog.cpp
+++ b/src/slic3r/GUI/CalibrationFlowDialog.cpp
@@ -58,7 +58,7 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) {
assert(objs_idx.size() == 5);
- const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
+ const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
/// --- scale ---
@@ -162,10 +162,10 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) {
//update plater
GLCanvas3D::set_warning_freeze(false);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config);
plat->on_config_change(new_print_config);
plat->changed_objects(objs_idx);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty();
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty();
//update everything, easier to code.
ObjectList* obj = this->gui_app->obj_list();
obj->update_after_undo_redo();
diff --git a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp
index 7f2be5240..e0a12e612 100644
--- a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp
+++ b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp
@@ -63,7 +63,7 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) {
Slic3r::resources_dir()+"/calibration/over-bridge_tuning/over-bridge_flow_ratio_test.amf"}, true, false, false);
assert(objs_idx.size() == 6);
- const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
+ const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
/// --- scale ---
@@ -138,7 +138,7 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) {
//update plater
GLCanvas3D::set_warning_freeze(false);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config);
plat->on_config_change(new_print_config);
plat->changed_objects(objs_idx);
//update everything, easier to code.
diff --git a/src/slic3r/GUI/CalibrationRetractionDialog.cpp b/src/slic3r/GUI/CalibrationRetractionDialog.cpp
index 71e111a75..99c45e3a3 100644
--- a/src/slic3r/GUI/CalibrationRetractionDialog.cpp
+++ b/src/slic3r/GUI/CalibrationRetractionDialog.cpp
@@ -39,7 +39,7 @@ void CalibrationRetractionDialog::create_buttons(wxStdDialogButtonSizer* buttons
//start_step = new wxComboBox(this, wxID_ANY, wxString{ "current" }, wxDefaultPosition, wxDefaultSize, 7, choices_start);
//start_step->SetToolTip(_(L("Select the highest temperature to test for.")));
//start_step->SetSelection(0);
- const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config();
+ const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config();
int temp = int((2 + filament_config->option<ConfigOptionInts>("temperature")->get_at(0)) / 5) * 5;
auto size = wxSize(4 * em_unit(), wxDefaultCoord);
temp_start = new wxTextCtrl(this, wxID_ANY, std::to_string(temp), wxDefaultPosition, size);
@@ -77,7 +77,7 @@ void CalibrationRetractionDialog::create_buttons(wxStdDialogButtonSizer* buttons
void CalibrationRetractionDialog::remove_slowdown(wxCommandEvent& event_args) {
- const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config();
+ const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config();
DynamicPrintConfig new_filament_config = *filament_config; //make a copy
const ConfigOptionInts *fil_conf = filament_config->option<ConfigOptionInts>("slowdown_below_layer_time");
@@ -93,9 +93,9 @@ void CalibrationRetractionDialog::remove_slowdown(wxCommandEvent& event_args) {
new_fil_conf->values[0] = 0;
new_filament_config.set_key_value("fan_below_layer_time", new_fil_conf);
- this->gui_app->get_tab(Preset::TYPE_FILAMENT)->load_config(new_filament_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->load_config(new_filament_config);
this->main_frame->plater()->on_config_change(new_filament_config);
- this->gui_app->get_tab(Preset::TYPE_FILAMENT)->update_dirty();
+ this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->update_dirty();
}
@@ -136,9 +136,9 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) {
assert(objs_idx.size() == nb_items);
- const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
+ const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
- const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config();
+ const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config();
double retraction_start = 0;
std::string str = temp_start->GetValue().ToStdString();
@@ -250,8 +250,8 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) {
if (print_config->option<ConfigOptionInt>("skirts")->getInt() > 0 && print_config->option<ConfigOptionInt>("skirt_height")->getInt() > 0) {
new_print_config.set_key_value("complete_objects_one_skirt", new ConfigOptionBool(true));
}
- this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty();
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty();
plat->on_config_change(new_print_config);
}
diff --git a/src/slic3r/GUI/CalibrationTempDialog.cpp b/src/slic3r/GUI/CalibrationTempDialog.cpp
index 3ff38e3eb..42b38f02e 100644
--- a/src/slic3r/GUI/CalibrationTempDialog.cpp
+++ b/src/slic3r/GUI/CalibrationTempDialog.cpp
@@ -65,8 +65,8 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) {
Slic3r::resources_dir()+"/calibration/filament_temp/Smart_compact_temperature_calibration_item.amf"}, true, false, false);
assert(objs_idx.size() == 1);
- const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config();
- const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config();
+ const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config();
+ const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config();
const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();
// -- get temps
@@ -168,12 +168,12 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) {
//update plater
GLCanvas3D::set_warning_freeze(false);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config);
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config);
plat->on_config_change(new_print_config);
//this->gui_app->get_tab(Preset::TYPE_PRINTER)->load_config(new_printer_config);
//plat->on_config_change(new_printer_config);
plat->changed_objects(objs_idx);
- this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty();
+ this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty();
//this->gui_app->get_tab(Preset::TYPE_PRINTER)->update_dirty();
plat->is_preview_shown();
//update everything, easier to code.
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index ef6df2ada..bbfb310aa 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -433,7 +433,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
bool have_default_acceleration = config->option<ConfigOptionFloatOrPercent>("default_acceleration")->value > 0;
for (auto el : { "perimeter_acceleration", "infill_acceleration",
- "bridge_acceleration", "first_layer_acceleration" })
+ "bridge_acceleration", "first_layer_acceleration", "travel_acceleration" })
toggle_field(el, have_default_acceleration);
bool have_skirt = config->opt_int("skirts") > 0;
@@ -483,7 +483,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
bool have_sequential_printing = config->opt_bool("complete_objects");
for (auto el : { /*"extruder_clearance_radius", "extruder_clearance_height",*/ "complete_objects_one_skirt",
- "complete_objects_sort"})
+ "complete_objects_sort", "complete_objects_one_brim"})
toggle_field(el, have_sequential_printing);
bool have_ooze_prevention = config->opt_bool("ooze_prevention");
diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp
index dbc6fa725..1fab1b727 100644
--- a/src/slic3r/GUI/ConfigWizard.cpp
+++ b/src/slic3r/GUI/ConfigWizard.cpp
@@ -59,7 +59,11 @@ 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);
+ // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
+ auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
+ path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
+ // 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;
@@ -2358,7 +2362,7 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
return true;
}
-void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
+bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const auto enabled_vendors = appconfig_new.vendors();
@@ -2407,14 +2411,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
break;
}
- if (snapshot) {
- SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason);
- }
+ if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
+ return false;
if (install_bundles.size() > 0) {
// Install bundles from resources.
// Don't create snapshot - we've already done that above if applicable.
- updater->install_bundles_rsrc(std::move(install_bundles), false);
+ if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
+ return false;
} else {
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
}
@@ -2490,7 +2494,11 @@ 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.
+ // Throw on substitutions in system profiles, as the system profiles provided over the air should be compatible with this PrusaSlicer version.
+ preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, preferred_model);
if (page_custom->custom_wanted()) {
page_firmware->apply_custom_config(*custom_config);
@@ -2504,6 +2512,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
+
+ return true;
}
@@ -2762,7 +2772,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
p->set_start_page(start_page);
if (ShowModal() == wxID_OK) {
- p->apply_config(app.app_config, app.preset_bundle, app.preset_updater);
+ if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
+ return false;
app.app_config->set_legacy_datadir(false);
app.update_mode();
app.obj_manipul()->update_ui_from_settings();
diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp
index 3aba9af8c..79577264a 100644
--- a/src/slic3r/GUI/ConfigWizard_private.hpp
+++ b/src/slic3r/GUI/ConfigWizard_private.hpp
@@ -622,7 +622,7 @@ struct ConfigWizard::priv
bool on_bnt_finish();
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
- void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
+ bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp
index 27e2c1224..ad7fe5d0f 100644
--- a/src/slic3r/GUI/ExtraRenderers.cpp
+++ b/src/slic3r/GUI/ExtraRenderers.cpp
@@ -306,9 +306,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
// to avoid event propagation to other sidebar items
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
evt.StopPropagation();
+#ifdef __linux__
// FinishEditing grabs new selection and triggers config update. We better call
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
this->FinishEditing();
+#endif
});
return c_editor;
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index b0177cd0d..edb81c843 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -1185,6 +1185,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 == "solid_fill_pattern"
|| m_opt_id == "fill_pattern" || m_opt_id == "support_material_interface_pattern" || m_opt_id == "brim_ears_pattern")
val = idx_from_enum_value<InfillPattern>(val);
@@ -1223,6 +1227,8 @@ void Choice::set_value(const boost::any& value, bool change_event)
val = idx_from_enum_value<WipeAlgo>(val);
else if (m_opt_id.compare("output_format") == 0)
val = idx_from_enum_value<OutputFormat>(val);
+ else if (m_opt_id.compare("config_compatibility") == 0)
+ val = idx_from_enum_value<ForwardCompatibilitySubstitutionRule>(val);
field->SetSelection(val);
break;
}
@@ -1279,7 +1285,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);
@@ -1301,7 +1307,10 @@ boost::any& Choice::get_value()
if (m_opt.type == coEnum)
{
- int ret_enum = field->GetSelection();
+ int ret_enum = field->GetSelection();
+ 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
+ ret_enum++;
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern"
|| m_opt_id == "support_material_interface_pattern" || m_opt_id == "fill_pattern" || m_opt_id == "brim_ears_pattern")
convert_to_enum_value<InfillPattern>(ret_enum);
@@ -1340,6 +1349,8 @@ boost::any& Choice::get_value()
convert_to_enum_value<WipeAlgo>(ret_enum);
else if (m_opt_id.compare("output_format") == 0)
convert_to_enum_value<OutputFormat>(ret_enum);
+ else if(m_opt_id.compare("config_compatibility") == 0)
+ convert_to_enum_value<ForwardCompatibilitySubstitutionRule>(ret_enum);
}
else if (m_opt.gui_type == "f_enum_open") {
const int ret_enum = field->GetSelection();
diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp
index 879e7fe34..1b4fafeac 100644
--- a/src/slic3r/GUI/FirmwareDialog.cpp
+++ b/src/slic3r/GUI/FirmwareDialog.cpp
@@ -65,6 +65,8 @@ enum {
USB_PID_MMU_APP = 4,
USB_PID_CW1_BOOT = 7,
USB_PID_CW1_APP = 8,
+ USB_PID_CW1S_BOOT = 14,
+ USB_PID_CW1S_APP = 15,
};
// This enum discriminates the kind of information in EVT_AVRDUDE,
@@ -308,7 +310,7 @@ void FirmwareDialog::priv::update_flash_enabled()
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
- const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
+ const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S;
set_autodetect(autodetect);
}
@@ -636,6 +638,10 @@ void FirmwareDialog::priv::perform_upload()
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
break;
+ case HexFile::DEV_CW1S:
+ this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP));
+ break;
+
default:
this->prepare_mk2();
break;
@@ -761,11 +767,10 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
switch (usb_pid.boot) {
case USB_PID_MMU_BOOT:
return "Original Prusa MMU 2.0 Control";
- break;
case USB_PID_CW1_BOOT:
return "Original Prusa CW1";
- break;
-
+ case USB_PID_CW1S_BOOT:
+ return "Original Prusa CW1S";
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
}
}
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 495e0ddf7..3edd7a578 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -6571,7 +6571,7 @@ void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y);
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation;
- wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
+ wxGetApp().get_tab(Preset::TYPE_FFF_PRINT)->load_config(cfg);
}
diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp
index 3ed8a7021..f810f3e2e 100644
--- a/src/slic3r/GUI/GUI.cpp
+++ b/src/slic3r/GUI/GUI.cpp
@@ -5,6 +5,7 @@
#include <string>
#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp>
#if __APPLE__
@@ -18,6 +19,7 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
+#include "format.hpp"
#include "libslic3r/Print.hpp"
@@ -273,6 +275,127 @@ void warning_catcher(wxWindow* parent, const wxString& message)
msg.ShowModal();
}
+static wxString bold(const wxString& str)
+{
+ return wxString::Format("<b>%s</b>", str);
+};
+
+static wxString bold_string(const wxString& str)
+{
+ return wxString::Format("<b>\"%s\"</b>", str);
+};
+
+static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
+{
+ changes += "<table>";
+ for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
+ wxString new_val;
+ const ConfigOptionDef* def = conf_substitution.opt_def;
+ if (!def)
+ continue;
+ switch (def->type) {
+ case coEnum:
+ {
+ const std::vector<std::string>& labels = def->enum_labels;
+ const std::vector<std::string>& values = def->enum_values;
+ int val = conf_substitution.new_value->getInt();
+
+ bool is_infill = def->opt_key == "top_fill_pattern" ||
+ def->opt_key == "bottom_fill_pattern" ||
+ def->opt_key == "fill_pattern";
+
+ // Each infill doesn't use all list of infill declared in PrintConfig.hpp.
+ // So we should "convert" val to the correct one
+ if (is_infill) {
+ for (const auto& key_val : *def->enum_keys_map)
+ if ((int)key_val.second == val) {
+ auto it = std::find(values.begin(), values.end(), key_val.first);
+ if (it == values.end())
+ break;
+ auto idx = it - values.begin();
+ new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")";
+ break;
+ }
+ if (new_val.IsEmpty()) {
+ assert(false);
+ new_val = _L("Undefined");
+ }
+ }
+ else
+ new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")";
+ break;
+ }
+ case coBool:
+ new_val = conf_substitution.new_value->getBool() ? "true" : "false";
+ break;
+ case coBools:
+ if (conf_substitution.new_value->nullable())
+ for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
+ new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
+ else
+ for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
+ new_val += std::string(v ? "true" : "false") + ", ";
+ if (! new_val.empty())
+ new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
+ break;
+ default:
+ assert(false);
+ }
+
+ changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
+ format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
+ "</td></tr>";
+ }
+ changes += "</table>";
+}
+
+static wxString substitution_message(const wxString& changes)
+{
+ return
+ _L("Most likely the configuration was produced by a newer version of " SLIC3R_APP_NAME " or PrusaSlicer.") + " " +
+ _L("The following values were substituted:") + "\n" + changes + "\n\n" +
+ _L("Review the substitutions and adjust them if needed.");
+}
+
+void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
+{
+ wxString changes;
+
+ auto preset_type_name = [](Preset::Type type) {
+ switch (type) {
+ case Preset::TYPE_FFF_PRINT: return _L("Print settings");
+ case Preset::TYPE_SLA_PRINT: return _L("SLA print settings");
+ case Preset::TYPE_FFF_FILAMENT: return _L("Filament");
+ case Preset::TYPE_SLA_MATERIAL: return _L("SLA material");
+ case Preset::TYPE_PRINTER: return _L("Printer");
+ case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer");
+ default: assert(false); return wxString();
+ }
+ };
+
+ for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
+ changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
+ if (!substitution.preset_file.empty())
+ changes += format_wxstr(" (%1%)", substitution.preset_file);
+
+ add_config_substitutions(substitution.substitutions, changes);
+ }
+
+ InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes));
+ msg.ShowModal();
+}
+
+void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
+{
+ wxString changes = "\n";
+ add_config_substitutions(config_substitutions, changes);
+
+ InfoDialog msg(nullptr,
+ format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
+ substitution_message(changes));
+ msg.ShowModal();
+}
+
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
{
if (comboCtrl == nullptr)
diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp
index a90115933..f80d18344 100644
--- a/src/slic3r/GUI/GUI.hpp
+++ b/src/slic3r/GUI/GUI.hpp
@@ -7,6 +7,7 @@ namespace boost::filesystem { class path; }
#include <wx/string.h>
#include "libslic3r/Config.hpp"
+#include "libslic3r/Preset.hpp"
class wxWindow;
class wxMenuBar;
@@ -49,6 +50,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); }
void warning_catcher(wxWindow* parent, const wxString& message);
+void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions);
+void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename);
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 625fb5197..63a062b5e 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -443,7 +443,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
- /* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
+ /* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S",
+ // Workaround for OSX file picker, for some reason it always saves with the 1st extension.
+ /* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1",
};
std::string out = defaults[file_type];
@@ -634,6 +636,9 @@ 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())
+ show_substitutions_info(this->init_params->preset_substitutions);
+
#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.
@@ -653,6 +658,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)
@@ -721,16 +744,21 @@ void GUI_App::init_app_config()
// Mac : "~/Library/Application Support/Slic3r"
if (data_dir().empty()) {
- #ifndef __linux__
+ //check if there is a "configuration" directory next to the resources
+ if (boost::filesystem::exists(boost::filesystem::path{ resources_dir() } / ".." / "configuration")) {
+ set_data_dir((boost::filesystem::path{ resources_dir() } / ".." / "configuration").string());
+ } else {
+#ifndef __linux__
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
- #else
+#else
// Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}.
// https://github.com/prusa3d/PrusaSlicer/issues/2911
wxString dir;
- if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() )
+ if (!wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty())
dir = wxFileName::GetHomeDir() + wxS("/.config");
set_data_dir((dir + "/" + GetAppName()).ToUTF8().data());
- #endif
+#endif
+ }
}
if (!app_config)
@@ -923,7 +951,10 @@ 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);
+ // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
+ // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
+ // installation of a compatible system preset, thus nullifying the system preset substitutions.
+ init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
} catch (const std::exception &ex) {
show_error(nullptr, ex.what());
}
@@ -976,7 +1007,6 @@ bool GUI_App::on_init_inner()
if (! plater_)
return;
-
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();
@@ -997,33 +1027,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;
@@ -1819,9 +1822,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
child->SetFont(normal_font());
if (dlg.ShowModal() == wxID_OK)
- app_config->set("on_snapshot",
- Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
- *app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
+ if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
+ *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
+ snapshot != nullptr)
+ app_config->set("on_snapshot", snapshot->id);
}
break;
case ConfigMenuSnapshots:
@@ -1832,13 +1836,24 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (!dlg.snapshot_to_activate().empty()) {
- if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
- Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
+ if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) &&
+ ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
+ GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
+ break;
try {
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
- preset_bundle->load_presets(*app_config);
+ // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
+ // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
+ // are known to be creative and mess with the config files in various ways.
+ if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
+ ! all_substitutions.empty())
+ show_substitutions_info(all_substitutions);
+
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
+
+ // update config wizard in respect to the new config
+ update_wizard_from_config();
} catch (std::exception &ex) {
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
}
@@ -2015,6 +2030,17 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
}
}
+void GUI_App::update_wizard_from_config()
+{
+ if (!m_wizard)
+ return;
+ // If ConfigWizard was created before changing of the configuration,
+ // we have to destroy it to have possibility to create it again in respect to the new config's parameters
+ m_wizard->Reparent(nullptr);
+ m_wizard->Destroy();
+ m_wizard = nullptr;
+}
+
bool GUI_App::OnExceptionInMainLoop()
{
generic_exception_handle();
@@ -2175,7 +2201,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
+ if (reason == ConfigWizard::RR_USER)
+ if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD);
+ result == PresetUpdater::R_ALL_CANCELED)
+ return false;
+
if (! m_wizard) {
+ wxBusyCursor wait;
m_wizard = new ConfigWizard(mainframe);
}
@@ -2334,7 +2366,7 @@ void GUI_App::check_updates(const bool verbose)
{
PresetUpdater::UpdateResult updater_result;
try {
- updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
+ updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close();
}
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index ba764badb..3dfb8cc38 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -63,7 +63,9 @@ enum FileType
FT_TEX,
- FT_PNGZIP,
+ FT_SL1,
+ // Workaround for OSX file picker, for some reason it always saves with the 1st extension.
+ FT_SL1S,
FT_SIZE,
};
@@ -226,6 +228,7 @@ public:
bool check_print_host_queue();
bool checked_tab(Tab* tab);
void load_current_presets(bool check_printer_presets = true);
+ void update_wizard_from_config();
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
@@ -250,7 +253,7 @@ public:
// 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 };
@@ -260,7 +263,7 @@ public:
std::mutex not_modal_dialog_mutex;
wxDialog* not_modal_dialog = nullptr;
- PresetUpdater* get_preset_updater() { return preset_updater; }
+ PresetUpdater* get_preset_updater() { return preset_updater; }
wxNotebook* tab_panel() const ;
int extruders_cnt() const;
diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp
index 413bc632d..313dbe201 100644
--- a/src/slic3r/GUI/GUI_Init.cpp
+++ b/src/slic3r/GUI/GUI_Init.cpp
@@ -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()), wxString::Format(_L("%s GUI initialization failed"), SLIC3R_APP_NAME), wxICON_STOP);
diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp
index c420c9554..2adf618a4 100644
--- a/src/slic3r/GUI/GUI_Init.hpp
+++ b/src/slic3r/GUI/GUI_Init.hpp
@@ -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;
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 2559e6f87..670d88661 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -4176,9 +4176,9 @@ void ObjectList::change_part_type()
}
}
- const wxString names[] = { _(L("Part")), _(L("Modifier")), _(L("Support Enforcer")), _(L("Support Blocker")) };
+ const wxString names[] = { _L("Part"), _L("Modifier"), _L("Support Enforcer"), _L("Support Blocker"), _L("Seam Position") };
- auto new_type = ModelVolumeType(wxGetSingleChoiceIndex(_(L("Type:")), _(L("Select type of part")), wxArrayString(4, names), int(type)));
+ auto new_type = ModelVolumeType(wxGetSingleChoiceIndex(_(L("Type:")), _(L("Select type of part")), wxArrayString(5, names), int(type)));
if (new_type == type || new_type == ModelVolumeType::INVALID)
return;
diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
index d8f563da1..6b1a67908 100644
--- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp
+++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp
@@ -69,17 +69,36 @@ void ArrangeJob::clear_input()
m_unprintable.reserve(cunprint /* for optional wti */);
}
+void add_brim(arrangement::ArrangePolygon &ap, const ModelConfigObject &config, const Plater* plater)
+{
+ if (!plater->config()->option("complete_objects_one_brim")->getBool()) {
+ // object-brim increase the size of the object
+ // Should be using the "inflation" field but it's non-functional right now.
+ coord_t diff = scale_(plater->config()->option("brim_width")->getFloat() - plater->config()->option("extruder_clearance_radius")->getFloat() / 2);
+ if (config.option("brim_width"))
+ diff = scale_(config.option("brim_width")->getFloat() - plater->config()->option("extruder_clearance_radius")->getFloat() / 2);
+ if (diff > 0) {
+ ExPolygons brimmed = offset_ex(ap.poly, diff);
+ assert(brimmed.size() == 1);
+ ap.poly = brimmed[0];
+ }
+ }
+}
+
void ArrangeJob::prepare_all() {
clear_input();
for (ModelObject *obj: m_plater->model().objects)
for (ModelInstance *mi : obj->instances) {
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
- cont.emplace_back(get_arrange_poly(PtrWrapper{mi}, m_plater));
+ arrangement::ArrangePolygon &&ap = get_arrange_poly(PtrWrapper{ mi }, m_plater);
+ add_brim(ap, obj->config, m_plater);
+ cont.emplace_back(std::move(ap));
}
- if (auto wti = get_wipe_tower_arrangepoly(*m_plater))
+ if (auto wti = get_wipe_tower_arrangepoly(*m_plater)) {
m_selected.emplace_back(std::move(*wti));
+ }
}
void ArrangeJob::prepare_selected() {
@@ -114,7 +133,8 @@ void ArrangeJob::prepare_selected() {
(inst_sel[i] ? m_selected :
m_unselected) :
m_unprintable;
-
+
+ add_brim(ap, model.objects[oidx]->config, m_plater);
cont.emplace_back(std::move(ap));
}
}
@@ -124,6 +144,7 @@ void ArrangeJob::prepare_selected() {
auto &cont = m_plater->get_selection().is_wipe_tower() ? m_selected :
m_unselected;
+
cont.emplace_back(std::move(ap));
}
diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp
index f5e4c976f..a309bd0d3 100644
--- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp
+++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp
@@ -33,7 +33,7 @@ public:
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
- "SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
+ "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
@@ -113,13 +113,14 @@ public:
Plater *plater;
Sel sel = Sel::modelAndProfile;
-
- TriangleMesh mesh;
- DynamicPrintConfig profile;
- wxString path;
- Vec2i32 win = {2, 2};
- std::string err;
-
+
+ TriangleMesh mesh;
+ DynamicPrintConfig profile;
+ wxString path;
+ Vec2i32 win = {2, 2};
+ std::string err;
+ ConfigSubstitutions config_substitutions;
+
priv(Plater *plt): plater{plt} {}
};
@@ -142,13 +143,13 @@ void SLAImportJob::process()
try {
switch (p->sel) {
case Sel::modelAndProfile:
- import_sla_archive(path, p->win, p->mesh, p->profile, progr);
+ p->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);
+ p->config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
break;
case Sel::profileOnly:
- import_sla_archive(path, p->profile);
+ p->config_substitutions = import_sla_archive(path, p->profile);
break;
}
@@ -181,6 +182,7 @@ void SLAImportJob::prepare()
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8();
p->sel = dlg.get_selection();
p->win = dlg.get_marchsq_windowsize();
+ p->config_substitutions.clear();
} else {
p->path = "";
}
@@ -223,8 +225,11 @@ void SLAImportJob::finalize()
bool is_centered = false;
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name, is_centered);
}
-
+
+ if (! p->config_substitutions.empty())
+ show_substitutions_info(p->config_substitutions, p->path.ToUTF8().data());
+
reset();
}
-}}
+}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 9ef9c43ce..1dac5e694 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -253,6 +253,61 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
}
}
+void MainFrame::update_icon() {
+
+ // icons for ESettingsLayout::Hidden
+ wxImageList* img_list = nullptr;
+ int icon_size = 0;
+ try {
+ icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str());
+ }
+ catch (std::exception e) {}
+ switch (m_layout)
+ {
+ case ESettingsLayout::Unknown:
+ {
+ break;
+ } case ESettingsLayout::Old:
+ case ESettingsLayout::Hidden:
+ {
+ if (m_tabpanel->GetPageCount() == 4 && icon_size >= 8) {
+ m_tabpanel->SetPageImage(0, 0);
+ m_tabpanel->SetPageImage(1, 3);
+ m_tabpanel->SetPageImage(2, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 6 : 4);
+ m_tabpanel->SetPageImage(3, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 7 : 5);
+ }
+ break;
+ }
+ case ESettingsLayout::Tabs:
+ {
+ if (icon_size >= 8)
+ {
+ m_tabpanel->SetPageImage(0, 0);
+ m_tabpanel->SetPageImage(1, 1);
+ m_tabpanel->SetPageImage(2, 2);
+ m_tabpanel->SetPageImage(3, 3);
+ m_tabpanel->SetPageImage(4, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 6 : 4);
+ m_tabpanel->SetPageImage(5, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 7 : 5);
+ }
+ break;
+ }
+ case ESettingsLayout::Dlg:
+ {
+ if (m_tabpanel->GetPageCount() == 4 && icon_size >= 8) {
+ m_tabpanel->SetPageImage(0, 3);
+ m_tabpanel->SetPageImage(1, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 6 : 4);
+ m_tabpanel->SetPageImage(2, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 7 : 5);
+ }
+ break;
+ }
+ case ESettingsLayout::GCodeViewer:
+ {
+ break;
+ }
+ }
+
+}
+
void MainFrame::update_layout()
{
auto restore_to_creation = [this]() {
@@ -262,6 +317,8 @@ void MainFrame::update_layout()
}
};
+ std::cout << "update_layout: " << m_tabpanel->GetPageCount() << "\n";
+
// On Linux m_plater needs to be removed from m_tabpanel before to reparent it
//clear if previous was old
m_tabpanel_stop_event = true;
@@ -360,6 +417,7 @@ void MainFrame::update_layout()
// From the very beginning the Print settings should be selected
m_last_selected_setting_tab = 0;
m_last_selected_plater_tab = 999;
+ std::cout << "update_layout1: " << m_tabpanel->GetPageCount() << "\n";
// Set new settings
switch (m_layout)
@@ -373,29 +431,7 @@ void MainFrame::update_layout()
m_plater->Reparent(m_tabpanel);
m_tabpanel->InsertPage(0, m_plater, _L("Plater"));
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
- // icons for ESettingsLayout::Old
- wxImageList* img_list = nullptr;
- int icon_size = 0;
- try {
- icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str());
- }
- catch (std::exception e) {}
- if (m_tabpanel->GetPageCount() == 4 && icon_size >= 8) {
- std::initializer_list<std::string> icon_list = { "plater", "cog", "spool_cog", "printer_cog" };
- if (icon_size < 16)
- icon_list = { "plater", "cog", "spool", "printer" };
- for (std::string icon_name : icon_list) {
- const wxBitmap& bmp = create_scaled_bitmap(icon_name, this, icon_size);
- if (img_list == nullptr)
- img_list = new wxImageList(bmp.GetWidth(), bmp.GetHeight());
- img_list->Add(bmp);
- }
- m_tabpanel->AssignImageList(img_list);
- m_tabpanel->SetPageImage(0, 0);
- m_tabpanel->SetPageImage(1, 1);
- m_tabpanel->SetPageImage(2, 2);
- m_tabpanel->SetPageImage(3, 3);
- }
+ update_icon();
// show
m_plater->Show();
m_tabpanel->Show();
@@ -407,42 +443,15 @@ void MainFrame::update_layout()
m_plater->enable_view_toolbar(false);
bool need_freeze = !this->IsFrozen();
if(need_freeze) this->Freeze();
- // icons for ESettingsLayout::Tabs
- wxImageList* img_list = nullptr;
- int icon_size = 0;
- try {
- icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str());
- }
- catch (std::exception e) {}
- if (icon_size >= 8) {
- std::initializer_list<std::string> icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool_cog", "printer_cog" };
- if (icon_size < 16)
- icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool", "printer" };
- for (std::string icon_name : icon_list) {
- const wxBitmap& bmp = create_scaled_bitmap(icon_name, this, icon_size);
- if (img_list == nullptr)
- img_list = new wxImageList(bmp.GetWidth(), bmp.GetHeight());
- img_list->Add(bmp);
- }
- }
wxPanel* first_panel = new wxPanel(m_tabpanel);
m_tabpanel->InsertPage(0, first_panel, _L("3D view"));
m_tabpanel->InsertPage(1, new wxPanel(m_tabpanel), _L("Sliced preview"));
m_tabpanel->InsertPage(2, new wxPanel(m_tabpanel), _L("Gcode preview"));
if (m_tabpanel->GetPageCount() == 6) {
- m_tabpanel->AssignImageList(img_list);
m_tabpanel->GetPage(0)->SetSizer(new wxBoxSizer(wxVERTICAL));
m_tabpanel->GetPage(1)->SetSizer(new wxBoxSizer(wxVERTICAL));
m_tabpanel->GetPage(2)->SetSizer(new wxBoxSizer(wxVERTICAL));
- if (icon_size >= 8)
- {
- m_tabpanel->SetPageImage(0, 0);
- m_tabpanel->SetPageImage(1, 1);
- m_tabpanel->SetPageImage(2, 2);
- m_tabpanel->SetPageImage(3, 3);
- m_tabpanel->SetPageImage(4, 4);
- m_tabpanel->SetPageImage(5, 5);
- }
+ update_icon();
}
m_plater->Reparent(first_panel);
first_panel->GetSizer()->Add(m_plater, 1, wxEXPAND);
@@ -460,6 +469,7 @@ void MainFrame::update_layout()
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
m_plater_page = new wxPanel(m_tabpanel);
m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */
+ update_icon();
m_plater->Show();
break;
}
@@ -468,6 +478,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);
+ update_icon();
m_tabpanel->Show();
m_plater->Show();
break;
@@ -602,6 +613,20 @@ void MainFrame::shutdown()
wxGetApp().plater_ = nullptr;
}
+void MainFrame::change_tab(Tab* old_tab, Tab* new_tab)
+{
+ int page_id = m_tabpanel->FindPage(old_tab);
+ if (page_id >= 0 && page_id < m_tabpanel->GetPageCount()) {
+ m_tabpanel->GetPage(page_id)->Show(false);
+ m_tabpanel->RemovePage(page_id);
+ }
+ m_tabpanel->InsertPage(page_id, new_tab, new_tab->title());
+ #ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563)
+ m_tabpanel->GetPage(page_id)->Show(true);
+ #endif // __linux__
+ MainFrame::update_icon();
+}
+
void MainFrame::update_title()
{
wxString title = wxEmptyString;
@@ -653,6 +678,25 @@ void MainFrame::init_tabpanel()
m_tabpanel->Hide();
m_settings_dialog.set_tabpanel(m_tabpanel);
+ // icons for m_tabpanel tabs
+ wxImageList* img_list = nullptr;
+ int icon_size = 0;
+ try {
+ icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str());
+ }
+ catch (std::exception e) {}
+ if (icon_size >= 8) {
+ std::initializer_list<std::string> icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool_cog", "printer_cog", "resin_cog", "sla_printer_cog" };
+ if (icon_size < 16)
+ icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool", "printer", "resin", "sla_printer" };
+ for (std::string icon_name : icon_list) {
+ const wxBitmap& bmp = create_scaled_bitmap(icon_name, this, icon_size);
+ if (img_list == nullptr)
+ img_list = new wxImageList(bmp.GetWidth(), bmp.GetHeight());
+ img_list->Add(bmp);
+ }
+ }
+ m_tabpanel->AssignImageList(img_list);
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) {
if (m_tabpanel_stop_event)
@@ -771,6 +815,7 @@ void MainFrame::init_tabpanel()
if (wxGetApp().is_editor())
create_preset_tabs();
+ std::cout << "create_preset_tabs: " << m_tabpanel->GetPageCount() << "\n";
if (m_plater) {
// load initial config
@@ -1224,7 +1269,7 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
- append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 archive"),
+ append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S archive") + dots, _L("Load an SL1 / Sl1S archive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
@@ -1581,6 +1626,7 @@ void MainFrame::update_menubar()
m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_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)
{
@@ -1703,6 +1749,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()
{
@@ -1785,7 +1832,9 @@ 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())
+ show_substitutions_info(config_substitutions, path);
} catch (const std::exception& ex) {
show_error(this, ex.what());
return false;
@@ -1841,14 +1890,20 @@ 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());
+ // Report all substitutions.
+ std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(
+ file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported, ForwardCompatibilitySubstitutionRule::Enable);
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;
}
+ if (! config_substitutions.empty())
+ show_substitutions_info(config_substitutions);
+
// Load the currently selected preset into the GUI, update the preset selection box.
wxGetApp().load_current_presets();
@@ -1900,11 +1955,11 @@ void MainFrame::select_tab(Tab* tab)
return;
ETabType tab_type = ETabType::LastSettings;
switch (tab->type()) {
- case Preset::Type::TYPE_FILAMENT:
+ case Preset::Type::TYPE_FFF_FILAMENT:
case Preset::Type::TYPE_SLA_MATERIAL:
tab_type = ETabType::FilamentSettings;
break;
- case Preset::Type::TYPE_PRINT:
+ case Preset::Type::TYPE_FFF_PRINT:
case Preset::Type::TYPE_SLA_PRINT:
tab_type = ETabType::PrintSettings;
break;
@@ -1964,7 +2019,7 @@ MainFrame::ETabType MainFrame::selected_tab() const
return ETabType((uint8_t)ETabType::PrintSettings + m_tabpanel->GetSelection() - 1);
}
} else if (m_layout == ESettingsLayout::Dlg) {
- if (!m_main_sizer->IsShown(m_tabpanel)) {
+ if (!m_settings_dialog.GetSizer()->IsShown(m_tabpanel)) {
if (m_plater->is_view3D_shown()) {
return ETabType::Plater3D;
} else {
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index 584f02d5e..167f86d72 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -41,11 +41,11 @@ enum QuickSlice
qsExportPNG = 8
};
-struct PresetTab {
- std::string name;
- Tab* panel;
- PrinterTechnology technology;
-};
+//struct PresetTab {
+// std::string name;
+// Tab* panel;
+// PrinterTechnology technology;
+//};
// ----------------------------------------------------------------------------
// SettingsDialog
@@ -89,6 +89,7 @@ class MainFrame : public DPIFrame
void on_presets_changed(SimpleEvent&);
void on_value_changed(wxCommandEvent&);
+ void update_icon();
bool can_start_new_project() const;
bool can_save() const;
@@ -173,6 +174,7 @@ public:
void create_preset_tabs();
void add_created_tab(Tab* panel);
bool is_active_and_shown_tab(Tab* tab);
+ void change_tab(Tab* old_tab, Tab* new_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?).
void register_win32_callbacks();
@@ -185,7 +187,7 @@ public:
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
ESettingsLayout get_layout() const { return m_layout; }
- void quick_slice(const int qs = qsUndef);
+// void quick_slice(const int qs = qsUndef);
void reslice_now();
void repair_stl();
void export_config(bool to_prusa = false);
diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp
index 36abe4616..992e60f5d 100644
--- a/src/slic3r/GUI/MsgDialog.cpp
+++ b/src/slic3r/GUI/MsgDialog.cpp
@@ -58,7 +58,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
- topsizer->Add(logo, 0, wxALL, BORDER);
+ topsizer->Add(logo, 0, /*wxALL*/wxTOP | wxBOTTOM | wxLEFT, BORDER);
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
SetSizerAndFit(topsizer);
@@ -107,5 +107,57 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
Fit();
}
+
+// InfoDialog
+
+InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg)
+ : MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title)
+ , msg(msg)
+{
+ this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+
+ // Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
+ wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
+ {
+ wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
+ wxFont monospace = wxGetApp().code_font();
+ wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
+ wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+ auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
+ auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
+ const int font_size = font.GetPointSize() - 1;
+ int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
+ html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
+ html->SetBorders(2);
+
+ // calculate html page size from text
+ int lines = msg.Freq('\n');
+
+ if (msg.Contains("<tr>")) {
+ int pos = 0;
+ while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
+ pos = msg.find("<tr>", pos + 1);
+ lines+=2;
+ }
+ }
+ int page_height = std::min((font.GetPixelSize().y + 1) * lines, 68 * wxGetApp().em_unit());
+ wxSize page_size(68 * wxGetApp().em_unit(), page_height);
+
+ html->SetMinSize(page_size);
+
+ std::string msg_escaped = xml_escape(msg.ToUTF8().data(), true);
+ boost::replace_all(msg_escaped, "\r\n", "<br>");
+ boost::replace_all(msg_escaped, "\n", "<br>");
+ html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
+ content_sizer->Add(html, 1, wxEXPAND);
+ }
+
+ // Set info bitmap
+ logo->SetBitmap(create_scaled_bitmap("info", this, 84));
+
+ Fit();
+}
+
+
}
}
diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp
index 70032089b..85d524a44 100644
--- a/src/slic3r/GUI/MsgDialog.hpp
+++ b/src/slic3r/GUI/MsgDialog.hpp
@@ -66,6 +66,22 @@ private:
};
+// Generic info dialog, used for displaying exceptions
+class InfoDialog : public MsgDialog
+{
+public:
+ InfoDialog(wxWindow *parent, const wxString &title, const wxString &msg);
+ InfoDialog(InfoDialog&&) = delete;
+ InfoDialog(const InfoDialog&) = delete;
+ InfoDialog&operator=(InfoDialog&&) = delete;
+ InfoDialog&operator=(const InfoDialog&) = delete;
+ virtual ~InfoDialog() = default;
+
+private:
+ wxString msg;
+};
+
+
}
}
diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp
index 20d5e4ea7..dfca32b7a 100644
--- a/src/slic3r/GUI/NotificationManager.cpp
+++ b/src/slic3r/GUI/NotificationManager.cpp
@@ -9,6 +9,7 @@
#include "libslic3r/AppConfig.hpp"
#include "wxExtensions.hpp"
+#include "libslic3r/Config.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
@@ -36,7 +37,25 @@ wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClicke
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent);
-namespace Notifications_Internal{
+const NotificationManager::NotificationData NotificationManager::basic_notifications[] = {
+ // {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
+ // {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
+ {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
+ // {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
+ // {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
+ {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr){
+ if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }},
+ {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){
+ wxLaunchDefaultBrowser("https://github.com/" SLIC3R_GITHUB "/releases"); return true; }},
+ {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
+ _u8L("You have just added a G-code for color change, but its value is empty.\n"
+ "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
+ //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
+ //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
+ //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
+};
+
+namespace {
ImFont* add_default_font(float pixel_size)
{
ImGuiIO& io = ImGui::GetIO();
@@ -48,7 +67,7 @@ namespace Notifications_Internal{
return font;
}
- static inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
+ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
{
if (fading_out)
ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
@@ -138,8 +157,8 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n,
, m_last_remaining_time (n.duration)
, m_counting_down (n.duration != 0)
, m_text1 (n.text1)
- , m_hypertext (n.hypertext)
- , m_text2 (n.text2)
+ , m_hypertext (n.hypertext)
+ , m_text2 (n.text2)
, m_evt_handler (evt_handler)
, m_notification_start (GLCanvas3D::timestamp_now())
{
@@ -187,26 +206,26 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
// color change based on fading out
bool fading_pop = false;
if (m_fading_out) {
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
- Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
fading_pop = true;
}
// background color
if (m_is_gray) {
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
else if (m_data.level == NotificationLevel::ErrorNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
else if (m_data.level == NotificationLevel::WarningNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
backcolor.y += 0.15f;
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
// name of window - probably indentifies window and is shown so last_end add whitespaces according to id
@@ -304,23 +323,23 @@ NotificationManager::PopNotification::RenderResult NotificationManager::PopNotif
if (m_fading_out) {
if (!m_paused)
m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
- Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
fading_pop = true;
}
// background color
if (m_is_gray) {
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
} else if (m_data.level == NotificationLevel::ErrorNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
} else if (m_data.level == NotificationLevel::WarningNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
backcolor.y += 0.15f;
- Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
@@ -414,7 +433,7 @@ void NotificationManager::PopNotification::init()
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) {
// more than one line till end
int next_space = text.find_first_of(' ', last_end);
- if (next_space > 0) {
+ 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;
@@ -524,7 +543,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
orange_color.y += 0.2f;
//text
- Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
ImGui::SetCursorPosX(text_x);
ImGui::SetCursorPosY(text_y);
imgui.text(text.c_str());
@@ -547,8 +566,8 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img
orange_color.w = 0.8f;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
- Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
- Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
@@ -647,9 +666,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper&
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
- Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
- Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
- Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
//button - if part if treggered
@@ -852,8 +871,9 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper&
for (size_t i = 0; i < m_lines_count; i++) {
if (m_text1.size() >= m_endlines[i]) {
std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
- if (i < m_lines_count - 1)
- last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
+ last_end = m_endlines[i];
+ if (m_text1.size() > m_endlines[i])
+ last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
@@ -881,8 +901,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
orange_color.w = 0.8f;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
- Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
- Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
+ push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
@@ -937,7 +957,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
}
bool NotificationManager::ExportFinishedNotification::on_text_click()
{
- Notifications_Internal::open_folder(m_export_dir_path);
+ open_folder(m_export_dir_path);
return false;
}
//------ProgressBar----------------
@@ -980,10 +1000,10 @@ NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
}
void NotificationManager::push_notification(const NotificationType type, int timestamp)
{
- auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
+ auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications),
boost::bind(&NotificationData::type, boost::placeholders::_1) == type);
- assert(it != basic_notifications.end());
- if (it != basic_notifications.end())
+ assert(it != std::end(basic_notifications));
+ if (it != std::end(basic_notifications))
push_notification_data(*it, timestamp);
}
void NotificationManager::push_notification(const std::string& text, int timestamp)
diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp
index de7204038..ee13ae8da 100644
--- a/src/slic3r/GUI/NotificationManager.hpp
+++ b/src/slic3r/GUI/NotificationManager.hpp
@@ -167,7 +167,7 @@ private:
// Callback for hypertext - returns true if notification should close after triggering
// Usually sends event to UI thread thru wxEvtHandler.
// Examples in basic_notifications.
- std::function<bool(wxEvtHandler*)> callback { nullptr };
+ std::function<bool(wxEvtHandler*)> callback;
const std::string text2;
};
@@ -231,7 +231,7 @@ private:
//returns top in actual frame
float get_current_top() const { return m_top_y; }
const NotificationType get_type() const { return m_data.type; }
- const NotificationData get_data() const { return m_data; }
+ const NotificationData& get_data() const { return m_data; }
const bool is_gray() const { return m_is_gray; }
// Call equals one second down
void substract_remaining_time(int seconds) { m_remaining_time -= seconds; }
@@ -383,10 +383,10 @@ private:
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); }
void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; }
protected:
- virtual void init();
- virtual void render_text(ImGuiWrapper& imgui,
+ void init();
+ void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
- const float win_pos_x, const float win_pos_y);
+ const float win_pos_x, const float win_pos_y) override;
void render_bar(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
@@ -410,20 +410,20 @@ private:
std::string m_export_dir_path;
protected:
// Reserves space on right for more buttons
- virtual void count_spaces() override;
- virtual void render_text(ImGuiWrapper& imgui,
- const float win_size_x, const float win_size_y,
- const float win_pos_x, const float win_pos_y) override;
+ void count_spaces() override;
+ void render_text(ImGuiWrapper& imgui,
+ const float win_size_x, const float win_size_y,
+ const float win_pos_x, const float win_pos_y) override;
// Renders also button to open directory with exported path and eject removable media
- virtual void render_close_button(ImGuiWrapper& imgui,
- const float win_size_x, const float win_size_y,
- const float win_pos_x, const float win_pos_y) override;
+ void render_close_button(ImGuiWrapper& imgui,
+ const float win_size_x, const float win_size_y,
+ const float win_pos_x, const float win_pos_y) override;
void render_eject_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
- virtual void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override
+ void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override
{ m_minimize_b_visible = false; }
- virtual bool on_text_click() override;
+ bool on_text_click() override;
// local time of last hover for showing tooltip
long m_hover_time { 0 };
};
@@ -465,23 +465,7 @@ private:
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
*/
//prepared (basic) notifications
- const std::vector<NotificationData> basic_notifications = {
-// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
-// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
- {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
-// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
-// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
- {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr){
- if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }},
- {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){
- wxLaunchDefaultBrowser("https://github.com/" SLIC3R_GITHUB "/releases"); return true; }},
- {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
- _u8L("You have just added a G-code for color change, but its value is empty.\n"
- "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
- //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
- //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
- //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
- };
+ static const NotificationData basic_notifications[];
};
}//namespace GUI
diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp
index 260126cd1..7035f8f5c 100644
--- a/src/slic3r/GUI/OptionsGroup.cpp
+++ b/src/slic3r/GUI/OptionsGroup.cpp
@@ -977,6 +977,8 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = static_cast<int>(config.option<ConfigOptionEnum<WipeAlgo>>(opt_key)->value);
} else if (opt_key == "output_format") {
ret = static_cast<int>(config.option<ConfigOptionEnum<OutputFormat>>(opt_key)->value);
+ } else if (opt_key == "config_compatibility") {
+ ret = static_cast<int>(config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>(opt_key)->value);
}
}
break;
diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
index 57a8f7700..280b0a045 100644
--- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp
+++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp
@@ -67,7 +67,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
@@ -153,7 +154,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());
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@@ -488,7 +490,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();
@@ -496,19 +498,30 @@ 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 && 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;
+ }
+
// hide api key for klipper
if (opt && opt->value == htKlipper) {
m_optgroup->hide_field("printhost_apikey");
}
- }
+ }
else {
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
m_optgroup->hide_field("host_type");
@@ -538,6 +551,58 @@ 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()
{
return m_printer_name->GetValue();
@@ -665,8 +730,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)
@@ -693,7 +759,8 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer)
this->Layout();
this->Fit();
-}
+ update_host_type(true);
+}
}} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp
index 7ee1f7d92..cb9a48b3e 100644
--- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp
+++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp
@@ -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;
};
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 26f930990..f51a4edc5 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -299,7 +299,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
m_og->hide_labels();
m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) {
- Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
+ Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_FFF_PRINT);
if (!tab_print) return;
if (opt_key == "fill_density") {
@@ -692,8 +692,8 @@ Sidebar::Sidebar(Plater *parent)
};
p->combos_filament.push_back(nullptr);
- init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_PRINT, false);
- init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FILAMENT, true);
+ init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_FFF_PRINT, false);
+ init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FFF_FILAMENT, true);
init_combo(&p->combo_sla_print, _L("SLA print settings"), Preset::TYPE_SLA_PRINT, false);
init_combo(&p->combo_sla_material, _L("SLA material"), Preset::TYPE_SLA_MATERIAL, false);
init_combo(&p->combo_printer, _L("Printer"), Preset::TYPE_PRINTER, false);
@@ -809,7 +809,7 @@ Sidebar::Sidebar(Plater *parent)
Sidebar::~Sidebar() {}
void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) {
- *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT);
+ *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FFF_FILAMENT);
// # copy icons from first choice
// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
@@ -865,7 +865,7 @@ void Sidebar::update_presets(Preset::Type preset_type)
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
switch (preset_type) {
- case Preset::TYPE_FILAMENT:
+ case Preset::TYPE_FFF_FILAMENT:
{
const size_t extruder_cnt = print_tech != ptFFF ? 1 :
dynamic_cast<ConfigOptionFloats*>(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
@@ -883,7 +883,7 @@ void Sidebar::update_presets(Preset::Type preset_type)
break;
}
- case Preset::TYPE_PRINT:
+ case Preset::TYPE_FFF_PRINT:
p->combo_print->update();
break;
@@ -1978,6 +1978,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
"complete_objects",
"complete_objects_sort",
"complete_objects_one_skirt",
+ "complete_objects_one_brim",
"duplicate_distance", "extruder_clearance_radius",
"skirt_extrusion_width",
"first_layer_extrusion_width",
@@ -2398,7 +2399,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);
@@ -2424,6 +2426,8 @@ 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())
+ show_substitutions_info(config_substitutions.substitutions, filename.string());
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
}
@@ -2444,11 +2448,15 @@ 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();
}
+ } catch (const ConfigurationError &e) {
+ std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
+ GUI::show_error(q, message);
+ continue;
} catch (const std::exception &e) {
GUI::show_error(q, e.what());
continue;
@@ -3324,7 +3332,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();
@@ -3610,13 +3618,13 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data()));
- if (preset_type == Preset::TYPE_FILAMENT) {
+ if (preset_type == Preset::TYPE_FFF_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
}
bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
// TODO: ?
- if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
+ if (preset_type == Preset::TYPE_FFF_FILAMENT && sidebar->is_multifilament()) {
// Only update the plater UI for the 2nd and other filaments.
combo->update();
}
@@ -4006,7 +4014,7 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt)
DynamicPrintConfig cfg;
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
- wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
+ wxGetApp().get_tab(Preset::TYPE_FFF_PRINT)->load_config(cfg);
}
void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt)
@@ -4015,7 +4023,7 @@ void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt)
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2));
- wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
+ wxGetApp().get_tab(Preset::TYPE_FFF_PRINT)->load_config(cfg);
}
void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
@@ -4725,7 +4733,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();
@@ -4744,7 +4754,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x()));
new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y()));
new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation));
- Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
+ Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_FFF_PRINT);
tab_print->load_config(new_config);
tab_print->update_dirty();
}
@@ -5496,13 +5506,14 @@ void Plater::export_gcode(bool prefer_removable)
fs::path output_path;
{
- wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"),
+ std::string ext = default_output_file.extension().string();
+ wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"),
start_dir,
from_path(default_output_file.filename()),
- GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
+ GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
- if (dlg.ShowModal() == wxID_OK)
+ if (dlg.ShowModal() == wxID_OK)
output_path = into_path(dlg.GetPath());
}
diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp
index 852121e80..d5ac676a9 100644
--- a/src/slic3r/GUI/PresetComboBoxes.cpp
+++ b/src/slic3r/GUI/PresetComboBoxes.cpp
@@ -78,12 +78,12 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const
switch (m_type)
{
- case Preset::TYPE_PRINT: {
+ case Preset::TYPE_FFF_PRINT: {
m_collection = &m_preset_bundle->prints;
m_main_bitmap_name = "cog";
break;
}
- case Preset::TYPE_FILAMENT: {
+ case Preset::TYPE_FFF_FILAMENT: {
m_collection = &m_preset_bundle->filaments;
m_main_bitmap_name = "spool";
break;
@@ -399,7 +399,7 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con
// Paint a red flag for incompatible presets.
bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp());
- if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty())
+ if (m_type == Preset::TYPE_FFF_FILAMENT && !filament_rgb.empty())
{
unsigned char rgb[3];
// Paint the color bars.
@@ -604,7 +604,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
}
});
- if (m_type == Preset::TYPE_FILAMENT)
+ if (m_type == Preset::TYPE_FFF_FILAMENT)
{
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
@@ -671,7 +671,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
/* In a case of a multi-material printing, for editing another Filament Preset
* it's needed to select this preset for the "Filament settings" Tab
*/
- if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
+ if (m_type == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
{
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
@@ -703,9 +703,9 @@ bool PlaterPresetComboBox::switch_to_tab()
wxGetApp().tab_panel()->SetSelection(page_id);
// Switch to Settings NotePad
- if(m_type == Preset::Type::TYPE_PRINT || m_type == Preset::Type::TYPE_SLA_PRINT)
+ if(m_type == Preset::Type::TYPE_FFF_PRINT || m_type == Preset::Type::TYPE_SLA_PRINT)
wxGetApp().mainframe->select_tab(MainFrame::ETabType::PrintSettings);
- else if (m_type == Preset::Type::TYPE_FILAMENT || m_type == Preset::Type::TYPE_SLA_MATERIAL)
+ else if (m_type == Preset::Type::TYPE_FFF_FILAMENT || m_type == Preset::Type::TYPE_SLA_MATERIAL)
wxGetApp().mainframe->select_tab(MainFrame::ETabType::FilamentSettings);
else if (m_type == Preset::Type::TYPE_PRINTER)
wxGetApp().mainframe->select_tab(MainFrame::ETabType::PrinterSettings);
@@ -763,7 +763,7 @@ void PlaterPresetComboBox::show_edit_menu()
// If an incompatible preset is selected, it is shown as well.
void PlaterPresetComboBox::update()
{
- if (m_type == Preset::TYPE_FILAMENT &&
+ if (m_type == Preset::TYPE_FFF_FILAMENT &&
(m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA ||
m_preset_bundle->filament_presets.size() <= (size_t)m_extruder_idx) )
return;
@@ -775,7 +775,7 @@ void PlaterPresetComboBox::update()
const Preset* selected_filament_preset = nullptr;
std::string extruder_color;
- if (m_type == Preset::TYPE_FILAMENT)
+ if (m_type == Preset::TYPE_FFF_FILAMENT)
{
unsigned char rgb[3];
extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx);
@@ -787,7 +787,7 @@ void PlaterPresetComboBox::update()
}
bool has_selection = m_collection->get_selected_idx() != size_t(-1);
- const Preset* selected_preset = m_type == Preset::TYPE_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr;
+ const Preset* selected_preset = m_type == Preset::TYPE_FFF_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr;
// Show wide icons if the currently selected preset is not compatible with the current printer,
// and draw a red flag in front of the selected preset.
bool wide_icons = selected_preset && !selected_preset->is_compatible;
@@ -804,7 +804,7 @@ void PlaterPresetComboBox::update()
for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
{
const Preset& preset = presets[i];
- bool is_selected = m_type == Preset::TYPE_FILAMENT ?
+ bool is_selected = m_type == Preset::TYPE_FFF_FILAMENT ?
m_preset_bundle->filament_presets[m_extruder_idx] == preset.name :
// The case, when some physical printer is selected
m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false :
@@ -817,7 +817,7 @@ void PlaterPresetComboBox::update()
std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
bool single_bar = false;
- if (m_type == Preset::TYPE_FILAMENT)
+ if (m_type == Preset::TYPE_FFF_FILAMENT)
{
// Assign an extruder color to the selected item if the extruder color is defined.
filament_rgb = is_selected ? selected_filament_preset->config.opt_string("filament_colour", 0) :
@@ -883,11 +883,11 @@ void PlaterPresetComboBox::update()
}
}
- if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) {
+ if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FFF_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) {
wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni");
assert(bmp);
- if (m_type == Preset::TYPE_FILAMENT)
+ if (m_type == Preset::TYPE_FFF_FILAMENT)
set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS);
else if (m_type == Preset::TYPE_SLA_MATERIAL)
set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS);
@@ -901,9 +901,13 @@ void PlaterPresetComboBox::update()
if (!tooltip.IsEmpty())
SetToolTip(tooltip);
+#ifdef __WXMSW__
+ // Use this part of code just on Windows to avoid of some layout issues on Linux
+ // see https://github.com/prusa3d/PrusaSlicer/issues/5163 and https://github.com/prusa3d/PrusaSlicer/issues/5505
// Update control min size after rescale (changed Display DPI under MSW)
if (GetMinWidth() != 20 * m_em_unit)
SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight()));
+#endif //__WXMSW__
}
void PlaterPresetComboBox::msw_rescale()
diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp
index fed47725d..459df570a 100644
--- a/src/slic3r/GUI/PresetHints.cpp
+++ b/src/slic3r/GUI/PresetHints.cpp
@@ -227,18 +227,26 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
return (speed_normal > 0.) ? speed_normal : speed_max;
};
if (perimeter_extruder_active) {
- double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,
- first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
- nozzle_diameter, lh, bfr).mm3_per_mm() *
+ Flow external_flow = Flow::new_from_config_width(frExternalPerimeter,
+ first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
+ nozzle_diameter, lh, bfr);
+ if (external_flow.height > external_flow.width)
+ external_flow.height = external_flow.width;
+ external_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("external_perimeter_overlap")->get_abs_value(1);
+ double external_perimeter_rate = external_flow.mm3_per_mm() *
(bridging ? bridge_speed :
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < external_perimeter_rate) {
max_flow = external_perimeter_rate;
max_flow_extrusion_type = _utf8(L("external perimeters"));
}
- double perimeter_rate = Flow::new_from_config_width(frPerimeter,
- first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
- nozzle_diameter, lh, bfr).mm3_per_mm() *
+ Flow perimeter_flow = Flow::new_from_config_width(frPerimeter,
+ first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
+ nozzle_diameter, lh, bfr);
+ if (perimeter_flow.height > perimeter_flow.width)
+ perimeter_flow.height = perimeter_flow.width;
+ perimeter_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("perimeter_overlap")->get_abs_value(1);
+ double perimeter_rate = perimeter_flow.mm3_per_mm() *
(bridging ? bridge_speed :
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < perimeter_rate) {
@@ -247,27 +255,36 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
}
}
if (! bridging && infill_extruder_active) {
- double infill_rate = Flow::new_from_config_width(frInfill,
- first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
- nozzle_diameter, lh, bfr).mm3_per_mm() * limit_infill_by_first_layer_speed(infill_speed, max_print_speed);
+ Flow infill_flow = Flow::new_from_config_width(frInfill,
+ first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
+ nozzle_diameter, lh, bfr);
+ if (infill_flow.height > infill_flow.width)
+ infill_flow.height = infill_flow.width;
+ double infill_rate = infill_flow.mm3_per_mm() * limit_infill_by_first_layer_speed(infill_speed, max_print_speed);
if (max_flow < infill_rate) {
max_flow = infill_rate;
max_flow_extrusion_type = _utf8(L("infill"));
}
}
if (solid_infill_extruder_active) {
- double solid_infill_rate = Flow::new_from_config_width(frInfill,
- first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
- nozzle_diameter, lh, 0).mm3_per_mm() *
+ Flow solid_infill_flow = Flow::new_from_config_width(frInfill,
+ first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
+ nozzle_diameter, lh, 0);
+ if (solid_infill_flow.height > solid_infill_flow.width)
+ solid_infill_flow.height = solid_infill_flow.width;
+ double solid_infill_rate = solid_infill_flow.mm3_per_mm() *
(bridging ? bridge_speed : limit_infill_by_first_layer_speed(solid_infill_speed, max_print_speed));
if (max_flow < solid_infill_rate) {
max_flow = solid_infill_rate;
max_flow_extrusion_type = _utf8(L("solid infill"));
}
if (! bridging) {
- double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
- first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
- nozzle_diameter, lh, bfr).mm3_per_mm() * limit_infill_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
+ Flow top_solid_infill_flow = Flow::new_from_config_width(frInfill,
+ first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
+ nozzle_diameter, lh, bfr);
+ if (top_solid_infill_flow.height > top_solid_infill_flow.width)
+ top_solid_infill_flow.height = top_solid_infill_flow.width;
+ double top_solid_infill_rate = top_solid_infill_flow.mm3_per_mm() * limit_infill_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
if (max_flow < top_solid_infill_rate) {
max_flow = top_solid_infill_rate;
max_flow_extrusion_type = _utf8(L("top solid infill"));
@@ -275,9 +292,12 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
}
}
if (support_material_extruder_active) {
- double support_material_rate = Flow::new_from_config_width(frSupportMaterial,
- first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
- nozzle_diameter, lh, bfr).mm3_per_mm() *
+ Flow support_material_flow = Flow::new_from_config_width(frSupportMaterial,
+ first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
+ nozzle_diameter, lh, bfr);
+ if (support_material_flow.height > support_material_flow.width)
+ support_material_flow.height = support_material_flow.width;
+ double support_material_rate = support_material_flow.mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
if (max_flow < support_material_rate) {
max_flow = support_material_rate;
@@ -285,9 +305,12 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
}
}
if (support_material_interface_extruder_active) {
- double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface,
+ Flow support_material_interface_flow = Flow::new_from_config_width(frSupportMaterialInterface,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
- nozzle_diameter, lh, bfr).mm3_per_mm() *
+ nozzle_diameter, lh, bfr);
+ if (support_material_interface_flow.height > support_material_interface_flow.width)
+ support_material_interface_flow.height = support_material_interface_flow.width;
+ double support_material_interface_rate = support_material_interface_flow.mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
if (max_flow < support_material_interface_rate) {
max_flow = support_material_interface_rate;
@@ -297,17 +320,19 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
//FIXME handle gap_fill_speed
if (! out.empty())
out += "\n";
- out += (first_layer ? _utf8(L("First layer volumetric")) : (bridging ? _utf8(L("Bridging volumetric")) : _utf8(L("Volumetric"))));
- out += " " + _utf8(L("flow rate is maximized")) + " ";
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
- out += (limited_by_max_volumetric_speed ?
- _utf8(L("by the print profile maximum")) :
- (_utf8(L("when printing"))+ " " + max_flow_extrusion_type))
- + " " + _utf8(L("with a volumetric rate"))+ " ";
+
+ std::string pattern = (boost::format(_u8L("%s flow rate is maximized "))
+ % (first_layer ? _u8L("First layer volumetric") : (bridging ? _u8L("Bridging volumetric") : _u8L("Volumetric")))).str();
+ std::string pattern2;
if (limited_by_max_volumetric_speed)
- max_flow = max_volumetric_speed;
+ pattern2 = (boost::format(_u8L("by the print profile maximum volumetric rate of %3.2f mm³/s at filament speed %3.2f mm/s."))
+ % max_volumetric_speed % (max_volumetric_speed / filament_crossection)).str();
+ else
+ pattern2 = (boost::format(_u8L("when printing %s with a volumetric rate of %3.2f mm³/s at filament speed %3.2f mm/s."))
+ % max_flow_extrusion_type % max_flow % (max_flow / filament_crossection)).str();
- out += (boost::format(_utf8(L("%3.2f mm³/s at filament speed %3.2f mm/s."))) % max_flow % (max_flow / filament_crossection)).str();
+ out += pattern + " " + pattern2;
}
return out;
@@ -338,7 +363,17 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle& pre
*print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),
nozzle_diameter, layer_height, false);
- //set spacing
+ // failsafe for too big height
+ if (external_perimeter_flow.height > external_perimeter_flow.width)
+ external_perimeter_flow.height = external_perimeter_flow.width;
+ if (perimeter_flow.height > perimeter_flow.width)
+ perimeter_flow.height = perimeter_flow.width;
+ if (external_perimeter_flow.height != perimeter_flow.height) {
+ perimeter_flow.height = std::min(perimeter_flow.height, external_perimeter_flow.height);
+ external_perimeter_flow.height = perimeter_flow.height;
+ }
+
+ // set spacing
external_perimeter_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("external_perimeter_overlap")->get_abs_value(1);
perimeter_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("perimeter_overlap")->get_abs_value(1);
diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp
index a3eadca15..e9ad959ae 100644
--- a/src/slic3r/GUI/Search.cpp
+++ b/src/slic3r/GUI/Search.cpp
@@ -31,10 +31,10 @@ namespace Search {
static char marker_by_type(Preset::Type type, PrinterTechnology pt)
{
switch(type) {
- case Preset::TYPE_PRINT:
+ case Preset::TYPE_FFF_PRINT:
case Preset::TYPE_SLA_PRINT:
return ImGui::PrintIconMarker;
- case Preset::TYPE_FILAMENT:
+ case Preset::TYPE_FFF_FILAMENT:
return ImGui::FilamentIconMarker;
case Preset::TYPE_SLA_MATERIAL:
return ImGui::MaterialIconMarker;
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 32ac2a30c..6f1171977 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -119,7 +119,7 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) :
m_compatible_printers.dialog_title = _L("Compatible printers");
m_compatible_printers.dialog_label = _L("Select the printers this profile is compatible with.");
- m_compatible_prints.type = Preset::TYPE_PRINT;
+ m_compatible_prints.type = Preset::TYPE_FFF_PRINT;
m_compatible_prints.key_list = "compatible_prints";
m_compatible_prints.key_condition = "compatible_prints_condition";
m_compatible_prints.dialog_title = _L("Compatible print profiles");
@@ -147,9 +147,9 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) :
void Tab::set_type()
{
- if (m_name == "print") { m_type = Slic3r::Preset::TYPE_PRINT; }
+ if (m_name == "print") { m_type = Slic3r::Preset::TYPE_FFF_PRINT; }
else if (m_name == "sla_print") { m_type = Slic3r::Preset::TYPE_SLA_PRINT; }
- else if (m_name == "filament") { m_type = Slic3r::Preset::TYPE_FILAMENT; }
+ else if (m_name == "filament") { m_type = Slic3r::Preset::TYPE_FFF_FILAMENT; }
else if (m_name == "sla_material") { m_type = Slic3r::Preset::TYPE_SLA_MATERIAL; }
else if (m_name == "printer") { m_type = Slic3r::Preset::TYPE_PRINTER; }
else { m_type = Slic3r::Preset::TYPE_INVALID; assert(false); }
@@ -817,7 +817,7 @@ void Tab::update_changed_tree_ui()
get_sys_and_mod_flags(opt_key, sys_page, modified_page);
}
}
- if (m_type == Preset::TYPE_FILAMENT && page->title() == "Advanced") {
+ if (m_type == Preset::TYPE_FFF_FILAMENT && page->title() == "Advanced") {
get_sys_and_mod_flags("filament_ramming_parameters", sys_page, modified_page);
}
if (page->title() == "Dependencies") {
@@ -825,7 +825,7 @@ void Tab::update_changed_tree_ui()
sys_page = m_presets->get_selected_preset_parent() != nullptr;
modified_page = false;
} else {
- if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL)
+ if (m_type == Slic3r::Preset::TYPE_FFF_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL)
get_sys_and_mod_flags("compatible_prints", sys_page, modified_page);
get_sys_and_mod_flags("compatible_printers", sys_page, modified_page);
}
@@ -913,7 +913,7 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/)
is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable();
}
// "compatible_prints" option exists only in Filament Settimgs and Materials Tabs
- if ((m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) && (m_options_list["compatible_prints"] & os) == 0) {
+ if ((m_type == Preset::TYPE_FFF_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) && (m_options_list["compatible_prints"] & os) == 0) {
to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints");
load_key_value("compatible_prints", true/*some value*/, true);
@@ -1186,6 +1186,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
}
//wxGetApp().preset_bundle->value_changed(opt_key);
+ // update phony fields
if (m_config->value_changed(opt_key, { wxGetApp().plater()->config() })) {
update_dirty();
//# Initialize UI components with the config values.
@@ -2979,24 +2980,30 @@ void TabPrinter::toggle_options()
//z step checks
{
double z_step = m_config->opt_float("z_step");
+ int64_t z_step_Mlong = (int64_t)(z_step * 1000000.);
DynamicPrintConfig new_conf;
bool has_changed = false;
const std::vector<double>& min_layer_height = m_config->option<ConfigOptionFloats>("min_layer_height")->values;
- for (int i = 0; i < min_layer_height.size(); i++)
- if (min_layer_height[i] / z_step != 0) {
- if(!has_changed )
+ for (int i = 0; i < min_layer_height.size(); i++) {
+ if (min_layer_height[i] != 0 && (int64_t)(min_layer_height[i] * 1000000.) % z_step_Mlong != 0) {
+ if (!has_changed)
new_conf = *m_config;
new_conf.option<ConfigOptionFloats>("min_layer_height")->values[i] = std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("min_layer_height")->values[i], z_step));
has_changed = true;
}
- const std::vector<double>& max_layer_height = m_config->option<ConfigOptionFloats>("max_layer_height")->values;
- for (int i = 0; i < max_layer_height.size(); i++)
- if (max_layer_height[i] / z_step != 0) {
+ }
+ const std::vector<double>& nozzle_diameters = m_config->option<ConfigOptionFloats>("nozzle_diameter")->values;
+ std::vector<double> max_layer_height = m_config->option<ConfigOptionFloats>("max_layer_height")->values;
+ for (int i = 0; i < max_layer_height.size(); i++) {
+ if (max_layer_height[i] == 0)
+ max_layer_height[i] = nozzle_diameters[i] * 0.75;
+ if ((int64_t)(max_layer_height[i] * 1000000.) % z_step_Mlong != 0) {
if (!has_changed)
new_conf = *m_config;
new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i] = std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i], z_step));
has_changed = true;
}
+ }
if (has_changed) {
load_config(new_conf);
}
@@ -3071,7 +3078,8 @@ void Tab::load_current_preset()
//merill note: this is a bit of anti-inheritance pattern
if (m_type == Slic3r::Preset::TYPE_PRINTER) {
const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology();
- if (printer_technology != static_cast<TabPrinter*>(this)->m_printer_technology)
+ const PrinterTechnology old_printer_technology = static_cast<TabPrinter*>(this)->m_printer_technology;
+ if (printer_technology != old_printer_technology)
{
// The change of the technology requires to remove some of unrelated Tabs
// During this action, wxNoteBook::RemovePage invoke wxEVT_NOTEBOOK_PAGE_CHANGED
@@ -3081,22 +3089,15 @@ void Tab::load_current_preset()
Page* tmp_page = m_active_page;
m_active_page = nullptr;
for (auto tab : wxGetApp().tabs_list) {
- if (tab->type() == Preset::TYPE_PRINTER) // Printer tab is shown every time
+ if (tab->type() == Preset::TYPE_PRINTER) // Printer tab shouln't be swapped
continue;
if (tab->supports_printer_technology(printer_technology))
{
- 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);
- #endif // __linux__
- }
- else {
- int page_id = wxGetApp().tab_panel()->FindPage(tab);
- //TODO shouldn't happen, emit an error here.
- if (page_id >= 0 && page_id < wxGetApp().tab_panel()->GetPageCount()) {
- wxGetApp().tab_panel()->GetPage(page_id)->Show(false);
- wxGetApp().tab_panel()->RemovePage(page_id);
+ //search the other one to be replaced
+ for (auto tab_old : wxGetApp().tabs_list) {
+ if ((tab->type() & Preset::TYPE_TAB) == (tab_old->type() & Preset::TYPE_TAB) && tab_old->supports_printer_technology(old_printer_technology) ) {
+ wxGetApp().mainframe->change_tab(tab_old, tab);
+ }
}
}
}
@@ -3116,11 +3117,11 @@ void Tab::load_current_preset()
}
else {
on_presets_changed();
- if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT)
+ if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_FFF_PRINT)
update_frequently_changed_parameters();
//update width/spacing links
- if (m_type == Preset::TYPE_PRINT) {
+ if (m_type == Preset::TYPE_FFF_PRINT) {
//verify that spacings are set
if (m_config && m_config->update_phony({ wxGetApp().plater()->config() })) {
update_dirty();
@@ -3218,7 +3219,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
}
assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user()));
bool current_dirty = ! delete_current && m_presets->current_is_dirty();
- bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT;
+ bool print_tab = m_presets->type() == Preset::TYPE_FFF_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT;
bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER;
bool canceled = false;
bool technology_changed = false;
@@ -3240,7 +3241,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
canceled = old_preset_dirty && ! new_preset_compatible && ! may_discard_current_dirty_preset(&dependent, preset_name);
if (! canceled) {
// The preset will be switched to a different, compatible preset, or the '-- default --'.
- m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL);
+ m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FFF_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL);
if (old_preset_dirty && ! new_preset_compatible)
dependent.discard_current_changes();
}
@@ -3267,9 +3268,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
bool new_preset_compatible;
};
std::vector<PresetUpdate> updates = {
- { Preset::Type::TYPE_PRINT, &m_preset_bundle->prints, ptFFF },
+ { Preset::Type::TYPE_FFF_PRINT, &m_preset_bundle->prints, ptFFF },
{ Preset::Type::TYPE_SLA_PRINT, &m_preset_bundle->sla_prints, ptSLA },
- { Preset::Type::TYPE_FILAMENT, &m_preset_bundle->filaments, ptFFF },
+ { Preset::Type::TYPE_FFF_FILAMENT, &m_preset_bundle->filaments, ptFFF },
{ Preset::Type::TYPE_SLA_MATERIAL, &m_preset_bundle->sla_materials,ptSLA }
};
for (PresetUpdate &pu : updates) {
@@ -3339,8 +3340,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
};
if (current_dirty || delete_current || print_tab || printer_tab)
m_preset_bundle->update_compatible(
- update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_PRINT))->m_show_incompatible_presets),
- update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FILAMENT)->m_show_incompatible_presets));
+ update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_FFF_PRINT))->m_show_incompatible_presets),
+ update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FFF_FILAMENT)->m_show_incompatible_presets));
// Initialize the UI from the current preset.
if (printer_tab)
static_cast<TabPrinter*>(this)->update_pages();
@@ -3356,8 +3357,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
* to the corresponding printer_technology
*/
const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology();
- if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT)
- m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT };
+ if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_FFF_PRINT)
+ m_dependent_tabs = { Preset::Type::TYPE_FFF_PRINT, Preset::Type::TYPE_FFF_FILAMENT };
else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT)
m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL };
}
@@ -3372,7 +3373,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
load_current_preset();
// apply duplicate_distance for print preset
- if (m_type == Preset::TYPE_PRINT) {
+ if (m_type == Preset::TYPE_FFF_PRINT || m_type == Preset::TYPE_SLA_PRINT) {
wxGetApp().mainframe->plater()->canvas3D()->set_arrange_settings(m_presets->get_edited_preset().config, m_presets->get_edited_preset().printer_technology());
}
if (m_type == Preset::TYPE_PRINTER) {
@@ -3411,7 +3412,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr
// If filament preset is saved for multi-material printer preset,
// there are cases when filament comboboxs are updated for old (non-modified) colors,
// but in full_config a filament_colors option aren't.
- if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
+ if (presets->type() == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
wxGetApp().plater()->force_filament_colors_update();
}
}
@@ -3615,7 +3616,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
/* If filament preset is saved for multi-material printer preset,
* there are cases when filament comboboxs are updated for old (non-modified) colors,
* but in full_config a filament_colors option aren't.*/
- if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
+ if (m_type == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
wxGetApp().plater()->force_filament_colors_update();
{
@@ -3623,15 +3624,15 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
// Update profile selection combo boxes at the depending tabs to reflect modifications in profile compatibility.
std::vector<Preset::Type> dependent;
switch (m_type) {
- case Preset::TYPE_PRINT:
- dependent = { Preset::TYPE_FILAMENT };
+ case Preset::TYPE_FFF_PRINT:
+ dependent = { Preset::TYPE_FFF_FILAMENT };
break;
case Preset::TYPE_SLA_PRINT:
dependent = { Preset::TYPE_SLA_MATERIAL };
break;
case Preset::TYPE_PRINTER:
if (static_cast<const TabPrinter*>(this)->m_printer_technology == ptFFF)
- dependent = { Preset::TYPE_PRINT, Preset::TYPE_FILAMENT };
+ dependent = { Preset::TYPE_FFF_PRINT, Preset::TYPE_FFF_FILAMENT };
else
dependent = { Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL };
break;
diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp
index 29d4699c3..fd541ead2 100644
--- a/src/slic3r/GUI/Tab.hpp
+++ b/src/slic3r/GUI/Tab.hpp
@@ -379,7 +379,7 @@ class TabPrint : public Tab
public:
TabPrint(wxNotebook* parent) :
// Tab(parent, _(L("Print Settings")), L("print")) {}
- Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {}
+ Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_FFF_PRINT) {}
~TabPrint() {}
void build() override;
@@ -411,7 +411,7 @@ protected:
public:
TabFilament(wxNotebook* parent) :
// Tab(parent, _(L("Filament Settings")), L("filament")) {}
- Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {}
+ Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FFF_FILAMENT) {}
~TabFilament() {}
void build() override;
diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp
index fc1395545..f04d422f9 100644
--- a/src/slic3r/GUI/UnsavedChangesDialog.cpp
+++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp
@@ -40,9 +40,9 @@ namespace GUI {
// ----------------------------------------------------------------------------
static const std::map<Preset::Type, std::string> type_icon_names = {
- {Preset::TYPE_PRINT, "cog" },
+ {Preset::TYPE_FFF_PRINT, "cog" },
{Preset::TYPE_SLA_PRINT, "cog" },
- {Preset::TYPE_FILAMENT, "spool" },
+ {Preset::TYPE_FFF_FILAMENT, "spool" },
{Preset::TYPE_SLA_MATERIAL, "resin" },
{Preset::TYPE_PRINTER, "printer" },
};
@@ -952,6 +952,8 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig&
return get_string_from_enum<InfillPattern>(opt_key, config);
if (opt_key == "complete_objects_sort")
return get_string_from_enum<CompleteObjectSort>(opt_key, config);
+ if (opt_key == "config_compatibility")
+ return get_string_from_enum<ForwardCompatibilitySubstitutionRule>(opt_key, config);
if (opt_key == "display_orientation")
return get_string_from_enum<SLADisplayOrientation>(opt_key, config);
if (opt_key == "output_format")
diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp
index 44e39990b..ad92e5ed1 100644
--- a/src/slic3r/GUI/UpdateDialogs.cpp
+++ b/src/slic3r/GUI/UpdateDialogs.cpp
@@ -85,8 +85,11 @@ bool MsgUpdateSlic3r::disable_version_check() const
// MsgUpdateConfig
-MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
- MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
+MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
+ MsgDialog(nullptr, force_before_wizard ? _L("Opening Configuration Wizard") : _L("Configuration update"),
+ force_before_wizard ? wxString::Format(_L("%s is not using the newest configuration available.\n"
+ "Configuration Wizard may not offer the latest printers, filaments and SLA materials to be installed. "), SLIC3R_APP_NAME) :
+ _L("Configuration update is available"), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"Would you like to install it?\n\n"
@@ -130,11 +133,17 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
- auto *btn_cancel = new wxButton(this, wxID_CANCEL);
- btn_sizer->Add(btn_cancel);
- btn_sizer->AddSpacer(HORIZ_SPACING);
- auto *btn_ok = new wxButton(this, wxID_OK);
+ auto* btn_ok = new wxButton(this, wxID_OK, force_before_wizard ? _L("Install") : "OK");
btn_sizer->Add(btn_ok);
+ btn_sizer->AddSpacer(HORIZ_SPACING);
+ if (force_before_wizard) {
+ auto* btn_no_install = new wxButton(this, wxID_ANY, _L("Don't install"));
+ btn_no_install->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CLOSE); });
+ btn_sizer->Add(btn_no_install);
+ btn_sizer->AddSpacer(HORIZ_SPACING);
+ }
+ auto* btn_cancel = new wxButton(this, wxID_CANCEL);
+ btn_sizer->Add(btn_cancel);
btn_ok->SetFocus();
Fit();
diff --git a/src/slic3r/GUI/UpdateDialogs.hpp b/src/slic3r/GUI/UpdateDialogs.hpp
index 6d355065a..aa3a10677 100644
--- a/src/slic3r/GUI/UpdateDialogs.hpp
+++ b/src/slic3r/GUI/UpdateDialogs.hpp
@@ -54,7 +54,8 @@ public:
{}
};
- MsgUpdateConfig(const std::vector<Update> &updates);
+ // force_before_wizard - indicates that check of updated is forced before ConfigWizard opening
+ MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard = false);
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp
index bcab6daaf..9df656701 100644
--- a/src/slic3r/Utils/FixModelByWin10.cpp
+++ b/src/slic3r/Utils/FixModelByWin10.cpp
@@ -377,7 +377,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"));
diff --git a/src/slic3r/Utils/HexFile.cpp b/src/slic3r/Utils/HexFile.cpp
index 26596f629..a13fcab02 100644
--- a/src/slic3r/Utils/HexFile.cpp
+++ b/src/slic3r/Utils/HexFile.cpp
@@ -19,6 +19,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str)
else if (str == "mk3") { return HexFile::DEV_MK3; }
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
else if (str == "cw1") { return HexFile::DEV_CW1; }
+ else if (str == "cw1s") { return HexFile::DEV_CW1S; }
else { return HexFile::DEV_GENERIC; }
}
diff --git a/src/slic3r/Utils/HexFile.hpp b/src/slic3r/Utils/HexFile.hpp
index 742ae00e6..b32d110ed 100644
--- a/src/slic3r/Utils/HexFile.hpp
+++ b/src/slic3r/Utils/HexFile.hpp
@@ -17,6 +17,7 @@ struct HexFile
DEV_MK3,
DEV_MM_CONTROL,
DEV_CW1,
+ DEV_CW1S,
};
boost::filesystem::path path;
diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp
index bb864c98d..f0d9982e4 100644
--- a/src/slic3r/Utils/OctoPrint.cpp
+++ b/src/slic3r/Utils/OctoPrint.cpp
@@ -178,7 +178,7 @@ const char* SL1Host::get_name() const { return "SL1Host"; }
wxString SL1Host::get_test_ok_msg () const
{
- return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1");
+ return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1 / SL1S");
}
wxString SL1Host::get_test_failed_msg (wxString &msg) const
@@ -209,4 +209,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());
+ }
+}
+
}
diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp
index c91dc5834..7c17f3793 100644
--- a/src/slic3r/Utils/OctoPrint.hpp
+++ b/src/slic3r/Utils/OctoPrint.hpp
@@ -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
diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp
index 37c32da16..076504243 100644
--- a/src/slic3r/Utils/PresetUpdater.cpp
+++ b/src/slic3r/Utils/PresetUpdater.cpp
@@ -56,15 +56,18 @@ 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);
+ // Permissions should be copied from the source file by copy_file(). We are not sure about the source
+ // permissions, let's rewrite them with 644.
+ static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(target, perms);
}
@@ -168,7 +171,7 @@ struct PresetUpdater::priv
void check_install_indices() const;
Updates get_config_updates(const Semver& old_slic3r_version) const;
- void perform_updates(Updates &&updates, bool snapshot = true) const;
+ bool perform_updates(Updates &&updates, bool snapshot = true) const;
void set_waiting_updates(Updates u);
};
@@ -512,7 +515,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
}
if (recommended->config_version < vp.config_version) {
- BOOST_LOG_TRIVIAL(warning) << format("Recommended config version for the currently running " SLIC3R_APP_NAME " is older than the currently installed config for vendor %1%. This should not happen.", idx.vendor());
+ BOOST_LOG_TRIVIAL(warning) << format("Recommended config version for the currently running %1% is older than the currently installed config for vendor %2%. This should not happen.", SLIC3R_APP_NAME, idx.vendor());
continue;
}
@@ -583,7 +586,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
found = true;
} else {
BOOST_LOG_TRIVIAL(warning) << format("The recommended config version for vendor `%1%` in resources does not match the recommended\n"
- " config version for this version of " SLIC3R_APP_NAME ". Corrupted installation?", idx.vendor());
+ " config version for this version of `%2%`. Corrupted installation?", idx.vendor(), SLIC3R_APP_NAME);
}
}
}
@@ -636,12 +639,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
return updates;
}
-void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
+bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
{
if (updates.incompats.size() > 0) {
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
- SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE);
+ if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
+ _u8L("Continue and install configuration updates?")))
+ return false;
}
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% incompatible bundles", updates.incompats.size());
@@ -656,7 +661,9 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
- SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE);
+ if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
+ _u8L("Continue and install configuration updates?")))
+ return false;
}
BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size());
@@ -667,7 +674,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
update.install();
PresetBundle bundle;
- bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
+ // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
+ bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
@@ -699,6 +707,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
}
}
+
+ return true;
}
void PresetUpdater::priv::set_waiting_updates(Updates u)
@@ -765,7 +775,20 @@ void PresetUpdater::slic3r_update_notify()
}
}
-PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
+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.
+ // However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
+ GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
+ GUI::wxGetApp().load_current_presets();
+ GUI::wxGetApp().plater()->set_bed_shape();
+ GUI::wxGetApp().update_wizard_from_config();
+}
+
+PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
{
if (! p->enabled_config_update) { return R_NOOP; }
@@ -799,11 +822,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
// This effectively removes the incompatible bundles:
// (snapshot is taken beforehand)
- p->perform_updates(std::move(updates));
-
- if (!GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) {
+ if (! p->perform_updates(std::move(updates)) ||
+ ! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT))
return R_INCOMPAT_EXIT;
- }
return R_INCOMPAT_CONFIGURED;
}
@@ -822,7 +843,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());
@@ -837,14 +858,9 @@ 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();
+ if (! p->perform_updates(std::move(updates)))
+ return R_INCOMPAT_EXIT;
+ reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
@@ -854,35 +870,35 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
}
// regular update
- if (no_notification) {
+ if (params == UpdateParams::SHOW_NOTIFICATION) {
+ p->set_waiting_updates(updates);
+ GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
+ }
+ else {
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
- std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
+ std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
for (const auto& update : updates.updates) {
- std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
- updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
- }
+ std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
+ updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
+ }
- GUI::MsgUpdateConfig dlg(updates_msg);
+ GUI::MsgUpdateConfig dlg(updates_msg, params == UpdateParams::FORCED_BEFORE_WIZARD);
- const auto res = dlg.ShowModal();
- 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();
- return R_UPDATE_INSTALLED;
+ const auto res = dlg.ShowModal();
+ if (res == wxID_OK) {
+ BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
+ if (! p->perform_updates(std::move(updates)))
+ return R_ALL_CANCELED;
+ reload_configs_update_gui();
+ return R_UPDATE_INSTALLED;
}
else {
- BOOST_LOG_TRIVIAL(info) << "User refused the update";
- return R_UPDATE_REJECT;
- }
- } else {
- p->set_waiting_updates(updates);
- GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
+ BOOST_LOG_TRIVIAL(info) << "User refused the update";
+ if (params == UpdateParams::FORCED_BEFORE_WIZARD && res == wxID_CANCEL)
+ return R_ALL_CANCELED;
+ return R_UPDATE_REJECT;
+ }
}
// MsgUpdateConfig will show after the notificaation is clicked
@@ -893,7 +909,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
return R_NOOP;
}
-void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
+bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
{
Updates updates;
@@ -905,7 +921,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
- p->perform_updates(std::move(updates), snapshot);
+ return p->perform_updates(std::move(updates), snapshot);
}
void PresetUpdater::on_update_notification_confirm()
@@ -925,20 +941,14 @@ void PresetUpdater::on_update_notification_confirm()
const auto res = dlg.ShowModal();
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();
- p->has_waiting_updates = false;
- //return R_UPDATE_INSTALLED;
+ if (p->perform_updates(std::move(p->waiting_updates))) {
+ reload_configs_update_gui();
+ p->has_waiting_updates = false;
+ }
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
- //return R_UPDATE_REJECT;
- }
-
+ }
}
}
diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp
index 0ca363c61..d7eeb5604 100644
--- a/src/slic3r/Utils/PresetUpdater.hpp
+++ b/src/slic3r/Utils/PresetUpdater.hpp
@@ -35,18 +35,24 @@ public:
R_INCOMPAT_CONFIGURED,
R_UPDATE_INSTALLED,
R_UPDATE_REJECT,
- R_UPDATE_NOTIFICATION
+ R_UPDATE_NOTIFICATION,
+ R_ALL_CANCELED
+ };
+
+ enum class UpdateParams {
+ SHOW_TEXT_BOX, // force modal textbox
+ SHOW_NOTIFICATION, // only shows notification
+ FORCED_BEFORE_WIZARD // indicates that check of updated is forced before ConfigWizard opening
};
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
// A false return value implies Slic3r should exit due to incompatibility of configuration.
// Providing old slic3r version upgrade profiles on upgrade of an application even in case
// that the config index installed from the Internet is equal to the index contained in the installation package.
- // no_notification = force modal textbox, otherwise some cases only shows notification
- UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
+ UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const;
// "Update" a list of bundles from resources (behaves like an online update).
- void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
+ bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
void on_update_notification_confirm();
private:
diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp
index 455494277..52ec8bbfe 100644
--- a/src/slic3r/Utils/PrintHost.cpp
+++ b/src/slic3r/Utils/PrintHost.cpp
@@ -60,6 +60,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
const auto host_type = opt != nullptr ? opt->value : htOctoPrint;
switch (host_type) {
+ case htPrusaLink: return new PrusaLink(config);
case htOctoPrint: return new OctoPrint(config);
case htDuet: return new Duet(config);
case htFlashAir: return new FlashAir(config);
diff --git a/tests/data/cpp/test_data.cpp b/tests/data/cpp/test_data.cpp
index e09976571..830aa1a5f 100644
--- a/tests/data/cpp/test_data.cpp
+++ b/tests/data/cpp/test_data.cpp
@@ -203,14 +203,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);
}
diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp
index c9052a668..2ba1c7de5 100644
--- a/tests/fff_print/test_flow.cpp
+++ b/tests/fff_print/test_flow.cpp
@@ -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 },
diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp
index 438a0283b..1594058f2 100644
--- a/tests/fff_print/test_gcodewriter.cpp
+++ b/tests/fff_print/test_gcodewriter.cpp
@@ -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<uint16_t> extruder_ids {0};
writer.set_extruders(extruder_ids);
diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp
index e2891efaa..9009cd97c 100644
--- a/tests/fff_print/test_print.cpp
+++ b/tests/fff_print/test_print.cpp
@@ -62,7 +62,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.5 }, // get a known number of layers
diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp
index 10d3af9a3..87bbea88d 100644
--- a/tests/fff_print/test_printgcode.cpp
+++ b/tests/fff_print/test_printgcode.cpp
@@ -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 },
diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp
index 097f72dcc..8f508f323 100644
--- a/tests/fff_print/test_skirt_brim.cpp
+++ b/tests/fff_print/test_skirt_brim.cpp
@@ -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 },
diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp
index fb41ef93b..035d47b11 100644
--- a/tests/libslic3r/test_3mf.cpp
+++ b/tests/libslic3r/test_3mf.cpp
@@ -14,7 +14,8 @@ SCENARIO("Reading 3mf file", "[3mf]") {
WHEN("3mf model is read") {
std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf";
DynamicPrintConfig config;
- bool ret = load_3mf(path.c_str(), &config, &model, false);
+ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
+ bool ret = load_3mf(path.c_str(), config, ctxt, &model, false);
THEN("load should succeed") {
REQUIRE(ret);
}
@@ -56,7 +57,10 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
// load back the model from the 3mf file
Model dst_model;
DynamicPrintConfig dst_config;
- load_3mf(test_file.c_str(), &dst_config, &dst_model, false);
+ {
+ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
+ load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false);
+ }
boost::filesystem::remove(test_file);
// compare meshes
diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp
index 9ffc45b61..ddfeecd80 100644
--- a/tests/libslic3r/test_config.cpp
+++ b/tests/libslic3r/test_config.cpp
@@ -12,7 +12,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
WHEN( "perimeter_extrusion_width is set to 250%, a valid value") {
- config.set_deserialize("perimeter_extrusion_width", "250%");
+ config.set_deserialize_strict("perimeter_extrusion_width", "250%");
THEN( "The config is read as valid.") {
REQUIRE(config.validate().empty());
}
@@ -43,7 +43,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
WHEN("A boolean option is set to a string value representing a 0 or 1") {
- CHECK_NOTHROW(config.set_deserialize("gcode_comments", "1"));
+ CHECK_NOTHROW(config.set_deserialize_strict("gcode_comments", "1"));
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
@@ -62,7 +62,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
WHEN("A numeric option is set from serialized string") {
- config.set_deserialize("bed_temperature", "100");
+ config.set_deserialize_strict("bed_temperature", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
}
@@ -95,7 +95,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
WHEN("A numeric option is set to a non-numeric value.") {
THEN("A BadOptionTypeException exception is thown.") {
- REQUIRE_THROWS_AS(config.set_deserialize("perimeter_speed", "zzzz"), BadOptionTypeException);
+ REQUIRE_THROWS_AS(config.set_deserialize_strict("perimeter_speed", "zzzz"), BadOptionValueException);
}
THEN("The value does not change.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0);
@@ -120,7 +120,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
WHEN("A float or percent is set as a percent through the string interface.") {
- config.set_deserialize("first_layer_extrusion_width", "100%");
+ config.set_deserialize_strict("first_layer_extrusion_width", "100%");
THEN("Value and percent flag are 100/true") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == true);
@@ -128,7 +128,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
WHEN("A float or percent is set as a float through the string interface.") {
- config.set_deserialize("first_layer_extrusion_width", "100");
+ config.set_deserialize_strict("first_layer_extrusion_width", "100");
THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
@@ -198,7 +198,7 @@ SCENARIO("Config ini load/save interface", "[Config]") {
WHEN("new_from_ini is called") {
Slic3r::DynamicPrintConfig config;
std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini";
- config.load_from_ini(path);
+ config.load_from_ini(path, ForwardCompatibilitySubstitutionRule::Disable);
THEN("Config object contains ini file options.") {
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1);
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD");
diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp
index 3dd8acda7..3035ddb75 100644
--- a/tests/libslic3r/test_placeholder_parser.cpp
+++ b/tests/libslic3r/test_placeholder_parser.cpp
@@ -10,7 +10,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
auto config = DynamicPrintConfig::full_print_config();
// To test the "first_layer_extrusion_width" over "nozzle_diameter" chain.
- config.set_deserialize( {
+ config.set_deserialize_strict( {
{ "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "temperature", "357;359;363;378" }
diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
index aec6ceb6a..20288243e 100644
--- a/xs/src/perlglue.cpp
+++ b/xs/src/perlglue.cpp
@@ -282,7 +282,7 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v
break;
}
default:
- if (! opt->deserialize(std::string(SvPV_nolen(value))))
+ if (! opt->deserialize(std::string(SvPV_nolen(value)), ForwardCompatibilitySubstitutionRule::Disable))
return false;
}
return true;
@@ -295,7 +295,8 @@ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &op
size_t len;
const char * c = SvPV(str, len);
std::string value(c, len);
- return THIS->set_deserialize_nothrow(opt_key, value);
+ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
+ return THIS->set_deserialize_nothrow(opt_key, value, ctxt);
}
void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize)
diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp
index b8f996797..d1c9bfa0c 100644
--- a/xs/xsp/Config.xsp
+++ b/xs/xsp/Config.xsp
@@ -55,7 +55,7 @@
%code%{
auto config = new DynamicPrintConfig();
try {
- config->load(path);
+ config->load(path, ForwardCompatibilitySubstitutionRule::Disable);
RETVAL = config;
} catch (std::exception& e) {
delete config;
@@ -119,7 +119,7 @@
%code%{
auto config = new FullPrintConfig();
try {
- config->load(path);
+ config->load(path, ForwardCompatibilitySubstitutionRule::Disable);
RETVAL = static_cast<GCodeConfig*>(config);
} catch (std::exception& e) {
delete config;
diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp
index b57df5e37..2f8a8f2f8 100644
--- a/xs/xsp/Flow.xsp
+++ b/xs/xsp/Flow.xsp
@@ -41,7 +41,7 @@ _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio)
float bridge_flow_ratio;
CODE:
ConfigOptionFloatOrPercent optwidth;
- optwidth.deserialize(width);
+ optwidth.deserialize(width, ForwardCompatibilitySubstitutionRule::Disable);
RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio));
OUTPUT:
RETVAL
diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp
index fdcc26eb6..5d006e676 100644
--- a/xs/xsp/Layer.xsp
+++ b/xs/xsp/Layer.xsp
@@ -18,8 +18,6 @@
%code%{ RETVAL = &THIS->thin_fills; %};
Ref<SurfaceCollection> fill_surfaces()
%code%{ RETVAL = &THIS->fill_surfaces; %};
- Polygons bridged()
- %code%{ RETVAL = THIS->bridged; %};
Ref<ExtrusionEntityCollection> perimeters()
%code%{ RETVAL = &THIS->perimeters; %};
Ref<ExtrusionEntityCollection> fills()
diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
index 93067ebe3..e7a171efd 100644
--- a/xs/xsp/Model.xsp
+++ b/xs/xsp/Model.xsp
@@ -22,7 +22,7 @@
%name{read_from_file} Model(std::string input_file, bool add_default_instances = true)
%code%{
try {
- RETVAL = new Model(Model::read_from_file(input_file, nullptr, add_default_instances));
+ RETVAL = new Model(Model::read_from_file(input_file, nullptr, nullptr, only_if(add_default_instances, Model::LoadAttribute::AddDefaultInstances)));
} catch (std::exception& e) {
croak("Error while opening %s: %s\n", input_file.c_str(), e.what());
}