Merge branch 'master' into fs_dir_per_glyph

# Conflicts:
#	src/slic3r/GUI/GLCanvas3D.cpp
This commit is contained in:
Filip Sykala - NTB T15p 2023-05-17 12:58:53 +02:00
commit bca6b7811d
78 changed files with 3462 additions and 994 deletions

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
width="800px"
height="800px"
viewBox="0 0 1024 1024"
class="icon"
version="1.1"
id="svg842"
sodipodi:docname="sla_view_original.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs846" />
<sodipodi:namedview
id="namedview844"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.69208076"
inkscape:cx="399.51985"
inkscape:cy="496.32936"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg842" />
<path
d="M964.266667 812.8c2.133333-8.533333 2.133333-17.066667 2.133333-23.466667s0-17.066667-2.133333-23.466666l49.066666-36.266667c4.266667-4.266667 6.4-10.666667 4.266667-14.933333l-49.066667-83.2c-2.133333-4.266667-8.533333-6.4-14.933333-4.266667l-55.466667 25.6c-12.8-10.666667-27.733333-19.2-42.666666-25.6l-6.4-61.866667c0-6.4-6.4-10.666667-10.666667-10.666666h-96c-6.4 0-10.666667 4.266667-10.666667 10.666666l-6.4 61.866667c-14.933333 6.4-29.866667 14.933333-42.666666 25.6l-55.466667-25.6c-6.4-2.133333-12.8 0-14.933333 4.266667l-49.066667 83.2c-2.133333 4.266667-2.133333 12.8 4.266667 14.933333l49.066666 36.266667c-2.133333 8.533333-2.133333 17.066667-2.133333 23.466666s0 17.066667 2.133333 23.466667l-49.066666 36.266667c-4.266667 4.266667-6.4 10.666667-4.266667 14.933333l49.066667 83.2c2.133333 4.266667 8.533333 6.4 14.933333 4.266667l55.466667-25.6c12.8 10.666667 27.733333 19.2 42.666666 25.6l6.4 61.866666c0 6.4 6.4 10.666667 10.666667 10.666667h96c6.4 0 10.666667-4.266667 10.666667-10.666667l6.4-61.866666c14.933333-6.4 29.866667-14.933333 42.666666-25.6l55.466667 25.6c6.4 2.133333 12.8 0 14.933333-4.266667l49.066667-83.2c2.133333-4.266667 2.133333-12.8-4.266667-14.933333l-49.066666-36.266667zM789.333333 900.266667c-61.866667 0-110.933333-49.066667-110.933333-110.933334 0-61.866667 49.066667-110.933333 110.933333-110.933333 61.866667 0 110.933333 49.066667 110.933334 110.933333 0 61.866667-49.066667 110.933333-110.933334 110.933334z"
fill="#607D8B"
id="path838"
style="fill:#ffffff;fill-opacity:1" />
<path
d="M789.333333 661.333333c-70.4 0-128 57.6-128 128s57.6 128 128 128 128-57.6 128-128-57.6-128-128-128z m0 192c-36.266667 0-64-27.733333-64-64s27.733333-64 64-64 64 27.733333 64 64-27.733333 64-64 64z"
fill="#455A64"
id="path840"
style="fill:#646464;fill-opacity:0" />
<path
d="M 464.75606,385.71647 H 220.52618 c -23.46667,0 -42.66667,19.2 -42.66667,42.66668 v 171.15708 c 0,23.46667 19.2,42.66667 42.66667,42.66667 h 244.22988 c 23.46668,0 42.66668,-19.2 42.66668,-42.66667 V 428.38315 c 0,-23.46668 -19.2,-42.66668 -42.66668,-42.66668 z"
style="fill:#f7c0a1;stroke-width:1.28;fill-opacity:1"
id="path1447"
sodipodi:nodetypes="sssssssss" />
<path
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
d="m 158.31475,281.62793 c -9.13538,-2.47451 -17.55246,-9.78885 -21.77597,-18.92305 -2.14973,-4.64921 -2.18374,-5.6297 -2.18374,-62.96031 v -58.23755 l 2.5171,-5.10855 c 3.53239,-7.16911 11.05319,-14.20236 17.94366,-16.78042 l 5.66954,-2.12124 H 400 639.51466 l 5.66953,2.12124 c 6.89048,2.57806 14.41128,9.61131 17.94367,16.78042 l 2.5171,5.10855 v 58.23755 58.23755 l -2.83063,5.76399 c -3.30254,6.725 -10.14711,13.42226 -16.91326,16.54928 l -4.77719,2.2078 -239.08046,0.19191 c -186.73355,0.14989 -240.09819,-0.0838 -243.72867,-1.06717 z"
id="path7485"
transform="scale(1.28)" />
<path
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
d="m 437.21118,499.60629 c -10.08409,-1.70675 -19.55357,-8.84836 -24.15393,-18.21621 l -2.84014,-5.78344 v -75.09578 -75.09579 l 2.84014,-5.78344 c 3.4061,-6.93594 9.17745,-12.3958 16.96062,-16.04525 l 5.74203,-2.69238 h 102.17113 102.17114 l 5.74203,2.69238 c 7.78317,3.64945 13.55451,9.10931 16.96062,16.04525 l 2.84014,5.78344 0.12731,66.92209 c 0.10556,55.49011 -0.0746,65.14125 -1.0544,56.49709 -0.97739,-8.62265 -1.60428,-10.87483 -3.62597,-13.02682 l -2.44427,-2.60181 h -41.24765 -41.24765 l -2.50783,2.50784 c -1.37931,1.37931 -2.5167,3.56322 -2.52753,4.85313 -0.0297,3.54851 -4.90813,49.01817 -5.27775,49.19231 -0.17708,0.0835 -5.14956,2.57971 -11.04995,5.54727 l -10.72797,5.39556 -14.94254,-0.0316 -14.94253,-0.0316 -12.03854,-5.65658 c -16.76712,-7.87841 -21.03033,-7.77771 -25.74014,0.60802 l -2.83433,5.04647 -15.83653,-0.11915 c -8.71009,-0.0655 -17.94207,-0.47549 -20.51551,-0.91106 z"
id="path7524"
transform="scale(1.28)" />
<path
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
d="m 154.98937,680.48972 c -7.22252,-2.82207 -13.15353,-8.19884 -17.12576,-15.5254 l -3.50857,-6.47134 v -58.23755 c 0,-57.52794 0.0269,-58.29576 2.20781,-63.01474 3.12702,-6.76614 9.82428,-13.61071 16.54928,-16.91326 l 5.76399,-2.83062 152.41872,-0.27172 152.41871,-0.27172 -12.21117,20.70595 c -6.71614,11.38827 -12.48085,21.74042 -12.81046,23.00479 -1.33796,5.13249 0.30977,6.86623 21.05574,22.15497 11.25014,8.29079 20.61354,15.37577 20.80755,15.7444 0.194,0.36863 -0.1096,4.69322 -0.67466,9.61021 -0.66837,5.81596 -0.68702,12.03751 -0.0533,17.80415 l 0.97403,8.86416 -19.66637,14.61906 c -10.8165,8.04047 -20.1462,15.51562 -20.73266,16.61144 -1.65622,3.09465 -1.2651,7.94874 0.99788,12.38455 l 2.06417,4.04614 -141.78323,-0.0478 c -136.20291,-0.0459 -141.9764,-0.12328 -146.69167,-1.96569 z"
id="path7563"
transform="scale(1.28)" />
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers;fill-rule:nonzero;stroke:none;stroke-opacity:1"
d="m 604.62085,664.56707 c -24.60818,-5.96525 -40.42276,-29.63578 -36.74461,-54.99766 4.89008,-33.71857 40.85293,-52.20054 71.62153,-36.80764 18.19443,9.1023 28.95735,30.19449 25.97417,50.90177 -4.18305,29.03597 -32.17825,47.85408 -60.85109,40.90353 z"
id="path7602"
transform="scale(1.28)" />
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
width="800px"
height="800px"
viewBox="0 0 1024 1024"
class="icon"
version="1.1"
id="svg842"
sodipodi:docname="sla_view_processed.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs846" />
<sodipodi:namedview
id="namedview844"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.69208076"
inkscape:cx="392.29526"
inkscape:cy="502.10903"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg842" />
<path
d="M789.333333 661.333333c-70.4 0-128 57.6-128 128s57.6 128 128 128 128-57.6 128-128-57.6-128-128-128z m0 192c-36.266667 0-64-27.733333-64-64s27.733333-64 64-64 64 27.733333 64 64-27.733333 64-64 64z"
fill="#455A64"
id="path840"
style="fill:#646464;fill-opacity:0" />
<path
d="M 809.35888,294.17114 H 565.129 c -23.46667,0 -42.66667,19.2 -42.66667,42.66668 l 0,356.86333 c 0,23.46667 19.2,42.66667 42.66667,42.66667 h 244.22988 c 23.46668,0 42.66668,-19.2 42.66668,-42.66667 l 0,-356.86333 c 0,-23.46668 -19.2,-42.66668 -42.66668,-42.66668 z"
style="fill:#ed6b21;fill-opacity:1;stroke-width:1.28"
id="path1447-4"
sodipodi:nodetypes="sssssssss" />
<path
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
d="m 158.31475,281.62793 c -9.13538,-2.47451 -17.55246,-9.78885 -21.77597,-18.92305 -2.14973,-4.64921 -2.18374,-5.6297 -2.18374,-62.96031 v -58.23755 l 2.5171,-5.10855 c 3.53239,-7.16911 11.05319,-14.20236 17.94366,-16.78042 l 5.66954,-2.12124 H 400 639.51466 l 5.66953,2.12124 c 6.89048,2.57806 14.41128,9.61131 17.94367,16.78042 l 2.5171,5.10855 v 58.23755 58.23755 l -2.83063,5.76399 c -3.30254,6.725 -10.14711,13.42226 -16.91326,16.54928 l -4.77719,2.2078 -239.08046,0.19191 c -186.73355,0.14989 -240.09819,-0.0838 -243.72867,-1.06717 z"
id="path7485"
transform="scale(1.28)" />
<path
style="fill:#ed6b21;fill-opacity:1;stroke-width:0;paint-order:stroke fill markers"
d="m 201.98898,872.0074 c -11.69328,-3.16737 -22.46714,-12.52973 -27.87324,-24.2215 -2.75165,-5.95099 -2.79518,-7.20602 -2.79518,-80.5892 v -74.54406 l 3.22188,-6.53895 c 4.52146,-9.17646 14.14809,-18.17902 22.96789,-21.47894 l 7.25701,-2.71518 H 511.3461 817.92486 l 7.257,2.71518 c 8.81982,3.29992 18.44644,12.30248 22.9679,21.47894 l 3.22189,6.53895 v 74.54406 74.54406 l -3.62321,7.37791 c -4.22725,8.608 -12.9883,17.18049 -21.64897,21.18308 l -6.1148,2.82598 -306.02299,0.24565 C 274.94273,873.56524 206.636,873.26611 201.98898,872.0074 Z"
id="path7485-7" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,3 +1,6 @@
min_slic3r_version = 2.6.0-beta0
1.9.0-beta1 Updated cooling settings for some ASA filaments to increase interlayer adhesion (XL/MK4).
1.9.0-beta0 Updated start g-code script for MK4/XL.
min_slic3r_version = 2.6.0-alpha5
1.9.0-alpha4 Updated XL and MK4 profiles. Updated PC Blend Carbon Fiber density.
1.9.0-alpha3 Updated compatibility condition for MMU1 filaments.
@ -12,11 +15,15 @@ min_slic3r_version = 2.6.0-alpha1
1.6.0-alpha1 Updated FW version notification. Decreased min layer time for PLA.
1.6.0-alpha0 Default top fill set to monotonic lines. Updated infill/perimeter overlap values. Updated output filename format. Enabled dynamic overhang speeds.
min_slic3r_version = 2.5.2-rc0
1.7.5 Updated cooling settings for some ASA filaments to increase interlayer adhesion (XL/MK4). Updated LA values (XL/MK4).
1.7.4 Updated start g-code script for MK4/XL (fixed pre-print temperature for PA filaments).
1.7.3 Updated XL and MK4 profiles. Updated PC Blend Carbon Fiber density.
1.7.2 Updated compatibility condition for MMU1 filaments.
1.7.1 Added SLA materials. Updated MK4 and XL profiles.
1.7.0 Added profiles for Original Prusa MK4.
min_slic3r_version = 2.5.1-rc0
1.6.6 Updated cooling settings for some ASA filaments to increase interlayer adhesion (XL/MK4).
1.6.5 Updated start g-code script for MK4/XL (fixed pre-print temperature for PA filaments).
1.6.4 Fixed compatibility condition for MMU1 filaments.
1.6.3 Added SLA materials.
1.6.2 Updated compatibility condition in some filament profiles (Prusa XL).

View File

@ -5,7 +5,7 @@
name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.9.0-alpha4
config_version = 1.9.0-beta1
# Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/
changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -3618,7 +3618,7 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and no
compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) and printer_notes!~/.*PG.*/
[filament:*PLAPG*]
start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.06{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.08{elsif nozzle_diameter[0]==0.35}0.07{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp"
start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.05{elsif nozzle_diameter[0]==0.25}0.14{elsif nozzle_diameter[0]==0.3}0.07{elsif nozzle_diameter[0]==0.35}0.06{elsif nozzle_diameter[0]==0.6}0.03{elsif nozzle_diameter[0]==0.5}0.035{elsif nozzle_diameter[0]==0.8}0.015{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp"
compatible_printers_condition = printer_notes=~/.*PG.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
slowdown_below_layer_time = 8
filament_cooling_final_speed = 2
@ -3675,7 +3675,7 @@ filament_max_volumetric_speed = 15
[filament:*PETPG*]
compatible_printers_condition = printer_notes=~/.*PG.*/ and nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=0.8
filament_max_volumetric_speed = 10
start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.08{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.1{elsif nozzle_diameter[0]==0.35}0.09{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\nM142 S40 ; set heatbreak target temp"
start_filament_gcode = "M900 K{if nozzle_diameter[0]==0.4}0.07{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.09{elsif nozzle_diameter[0]==0.35}0.08{elsif nozzle_diameter[0]==0.6}0.04{elsif nozzle_diameter[0]==0.5}0.05{elsif nozzle_diameter[0]==0.8}0.02{else}0{endif} ; Filament gcode\n\nM142 S40 ; set heatbreak target temp"
filament_cooling_final_speed = 1
filament_cooling_initial_speed = 2
filament_cooling_moves = 1
@ -4498,6 +4498,8 @@ filament_type = ASA
[filament:Fillamentum ASA @PG]
inherits = Fillamentum ASA; *ABSPG*
bed_temperature = 105
min_fan_speed = 10
max_fan_speed = 10
[filament:Fillamentum ASA @PG 0.6]
inherits = Fillamentum ASA @PG; *ABS06PG*
@ -4507,6 +4509,8 @@ inherits = Fillamentum ASA @PG; *ABS08PG*
[filament:Fillamentum ASA @MK4]
inherits = Fillamentum ASA; *ABSMK4*
min_fan_speed = 10
max_fan_speed = 10
[filament:Fillamentum ASA @MK4 0.6]
inherits = Fillamentum ASA @MK4; *ABS06MK4*
@ -4541,6 +4545,8 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI
inherits = Prusament ASA; *ABSPG*
first_layer_bed_temperature = 100
bed_temperature = 105
min_fan_speed = 10
max_fan_speed = 10
[filament:Prusament ASA @PG 0.6]
inherits = Prusament ASA @PG; *ABS06PG*
@ -4552,6 +4558,8 @@ temperature = 265
[filament:Prusament ASA @MK4]
inherits = Prusament ASA; *ABSMK4*
min_fan_speed = 10
max_fan_speed = 10
[filament:Prusament ASA @MK4 0.6]
inherits = Prusament ASA @MK4; *ABS06MK4*
@ -4590,6 +4598,8 @@ start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and no
[filament:Prusament PC Blend @PG]
inherits = Prusament PC Blend; *PCPG*
filament_max_volumetric_speed = 9
min_fan_speed = 10
max_fan_speed = 10
[filament:Prusament PC Blend @PG 0.6]
inherits = Prusament PC Blend @PG; *PC06PG*
@ -4602,6 +4612,8 @@ filament_max_volumetric_speed = 18
[filament:Prusament PC Blend @MK4]
inherits = Prusament PC Blend; *PCMK4*
filament_max_volumetric_speed = 9
min_fan_speed = 10
max_fan_speed = 10
[filament:Prusament PC Blend @MK4 0.6]
inherits = Prusament PC Blend @MK4; *PC06MK4*
@ -4634,24 +4646,28 @@ compatible_printers_condition = printer_notes!~/.*PRINTER_MODEL_MK(2|2.5).*/ and
[filament:Prusament PC Blend Carbon Fiber @PG]
inherits = Prusament PC Blend Carbon Fiber; *PCPG*
min_fan_speed = 10
max_fan_speed = 10
[filament:Prusament PC Blend Carbon Fiber @PG 0.6]
inherits = Prusament PC Blend Carbon Fiber; *PC06PG*
inherits = Prusament PC Blend Carbon Fiber @PG; *PC06PG*
filament_max_volumetric_speed = 13
[filament:Prusament PC Blend Carbon Fiber @PG 0.8]
inherits = Prusament PC Blend Carbon Fiber; *PC08PG*
inherits = Prusament PC Blend Carbon Fiber @PG; *PC08PG*
filament_max_volumetric_speed = 18
[filament:Prusament PC Blend Carbon Fiber @MK4]
inherits = Prusament PC Blend Carbon Fiber; *PCMK4*
min_fan_speed = 10
max_fan_speed = 10
[filament:Prusament PC Blend Carbon Fiber @MK4 0.6]
inherits = Prusament PC Blend Carbon Fiber; *PC06MK4*
inherits = Prusament PC Blend Carbon Fiber @MK4; *PC06MK4*
filament_max_volumetric_speed = 13
[filament:Prusament PC Blend Carbon Fiber @MK4 0.8]
inherits = Prusament PC Blend Carbon Fiber; *PC08MK4*
inherits = Prusament PC Blend Carbon Fiber @MK4; *PC08MK4*
filament_max_volumetric_speed = 18
[filament:Prusament PC Blend Carbon Fiber @MK2]
@ -7290,6 +7306,8 @@ inherits = Ultrafuse ASA; *ABSPG*
first_layer_bed_temperature = 105
bed_temperature = 105
filament_max_volumetric_speed = 5
min_fan_speed = 15
max_fan_speed = 40
[filament:Ultrafuse ASA @PG 0.6]
inherits = Ultrafuse ASA @PG; *ABS06PG*
@ -7302,6 +7320,8 @@ filament_max_volumetric_speed = 12
[filament:Ultrafuse ASA @MK4]
inherits = Ultrafuse ASA; *ABSMK4*
filament_max_volumetric_speed = 5
min_fan_speed = 15
max_fan_speed = 40
[filament:Ultrafuse ASA @MK4 0.6]
inherits = Ultrafuse ASA @MK4; *ABS06MK4*
@ -15778,7 +15798,7 @@ retract_before_travel = 1.5
retract_before_wipe = 80%
retract_layer_change = 1
retract_length = 0.8
start_gcode = M17 ; enable steppers\nM862.3 P "[printer_model]" ; printer model check\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n; set print area\nM555 X{first_layer_print_min[0]} Y{first_layer_print_min[1]} W{(first_layer_print_max[0]) - (first_layer_print_min[0])} H{(first_layer_print_max[1]) - (first_layer_print_min[1])}\n; inform about nozzle diameter\nM862.1 P[nozzle_diameter]\n; set & wait for bed and extruder temp for MBL\nM140 S[first_layer_bed_temperature] ; set bed temp\nM104 T0 S{((filament_type[0] == "PC" or filament_type[0] == "NYLON") ? (first_layer_temperature[0] - 25) : (filament_type[0] == "FLEX" ? 210 : 170))} ; set extruder temp for bed leveling\nM109 T0 R{((filament_type[0] == "PC" or filament_type[0] == "NYLON") ? (first_layer_temperature[0] - 25) : (filament_type[0] == "FLEX" ? 210 : 170))} ; wait for temp\n; home carriage, pick tool, home all\nG28 XY\nM84 E ; turn off E motor\nG28 Z\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG29 G ; absorb heat\n; move to the nozzle cleanup area\nG1 X{(min(((((first_layer_print_min[0] + first_layer_print_max[0]) / 2) < ((print_bed_min[0] + print_bed_max[0]) / 2)) ? (((first_layer_print_min[1] - 7) < -2) ? 70 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)) : (((first_layer_print_min[1] - 7) < -2) ? 260 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))), first_layer_print_min[0])) + 32} Y{(min((first_layer_print_min[1] - 7), first_layer_print_min[1]))} Z{5} F4800\nM302 S160 ; lower cold extrusion limit to 160C\nG1 E{-(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; retraction for nozzle cleanup\n; nozzle cleanup\nM84 E ; turn off E motor\nG29 P9 X{((((first_layer_print_min[0] + first_layer_print_max[0]) / 2) < ((print_bed_min[0] + print_bed_max[0]) / 2)) ? (((first_layer_print_min[1] - 7) < -2) ? 70 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)) : (((first_layer_print_min[1] - 7) < -2) ? 260 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)))} Y{(first_layer_print_min[1] - 7)} W{32} H{7}\nG0 Z10 F480 ; move away in Z\n{if first_layer_bed_temperature[0] > 60}\nG0 Z70 F480 ; move away (a bit more) in Z\nG0 X30 Y{print_bed_min[1]} F6000 ; move away in X/Y for higher bed temperatures\n{endif}\nM106 S100 ; cool off the nozzle\nM107 ; stop cooling off the nozzle - turn off the fan\n; MBL\nM84 E ; turn off E motor\nG29 P1 ; invalidate mbl & probe print area\nG29 P1 X30 Y0 W50 H20 C ; probe near purge place\nG29 P3.2 ; interpolate mbl probes\nG29 P3.13 ; extrapolate mbl outside probe area\nG29 A ; activate mbl\nM104 S[first_layer_temperature] ; set extruder temp\nG1 Z10 F720 ; move away in Z\nG0 X30 Y-8 F6000 ; move next to the sheet\n; wait for extruder temp\nM109 T0 S{first_layer_temperature[0]}\n;\n; purge\n;\nG92 E0 ; reset extruder position\nG0 X{(0 == 0 ? 30 : (0 == 1 ? 150 : (0 == 2 ? 210 : 330)))} Y{(0 < 4 ? -8 : -5.5)} ; move close to the sheet's edge\nG1 E{(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; deretraction after the initial one before nozzle cleaning\nG0 E10 X40 Z0.2 F500 ; purge\nG0 X70 E9 F800 ; purge\nG0 X{70 + 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{70 + 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG92 E0 ; reset extruder position\n
start_gcode = M17 ; enable steppers\nM862.3 P "[printer_model]" ; printer model check\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n; set print area\nM555 X{first_layer_print_min[0]} Y{first_layer_print_min[1]} W{(first_layer_print_max[0]) - (first_layer_print_min[0])} H{(first_layer_print_max[1]) - (first_layer_print_min[1])}\n; inform about nozzle diameter\nM862.1 P[nozzle_diameter]\n; set & wait for bed and extruder temp for MBL\nM140 S[first_layer_bed_temperature] ; set bed temp\nM104 T0 S{((filament_type[0] == "PC" or filament_type[0] == "PA") ? (first_layer_temperature[0] - 25) : (filament_type[0] == "FLEX" ? 210 : 170))} ; set extruder temp for bed leveling\nM109 T0 R{((filament_type[0] == "PC" or filament_type[0] == "PA") ? (first_layer_temperature[0] - 25) : (filament_type[0] == "FLEX" ? 210 : 170))} ; wait for temp\n; home carriage, pick tool, home all\nG28 XY\nM84 E ; turn off E motor\nG28 Z\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nG29 G ; absorb heat\n; move to the nozzle cleanup area\nG1 X{(min(((((first_layer_print_min[0] + first_layer_print_max[0]) / 2) < ((print_bed_min[0] + print_bed_max[0]) / 2)) ? (((first_layer_print_min[1] - 7) < -2) ? 70 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)) : (((first_layer_print_min[1] - 7) < -2) ? 260 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))), first_layer_print_min[0])) + 32} Y{(min((first_layer_print_min[1] - 7), first_layer_print_min[1]))} Z{5} F4800\nM302 S160 ; lower cold extrusion limit to 160C\nG1 E{-(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; retraction for nozzle cleanup\n; nozzle cleanup\nM84 E ; turn off E motor\nG29 P9 X{((((first_layer_print_min[0] + first_layer_print_max[0]) / 2) < ((print_bed_min[0] + print_bed_max[0]) / 2)) ? (((first_layer_print_min[1] - 7) < -2) ? 70 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)) : (((first_layer_print_min[1] - 7) < -2) ? 260 : (min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)))} Y{(first_layer_print_min[1] - 7)} W{32} H{7}\nG0 Z10 F480 ; move away in Z\n{if first_layer_bed_temperature[0] > 60}\nG0 Z70 F480 ; move away (a bit more) in Z\nG0 X30 Y{print_bed_min[1]} F6000 ; move away in X/Y for higher bed temperatures\n{endif}\nM106 S100 ; cool off the nozzle\nM107 ; stop cooling off the nozzle - turn off the fan\n; MBL\nM84 E ; turn off E motor\nG29 P1 ; invalidate mbl & probe print area\nG29 P1 X30 Y0 W50 H20 C ; probe near purge place\nG29 P3.2 ; interpolate mbl probes\nG29 P3.13 ; extrapolate mbl outside probe area\nG29 A ; activate mbl\nM104 S[first_layer_temperature] ; set extruder temp\nG1 Z10 F720 ; move away in Z\nG0 X30 Y-8 F6000 ; move next to the sheet\n; wait for extruder temp\nM109 T0 S{first_layer_temperature[0]}\n;\n; purge\n;\nG92 E0 ; reset extruder position\nG0 X{(0 == 0 ? 30 : (0 == 1 ? 150 : (0 == 2 ? 210 : 330)))} Y{(0 < 4 ? -8 : -5.5)} ; move close to the sheet's edge\nG1 E{(filament_type[0] == "FLEX" ? 4 : 2)} F2400 ; deretraction after the initial one before nozzle cleaning\nG0 E10 X40 Z0.2 F500 ; purge\nG0 X70 E9 F800 ; purge\nG0 X{70 + 3} Z{0.05} F{8000} ; wipe, move close to the bed\nG0 X{70 + 3 * 2} Z0.2 F{8000} ; wipe, move quickly away from the bed\nG92 E0 ; reset extruder position\n
default_print_profile = 0.20mm QUALITY @XL 0.4
default_filament_profile = "Prusament PLA @PG"
thumbnails = 16x16,313x173,440x240
@ -15884,7 +15904,7 @@ retract_before_travel = 1.5
retract_before_wipe = 80%
retract_layer_change = 1
retract_length = 0.8
start_gcode = M17 ; enable steppers\nM862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM555 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{((min(print_bed_max[0], max(first_layer_print_min[0] + 32, first_layer_print_max[0])))) - ((min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))} H{((first_layer_print_max[1])) - ((max(0, first_layer_print_min[1]) - 4))}\n\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n\nM140 S[first_layer_bed_temperature] ; set bed temp\n{if filament_type[initial_tool]=="PC" or filament_type[initial_tool]=="NYLON"}\nM104 S{first_layer_temperature[initial_tool]-25} ; set extruder temp for bed leveling\nM109 R{first_layer_temperature[initial_tool]-25} ; wait for temp\n{elsif filament_type[initial_tool]=="FLEX"}\nM104 S210 ; set extruder temp for bed leveling\nM109 R210 ; wait for temp\n{else}\nM104 S170 ; set extruder temp for bed leveling\nM109 R170 ; wait for temp\n{endif}\n\nM84 E ; turn off E motor\n\nG28 ; home all without mesh bed level\n\n; probe to clean the nozzle\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)+32} Y{((first_layer_print_min[1]) - 4)} Z{5} F4800\n\nM302 S160 ; lower cold extrusion limit to 160C\n\n{if filament_type[initial_tool]=="FLEX"}\nG1 E-4 F2400 ; retraction\n{else}\nG1 E-2 F2400 ; retraction\n{endif}\n\nM84 E ; turn off E motor\n\nG29 P9 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{32} H{4}\n\n{if first_layer_bed_temperature[initial_tool]<=60}M106 S100{endif}\n\nG0 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} Z{40} F10000\n\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nM107\n\n;\n; MBL\n;\nM84 E ; turn off E motor\nG29 ; mesh bed leveling\nM104 S[first_layer_temperature] ; set extruder temp\nG0 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4) + 4 - 4.5} Z{30} F4800\n\nM109 S[first_layer_temperature] ; wait for extruder temp\nG1 Z0.2 F720\nG92 E0\n\nM569 S0 E ; set spreadcycle mode for extruder\n\n;\n; Extrude purge line\n;\n{if filament_type[initial_tool]=="FLEX"}\nG1 E4 F2400 ; deretraction\n{else}\nG1 E2 F2400 ; deretraction\n{endif}\n\n; move right\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32) + 32} E{32 * 0.15} F1000\n; move down\nG1 Y{(max(0, first_layer_print_min[1]) - 4) + 4 - 4.5 - 1.5} E{1.5 * 0.15} F1000\n; move left\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} E{32 * 0.30} F800\n\nG92 E0\nM221 S100 ; set flow to 100%
start_gcode = M17 ; enable steppers\nM862.3 P "[printer_model]" ; printer model check\nM862.1 P[nozzle_diameter] ; nozzle diameter check\nM555 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{((min(print_bed_max[0], max(first_layer_print_min[0] + 32, first_layer_print_max[0])))) - ((min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))} H{((first_layer_print_max[1])) - ((max(0, first_layer_print_min[1]) - 4))}\n\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n\nM140 S[first_layer_bed_temperature] ; set bed temp\n{if filament_type[initial_tool]=="PC" or filament_type[initial_tool]=="PA"}\nM104 S{first_layer_temperature[initial_tool]-25} ; set extruder temp for bed leveling\nM109 R{first_layer_temperature[initial_tool]-25} ; wait for temp\n{elsif filament_type[initial_tool]=="FLEX"}\nM104 S210 ; set extruder temp for bed leveling\nM109 R210 ; wait for temp\n{else}\nM104 S170 ; set extruder temp for bed leveling\nM109 R170 ; wait for temp\n{endif}\n\nM84 E ; turn off E motor\n\nG28 ; home all without mesh bed level\n\n; probe to clean the nozzle\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)+32} Y{((first_layer_print_min[1]) - 4)} Z{5} F4800\n\nM302 S160 ; lower cold extrusion limit to 160C\n\n{if filament_type[initial_tool]=="FLEX"}\nG1 E-4 F2400 ; retraction\n{else}\nG1 E-2 F2400 ; retraction\n{endif}\n\nM84 E ; turn off E motor\n\nG29 P9 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{32} H{4}\n\n{if first_layer_bed_temperature[initial_tool]<=60}M106 S100{endif}\n\nG0 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} Z{40} F10000\n\nM190 S[first_layer_bed_temperature] ; wait for bed temp\n\nM107\n\n;\n; MBL\n;\nM84 E ; turn off E motor\nG29 ; mesh bed leveling\nM104 S[first_layer_temperature] ; set extruder temp\nG0 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4) + 4 - 4.5} Z{30} F4800\n\nM109 S[first_layer_temperature] ; wait for extruder temp\nG1 Z0.2 F720\nG92 E0\n\nM569 S0 E ; set spreadcycle mode for extruder\n\n;\n; Extrude purge line\n;\n{if filament_type[initial_tool]=="FLEX"}\nG1 E4 F2400 ; deretraction\n{else}\nG1 E2 F2400 ; deretraction\n{endif}\n\n; move right\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32) + 32} E{32 * 0.15} F1000\n; move down\nG1 Y{(max(0, first_layer_print_min[1]) - 4) + 4 - 4.5 - 1.5} E{1.5 * 0.15} F1000\n; move left\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} E{32 * 0.30} F800\n\nG92 E0\nM221 S100 ; set flow to 100%
default_print_profile = 0.20mm QUALITY @MK4 0.4
default_filament_profile = "Prusament PLA @PG"
thumbnails = 16x16,313x173,440x240

View File

@ -1,3 +1,4 @@
min_slic3r_version = 2.6.0-alpha0
1.0.2 Updated compatible printer conditions.
1.0.1 Added Prusament PETG Carbon Fiber, Fiberthree F3 PA-GF30 Pro.
1.0.0 Initial

View File

@ -2,7 +2,7 @@
[vendor]
name = Templates
config_version = 1.0.1
config_version = 1.0.2
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/
templates_profile = 1
@ -11,12 +11,12 @@ templates_profile = 1
[filament:*common*]
cooling = 1
compatible_printers =
compatible_printers_condition =
compatible_printers_condition = printer_notes!~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes!~/.*PRINTER_MODEL_MK4IS.*/
end_filament_gcode = "; Filament-specific end gcode"
extrusion_multiplier = 1
filament_loading_speed = 14
filament_loading_speed_start = 19
filament_unloading_speed = 90
filament_unloading_speed = 20
filament_unloading_speed_start = 100
filament_toolchange_delay = 0
filament_cooling_moves = 1
@ -1460,7 +1460,6 @@ cooling = 0
fan_always_on = 0
filament_max_volumetric_speed = 4
filament_type = METAL
compatible_printers_condition = nozzle_diameter[0]>=0.4
filament_colour = #FFFFFF
[filament:Polymaker PC-Max]
@ -1519,7 +1518,6 @@ first_layer_temperature = 230
max_fan_speed = 20
min_fan_speed = 20
temperature = 230
compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Prusa PETG]
inherits = *PET*

View File

@ -166,6 +166,8 @@ namespace ImGui
const wchar_t PauseHoverButton = 0x261B;
const wchar_t OpenButton = 0x261C;
const wchar_t OpenHoverButton = 0x261D;
const wchar_t SlaViewOriginal = 0x261E;
const wchar_t SlaViewProcessed = 0x261F;
const wchar_t LegendTravel = 0x2701;
const wchar_t LegendWipe = 0x2702;

View File

@ -0,0 +1,128 @@
#ifndef SRC_LIBSLIC3R_PATH_SORTING_HPP_
#define SRC_LIBSLIC3R_PATH_SORTING_HPP_
#include "AABBTreeLines.hpp"
#include "BoundingBox.hpp"
#include "Line.hpp"
#include "ankerl/unordered_dense.h"
#include <algorithm>
#include <iterator>
#include <libslic3r/Point.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <limits>
#include <type_traits>
#include <unordered_set>
namespace Slic3r {
namespace Algorithm {
//Sorts the paths such that all paths between begin and last_seed are printed first, in some order. The rest of the paths is sorted
// such that the paths that are touching some of the already printed are printed first, sorted secondary by the distance to the last point of the last
// printed path.
// begin, end, and last_seed are random access iterators. touch_limit_distance is used to check if the paths are touching - if any part of the path gets this close
// to the second, then they touch.
// convert_to_lines is a lambda that should accept the path as argument and return it as Lines vector, in correct order.
template<typename RandomAccessIterator, typename ToLines>
void sort_paths(RandomAccessIterator begin, RandomAccessIterator end, Point start, double touch_limit_distance, ToLines convert_to_lines)
{
size_t paths_count = std::distance(begin, end);
if (paths_count <= 1)
return;
auto paths_touch = [touch_limit_distance](const AABBTreeLines::LinesDistancer<Line> &left,
const AABBTreeLines::LinesDistancer<Line> &right) {
for (const Line &l : left.get_lines()) {
if (right.distance_from_lines<false>(l.a) < touch_limit_distance) {
return true;
}
}
if (right.distance_from_lines<false>(left.get_lines().back().b) < touch_limit_distance) {
return true;
}
for (const Line &l : right.get_lines()) {
if (left.distance_from_lines<false>(l.a) < touch_limit_distance) {
return true;
}
}
if (left.distance_from_lines<false>(right.get_lines().back().b) < touch_limit_distance) {
return true;
}
return false;
};
std::vector<AABBTreeLines::LinesDistancer<Line>> distancers(paths_count);
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
distancers[path_idx] = AABBTreeLines::LinesDistancer<Line>{convert_to_lines(*std::next(begin, path_idx))};
}
std::vector<std::unordered_set<size_t>> dependencies(paths_count);
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
for (size_t next_path_idx = path_idx + 1; next_path_idx < paths_count; next_path_idx++) {
if (paths_touch(distancers[path_idx], distancers[next_path_idx])) {
dependencies[next_path_idx].insert(path_idx);
}
}
}
Point current_point = start;
std::vector<std::pair<size_t, bool>> correct_order_and_direction(paths_count);
size_t unsorted_idx = 0;
size_t null_idx = size_t(-1);
size_t next_idx = null_idx;
bool reverse = false;
while (unsorted_idx < paths_count) {
next_idx = null_idx;
double lines_dist = std::numeric_limits<double>::max();
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
if (!dependencies[path_idx].empty())
continue;
double ldist = distancers[path_idx].distance_from_lines<false>(current_point);
if (ldist < lines_dist) {
const auto &lines = distancers[path_idx].get_lines();
double dist_a = (lines.front().a - current_point).cast<double>().squaredNorm();
double dist_b = (lines.back().b - current_point).cast<double>().squaredNorm();
next_idx = path_idx;
reverse = dist_b < dist_a;
lines_dist = ldist;
}
}
// we have valid next_idx, sort it, update dependencies, update current point
correct_order_and_direction[next_idx] = {unsorted_idx, reverse};
unsorted_idx++;
current_point = reverse ? distancers[next_idx].get_lines().front().a : distancers[next_idx].get_lines().back().b;
dependencies[next_idx].insert(null_idx); // prevent it from being selected again
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
dependencies[path_idx].erase(next_idx);
}
}
for (size_t path_idx = 0; path_idx < paths_count; path_idx++) {
if (correct_order_and_direction[path_idx].second) {
std::next(begin, path_idx)->reverse();
}
}
for (size_t i = 0; i < correct_order_and_direction.size() - 1; i++) {
bool swapped = false;
for (size_t j = 0; j < correct_order_and_direction.size() - i - 1; j++) {
if (correct_order_and_direction[j].first > correct_order_and_direction[j + 1].first) {
std::swap(correct_order_and_direction[j], correct_order_and_direction[j + 1]);
std::iter_swap(std::next(begin, j), std::next(begin, j + 1));
swapped = true;
}
}
if (swapped == false) {
break;
}
}
}
}} // namespace Slic3r::Algorithm
#endif /*SRC_LIBSLIC3R_PATH_SORTING_HPP_*/

View File

@ -22,8 +22,9 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
Algorithm/RegionExpansion.cpp
Algorithm/PathSorting.hpp
Algorithm/RegionExpansion.hpp
Algorithm/RegionExpansion.cpp
AnyPtr.hpp
BoundingBox.cpp
BoundingBox.hpp
@ -131,6 +132,8 @@ set(SLIC3R_SOURCES
Format/AnycubicSLA.cpp
Format/STEP.hpp
Format/STEP.cpp
Format/SLAArchiveFormatRegistry.hpp
Format/SLAArchiveFormatRegistry.cpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/Thumbnails.cpp

View File

@ -306,43 +306,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
}
}
// Detect narrow internal solid infill area and use ipEnsuring pattern instead.
// Use ipEnsuring pattern for all internal Solids.
{
std::vector<char> narrow_expolygons;
static constexpr const auto narrow_pattern = ipEnsuring;
for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id)
for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id)
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
size_t num_expolygons = fill.expolygons.size();
narrow_expolygons.clear();
narrow_expolygons.reserve(num_expolygons);
// Detect narrow expolygons.
int num_narrow = 0;
for (const ExPolygon &ex : fill.expolygons) {
bool narrow = offset_ex(ex, -scaled<float>(NarrowInfillAreaThresholdMM)).empty();
num_narrow += int(narrow);
narrow_expolygons.emplace_back(narrow);
}
if (num_narrow == num_expolygons) {
// All expolygons are narrow, change the fill pattern.
fill.params.pattern = narrow_pattern;
} else if (num_narrow > 0) {
// Some expolygons are narrow, split the fills.
params = fill.params;
params.pattern = narrow_pattern;
surface_fills.emplace_back(params);
SurfaceFill &old_fill = surface_fills[surface_fill_id];
SurfaceFill &new_fill = surface_fills.back();
new_fill.region_id = old_fill.region_id;
new_fill.surface.surface_type = stInternalSolid;
new_fill.surface.thickness = old_fill.surface.thickness;
new_fill.expolygons.reserve(num_narrow);
for (size_t i = 0; i < narrow_expolygons.size(); ++ i)
if (narrow_expolygons[i])
new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i]));
old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(),
[&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }),
old_fill.expolygons.end());
}
fill.params.pattern = ipEnsuring;
}
}

View File

@ -147,9 +147,9 @@ protected:
virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; }
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
public:
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams &params);
static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams &params);
static void connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams &params);

View File

@ -2,28 +2,303 @@
#include "../ShortestPath.hpp"
#include "../Arachne/WallToolPaths.hpp"
#include "AABBTreeLines.hpp"
#include "Algorithm/PathSorting.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "FillEnsuring.hpp"
#include "KDTreeIndirect.hpp"
#include "Line.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "SVG.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <boost/log/trivial.hpp>
#include <functional>
#include <string>
#include <type_traits>
#include <unordered_set>
#include <vector>
namespace Slic3r {
ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams &params)
ThickPolylines make_fill_polylines(
const Fill *fill, const Surface *surface, const FillParams &params, bool stop_vibrations, bool fill_gaps, bool connect_extrusions)
{
assert(params.use_arachne);
assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr);
assert(fill->print_config != nullptr && fill->print_object_config != nullptr);
const coord_t scaled_spacing = scaled<coord_t>(this->spacing);
auto rotate_thick_polylines = [](ThickPolylines &tpolylines, double cos_angle, double sin_angle) {
for (ThickPolyline &tp : tpolylines) {
for (auto &p : tp.points) {
double px = double(p.x());
double py = double(p.y());
p.x() = coord_t(round(cos_angle * px - sin_angle * py));
p.y() = coord_t(round(cos_angle * py + sin_angle * px));
}
}
};
// Perform offset.
Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled<float>(this->overlap)) : ExPolygons{surface->expolygon};
// Create the infills for each of the regions.
ThickPolylines thick_polylines_out;
for (ExPolygon &ex_poly : expp) {
Point bbox_size = ex_poly.contour.bounding_box().size();
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1;
auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) {
return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) ||
(bhigh >= alow && bhigh <= ahigh);
};
const coord_t scaled_spacing = scaled<coord_t>(fill->spacing);
double distance_limit_reconnection = 2.0 * double(scaled_spacing);
double squared_distance_limit_reconnection = distance_limit_reconnection * distance_limit_reconnection;
Polygons filled_area = to_polygons(surface->expolygon);
std::pair<float, Point> rotate_vector = fill->_infill_direction(surface);
double aligning_angle = -rotate_vector.first + PI;
polygons_rotate(filled_area, aligning_angle);
BoundingBox bb = get_extents(filled_area);
Polygons inner_area = stop_vibrations ? intersection(filled_area, opening(filled_area, 2 * scaled_spacing, 3 * scaled_spacing)) :
filled_area;
inner_area = shrink(inner_area, scaled_spacing * 0.5 - scaled<double>(fill->overlap));
AABBTreeLines::LinesDistancer<Line> area_walls{to_lines(inner_area)};
const size_t n_vlines = (bb.max.x() - bb.min.x() + scaled_spacing - 1) / scaled_spacing;
std::vector<Line> vertical_lines(n_vlines);
coord_t y_min = bb.min.y();
coord_t y_max = bb.max.y();
for (size_t i = 0; i < n_vlines; i++) {
coord_t x = bb.min.x() + i * double(scaled_spacing);
vertical_lines[i].a = Point{x, y_min};
vertical_lines[i].b = Point{x, y_max};
}
if (vertical_lines.size() > 0) {
vertical_lines.push_back(vertical_lines.back());
vertical_lines.back().a = Point{coord_t(bb.min.x() + n_vlines * double(scaled_spacing) + scaled_spacing * 0.5), y_min};
vertical_lines.back().b = Point{vertical_lines.back().a.x(), y_max};
}
std::vector<std::vector<Line>> polygon_sections(n_vlines);
for (size_t i = 0; i < n_vlines; i++) {
const auto intersections = area_walls.intersections_with_line<true>(vertical_lines[i]);
for (int intersection_idx = 0; intersection_idx < int(intersections.size()) - 1; intersection_idx++) {
const auto &a = intersections[intersection_idx];
const auto &b = intersections[intersection_idx + 1];
if (area_walls.outside((a.first + b.first) / 2) < 0) {
if (std::abs(a.first.y() - b.first.y()) > scaled_spacing) {
polygon_sections[i].emplace_back(a.first, b.first);
}
}
}
}
if (stop_vibrations) {
struct Node
{
int section_idx;
int line_idx;
int skips_taken = 0;
bool neighbours_explored = false;
std::vector<std::pair<int, int>> neighbours{};
};
coord_t length_filter = scale_(4);
size_t skips_allowed = 2;
size_t min_removal_conut = 5;
for (int section_idx = 0; section_idx < polygon_sections.size(); section_idx++) {
for (int line_idx = 0; line_idx < polygon_sections[section_idx].size(); line_idx++) {
if (const Line &line = polygon_sections[section_idx][line_idx]; line.a != line.b && line.length() < length_filter) {
std::set<std::pair<int, int>> to_remove{{section_idx, line_idx}};
std::vector<Node> to_visit{{section_idx, line_idx}};
bool initial_touches_long_lines = false;
if (section_idx > 0) {
for (int prev_line_idx = 0; prev_line_idx < polygon_sections[section_idx - 1].size(); prev_line_idx++) {
if (const Line &nl = polygon_sections[section_idx - 1][prev_line_idx];
nl.a != nl.b && segments_overlap(line.a.y(), line.b.y(), nl.a.y(), nl.b.y())) {
initial_touches_long_lines = true;
}
}
}
while (!to_visit.empty()) {
Node curr = to_visit.back();
const Line &curr_l = polygon_sections[curr.section_idx][curr.line_idx];
if (curr.neighbours_explored) {
bool is_valid_for_removal = (curr_l.length() < length_filter) &&
((int(to_remove.size()) - curr.skips_taken > min_removal_conut) ||
(curr.neighbours.empty() && !initial_touches_long_lines));
if (!is_valid_for_removal) {
for (const auto &n : curr.neighbours) {
if (to_remove.find(n) != to_remove.end()) {
is_valid_for_removal = true;
break;
}
}
}
if (!is_valid_for_removal) {
to_remove.erase({curr.section_idx, curr.line_idx});
}
to_visit.pop_back();
} else {
to_visit.back().neighbours_explored = true;
int curr_index = to_visit.size() - 1;
bool can_use_skip = curr_l.length() <= length_filter && curr.skips_taken < skips_allowed;
if (curr.section_idx + 1 < polygon_sections.size()) {
for (int lidx = 0; lidx < polygon_sections[curr.section_idx + 1].size(); lidx++) {
if (const Line &nl = polygon_sections[curr.section_idx + 1][lidx];
nl.a != nl.b && segments_overlap(curr_l.a.y(), curr_l.b.y(), nl.a.y(), nl.b.y()) &&
(nl.length() < length_filter || can_use_skip)) {
to_visit[curr_index].neighbours.push_back({curr.section_idx + 1, lidx});
to_remove.insert({curr.section_idx + 1, lidx});
Node next_node{curr.section_idx + 1, lidx, curr.skips_taken + (nl.length() >= length_filter)};
to_visit.push_back(next_node);
}
}
}
}
}
for (const auto &pair : to_remove) {
Line &l = polygon_sections[pair.first][pair.second];
l.a = l.b;
}
}
}
}
}
for (size_t section_idx = 0; section_idx < polygon_sections.size(); section_idx++) {
polygon_sections[section_idx].erase(std::remove_if(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(),
[](const Line &s) { return s.a == s.b; }),
polygon_sections[section_idx].end());
std::sort(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(),
[](const Line &a, const Line &b) { return a.a.y() < b.b.y(); });
}
ThickPolylines thick_polylines;
{
for (const auto &polygon_slice : polygon_sections) {
for (const Line &segment : polygon_slice) {
ThickPolyline &new_path = thick_polylines.emplace_back();
new_path.points.push_back(segment.a);
new_path.width.push_back(scaled_spacing);
new_path.points.push_back(segment.b);
new_path.width.push_back(scaled_spacing);
new_path.endpoints = {true, true};
}
}
}
if (fill_gaps) {
Polygons reconstructed_area{};
// reconstruct polygon from polygon sections
{
struct TracedPoly
{
Points lows;
Points highs;
};
std::vector<std::vector<Line>> polygon_sections_w_width = polygon_sections;
for (auto &slice : polygon_sections_w_width) {
for (Line &l : slice) {
l.a -= Point{0.0, 0.5 * scaled_spacing};
l.b += Point{0.0, 0.5 * scaled_spacing};
}
}
std::vector<TracedPoly> current_traced_polys;
for (const auto &polygon_slice : polygon_sections_w_width) {
std::unordered_set<const Line *> used_segments;
for (TracedPoly &traced_poly : current_traced_polys) {
auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(),
[](const Point &high, const Line &seg) { return seg.a.y() > high.y(); });
bool segment_added = false;
for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) {
if (used_segments.find(&(*candidate)) != used_segments.end()) {
continue;
}
if (connect_extrusions && (traced_poly.lows.back() - candidates_begin->a).cast<double>().squaredNorm() <
squared_distance_limit_reconnection) {
traced_poly.lows.push_back(candidates_begin->a);
} else {
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0});
traced_poly.lows.push_back(candidates_begin->a - Point{scaled_spacing / 2, 0});
traced_poly.lows.push_back(candidates_begin->a);
}
if (connect_extrusions && (traced_poly.highs.back() - candidates_begin->b).cast<double>().squaredNorm() <
squared_distance_limit_reconnection) {
traced_poly.highs.push_back(candidates_begin->b);
} else {
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0});
traced_poly.highs.push_back(candidates_begin->b - Point{scaled_spacing / 2, 0});
traced_poly.highs.push_back(candidates_begin->b);
}
segment_added = true;
used_segments.insert(&(*candidates_begin));
}
if (!segment_added) {
// Zero or multiple overlapping segments. Resolving this is nontrivial,
// so we just close this polygon and maybe open several new. This will hopefully happen much less often
traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0});
traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0});
Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows));
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
traced_poly.lows.clear();
traced_poly.highs.clear();
}
}
current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(),
[](const TracedPoly &tp) { return tp.lows.empty(); }),
current_traced_polys.end());
for (const auto &segment : polygon_slice) {
if (used_segments.find(&segment) == used_segments.end()) {
TracedPoly &new_tp = current_traced_polys.emplace_back();
new_tp.lows.push_back(segment.a - Point{scaled_spacing / 2, 0});
new_tp.lows.push_back(segment.a);
new_tp.highs.push_back(segment.b - Point{scaled_spacing / 2, 0});
new_tp.highs.push_back(segment.b);
}
}
}
// add not closed polys
for (TracedPoly &traced_poly : current_traced_polys) {
Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows));
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
}
}
reconstructed_area = union_safety_offset(reconstructed_area);
ExPolygons gaps_for_additional_filling = diff_ex(filled_area, reconstructed_area);
if (fill->overlap != 0) {
gaps_for_additional_filling = offset_ex(gaps_for_additional_filling, scaled<float>(fill->overlap));
}
// BoundingBox bbox = get_extents(filled_area);
// bbox.offset(scale_(1.));
// ::Slic3r::SVG svg(debug_out_path(("surface" + std::to_string(surface->area())).c_str()).c_str(), bbox);
// svg.draw(to_lines(filled_area), "red", scale_(0.4));
// svg.draw(to_lines(reconstructed_area), "blue", scale_(0.3));
// svg.draw(to_lines(gaps_for_additional_filling), "green", scale_(0.2));
// svg.draw(vertical_lines, "black", scale_(0.1));
// svg.Close();
for (ExPolygon &ex_poly : gaps_for_additional_filling) {
BoundingBox ex_bb = ex_poly.contour.bounding_box();
coord_t loops_count = (std::max(ex_bb.size().x(), ex_bb.size().y()) + scaled_spacing - 1) / scaled_spacing;
Polygons polygons = to_polygons(ex_poly);
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config);
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height,
*fill->print_object_config, *fill->print_config);
if (std::vector<Arachne::VariableWidthLines> loops = wall_tool_paths.getToolPaths(); !loops.empty()) {
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
for (Arachne::VariableWidthLines &loop : loops) {
@ -33,50 +308,166 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const
all_extrusions.emplace_back(&wall);
}
// Split paths using a nearest neighbor search.
size_t firts_poly_idx = thick_polylines_out.size();
Point last_pos(0, 0);
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
if (extrusion->empty())
if (extrusion->junctions.size() < 2) {
continue;
}
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
if (thick_polyline.length() == 0.)
//FIXME this should not happen.
if (extrusion->is_closed) {
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, ex_bb.min));
thick_polyline.clip_end(scaled_spacing * 0.5);
}
if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) {
thick_polylines.push_back(thick_polyline);
}
}
}
}
std::sort(thick_polylines.begin(), thick_polylines.end(), [](const ThickPolyline &left, const ThickPolyline &right) {
BoundingBox lbb(left.points);
BoundingBox rbb(right.points);
if (lbb.min.x() == rbb.min.x())
return lbb.min.y() < rbb.min.y();
else
return lbb.min.x() < rbb.min.x();
});
// connect tiny gap fills to close colinear line
struct EndPoint
{
Vec2d position;
size_t polyline_idx;
size_t other_end_point_idx;
bool is_first;
bool used = false;
};
std::vector<EndPoint> connection_endpoints;
connection_endpoints.reserve(thick_polylines.size() * 2);
for (size_t pl_idx = 0; pl_idx < thick_polylines.size(); pl_idx++) {
size_t current_idx = connection_endpoints.size();
connection_endpoints.push_back({thick_polylines[pl_idx].first_point().cast<double>(), pl_idx, current_idx + 1, true});
connection_endpoints.push_back({thick_polylines[pl_idx].last_point().cast<double>(), pl_idx, current_idx, false});
}
std::vector<bool> linear_segment_flags(thick_polylines.size());
for (size_t i = 0;i < thick_polylines.size(); i++) {
const ThickPolyline& tp = thick_polylines[i];
linear_segment_flags[i] = tp.points.size() == 2 && tp.points.front().x() == tp.points.back().x() &&
tp.width.front() == scaled_spacing && tp.width.back() == scaled_spacing;
}
auto coord_fn = [&connection_endpoints](size_t idx, size_t dim) { return connection_endpoints[idx].position[dim]; };
KDTreeIndirect<2, double, decltype(coord_fn)> endpoints_tree{coord_fn, connection_endpoints.size()};
for (size_t ep_idx = 0; ep_idx < connection_endpoints.size(); ep_idx++) {
EndPoint &ep1 = connection_endpoints[ep_idx];
if (!ep1.used) {
std::vector<size_t> close_endpoints = find_nearby_points(endpoints_tree, ep1.position, double(scaled_spacing));
for (size_t close_endpoint_idx : close_endpoints) {
EndPoint &ep2 = connection_endpoints[close_endpoint_idx];
if (ep2.used || ep2.polyline_idx == ep1.polyline_idx ||
(linear_segment_flags[ep1.polyline_idx] && linear_segment_flags[ep2.polyline_idx])) {
continue;
assert(thick_polyline.size() > 1);
assert(thick_polyline.length() > 0.);
//assert(thick_polyline.points.size() == thick_polyline.width.size());
if (extrusion->is_closed)
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
assert(thick_polyline.size() > 1);
//assert(thick_polyline.points.size() == thick_polyline.width.size());
thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point();
}
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
// Keep valid paths only.
size_t j = firts_poly_idx;
for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
assert(thick_polylines_out[i].size() > 1);
assert(thick_polylines_out[i].length() > 0.);
//assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size());
thick_polylines_out[i].clip_end(this->loop_clipping);
assert(thick_polylines_out[i].size() > 1);
if (thick_polylines_out[i].is_valid()) {
if (j < i)
thick_polylines_out[j] = std::move(thick_polylines_out[i]);
++j;
EndPoint &target_ep = ep1.polyline_idx > ep2.polyline_idx ? ep1 : ep2;
EndPoint &source_ep = ep1.polyline_idx > ep2.polyline_idx ? ep2 : ep1;
ThickPolyline &target_tp = thick_polylines[target_ep.polyline_idx];
ThickPolyline &source_tp = thick_polylines[source_ep.polyline_idx];
linear_segment_flags[target_ep.polyline_idx] = linear_segment_flags[ep1.polyline_idx] ||
linear_segment_flags[ep2.polyline_idx];
Vec2d v1 = target_ep.is_first ?
(target_tp.points[0] - target_tp.points[1]).cast<double>() :
(target_tp.points.back() - target_tp.points[target_tp.points.size() - 1]).cast<double>();
Vec2d v2 = source_ep.is_first ?
(source_tp.points[1] - source_tp.points[0]).cast<double>() :
(source_tp.points[source_tp.points.size() - 1] - source_tp.points.back()).cast<double>();
if (std::abs(Slic3r::angle(v1, v2)) > PI / 6.0) {
continue;
}
// connect target_ep and source_ep, result is stored in target_tp, source_tp will be cleared
if (target_ep.is_first) {
target_tp.reverse();
target_ep.is_first = false;
connection_endpoints[target_ep.other_end_point_idx].is_first = true;
}
size_t new_start_idx = target_ep.other_end_point_idx;
if (!source_ep.is_first) {
source_tp.reverse();
source_ep.is_first = true;
connection_endpoints[source_ep.other_end_point_idx].is_first = false;
}
size_t new_end_idx = source_ep.other_end_point_idx;
target_tp.points.insert(target_tp.points.end(), source_tp.points.begin(), source_tp.points.end());
target_tp.width.push_back(target_tp.width.back());
target_tp.width.push_back(source_tp.width.front());
target_tp.width.insert(target_tp.width.end(), source_tp.width.begin(), source_tp.width.end());
target_ep.used = true;
source_ep.used = true;
connection_endpoints[new_start_idx].polyline_idx = target_ep.polyline_idx;
connection_endpoints[new_end_idx].polyline_idx = target_ep.polyline_idx;
connection_endpoints[new_start_idx].other_end_point_idx = new_end_idx;
connection_endpoints[new_end_idx].other_end_point_idx = new_start_idx;
source_tp.clear();
break;
}
if (j < thick_polylines_out.size())
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
}
}
return thick_polylines_out;
thick_polylines.erase(std::remove_if(thick_polylines.begin(), thick_polylines.end(),
[scaled_spacing](const ThickPolyline &tp) {
return tp.length() < scaled_spacing &&
std::all_of(tp.width.begin(), tp.width.end(),
[scaled_spacing](double w) { return w < scaled_spacing; });
}),
thick_polylines.end());
}
Algorithm::sort_paths(thick_polylines.begin(), thick_polylines.end(), bb.min, double(scaled_spacing) * 1.2, [](const ThickPolyline &tp) {
Lines ls;
Point prev = tp.first_point();
for (size_t i = 1; i < tp.points.size(); i++) {
ls.emplace_back(prev, tp.points[i]);
prev = ls.back().b;
}
return ls;
});
if (connect_extrusions) {
ThickPolylines connected_thick_polylines;
if (!thick_polylines.empty()) {
connected_thick_polylines.push_back(thick_polylines.front());
for (size_t tp_idx = 1; tp_idx < thick_polylines.size(); tp_idx++) {
ThickPolyline &tp = thick_polylines[tp_idx];
ThickPolyline &tail = connected_thick_polylines.back();
Point last = tail.last_point();
if ((last - tp.last_point()).cast<double>().squaredNorm() < (last - tp.first_point()).cast<double>().squaredNorm()) {
tp.reverse();
}
if ((last - tp.first_point()).cast<double>().squaredNorm() < squared_distance_limit_reconnection) {
tail.points.insert(tail.points.end(), tp.points.begin(), tp.points.end());
tail.width.push_back(scaled_spacing);
tail.width.push_back(scaled_spacing);
tail.width.insert(tail.width.end(), tp.width.begin(), tp.width.end());
} else {
connected_thick_polylines.push_back(tp);
}
}
}
thick_polylines = connected_thick_polylines;
}
rotate_thick_polylines(thick_polylines, cos(-aligning_angle), sin(-aligning_angle));
return thick_polylines;
}
} // namespace Slic3r

View File

@ -6,13 +6,19 @@
namespace Slic3r {
class FillEnsuring : public FillRectilinear
ThickPolylines make_fill_polylines(
const Fill *fill, const Surface *surface, const FillParams &params, bool stop_vibrations, bool fill_gaps, bool connect_extrusions);
class FillEnsuring : public Fill
{
public:
Fill *clone() const override { return new FillEnsuring(*this); }
~FillEnsuring() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override { return {}; };
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams &params) override;
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams &params) override
{
return make_fill_polylines(this, surface, params, true, true, true);
};
protected:
void fill_surface_single_arachne(const Surface &surface, const FillParams &params, ThickPolylines &thick_polylines_out);

View File

@ -4,44 +4,14 @@
#include <string>
#include "SLAArchiveWriter.hpp"
#include "SLAArchiveFormatRegistry.hpp"
#include "libslic3r/PrintConfig.hpp"
#define ANYCUBIC_SLA_FORMAT_VERSION_1 1
#define ANYCUBIC_SLA_FORMAT_VERSION_515 515
#define ANYCUBIC_SLA_FORMAT_VERSION_516 516
#define ANYCUBIC_SLA_FORMAT_VERSION_517 517
#define ANYCUBIC_SLA_FORMAT_VERSIONED(FILEFORMAT, NAME, VERSION) \
{ FILEFORMAT, { FILEFORMAT, [] (const auto &cfg) { return std::make_unique<AnycubicSLAArchive>(cfg, VERSION); } } }
#define ANYCUBIC_SLA_FORMAT(FILEFORMAT, NAME) \
ANYCUBIC_SLA_FORMAT_VERSIONED(FILEFORMAT, NAME, ANYCUBIC_SLA_FORMAT_VERSION_1)
/**
// Supports only ANYCUBIC_SLA_VERSION_1
ANYCUBIC_SLA_FORMAT_VERSIONED("pws", "Photon / Photon S", ANYCUBIC_SLA_VERSION_1),
ANYCUBIC_SLA_FORMAT_VERSIONED("pw0", "Photon Zero", ANYCUBIC_SLA_VERSION_1),
ANYCUBIC_SLA_FORMAT_VERSIONED("pwx", "Photon X", ANYCUBIC_SLA_VERSION_1),
// Supports ANYCUBIC_SLA_VERSION_1 and ANYCUBIC_SLA_VERSION_515
ANYCUBIC_SLA_FORMAT_VERSIONED("pwmo", "Photon Mono", ANYCUBIC_SLA_VERSION_1),
ANYCUBIC_SLA_FORMAT_VERSIONED("pwms", "Photon Mono SE", ANYCUBIC_SLA_VERSION_1),
ANYCUBIC_SLA_FORMAT_VERSIONED("dlp", "Photon Ultra", ANYCUBIC_SLA_VERSION_1),
ANYCUBIC_SLA_FORMAT_VERSIONED("pwmx", "Photon Mono X", ANYCUBIC_SLA_VERSION_1),
ANYCUBIC_SLA_FORMAT_VERSIONED("pmsq", "Photon Mono SQ", ANYCUBIC_SLA_VERSION_1),
// Supports ANYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516
ANYCUBIC_SLA_FORMAT_VERSIONED("pwma", "Photon Mono 4K", ANYCUBIC_SLA_VERSION_515),
ANYCUBIC_SLA_FORMAT_VERSIONED("pm3", "Photon M3", ANYCUBIC_SLA_VERSION_515),
ANYCUBIC_SLA_FORMAT_VERSIONED("pm3m", "Photon M3 Max", ANYCUBIC_SLA_VERSION_515),
// Supports NYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516 and ANYCUBIC_SLA_VERSION_517
ANYCUBIC_SLA_FORMAT_VERSIONED("pwmb", "Photon Mono X 6K / Photon M3 Plus", ANYCUBIC_SLA_VERSION_515),
ANYCUBIC_SLA_FORMAT_VERSIONED("dl2p", "Photon Photon D2", ANYCUBIC_SLA_VERSION_515),
ANYCUBIC_SLA_FORMAT_VERSIONED("pmx2", "Photon Mono X2", ANYCUBIC_SLA_VERSION_515),
ANYCUBIC_SLA_FORMAT_VERSIONED("pm3r", "Photon M3 Premium", ANYCUBIC_SLA_VERSION_515),
*/
constexpr uint16_t ANYCUBIC_SLA_FORMAT_VERSION_1 = 1;
constexpr uint16_t ANYCUBIC_SLA_FORMAT_VERSION_515 = 515;
constexpr uint16_t ANYCUBIC_SLA_FORMAT_VERSION_516 = 516;
constexpr uint16_t ANYCUBIC_SLA_FORMAT_VERSION_517 = 517;
namespace Slic3r {
@ -75,6 +45,21 @@ public:
const std::string &projectname = "") override;
};
inline Slic3r::ArchiveEntry anycubic_sla_format_versioned(const char *fileformat, const char *desc, uint16_t version)
{
Slic3r::ArchiveEntry entry(fileformat);
entry.desc = desc;
entry.ext = fileformat;
entry.wrfactoryfn = [version] (const auto &cfg) { return std::make_unique<AnycubicSLAArchive>(cfg, version); };
return entry;
}
inline Slic3r::ArchiveEntry anycubic_sla_format(const char *fileformat, const char *desc)
{
return anycubic_sla_format_versioned(fileformat, desc, ANYCUBIC_SLA_FORMAT_VERSION_1);
}
} // namespace Slic3r::sla

View File

@ -17,6 +17,7 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "SLAArchiveReader.hpp"
#include "SLAArchiveFormatRegistry.hpp"
#include "ZipperArchiveImport.hpp"
#include "libslic3r/MarchingSquares.hpp"
@ -26,6 +27,7 @@
#include "libslic3r/SLA/RasterBase.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
@ -436,7 +438,7 @@ ConfigSubstitutions SL1Reader::read(std::vector<ExPolygons> &slices,
ConfigSubstitutions SL1Reader::read(DynamicPrintConfig &out)
{
ZipperArchive arch = read_zipper_archive(m_fname, {}, {"png"});
ZipperArchive arch = read_zipper_archive(m_fname, {"ini"}, {"png", "thumbnail"});
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
}

View File

@ -0,0 +1,148 @@
#include <set>
#include <mutex>
#include <memory>
#include "SL1.hpp"
#include "SL1_SVG.hpp"
#include "AnycubicSLA.hpp"
#include "I18N.hpp"
#include "SLAArchiveFormatRegistry.hpp"
namespace Slic3r {
static std::mutex arch_mtx;
class Registry {
static std::unique_ptr<Registry> registry;
std::set<ArchiveEntry> entries;
public:
Registry ()
{
entries = {
{
"SL1", // id
L("SL1 archive format"), // description
"sl1", // main extension
{"sl1s", "zip"}, // extension aliases
// Writer factory
[] (const auto &cfg) { return std::make_unique<SL1Archive>(cfg); },
// Reader factory
[] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) {
return std::make_unique<SL1Reader>(fname, quality, progr);
}
},
{
"SL1SVG",
L("SL1SVG archive files"),
"sl1_svg",
{},
[] (const auto &cfg) { return std::make_unique<SL1_SVGArchive>(cfg); },
[] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) {
return std::make_unique<SL1_SVGReader>(fname, quality, progr);
}
},
{
"SL2",
"",
"sl1_svg",
{},
[] (const auto &cfg) { return std::make_unique<SL1_SVGArchive>(cfg); },
nullptr
},
anycubic_sla_format("pwmo", "Photon Mono"),
anycubic_sla_format("pwmx", "Photon Mono X"),
anycubic_sla_format("pwms", "Photon Mono SE"),
/**
// Supports only ANYCUBIC_SLA_VERSION_1
anycubic_sla_format_versioned("pws", "Photon / Photon S", ANYCUBIC_SLA_VERSION_1),
anycubic_sla_format_versioned("pw0", "Photon Zero", ANYCUBIC_SLA_VERSION_1),
anycubic_sla_format_versioned("pwx", "Photon X", ANYCUBIC_SLA_VERSION_1),
// Supports ANYCUBIC_SLA_VERSION_1 and ANYCUBIC_SLA_VERSION_515
anycubic_sla_format_versioned("pwmo", "Photon Mono", ANYCUBIC_SLA_VERSION_1),
anycubic_sla_format_versioned("pwms", "Photon Mono SE", ANYCUBIC_SLA_VERSION_1),
anycubic_sla_format_versioned("dlp", "Photon Ultra", ANYCUBIC_SLA_VERSION_1),
anycubic_sla_format_versioned("pwmx", "Photon Mono X", ANYCUBIC_SLA_VERSION_1),
anycubic_sla_format_versioned("pmsq", "Photon Mono SQ", ANYCUBIC_SLA_VERSION_1),
// Supports ANYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516
anycubic_sla_format_versioned("pwma", "Photon Mono 4K", ANYCUBIC_SLA_VERSION_515),
anycubic_sla_format_versioned("pm3", "Photon M3", ANYCUBIC_SLA_VERSION_515),
anycubic_sla_format_versioned("pm3m", "Photon M3 Max", ANYCUBIC_SLA_VERSION_515),
// Supports NYCUBIC_SLA_VERSION_515 and ANYCUBIC_SLA_VERSION_516 and ANYCUBIC_SLA_VERSION_517
anycubic_sla_format_versioned("pwmb", "Photon Mono X 6K / Photon M3 Plus", ANYCUBIC_SLA_VERSION_515),
anycubic_sla_format_versioned("dl2p", "Photon Photon D2", ANYCUBIC_SLA_VERSION_515),
anycubic_sla_format_versioned("pmx2", "Photon Mono X2", ANYCUBIC_SLA_VERSION_515),
anycubic_sla_format_versioned("pm3r", "Photon M3 Premium", ANYCUBIC_SLA_VERSION_515),
*/
};
}
static Registry& get_instance()
{
if (!registry)
registry = std::make_unique<Registry>();
return *registry;
}
static std::set<ArchiveEntry>& get()
{
return get_instance().entries;
}
std::set<ArchiveEntry>& get_entries() { return entries; }
};
std::unique_ptr<Registry> Registry::registry = nullptr;
std::set<ArchiveEntry> registered_sla_archives()
{
std::lock_guard lk{arch_mtx};
return Registry::get();
}
std::vector<std::string> get_extensions(const ArchiveEntry &entry)
{
auto ret = reserve_vector<std::string>(entry.ext_aliases.size() + 1);
ret.emplace_back(entry.ext);
for (const char *alias : entry.ext_aliases)
ret.emplace_back(alias);
return ret;
}
ArchiveWriterFactory get_writer_factory(const char *formatid)
{
std::lock_guard lk{arch_mtx};
ArchiveWriterFactory ret;
auto entry = Registry::get().find(ArchiveEntry{formatid});
if (entry != Registry::get().end())
ret = entry->wrfactoryfn;
return ret;
}
ArchiveReaderFactory get_reader_factory(const char *formatid)
{
std::lock_guard lk{arch_mtx};
ArchiveReaderFactory ret;
auto entry = Registry::get().find(ArchiveEntry{formatid});
if (entry != Registry::get().end())
ret = entry->rdfactoryfn;
return ret;
}
} // namespace Slic3r::sla

View File

@ -0,0 +1,71 @@
#ifndef SLA_ARCHIVE_FORMAT_REGISTRY_HPP
#define SLA_ARCHIVE_FORMAT_REGISTRY_HPP
#include "SLAArchiveWriter.hpp"
#include "SLAArchiveReader.hpp"
#include <cstring>
namespace Slic3r {
// Factory function that returns an implementation of SLAArchiveWriter given
// a printer configuration.
using ArchiveWriterFactory = std::function<
std::unique_ptr<SLAArchiveWriter>(const SLAPrinterConfig &)
>;
// Factory function that returns an implementation of SLAArchiveReader
using ArchiveReaderFactory = std::function<
std::unique_ptr<SLAArchiveReader>(const std::string &fname,
SLAImportQuality quality,
const ProgrFn & progr)
>;
struct ArchiveEntry {
// Main ID for the format, for internal unique identification
const char *id;
// Generic description (usable in GUI) about an archive format. Should only
// be marked for localization (macro L).
const char *desc = "";
// Main extension of the format.
const char *ext = "zip";
ArchiveWriterFactory wrfactoryfn;
ArchiveReaderFactory rdfactoryfn;
// Secondary, alias extensions
std::vector<const char *> ext_aliases;
explicit ArchiveEntry(const char *formatid) : id{formatid} {}
ArchiveEntry(const char *formatid,
const char *description,
const char *extension,
std::initializer_list<const char *> extaliases,
const ArchiveWriterFactory &wrfn,
const ArchiveReaderFactory &rdfn)
: id{formatid}
, desc{description}
, ext{extension}
, ext_aliases{extaliases}
, wrfactoryfn{wrfn}
, rdfactoryfn{rdfn}
{}
bool operator <(const ArchiveEntry &other) const
{
return std::strcmp(id, other.id) < 0;
}
};
std::vector<std::string> get_extensions(const ArchiveEntry &entry);
std::set<ArchiveEntry> registered_sla_archives();
ArchiveWriterFactory get_writer_factory(const char *formatid);
ArchiveReaderFactory get_reader_factory(const char *formatid);
} // namespace Slic3r
#endif // ARCHIVEREGISTRY_HPP

View File

@ -8,44 +8,13 @@
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
#include "SLAArchiveFormatRegistry.hpp"
#include <array>
#include <map>
namespace Slic3r {
namespace {
// Factory function that returns an implementation of SLAArchiveReader.
using ArchiveFactory = std::function<
std::unique_ptr<SLAArchiveReader>(const std::string &fname,
SLAImportQuality quality,
const ProgrFn & progr)>;
// Entry in the global registry of readable archive formats.
struct ArchiveEntry {
const char *descr;
std::vector<const char *> extensions;
ArchiveFactory factoryfn;
};
// This is where the readable archive formats are registered.
static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
{
"SL1",
{ L("SL1 / SL1S archive files"), {"sl1", "sl1s", "zip"},
[] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique<SL1Reader>(fname, quality, progr); } }
},
{
"SL1SVG",
{ L("SL1SVG archive files"), {"sl1_svg"/*, "zip"*/}, // also a zip but unnecessary hassle to implement single extension for multiple archives
[] (const std::string &fname, SLAImportQuality quality, const ProgrFn &progr) { return std::make_unique<SL1_SVGReader>(fname, quality, progr); }}
},
// TODO: pwmx and future others.
};
} // namespace
std::unique_ptr<SLAArchiveReader> SLAArchiveReader::create(
const std::string &fname,
const std::string &format_id,
@ -64,11 +33,13 @@ std::unique_ptr<SLAArchiveReader> SLAArchiveReader::create(
std::unique_ptr<SLAArchiveReader> ret;
auto arch_from = REGISTERED_ARCHIVES.begin();
auto arch_to = REGISTERED_ARCHIVES.end();
auto registry = registered_sla_archives();
auto arch_it = REGISTERED_ARCHIVES.find(format_id);
if (arch_it != REGISTERED_ARCHIVES.end()) {
auto arch_from = registry.begin();
auto arch_to = registry.end();
auto arch_it = registry.find(ArchiveEntry{format_id.c_str()});
if (arch_it != registry.end()) {
arch_from = arch_it;
arch_to = arch_it;
}
@ -77,52 +48,23 @@ std::unique_ptr<SLAArchiveReader> SLAArchiveReader::create(
if (ext.front() == '.')
ext.erase(ext.begin());
auto extcmp = [&ext](const auto &e) { return e == ext; };
for (auto it = arch_from; it != arch_to; ++it) {
const auto &[format_id, entry] = *it;
if (std::any_of(entry.extensions.begin(), entry.extensions.end(), extcmp))
ret = entry.factoryfn(fname, quality, progr);
for (auto it = arch_from; !ret && it != arch_to; ++it) {
const auto &entry = *it;
if (entry.rdfactoryfn) {
auto extensions = get_extensions(entry);
for (const std::string& supportedext : extensions) {
if (ext == supportedext) {
ret = entry.rdfactoryfn(fname, quality, progr);
break;
}
}
}
}
}
return ret;
}
const std::vector<const char *> &SLAArchiveReader::registered_archives()
{
static std::vector<const char*> archnames;
if (archnames.empty()) {
archnames.reserve(REGISTERED_ARCHIVES.size());
for (auto &[name, _] : REGISTERED_ARCHIVES)
archnames.emplace_back(name.c_str());
}
return archnames;
}
std::vector<const char *> SLAArchiveReader::get_extensions(const char *archtype)
{
auto it = REGISTERED_ARCHIVES.find(archtype);
if (it != REGISTERED_ARCHIVES.end())
return it->second.extensions;
return {};
}
const char *SLAArchiveReader::get_description(const char *archtype)
{
auto it = REGISTERED_ARCHIVES.find(archtype);
if (it != REGISTERED_ARCHIVES.end())
return it->second.descr;
return nullptr;
}
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
static SliceParams get_slice_params(const DynamicPrintConfig &cfg)

View File

@ -47,15 +47,6 @@ public:
const std::string &format_id,
SLAImportQuality quality = SLAImportQuality::Balanced,
const ProgrFn &progr = [](int) { return false; });
// Get the names of currently known archive reader implementations
static const std::vector<const char *> & registered_archives();
// Get the understood file extensions belonging to an archive format
static std::vector<const char *> get_extensions(const char *archtype);
// Generic description (usable in GUI) about an archive format
static const char * get_description(const char *archtype);
};
// Raised in import_sla_archive when a nullptr reader is returned by

View File

@ -1,77 +1,18 @@
#include "SLAArchiveWriter.hpp"
#include "SL1.hpp"
#include "SL1_SVG.hpp"
#include "AnycubicSLA.hpp"
#include "libslic3r/libslic3r.h"
#include <string>
#include <map>
#include <memory>
#include <tuple>
#include "SLAArchiveFormatRegistry.hpp"
namespace Slic3r {
using ArchiveFactory = std::function<std::unique_ptr<SLAArchiveWriter>(const SLAPrinterConfig&)>;
struct ArchiveEntry {
const char *ext;
ArchiveFactory factoryfn;
};
static const std::map<std::string, ArchiveEntry> REGISTERED_ARCHIVES {
{
"SL1",
{ "sl1", [] (const auto &cfg) { return std::make_unique<SL1Archive>(cfg); } }
},
{
"SL1SVG",
{ "sl1_svg", [] (const auto &cfg) { return std::make_unique<SL1_SVGArchive>(cfg); } }
},
{
"SL2",
{ "sl1_svg", [] (const auto &cfg) { return std::make_unique<SL1_SVGArchive>(cfg); } }
},
ANYCUBIC_SLA_FORMAT("pwmo", "Photon Mono"),
ANYCUBIC_SLA_FORMAT("pwmx", "Photon Mono X"),
ANYCUBIC_SLA_FORMAT("pwms", "Photon Mono SE"),
};
std::unique_ptr<SLAArchiveWriter>
SLAArchiveWriter::create(const std::string &archtype, const SLAPrinterConfig &cfg)
{
auto entry = REGISTERED_ARCHIVES.find(archtype);
std::unique_ptr<SLAArchiveWriter> ret;
auto factory = get_writer_factory(archtype.c_str());
if (entry != REGISTERED_ARCHIVES.end())
return entry->second.factoryfn(cfg);
if (factory)
ret = factory(cfg);
return nullptr;
}
const std::vector<const char*>& SLAArchiveWriter::registered_archives()
{
static std::vector<const char*> archnames;
if (archnames.empty()) {
archnames.reserve(REGISTERED_ARCHIVES.size());
for (auto &[name, _] : REGISTERED_ARCHIVES)
archnames.emplace_back(name.c_str());
}
return archnames;
}
const char *SLAArchiveWriter::get_extension(const char *archtype)
{
constexpr const char* DEFAULT_EXT = "zip";
auto entry = REGISTERED_ARCHIVES.find(archtype);
if (entry != REGISTERED_ARCHIVES.end())
return entry->second.ext;
return DEFAULT_EXT;
return ret;
}
} // namespace Slic3r

View File

@ -53,12 +53,6 @@ public:
// Factory method to create an archiver instance
static std::unique_ptr<SLAArchiveWriter> create(
const std::string &archtype, const SLAPrinterConfig &);
// Get the names of currently known archiver implementations
static const std::vector<const char *> & registered_archives();
// Get the default file extension belonging to an archive format
static const char *get_extension(const char *archtype);
};
} // namespace Slic3r

View File

@ -83,8 +83,15 @@ ZipperArchive read_zipper_archive(const std::string &zipfname,
}))
continue;
if (name == CONFIG_FNAME) { arch.config = read_ini(entry, zip); continue; }
if (name == PROFILE_FNAME) { arch.profile = read_ini(entry, zip); continue; }
if (name == CONFIG_FNAME) {
arch.config = read_ini(entry, zip);
continue;
}
if (name == PROFILE_FNAME) {
arch.profile = read_ini(entry, zip);
continue;
}
auto it = std::lower_bound(
arch.entries.begin(), arch.entries.end(),

View File

@ -5,6 +5,7 @@
#include "libslic3r/format.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/I18N.hpp"
#include "GCodeProcessor.hpp"
#include <boost/algorithm/string/case_conv.hpp>

View File

@ -1353,6 +1353,7 @@ void ModelObject::synchronize_model_after_cut()
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
obj->cut_id.copy(this->cut_id);
}
this->invalidate_cut();
}
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
@ -1378,7 +1379,7 @@ void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
void ModelObject::clone_for_cut(ModelObject** obj)
{
(*obj) = ModelObject::new_clone(*this);
(*obj)->set_model(nullptr);
(*obj)->set_model(this->get_model());
(*obj)->sla_support_points.clear();
(*obj)->sla_drain_holes.clear();
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
@ -1461,7 +1462,7 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
std::vector<ModelObject*>& dowels)
{
assert(volume->cut_info.is_connector);
volume->cut_info.set_processed();
@ -1497,9 +1498,6 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d&
vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0);
// Compute the displacement (in instance coordinates) to be applied to place the dowels
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
dowels.push_back(dowel);
}
@ -1565,9 +1563,8 @@ void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& ins
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
{
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
@ -1584,31 +1581,12 @@ void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d&
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
}
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
{
if (!object->origin_translation.isApprox(Vec3d::Zero()) && src_instance->get_offset().isApprox(Vec3d::Zero())) {
object->center_around_origin();
object->translate_instances(-object->origin_translation);
object->origin_translation = Vec3d::Zero();
}
else {
object->invalidate_bounding_box();
object->center_around_origin();
}
}
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut = false, bool flip = false, Vec3d local_displace = Vec3d::Zero())
void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut/* = false*/, bool flip/* = false*/)
{
using namespace Geometry;
@ -1616,14 +1594,9 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan
for (size_t i = 0; i < object->instances.size(); ++i) {
auto& obj_instance = object->instances[i];
const Vec3d offset = obj_instance->get_offset();
const double rot_z = obj_instance->get_rotation().z();
obj_instance->set_transformation(Transformation());
const Vec3d displace = local_displace.isApprox(Vec3d::Zero()) ? Vec3d::Zero() :
rotation_transform(obj_instance->get_rotation()) * local_displace;
obj_instance->set_offset(offset + displace);
obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()));
Vec3d rotation = Vec3d::Zero();
if (!flip && !place_on_cut) {
@ -1681,10 +1654,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
// Displacement (in instance coordinates) to be applied to place the upper parts
Vec3d local_displace = Vec3d::Zero();
Vec3d local_dowels_displace = Vec3d::Zero();
for (ModelVolume* volume : volumes) {
volume->reset_extra_facets();
@ -1692,10 +1661,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
if (volume->cut_info.is_processed)
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
else
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace);
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
}
else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
}
// Post-process cut parts
@ -1708,31 +1677,22 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
}
else {
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
invalidate_translations(upper, instances[instance]);
reset_instance_transformation(upper, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
attributes.has(ModelObjectCutAttribute::FlipUpper),
local_displace);
attributes.has(ModelObjectCutAttribute::FlipUpper));
res.push_back(upper);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
invalidate_translations(lower, instances[instance]);
reset_instance_transformation(lower, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
res.push_back(lower);
}
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
for (auto dowel : dowels) {
invalidate_translations(dowel, instances[instance]);
reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace);
local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0));
reset_instance_transformation(dowel, instance, Transform3d::Identity());
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
res.push_back(dowel);
}
@ -2097,6 +2057,14 @@ bool ModelObject::has_solid_mesh() const
return false;
}
bool ModelObject::has_negative_volume_mesh() const
{
for (const ModelVolume* volume : volumes)
if (volume->is_negative_volume())
return true;
return false;
}
void ModelVolume::set_material_id(t_model_material_id material_id)
{
m_material_id = material_id;
@ -2661,14 +2629,6 @@ bool model_has_multi_part_objects(const Model &model)
return false;
}
bool model_has_connectors(const Model &model)
{
for (const ModelObject *model_object : model.objects)
if (!model_object->cut_connectors.empty())
return true;
return false;
}
bool model_has_advanced_features(const Model &model)
{
auto config_is_advanced = [](const ModelConfig &config) {

View File

@ -467,18 +467,28 @@ public:
void invalidate_cut();
// delete volumes which are marked as connector for this object
void delete_connectors();
void synchronize_model_after_cut();
void apply_cut_attributes(ModelObjectCutAttributes attributes);
void clone_for_cut(ModelObject **obj);
void apply_cut_attributes(ModelObjectCutAttributes attributes);
private:
// FIXME: These functions would best not be here at all. It might make sense to separate the
// cut-related methods elsewhere. Same holds for cut_connectors data member, which is currently
// just a temporary variable used by cut gizmo only.
void synchronize_model_after_cut();
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
std::vector<ModelObject*>& dowels);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
public:
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut = false, bool flip = false);
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs*new_objects);
void merge();
@ -503,6 +513,8 @@ public:
// Detect if object has at least one solid mash
bool has_solid_mesh() const;
// Detect if object has at least one negative volume mash
bool has_negative_volume_mesh() const;
bool is_cut() const { return cut_id.id().valid(); }
bool has_connectors() const;
@ -1389,8 +1401,6 @@ bool model_has_parameter_modifiers_in_objects(const Model& model);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_multi_part_objects(const Model &model);
// If the model has objects with cut connectrs, then it is currently not supported by the SLA mode.
bool model_has_connectors(const Model& model);
// If the model has advanced features, then it cannot be processed in simple mode.
bool model_has_advanced_features(const Model &model);

View File

@ -40,6 +40,7 @@
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@ -728,7 +729,7 @@ Polylines reconnect_polylines(const Polylines &polylines, double limit_distance)
return result;
}
ExtrusionPaths sort_extra_perimeters(ExtrusionPaths extra_perims, int index_of_first_unanchored, double extrusion_spacing)
ExtrusionPaths sort_extra_perimeters(const ExtrusionPaths& extra_perims, int index_of_first_unanchored, double extrusion_spacing)
{
if (extra_perims.empty()) return {};

View File

@ -206,6 +206,9 @@ struct ThickPolyline {
void start_at_index(int index);
Points points;
// vector of startpoint width and endpoint width of each line segment. The size should be always (points.size()-1) * 2
// e.g. let four be points a,b,c,d. that are three lines ab, bc, cd. for each line, there should be start width, so the width vector is:
// w(a), w(b), w(b), w(c), w(c), w(d)
std::vector<coordf_t> width;
std::pair<bool,bool> endpoints { false, false };
};

View File

@ -403,7 +403,9 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config)
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;
if (app_config.has_section(section_name)) {
if (type == TYPE_FILAMENT && app_config.get_bool("no_templates") && vendor && vendor->templates_profile)
is_visible = false;
else 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.
const std::map<std::string, std::string> &installed = app_config.get_section(section_name);
@ -896,8 +898,9 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
return preset;
}
void PresetCollection::save_current_preset(const std::string &new_name, bool detach)
bool PresetCollection::save_current_preset(const std::string &new_name, bool detach)
{
bool is_saved_as_new{ false };
// 1) Find the preset with a new_name or create a new one,
// initialize it with the edited config.
auto it = this->find_preset_internal(new_name);
@ -906,7 +909,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
Preset &preset = *it;
if (preset.is_default || preset.is_external || preset.is_system)
// Cannot overwrite the default preset.
return;
return false;
// Overwriting an existing preset.
preset.config = std::move(m_edited_preset.config);
// The newly saved preset will be activated -> make it visible.
@ -919,6 +922,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
preset.renamed_from.clear();
}
} else {
is_saved_as_new = true;
// Creating a new preset.
Preset &preset = *m_presets.insert(it, m_edited_preset);
std::string &inherits = preset.inherits();
@ -953,6 +957,8 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
this->select_preset_by_name(new_name, true);
// 2) Store the active preset to disk.
this->get_selected_preset().save();
return is_saved_as_new;
}
Preset& PresetCollection::get_preset_with_name(const std::string& new_name, const Preset* initial_preset)
@ -1212,7 +1218,13 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
if (preset_edited.vendor && preset_edited.vendor->templates_profile) {
if (preset_selected.is_visible)
indices_of_template_presets.push_back(idx_preset);
else {
preset_selected.is_compatible = false;
if (selected)
m_idx_selected = size_t(-1);
}
}
}
// filter out template profiles where profile with same alias and compability exists
@ -2092,6 +2104,136 @@ bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIter
m_selected_preset == preset_name;
}
ExtruderFilaments::ExtruderFilaments(PresetCollection* filaments_collection, size_t extruder_id, std::string selected_name/* = std::string()*/)
: m_filaments (filaments_collection)
, m_extruder_id(extruder_id)
{
const std::deque<Preset>& presets = m_filaments->get_presets();
for (size_t id = 0; id < presets.size(); id ++)
m_extr_filaments.emplace_back(&(presets[id]));
select_filament(selected_name.empty() ? m_filaments->get_selected_preset_name() : selected_name);
}
const std::string& ExtruderFilaments::get_preset_name_by_alias(const std::string& alias) const
{
const auto& aliases_map = m_filaments->map_alias_to_profile_name();
for (
// Find the 1st profile name with the alias.
auto it = Slic3r::lower_bound_by_predicate(aliases_map.begin(), aliases_map.end(), [&alias](auto& l) { return l.first < alias; });
// Continue over all profile names with the same alias.
it != aliases_map.end() && it->first == alias; ++it)
if (auto it_filament = find_filament_internal(it->second);
it_filament != m_extr_filaments.end() && it_filament->preset->name == it->second &&
it_filament->preset->is_visible && (it_filament->is_compatible || size_t(it_filament - m_extr_filaments.begin()) == m_idx_selected))
return it_filament->preset->name;
return alias;
}
bool ExtruderFilaments::select_filament(const std::string &name_w_suffix, bool force/*= false*/)
{
std::string name = Preset::remove_suffix_modified(name_w_suffix);
// 1) Try to find the preset by its name.
auto it = this->find_filament_internal(name);
size_t idx = 0;
if (it != m_extr_filaments.end() && it->preset->name == name && it->preset->is_visible)
// Preset found by its name and it is visible.
idx = it - m_extr_filaments.begin();
else {
// Find the first visible preset.
for (size_t i = 0; i < m_extr_filaments.size(); ++i)
if (m_extr_filaments[i].preset->is_visible/* && m_extr_filaments[i].is_compatible*/) {
idx = i;
break;
}
// If the first visible preset was not found, return the 0th element, which is the default preset.
}
// 2) Select the new preset.
if (m_idx_selected != idx || force) {
this->select_filament(idx);
return true;
}
return false;
}
size_t ExtruderFilaments::update_compatible_internal(const PresetWithVendorProfile &active_printer,
const PresetWithVendorProfile *active_print,
PresetSelectCompatibleType unselect_if_incompatible)
{
DynamicPrintConfig config;
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
const ConfigOption* opt = active_printer.preset.config.option("nozzle_diameter");
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
bool some_compatible = false;
// Adjust printer preset config to the first extruder from m_extruder_id
Preset printer_preset_adjusted = active_printer.preset;
if (m_extruder_id > 0 && !printer_preset_adjusted.config.opt_bool("single_extruder_multi_material")) {
DynamicPrintConfig& active_printer_config = printer_preset_adjusted.config;
for (const std::string& key : print_config_def.extruder_option_keys()) {
if (key == "default_filament_profile")
continue;// Ignore this field, because this parameter is not related to the extruder but to whole printer.
auto* opt = active_printer_config.option(key, false);
if (opt != nullptr && opt->is_vector())
static_cast<ConfigOptionVectorBase*>(opt)->set_at(opt, 0, m_extruder_id);
}
}
PresetWithVendorProfile active_printer_adjusted(printer_preset_adjusted, active_printer.vendor);
std::vector<size_t> indices_of_template_presets;
indices_of_template_presets.reserve(m_extr_filaments.size());
size_t num_default_presets = m_filaments->num_default_presets();
for (size_t idx_preset = num_default_presets; idx_preset < m_extr_filaments.size(); ++idx_preset) {
const bool is_selected = idx_preset == m_idx_selected;
const Preset* preset = m_extr_filaments[idx_preset].preset;
Filament& extr_filament = m_extr_filaments[idx_preset];
const PresetWithVendorProfile this_preset_with_vendor_profile = m_filaments->get_preset_with_vendor_profile(*preset);
bool was_compatible = extr_filament.is_compatible;
extr_filament.is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer_adjusted, &config);
some_compatible |= extr_filament.is_compatible;
if (active_print != nullptr)
extr_filament.is_compatible &= is_compatible_with_print(this_preset_with_vendor_profile, *active_print, active_printer_adjusted);
if (!extr_filament.is_compatible && is_selected &&
(unselect_if_incompatible == PresetSelectCompatibleType::Always || (unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible && was_compatible)))
m_idx_selected = size_t(-1);
if (preset->vendor && preset->vendor->templates_profile) {
if (preset->is_visible)
indices_of_template_presets.push_back(idx_preset);
else {
extr_filament.is_compatible = false;
if (is_selected)
m_idx_selected = size_t(-1);
}
}
}
// filter out template profiles where profile with same alias and compability exists
if (!indices_of_template_presets.empty()) {
for (size_t idx = num_default_presets; idx < m_extr_filaments.size(); ++idx) {
const Filament& filament = m_extr_filaments[idx];
const VendorProfile* vendor = filament.preset->vendor;
if (vendor && !vendor->templates_profile && filament.is_compatible) {
const std::string& preset_alias = filament.preset->alias;
for (const auto& template_idx : indices_of_template_presets) {
if (m_extr_filaments[template_idx].preset->alias == preset_alias) {
m_extr_filaments[template_idx].is_compatible = false;
// unselect selected template filament if there is non-template alias compatible
if (template_idx == m_idx_selected && (unselect_if_incompatible != PresetSelectCompatibleType::Never))
m_idx_selected = size_t(-1);
break;
}
}
}
}
}
return m_idx_selected;
}
namespace PresetUtils {
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)

View File

@ -341,7 +341,8 @@ public:
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
void save_current_preset(const std::string &new_name, bool detach = false);
// return true, if new preset is stored
bool save_current_preset(const std::string &new_name, bool detach = false);
// Find the preset with a new_name or create a new one,
// initialize it with the initial_preset config.
@ -507,7 +508,7 @@ public:
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string &new_name) const;
size_t num_default_presets() { return m_num_default_presets; }
size_t num_default_presets() const { return m_num_default_presets; }
protected:
PresetCollection() = default;
@ -566,6 +567,8 @@ public:
static bool is_dirty(const Preset *edited, const Preset *reference);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare = false);
static bool is_independent_from_extruder_number_option(const std::string& opt_key);
const std::vector<std::pair<std::string, std::string>>& map_alias_to_profile_name() { return m_map_alias_to_profile_name; }
private:
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type;
@ -827,6 +830,142 @@ private:
};
// ---------------------------------
// *** ExtruderFilaments ***
// ---------------------------------
class Filament
{
public:
Filament(const Preset* preset) : preset(preset) {}
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
bool operator<(const Filament& other) const { return this->preset->name < other.preset->name; }
const Preset* preset;
bool is_compatible{ true };
};
// Collections of filaments for extruder
class ExtruderFilaments
{
PresetCollection* m_filaments{ nullptr };
// Selected filament.
size_t m_idx_selected{ size_t(-1) };
// List of filaments for this extruder
std::deque<Filament> m_extr_filaments;
size_t m_extruder_id;
std::string m_cached_selected_name{ std::string() };
public:
ExtruderFilaments(PresetCollection* filaments_collection, size_t extruder_id = 0, std::string selected_name = std::string());
typedef std::deque<Filament>::iterator Iterator;
typedef std::deque<Filament>::const_iterator ConstIterator;
Iterator begin() { return m_extr_filaments.begin(); }
ConstIterator begin() const { return m_extr_filaments.cbegin(); }
ConstIterator cbegin() const { return m_extr_filaments.cbegin(); }
Iterator end() { return m_extr_filaments.end(); }
ConstIterator end() const { return m_extr_filaments.cend(); }
ConstIterator cend() const { return m_extr_filaments.cend(); }
bool empty() const { return m_extr_filaments.empty(); }
const std::deque<Filament>& operator()() const { return m_extr_filaments; }
// Return a filament by an index. If the filament is active, a temporary copy is returned.
Filament& filament(size_t idx) { return m_extr_filaments[idx]; }
const Filament& filament(size_t idx) const { return const_cast<ExtruderFilaments*>(this)->filament(idx); }
// Select filament by the full filament name, which contains name of filament, separator and name of selected preset
// If full_name doesn't contain name of selected preset, then select first preset in the list for this filament
bool select_filament(const std::string& name, bool force = false);
void select_filament(size_t idx) { m_idx_selected = idx; }
std::string get_selected_preset_name() const { return m_idx_selected == size_t(-1) ? std::string() : m_extr_filaments[m_idx_selected].preset->name; }
const Preset* get_selected_preset() const { return m_idx_selected == size_t(-1) ? nullptr : m_extr_filaments[m_idx_selected].preset; }
const Filament* get_selected_filament() const { return m_idx_selected == size_t(-1) ? nullptr : &m_extr_filaments[m_idx_selected]; }
size_t get_selected_idx() const { return m_idx_selected; }
friend class PresetBundle;
ExtruderFilaments() = default;
ExtruderFilaments& operator=(const ExtruderFilaments& other) = default;
private:
// Find a preset position in the sorted list of presets.
// The "-- default -- " preset is always the first, so it needs
// to be handled differently.
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<Filament>::iterator find_filament_internal(const std::string& name)
{
return Slic3r::lower_bound_by_predicate(m_extr_filaments.begin(), m_extr_filaments.end(), [&name](const auto& l) {
return l.preset->name < name;
});
}
std::deque<Filament>::const_iterator find_filament_internal(const std::string& name) const
{
return const_cast<ExtruderFilaments*>(this)->find_filament_internal(name);
}
void cache_selected_name() { m_cached_selected_name = get_selected_preset_name(); }
std::string get_cached_selected_name() const { return m_cached_selected_name; }
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
// If one of the prefered_alternates is compatible, select it.
template<typename PreferedCondition>
size_t first_compatible_idx(PreferedCondition prefered_condition) const
{
size_t i = m_filaments->is_default_suppressed() ? m_filaments->num_default_presets() : 0;
size_t n = m_extr_filaments.size();
size_t i_compatible = n;
int match_quality = -1;
for (; i < n; ++i)
// Since we use the filament selection from Wizard, it's needed to control the preset visibility too
if (m_extr_filaments[i].is_compatible && m_filaments->preset(i).is_visible) {
int this_match_quality = prefered_condition(*(m_extr_filaments[i].preset));
if (this_match_quality > match_quality) {
if (match_quality == std::numeric_limits<int>::max())
// Better match will not be found.
return i;
// Store the first compatible profile with highest match quality into i_compatible.
i_compatible = i;
match_quality = this_match_quality;
}
}
return (i_compatible == n) ?
// No compatible preset found, return the default preset.
0 :
// Compatible preset found.
i_compatible;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t first_compatible_idx() const { return this->first_compatible_idx([](const /*Filament*/Preset&) -> int { return 0; }); }
template<typename PreferedCondition>
const Preset* first_compatible(PreferedCondition prefered_condition) { return m_extr_filaments[this->first_compatible_idx(prefered_condition)].preset;}
const Preset* first_compatible() { return m_extr_filaments[this->first_compatible_idx()].preset; }
const std::string& get_preset_name_by_alias(const std::string& alias) const;
size_t update_compatible_internal(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType unselect_if_incompatible);
// For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition>
void update_compatible(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType select_other_if_incompatible, PreferedCondition prefered_condition)
{
if (this->update_compatible_internal(active_printer, active_print, select_other_if_incompatible) == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_filament(this->first_compatible_idx(prefered_condition));
}
void update_compatible(const PresetWithVendorProfile& active_printer, const PresetWithVendorProfile* active_print, PresetSelectCompatibleType select_other_if_incompatible)
{
this->update_compatible(active_printer, active_print, select_other_if_incompatible, [](const /*Filament*/Preset&) -> int { return 0; });
}
};
} // namespace Slic3r
#endif /* slic3r_Preset_hpp_ */

View File

@ -119,7 +119,7 @@ PresetBundle& PresetBundle::operator=(const PresetBundle &rhs)
printers = rhs.printers;
physical_printers = rhs.physical_printers;
filament_presets = rhs.filament_presets;
extruders_filaments = rhs.extruders_filaments;
project_config = rhs.project_config;
vendors = rhs.vendors;
obsolete_presets = rhs.obsolete_presets;
@ -143,8 +143,7 @@ void PresetBundle::reset(bool delete_files)
this->filaments .reset(delete_files);
this->sla_materials.reset(delete_files);
this->printers .reset(delete_files);
this->filament_presets.clear();
this->filament_presets.emplace_back(this->filaments.get_selected_preset_name());
this->extruders_filaments.clear();
this->obsolete_presets.prints.clear();
this->obsolete_presets.sla_prints.clear();
this->obsolete_presets.filaments.clear();
@ -426,6 +425,25 @@ void PresetBundle::load_installed_printers(const AppConfig &config)
preset.set_visible_from_appconfig(config);
}
void PresetBundle::cache_extruder_filaments_names()
{
for (ExtruderFilaments& extr_filaments : extruders_filaments)
extr_filaments.cache_selected_name();
}
void PresetBundle::reset_extruder_filaments()
{
// save previously cached selected names
std::vector<std::string> names;
for (const ExtruderFilaments& extr_filaments : extruders_filaments)
names.push_back(extr_filaments.get_cached_selected_name());
// Reset extruder_filaments and set names
this->extruders_filaments.clear();
for (size_t id = 0; id < names.size(); ++id)
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, names[id]));
}
PresetCollection&PresetBundle::get_presets(Preset::Type type)
{
assert(type >= Preset::TYPE_PRINT && type <= Preset::TYPE_PRINTER);
@ -437,12 +455,15 @@ PresetCollection& PresetBundle::get_presets(Preset::Type type)
}
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias)
const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias, int extruder_id /*= -1*/)
{
// there are not aliases for Printers profiles
if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
return alias;
if (preset_type == Preset::TYPE_FILAMENT)
return extruders_filaments[extruder_id].get_preset_name_by_alias(alias);
const PresetCollection& presets = get_presets(preset_type);
return presets.get_preset_name_by_alias(alias);
@ -462,8 +483,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
if (type == Preset::TYPE_PRINTER)
copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config);
if (type == Preset::TYPE_FILAMENT)
cache_extruder_filaments_names();
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
presets.save_current_preset(new_name);
if (presets.save_current_preset(new_name) && type == Preset::TYPE_FILAMENT)
reset_extruder_filaments();
// Mark the print & filament enabled if they are compatible with the currently selected 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);
@ -602,35 +626,37 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
// Load it even if the current printer technology is SLA.
// The possibly excessive filament names will be later removed with this->update_multi_material_filament_presets()
// once the FFF technology gets selected.
this->filament_presets = { filaments.get_selected_preset_name() };
this->extruders_filaments.clear();
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments));
for (unsigned int i = 1; i < 1000; ++ i) {
char name[64];
sprintf(name, "filament_%u", i);
if (! config.has("presets", name))
break;
this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name)));
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, i, remove_ini_suffix(config.get("presets", name))));
}
// ! update MM filaments presets before update compatibility
this->update_multi_material_filament_presets();
// Update visibility of presets based on their compatibility with the active printer.
// Always try to select a compatible print and filament preset to the current printer preset,
// as the application may have been closed with an active "external" preset, which does not
// exist.
this->update_compatible(PresetSelectCompatibleType::Always);
this->update_multi_material_filament_presets();
if (initial_printer != nullptr && (preferred_printer == nullptr || initial_printer == preferred_printer)) {
// Don't run the following code, as we want to activate default filament / SLA material profiles when installing and selecting a new printer.
// Only run this code if just a filament / SLA material was installed by Config Wizard for an active Printer.
auto printer_technology = printers.get_selected_preset().printer_technology();
if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) {
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament);
const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_FILAMENT, preferred_selection.filament, 0);
if (auto it = filaments.find_preset_internal(preferred_preset_name);
it != filaments.end() && it->is_visible && it->is_compatible) {
filaments.select_preset_by_name_strict(preferred_preset_name);
this->filament_presets.front() = filaments.get_selected_preset_name();
this->extruders_filaments.front().select_filament(filaments.get_selected_preset_name());
}
} else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) {
std::string preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material);
const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material);
if (auto it = sla_materials.find_preset_internal(preferred_preset_name);
it != sla_materials.end() && it->is_visible && it->is_compatible)
sla_materials.select_preset_by_name_strict(preferred_preset_name);
@ -648,15 +674,15 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
// Export selections (current print, current filaments, current printer) into config.ini
void PresetBundle::export_selections(AppConfig &config)
{
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() >= 1);
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front());
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() >= 1);
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias);
config.clear_section("presets");
config.set("presets", "print", prints.get_selected_preset_name());
config.set("presets", "filament", filament_presets.front());
for (unsigned i = 1; i < filament_presets.size(); ++i) {
config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name());
for (unsigned i = 1; i < extruders_filaments.size(); ++i) {
char name[64];
sprintf(name, "filament_%u", i);
config.set("presets", name, filament_presets[i]);
config.set("presets", name, extruders_filaments[i].get_selected_preset_name());
}
config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
@ -711,8 +737,8 @@ DynamicPrintConfig PresetBundle::full_fff_config() const
// First collect the filament configurations based on the user selection of this->filament_presets.
// Here this->filaments.find_preset() and this->filaments.first_visible() return the edited copy of the preset if active.
std::vector<const DynamicPrintConfig*> filament_configs;
for (const std::string &filament_preset_name : this->filament_presets)
filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config);
for (const auto& extr_filaments : this->extruders_filaments)
filament_configs.emplace_back(&this->filaments.find_preset(extr_filaments.get_selected_preset_name(), true)->config);
while (filament_configs.size() < num_extruders)
filament_configs.emplace_back(&this->filaments.first_visible().config);
for (const DynamicPrintConfig *cfg : filament_configs) {
@ -763,7 +789,10 @@ DynamicPrintConfig PresetBundle::full_fff_config() const
}
out.option<ConfigOptionString >("print_settings_id", true)->value = this->prints.get_selected_preset_name();
out.option<ConfigOptionStrings>("filament_settings_id", true)->values = this->filament_presets;
auto& filament_settings_id = out.option<ConfigOptionStrings>("filament_settings_id", true)->values;
filament_settings_id.clear();
for (const auto& extr_filaments : this->extruders_filaments)
filament_settings_id.emplace_back(extr_filaments.get_selected_preset_name());
out.option<ConfigOptionString >("printer_settings_id", true)->value = this->printers.get_selected_preset_name();
out.option<ConfigOptionString >("physical_printer_settings_id", true)->value = this->physical_printers.get_selected_printer_name();
@ -981,6 +1010,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
auto old_filament_profile_names = config.option<ConfigOptionStrings>("filament_settings_id", true);
old_filament_profile_names->values.resize(num_extruders, std::string());
this->extruders_filaments.clear();
if (num_extruders <= 1) {
// Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets.
inherits = inherits_values[1];
@ -994,8 +1024,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
loaded= &this->filaments.load_preset(this->filaments.path_from_name(name), name, config);
loaded->save();
}
this->filament_presets.clear();
this->filament_presets.emplace_back(loaded->name);
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments));
} else {
assert(is_external);
// Split the filament presets, load each of them separately.
@ -1014,7 +1043,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
}
}
// Load the configs into this->filaments and make them active.
this->filament_presets = std::vector<std::string>(configs.size());
std::vector<std::string> extr_names = std::vector<std::string>(configs.size());
// To avoid incorrect selection of the first filament preset (means a value of Preset->m_idx_selected)
// in a case when next added preset take a place of previosly selected preset,
// we should add presets from last to first
@ -1035,8 +1064,11 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
PresetCollection::LoadAndSelect::Never :
PresetCollection::LoadAndSelect::OnlyIfModified);
any_modified |= modified;
this->filament_presets[i] = loaded->name;
extr_names[i] = loaded->name;
}
// create extruders_filaments only when all filaments are loaded
for (size_t id = 0; id < extr_names.size(); ++id)
this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, extr_names[id]));
}
// 4) Load the project config values (the per extruder wipe matrix etc).
@ -1137,9 +1169,11 @@ ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
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->extruders_filaments.clear();
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);
for (size_t i = 1; i < std::min(tmp_bundle.extruders_filaments.size(), this->extruders_filaments.size()); ++i)
this->extruders_filaments[i].select_filament(load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.extruders_filaments[i].get_selected_preset_name(), false));
this->update_compatible(PresetSelectCompatibleType::Never);
@ -1622,9 +1656,12 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
// Activate the first filament preset.
if (! active_filaments.empty() && ! active_filaments.front().empty())
filaments.select_preset_by_name(active_filaments.front(), true);
// Extruder_filaments have to be recreated with new loaded filaments
this->extruders_filaments.clear();
this->update_multi_material_filament_presets();
for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i)
this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name;
for (size_t i = 0; i < std::min(this->extruders_filaments.size(), active_filaments.size()); ++ i)
this->extruders_filaments[i].select_filament(filaments.find_preset(active_filaments[i], true)->name);
this->update_compatible(PresetSelectCompatibleType::Never);
}
@ -1640,10 +1677,15 @@ void PresetBundle::update_multi_material_filament_presets()
auto *nozzle_diameter = static_cast<const ConfigOptionFloats*>(printers.get_edited_preset().config.option("nozzle_diameter"));
size_t num_extruders = nozzle_diameter->values.size();
// Verify validity of the current filament presets.
for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i)
this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name;
for (size_t i = 0; i < std::min(this->extruders_filaments.size(), num_extruders); ++i)
this->extruders_filaments[i].select_filament(this->filaments.find_preset(this->extruders_filaments[i].get_selected_preset_name(), true)->name);
if (this->extruders_filaments.size() > num_extruders)
this->extruders_filaments.resize(num_extruders);
else
// Append the rest of filament presets.
this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back());
for (size_t id = extruders_filaments.size(); id < num_extruders; id++)
extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, id == 0 ? filaments.first_visible().name : extruders_filaments[id - 1].get_selected_preset_name()));
// Now verify if wiping_volumes_matrix has proper size (it is used to deduce number of extruders in wipe tower generator):
std::vector<double> old_matrix = this->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values;
@ -1673,6 +1715,99 @@ void PresetBundle::update_multi_material_filament_presets()
}
}
void PresetBundle::update_filaments_compatible(PresetSelectCompatibleType select_other_filament_if_incompatible, int extruder_idx/* = -1*/)
{
const Preset& printer_preset = this->printers.get_edited_preset();
const PresetWithVendorProfile printer_preset_with_vendor_profile = this->printers.get_preset_with_vendor_profile(printer_preset);
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
const std::vector<std::string>& prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
class PreferedFilamentsProfileMatch
{
public:
PreferedFilamentsProfileMatch(const Preset* preset, const std::vector<std::string>& prefered_names, int extruder_id = 0) :
m_extruder_id(extruder_id),
m_prefered_alias(preset ? preset->alias : std::string()),
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", extruder_id) : std::string()),
m_prefered_names(prefered_names) {}
int operator()(const Preset& preset) const
{
// Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile.
if (preset.is_default || preset.is_external)
return 0;
if (!m_prefered_alias.empty() && m_prefered_alias == preset.alias)
// Matching an alias, always take this preset with priority.
return std::numeric_limits<int>::max();
int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1;
if (!m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", m_extruder_id))
match_quality *= 10;
return match_quality;
}
private:
int m_extruder_id;
const std::string m_prefered_alias;
const std::string m_prefered_filament_type;
const std::vector<std::string>& m_prefered_names;
};
//! ysFIXME - delete after testing
//!// First select a first compatible profile for the preset editor.
//!this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
//! PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles));
// Update compatible for extruder filaments
auto update_filament_compatible = [this, select_other_filament_if_incompatible, printer_preset_with_vendor_profile, print_preset_with_vendor_profile, prefered_filament_profiles](int idx)
{
ExtruderFilaments& extr_filaments = extruders_filaments[idx];
// Remember whether the filament profiles were compatible before updating the filament compatibility.
bool filament_preset_was_compatible = false;
const Filament* filament_old = extr_filaments.get_selected_filament();
if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never)
filament_preset_was_compatible = filament_old && filament_old->is_compatible;
extr_filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
PreferedFilamentsProfileMatch(filament_old ? filament_old->preset : nullptr, prefered_filament_profiles, idx));
const Filament* filament = extr_filaments.get_selected_filament();
const bool is_compatible = filament && filament->is_compatible;
if (is_compatible || select_other_filament_if_incompatible == PresetSelectCompatibleType::Never)
return;
// Verify validity of the current filament presets.
if (this->extruders_filaments.size() == 1) {
// The compatible profile should have been already selected for the preset editor. Just use it.
if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible)
extr_filaments.select_filament(this->filaments.get_edited_preset().name);
}
else {
const std::string filament_name = extr_filaments.get_selected_preset_name();
if (!filament || (!is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible))) {
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
std::string compat_filament_name = extr_filaments.first_compatible(PreferedFilamentsProfileMatch(filament->preset, prefered_filament_profiles, idx))->name;
if (filament_name != compat_filament_name)
extr_filaments.select_filament(compat_filament_name);
}
}
};
if (extruder_idx < 0) {
// update compatibility for all extruders
const size_t num_extruders = static_cast<const ConfigOptionFloats*>(printer_preset.config.option("nozzle_diameter"))->values.size();
for (size_t idx = 0; idx < std::min(this->extruders_filaments.size(), num_extruders); idx++)
update_filament_compatible(idx);
}
else
update_filament_compatible(extruder_idx);
if (this->filaments.get_idx_selected() == size_t(-1))
this->filaments.select_preset(extruders_filaments[0].get_selected_idx());
}
void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible)
{
const Preset &printer_preset = this->printers.get_edited_preset();
@ -1727,99 +1862,18 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
const double m_prefered_layer_height;
};
// Matching by the layer height in addition.
class PreferedFilamentProfileMatch : public PreferedProfileMatch
{
public:
PreferedFilamentProfileMatch(const Preset *preset, const std::string &prefered_name) :
PreferedProfileMatch(preset ? preset->alias : std::string(), prefered_name),
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()) {}
int operator()(const Preset &preset) const
{
// Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile.
if (preset.is_default || preset.is_external)
return 0;
int match_quality = PreferedProfileMatch::operator()(preset);
if (match_quality < std::numeric_limits<int>::max()) {
match_quality += 1;
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
match_quality *= 10;
}
return match_quality;
}
private:
const std::string m_prefered_filament_type;
};
// Matching by the layer height in addition.
class PreferedFilamentsProfileMatch
{
public:
PreferedFilamentsProfileMatch(const Preset *preset, const std::vector<std::string> &prefered_names) :
m_prefered_alias(preset ? preset->alias : std::string()),
m_prefered_filament_type(preset ? preset->config.opt_string("filament_type", 0) : std::string()),
m_prefered_names(prefered_names)
{}
int operator()(const Preset &preset) const
{
// Don't match any properties of the "-- default --" profile or the external profiles when switching printer profile.
if (preset.is_default || preset.is_external)
return 0;
if (! m_prefered_alias.empty() && m_prefered_alias == preset.alias)
// Matching an alias, always take this preset with priority.
return std::numeric_limits<int>::max();
int match_quality = (std::find(m_prefered_names.begin(), m_prefered_names.end(), preset.name) != m_prefered_names.end()) + 1;
if (! m_prefered_filament_type.empty() && m_prefered_filament_type == preset.config.opt_string("filament_type", 0))
match_quality *= 10;
return match_quality;
}
private:
const std::string m_prefered_alias;
const std::string m_prefered_filament_type;
const std::vector<std::string> &m_prefered_names;
};
switch (printer_preset.printer_technology()) {
case ptFFF:
{
assert(printer_preset.config.has("default_print_profile"));
assert(printer_preset.config.has("default_filament_profile"));
const std::vector<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
this->prints.update_compatible(printer_preset_with_vendor_profile, nullptr, select_other_print_if_incompatible,
PreferedPrintProfileMatch(this->prints.get_selected_idx() == size_t(-1) ? nullptr : &this->prints.get_edited_preset(), printer_preset.config.opt_string("default_print_profile")));
const PresetWithVendorProfile print_preset_with_vendor_profile = this->prints.get_edited_preset_with_vendor_profile();
// Remember whether the filament profiles were compatible before updating the filament compatibility.
std::vector<char> filament_preset_was_compatible(this->filament_presets.size(), false);
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
Preset *preset = this->filaments.find_preset(this->filament_presets[idx], false);
filament_preset_was_compatible[idx] = preset != nullptr && preset->is_compatible;
}
// First select a first compatible profile for the preset editor.
this->filaments.update_compatible(printer_preset_with_vendor_profile, &print_preset_with_vendor_profile, select_other_filament_if_incompatible,
PreferedFilamentsProfileMatch(this->filaments.get_selected_idx() == size_t(-1) ? nullptr : &this->filaments.get_edited_preset(), prefered_filament_profiles));
if (select_other_filament_if_incompatible != PresetSelectCompatibleType::Never) {
// Verify validity of the current filament presets.
const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front();
if (this->filament_presets.size() == 1) {
// The compatible profile should have been already selected for the preset editor. Just use it.
if (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible.front())
this->filament_presets.front() = this->filaments.get_edited_preset().name;
} else {
for (size_t idx = 0; idx < this->filament_presets.size(); ++ idx) {
std::string &filament_name = this->filament_presets[idx];
Preset *preset = this->filaments.find_preset(filament_name, false);
if (preset == nullptr || (! preset->is_compatible && (select_other_filament_if_incompatible == PresetSelectCompatibleType::Always || filament_preset_was_compatible[idx])))
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
filament_name = this->filaments.first_compatible(
PreferedFilamentProfileMatch(preset,
(idx < prefered_filament_profiles.size()) ? prefered_filament_profiles[idx] : prefered_filament_profile)).name;
}
}
}
// Update compatibility for all currently existent extruder_filaments.
update_filaments_compatible(select_other_filament_if_incompatible);
break;
}
case ptSLA:
@ -1875,13 +1929,13 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
c << "sla_print = " << this->sla_prints.get_selected_preset_name() << std::endl;
c << "sla_material = " << this->sla_materials.get_selected_preset_name() << std::endl;
c << "printer = " << this->printers.get_selected_preset_name() << std::endl;
for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
for (size_t i = 0; i < this->extruders_filaments.size(); ++ i) {
char suffix[64];
if (i > 0)
sprintf(suffix, "_%d", (int)i);
else
suffix[0] = 0;
c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl;
c << "filament" << suffix << " = " << this->extruders_filaments[i].get_selected_preset_name() << std::endl;
}
if (export_physical_printers && this->physical_printers.get_selected_idx() >= 0)
@ -1901,9 +1955,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
// an optional "(modified)" suffix will be removed from the filament name.
void PresetBundle::set_filament_preset(size_t idx, const std::string &name)
{
if (idx >= filament_presets.size())
filament_presets.resize(idx + 1, filaments.default_preset().name);
filament_presets[idx] = Preset::remove_suffix_modified(name);
if (idx >= extruders_filaments.size()) {
for (size_t id = extruders_filaments.size(); id < idx; id++)
extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, filaments.default_preset().name));
}
extruders_filaments[idx].select_filament(Preset::remove_suffix_modified(name));
}
void PresetBundle::set_default_suppressed(bool default_suppressed)

View File

@ -51,9 +51,12 @@ public:
const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; }
PrinterPresetCollection printers;
PhysicalPrinterCollection physical_printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<std::string> filament_presets;
// Filament presets per extruder for a multi-extruder or multi-material print.
// extruders_filaments.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<ExtruderFilaments> extruders_filaments;
void cache_extruder_filaments_names();
void reset_extruder_filaments();
PresetCollection& get_presets(Preset::Type preset_type);
@ -132,6 +135,8 @@ public:
// update size and content of filament_presets.
void update_multi_material_filament_presets();
void update_filaments_compatible(PresetSelectCompatibleType select_other_filament_if_incompatible, int extruder_idx = -1);
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
// as compatible with the currently selected printer (and print in case of filament presets).
// Also updates the is_visible flag of each preset.
@ -145,7 +150,7 @@ public:
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void load_installed_printers(const AppConfig &config);
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias);
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1);
// Save current preset of a provided type under a new name. If the name is different from the old one,
// Unselected option would be reverted to the beginning values

View File

@ -21,6 +21,7 @@
#include "Support/TreeSupport.hpp"
#include "Surface.hpp"
#include "Slicing.hpp"
#include "SurfaceCollection.hpp"
#include "Tesselate.hpp"
#include "TriangleMeshSlicer.hpp"
#include "Utils.hpp"
@ -36,7 +37,9 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <float.h>
#include <functional>
#include <limits>
#include <map>
#include <oneapi/tbb/blocked_range.h>
@ -1607,7 +1610,7 @@ void PrintObject::discover_vertical_shells()
#ifdef DEBUG_BRIDGE_OVER_INFILL
template<typename T> void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d)
{
std::vector<std::string> colors = {"red", "blue", "orange", "green"};
std::vector<std::string> colors = {"red", "green", "blue", "orange"};
BoundingBox bbox = get_extents(a);
bbox.merge(get_extents(b));
bbox.merge(get_extents(c));
@ -1664,11 +1667,7 @@ void PrintObject::bridge_over_infill()
// unsupported area will serve as a filter for polygons worth bridging.
Polygons unsupported_area;
Polygons lower_layer_solids;
bool contains_only_lightning = true;
for (const LayerRegion *region : layer->lower_layer->regions()) {
if (region->region().config().fill_pattern.value != ipLightning) {
contains_only_lightning = false;
}
Polygons fill_polys = to_polygons(region->fill_expolygons());
// initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts
unsupported_area.insert(unsupported_area.end(), fill_polys.begin(), fill_polys.end());
@ -1729,6 +1728,98 @@ void PrintObject::bridge_over_infill()
}
}
// LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the
// lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends.
// It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure.
bool has_lightning_infill = false;
for (size_t i = 0; i < this->num_printing_regions(); i++) {
if (this->printing_region(i).config().fill_pattern == ipLightning) {
has_lightning_infill = true;
break;
}
}
if (has_lightning_infill) {
// Prepare backup data for the Layer Region infills. Before modfiyng the layer region, we backup its fill surfaces by moving! them into this map.
// then a copy is created, modifiyed and passed to lightning infill generator. After generator is created, we restore the original state of the fills
// again by moving the data from this map back to the layer regions. This ensures that pointers to surfaces stay valid.
std::map<size_t, std::map<const LayerRegion *, SurfaceCollection>> backup_surfaces;
for (size_t lidx = 0; lidx < this->layer_count(); lidx++) {
backup_surfaces[lidx] = {};
}
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
&surfaces_by_layer](tbb::blocked_range<size_t> r) {
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
continue;
Layer *layer = po->get_layer(lidx);
const Layer *lower_layer = layer->lower_layer;
if (lower_layer == nullptr)
continue;
Polygons lightning_fill;
for (const LayerRegion *region : lower_layer->regions()) {
if (region->region().config().fill_pattern == ipLightning) {
Polygons lf = to_polygons(region->fill_surfaces().filter_by_type(stInternal));
lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end());
}
}
if (lightning_fill.empty())
continue;
for (LayerRegion *region : layer->regions()) {
backup_surfaces[lidx][region] = std::move(
region->m_fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid
// Copy the surfaces back, this will make copy, but we will later discard it anyway
region->m_fill_surfaces = backup_surfaces[lidx][region];
}
for (LayerRegion *region : layer->regions()) {
ExPolygons sparse_infill = to_expolygons(region->fill_surfaces().filter_by_type(stInternal));
ExPolygons solid_infill = to_expolygons(region->fill_surfaces().filter_by_type(stInternalSolid));
if (sparse_infill.empty()) {
break;
}
for (const auto &surface : surfaces_by_layer[lidx]) {
if (surface.region != region)
continue;
ExPolygons expansion = intersection_ex(sparse_infill, expand(surface.new_polys, scaled<float>(3.0)));
solid_infill.insert(solid_infill.end(), expansion.begin(), expansion.end());
}
solid_infill = union_safety_offset_ex(solid_infill);
sparse_infill = diff_ex(sparse_infill, solid_infill);
region->m_fill_surfaces.remove_types({stInternalSolid, stInternal});
for (const ExPolygon &ep : solid_infill) {
region->m_fill_surfaces.surfaces.emplace_back(stInternalSolid, ep);
}
for (const ExPolygon &ep : sparse_infill) {
region->m_fill_surfaces.surfaces.emplace_back(stInternal, ep);
}
}
}
});
// Use the modified surfaces to generate expanded lightning anchors
this->m_lightning_generator = this->prepare_lightning_infill_data();
// And now restore carefully the original surfaces, again using move to avoid reallocation and preserving the validity of the
// pointers in surface candidates
for (size_t lidx = 0; lidx < this->layer_count(); lidx++) {
Layer *layer = this->get_layer(lidx);
for (LayerRegion *region : layer->regions()) {
if (backup_surfaces[lidx].find(region) != backup_surfaces[lidx].end()) {
region->m_fill_surfaces = std::move(backup_surfaces[lidx][region]);
}
}
}
}
std::map<size_t, Polylines> infill_lines;
// SECTION to generate infill polylines
{
@ -1740,7 +1831,6 @@ void PrintObject::bridge_over_infill()
}
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z);
this->m_lightning_generator = this->prepare_lightning_infill_data();
std::vector<size_t> layers_to_generate_infill;
for (const auto &pair : surfaces_by_layer) {
@ -2018,6 +2108,8 @@ void PrintObject::bridge_over_infill()
polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(),
[](const Line &s) { return s.a == s.b; }),
polygon_sections[i].end());
std::sort(polygon_sections[i].begin(), polygon_sections[i].end(),
[](const Line &a, const Line &b) { return a.a.y() < b.b.y(); });
}
// reconstruct polygon from polygon sections
@ -2031,36 +2123,40 @@ void PrintObject::bridge_over_infill()
for (const auto &polygon_slice : polygon_sections) {
std::unordered_set<const Line *> used_segments;
for (TracedPoly &traced_poly : current_traced_polys) {
auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(),
[](const Point &high, const Line &seg) { return seg.a.y() > high.y(); });
if (maybe_first_overlap != polygon_slice.end() && // segment exists
segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(),
maybe_first_overlap->b.y())) // segment is overlapping
{
// Overlapping segment. In that case, add it
// to the traced polygon and add segment to used segments
if ((traced_poly.lows.back() - maybe_first_overlap->a).cast<double>().squaredNorm() <
bool segment_added = false;
for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) {
if (used_segments.find(&(*candidate)) != used_segments.end()) {
continue;
}
if ((traced_poly.lows.back() - candidate->a).cast<double>().squaredNorm() <
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
traced_poly.lows.push_back(maybe_first_overlap->a);
traced_poly.lows.push_back(candidate->a);
} else {
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.lows.push_back(maybe_first_overlap->a - Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.lows.push_back(maybe_first_overlap->a);
traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.lows.push_back(candidate->a);
}
if ((traced_poly.highs.back() - maybe_first_overlap->b).cast<double>().squaredNorm() <
if ((traced_poly.highs.back() - candidate->b).cast<double>().squaredNorm() <
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
traced_poly.highs.push_back(maybe_first_overlap->b);
traced_poly.highs.push_back(candidate->b);
} else {
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(maybe_first_overlap->b - Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(maybe_first_overlap->b);
traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(candidate->b);
}
used_segments.insert(&(*maybe_first_overlap));
} else {
// Zero or multiple overlapping segments. Resolving this is nontrivial,
// so we just close this polygon and maybe open several new. This will hopefully happen much less often
segment_added = true;
used_segments.insert(&(*candidate));
}
if (!segment_added) {
// Zero overlapping segments, we just close this polygon
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
@ -2090,6 +2186,7 @@ void PrintObject::bridge_over_infill()
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
}
expanded_bridged_area = union_safety_offset(expanded_bridged_area);
}
polygons_rotate(expanded_bridged_area, -aligning_angle);
@ -2278,25 +2375,42 @@ void PrintObject::bridge_over_infill()
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range<size_t> r) {
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end())
continue;
Layer *layer = po->get_layer(lidx);
Polygons cut_from_infill{};
if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) {
for (const auto &surface : surfaces_by_layer.at(lidx)) {
cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end());
}
}
Polygons additional_ensuring_areas{};
if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) {
for (const auto &surface : surfaces_by_layer.at(lidx + 1)) {
auto additional_area = diff(surface.new_polys,
shrink(surface.new_polys, surface.region->flow(frSolidInfill).scaled_spacing()));
additional_ensuring_areas.insert(additional_ensuring_areas.end(), additional_area.begin(), additional_area.end());
}
}
for (LayerRegion *region : layer->regions()) {
Surfaces new_surfaces;
Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces().surfaces)));
near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing()));
ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters);
SurfacesPtr internal_infills = region->m_fill_surfaces.filter_by_type(stInternal);
ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill);
new_internal_infills = diff_ex(new_internal_infills, additional_ensuring);
for (const ExPolygon &ep : new_internal_infills) {
new_surfaces.emplace_back(*internal_infills.front(), ep);
new_surfaces.emplace_back(stInternal, ep);
}
SurfacesPtr internal_solids = region->m_fill_surfaces.filter_by_type(stInternalSolid);
if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) {
for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) {
for (const Surface *surface : internal_solids) {
if (cs.original_surface == surface) {
@ -2310,10 +2424,23 @@ void PrintObject::bridge_over_infill()
}
}
}
ExPolygons new_internal_solids = diff_ex(internal_solids, cut_from_infill);
for (const ExPolygon &ep : new_internal_solids) {
new_surfaces.emplace_back(*internal_solids.front(), ep);
}
ExPolygons new_internal_solids = to_expolygons(internal_solids);
new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end());
new_internal_solids = diff_ex(new_internal_solids, cut_from_infill);
new_internal_solids = union_safety_offset_ex(new_internal_solids);
for (const ExPolygon &ep : new_internal_solids) {
new_surfaces.emplace_back(stInternalSolid, ep);
}
#ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw("Aensuring_" + std::to_string(reinterpret_cast<uint64_t>(&region)), to_polylines(additional_ensuring),
to_polylines(near_perimeters), to_polylines(to_polygons(internal_infills)),
to_polylines(to_polygons(internal_solids)));
debug_draw("Aensuring_" + std::to_string(reinterpret_cast<uint64_t>(&region)) + "_new", to_polylines(additional_ensuring),
to_polylines(near_perimeters), to_polylines(to_polygons(new_internal_infills)),
to_polylines(to_polygons(new_internal_solids)));
#endif
region->m_fill_surfaces.remove_types({stInternalSolid, stInternal});
region->m_fill_surfaces.append(new_surfaces);

View File

@ -497,7 +497,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
const bool calculate_placable = m_support_rests_on_model && radius == 0;
LayerPolygonCache data_placeable;
if (calculate_placable)
data_placeable.allocate(data.idx_begin, data.idx_end);
data_placeable.allocate(data.begin(), data.end());
for (size_t outline_idx : layer_outline_indices)
if (const std::vector<Polygons> &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) {
@ -517,9 +517,9 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
// 1) Calculate offsets of collision areas in parallel.
LayerPolygonCache collision_areas_offsetted;
collision_areas_offsetted.allocate(
std::max<LayerIndex>(0, data.idx_begin - z_distance_bottom_layers),
std::min<LayerIndex>(outlines.size(), data.idx_end + z_distance_top_layers));
tbb::parallel_for(tbb::blocked_range<LayerIndex>(collision_areas_offsetted.idx_begin, collision_areas_offsetted.idx_end),
std::max<LayerIndex>(0, data.begin() - z_distance_bottom_layers),
std::min<LayerIndex>(outlines.size(), data.end() + z_distance_top_layers));
tbb::parallel_for(tbb::blocked_range<LayerIndex>(collision_areas_offsetted.begin(), collision_areas_offsetted.end()),
[&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, &collision_areas_offsetted, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex> &range) {
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
@ -536,7 +536,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
// 2) Sum over top / bottom ranges.
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.idx_begin, data.idx_end),
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex>& range) {
@ -600,7 +600,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
// 3) Optionally calculate placables.
if (calculate_placable) {
// Now calculate the placable areas.
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(z_distance_bottom_layers + 1, data.idx_begin), data.idx_end),
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(z_distance_bottom_layers + 1, data.begin()), data.end()),
[&collision_areas_offsetted, &outlines, &anti_overhang = m_anti_overhang, processing_last_mesh,
min_resolution = m_min_resolution, z_distance_bottom_layers, xy_distance, &data_placeable, &throw_on_cancel]
(const tbb::blocked_range<LayerIndex>& range) {

View File

@ -332,23 +332,26 @@ public:
private:
// Caching polygons for a range of layers.
struct LayerPolygonCache {
std::vector<Polygons> polygons;
LayerIndex idx_begin;
LayerIndex idx_end;
class LayerPolygonCache {
public:
void allocate(LayerIndex aidx_begin, LayerIndex aidx_end) {
this->idx_begin = aidx_begin;
this->idx_end = aidx_end;
this->polygons.assign(aidx_end - aidx_begin, {});
m_idx_begin = aidx_begin;
m_idx_end = aidx_end;
m_polygons.assign(aidx_end - aidx_begin, {});
}
LayerIndex begin() const { return idx_begin; }
LayerIndex end() const { return idx_end; }
size_t size() const { return polygons.size(); }
LayerIndex begin() const { return m_idx_begin; }
LayerIndex end() const { return m_idx_end; }
size_t size() const { return m_polygons.size(); }
bool has(LayerIndex idx) const { return idx >= idx_begin && idx < idx_end; }
Polygons& operator[](LayerIndex idx) { return polygons[idx + idx_begin]; }
bool has(LayerIndex idx) const { return idx >= m_idx_begin && idx < m_idx_end; }
Polygons& operator[](LayerIndex idx) { assert(idx >= m_idx_begin && idx < m_idx_end); return m_polygons[idx - m_idx_begin]; }
std::vector<Polygons>& polygons_mutable() { return m_polygons; }
private:
std::vector<Polygons> m_polygons;
LayerIndex m_idx_begin;
LayerIndex m_idx_end;
};
/*!
@ -388,9 +391,9 @@ private:
}
void insert(LayerPolygonCache &&in, coord_t radius) {
std::lock_guard<std::mutex> guard(m_mutex);
LayerIndex i = in.idx_begin;
LayerIndex i = in.begin();
allocate_layers(i + LayerIndex(in.size()));
for (auto &d : in.polygons)
for (auto &d : in.polygons_mutable())
m_data[i ++].emplace(radius, std::move(d));
}
/*!

View File

@ -2249,6 +2249,7 @@ static void increase_areas_one_layer(
// But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set.
// A point can be set on the top most tip layer (maybe more if it should not move for a few layers).
parent.state.result_on_layer_reset();
parent.state.to_model_gracious = false;
#ifdef TREE_SUPPORTS_TRACK_LOST
parent.state.verylost = true;
#endif // TREE_SUPPORTS_TRACK_LOST
@ -4410,11 +4411,14 @@ static void draw_branches(
// Don't propagate further than 1.5 * bottom radius.
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
LayerIndex layer_bottommost = std::max(0, layer_begin - layers_propagate_max);
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
// If the tree bottom is hanging in the air, bring it down to some surface.
0 :
std::max(0, layer_begin - layers_propagate_max);
double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius);
// Only propagate until the rest area is smaller than this threshold.
double support_area_stop = 0.2 * M_PI * sqr(double(bottom_radius));
// Only propagate until the rest area is smaller than this threshold.
double support_area_min = 0.1 * M_PI * sqr(double(config.min_radius));
double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false));
double rest_support_area = area(rest_support);
@ -4558,7 +4562,7 @@ static void draw_branches(
}
// Subtract top contact layer polygons from support base.
SupportGeneratorLayer *top_contact_layer = top_contacts[layer_idx];
SupportGeneratorLayer *top_contact_layer = top_contacts.empty() ? nullptr : top_contacts[layer_idx];
if (top_contact_layer && ! top_contact_layer->polygons.empty() && ! base_layer_polygons.empty()) {
base_layer_polygons = diff(base_layer_polygons, top_contact_layer->polygons);
if (! bottom_contact_polygons.empty())
@ -4645,6 +4649,7 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
// ### Precalculate avoidances, collision etc.
size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes, throw_on_cancel);
bool has_support = num_support_layers > 0;
bool has_raft = config.raft_layers.size() > 0;
num_support_layers = std::max(num_support_layers, config.raft_layers.size());
SupportParameters support_params(print_object);
@ -4657,13 +4662,13 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume
SupportGeneratorLayersPtr interface_layers;
SupportGeneratorLayersPtr base_interface_layers;
SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr);
if (support_params.has_top_contacts)
if (support_params.has_top_contacts || has_raft)
top_contacts.assign(num_support_layers, nullptr);
if (support_params.has_bottom_contacts)
bottom_contacts.assign(num_support_layers, nullptr);
if (support_params.has_interfaces())
if (support_params.has_interfaces() || has_raft)
interface_layers.assign(num_support_layers, nullptr);
if (support_params.has_base_interfaces())
if (support_params.has_base_interfaces() || has_raft)
base_interface_layers.assign(num_support_layers, nullptr);
InterfacePlacer interface_placer{

View File

@ -91,7 +91,7 @@ struct AreaIncreaseSettings
struct TreeSupportSettings;
// #define TREE_SUPPORTS_TRACK_LOST
#define TREE_SUPPORTS_TRACK_LOST
// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided.
struct SupportElementStateBits {

View File

@ -36,6 +36,8 @@
#define ENABLE_MATRICES_DEBUG 0
// Shows an imgui dialog containing data from class ObjectManipulation
#define ENABLE_OBJECT_MANIPULATION_DEBUG 0
// Shows an imgui dialog containing data for class GLCanvas3D::SLAView
#define ENABLE_SLA_VIEW_DEBUG_WINDOW 0
// Enable rendering of objects using environment map

View File

@ -11,6 +11,9 @@ TriangleSelectorWrapper::TriangleSelectorWrapper(const TriangleMesh &mesh, const
void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &origin, float radius) {
std::vector<igl::Hit> hits;
Vec3f dir = (point - origin).normalized();
static constexpr const auto eps_angle = 89.99f;
Transform3d trafo_no_translate = mesh_transform;
trafo_no_translate.translation() = Vec3d::Zero();
if (AABBTreeIndirect::intersect_ray_all_hits(mesh.its.vertices, mesh.its.indices, triangles_tree,
Vec3d(origin.cast<double>()),
Vec3d(dir.cast<double>()),
@ -22,8 +25,8 @@ void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &orig
if ((point - pos).norm() < radius && face_normal.dot(dir) < 0) {
std::unique_ptr<TriangleSelector::Cursor> cursor = std::make_unique<TriangleSelector::Sphere>(
pos, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { });
selector.select_patch(hit.id, std::move(cursor), EnforcerBlockerType::ENFORCER, Transform3d::Identity(),
true, 0.0f);
selector.select_patch(hit.id, std::move(cursor), EnforcerBlockerType::ENFORCER, trafo_no_translate,
true, eps_angle);
break;
}
}
@ -36,8 +39,8 @@ void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &orig
std::unique_ptr<TriangleSelector::Cursor> cursor = std::make_unique<TriangleSelector::Sphere>(
point, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { });
selector.select_patch(hit_idx_out, std::move(cursor), EnforcerBlockerType::ENFORCER,
Transform3d::Identity(),
true, 0.0f);
trafo_no_translate,
true, eps_angle);
}
}
}

View File

@ -439,6 +439,14 @@ void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& proj
m_model_offset = to_3d(m_build_volume.bounding_volume2d().center(), -0.03);
// register for picking
const std::vector<std::shared_ptr<SceneRaycasterItem>>* const raycaster = wxGetApp().plater()->canvas3D()->get_raycasters_for_picking(SceneRaycaster::EType::Bed);
if (!raycaster->empty()) {
// The raycaster may have been set by the call to init_triangles() made from render_texture() if the printbed was
// changed while the camera was pointing upward.
// In this case we need to remove it before creating a new using the model geometry
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
m_model.mesh_raycaster.reset();
}
register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::translation_transform(m_model_offset));
// update extended bounding box

View File

@ -611,10 +611,28 @@ void GLVolumeCollection::load_object_auxiliary(
if (convex_hull.has_value())
v.set_convex_hull(*convex_hull);
v.is_modifier = false;
v.shader_outside_printer_detection_enabled = (step == slaposSupportTree);
v.shader_outside_printer_detection_enabled = (step == slaposSupportTree || step == slaposDrillHoles);
v.set_instance_transformation(model_instance.get_transformation());
};
if (milestone == SLAPrintObjectStep::slaposDrillHoles) {
if (print_object->get_parts_to_slice().size() > 1) {
// Get the mesh.
TriangleMesh backend_mesh;
std::shared_ptr<const indexed_triangle_set> preview_mesh_ptr = print_object->get_mesh_to_print();
if (preview_mesh_ptr != nullptr)
backend_mesh = TriangleMesh(*preview_mesh_ptr);
if (!backend_mesh.empty()) {
backend_mesh.transform(mesh_trafo_inv);
TriangleMesh convex_hull = backend_mesh.convex_hull_3d();
for (const std::pair<size_t, size_t>& instance_idx : instances) {
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
add_volume(obj_idx, (int)instance_idx.first, model_instance, slaposDrillHoles, backend_mesh, GLVolume::MODEL_COLOR[0], convex_hull);
}
}
}
}
// Get the support mesh.
if (milestone == SLAPrintObjectStep::slaposSupportTree) {
TriangleMesh supports_mesh = print_object->support_mesh();
@ -922,7 +940,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
}
for (GLVolume* volume : volumes) {
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0)
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->is_sla_pad() || volume->is_sla_support())
continue;
int extruder_id = volume->extruder_id - 1;

View File

@ -57,6 +57,7 @@
#include "UnsavedChangesDialog.hpp"
#include "slic3r/Utils/AppUpdater.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Config/Version.hpp"
#if defined(__linux__) && defined(__WXGTK3__)
#define wxLinux_gtk3 true
@ -118,7 +119,7 @@ BundleMap BundleMap::load()
const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred();
const auto archive_dir = (boost::filesystem::path(Slic3r::data_dir()) / "cache" / "vendor").make_preferred();
const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
const auto cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache"; // for Index
// Load Prusa bundle from the datadir/vendor directory or from datadir/cache/vendor (archive) or from resources/profiles.
auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini");
BundleLocation prusa_bundle_loc = BundleLocation::IN_VENDOR;
@ -138,7 +139,7 @@ BundleMap BundleMap::load()
// Load the other bundles in the datadir/vendor directory
// and then additionally from datadir/cache/vendor (archive) and resources/profiles.
// Should we concider case where archive has older profiles than resources (shouldnt happen)?
// Should we concider case where archive has older profiles than resources (shouldnt happen)? -> YES, it happens during re-configuration when running older PS after newer version
typedef std::pair<const fs::path&, BundleLocation> DirData;
std::vector<DirData> dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} };
for ( auto dir : dir_list) {
@ -151,6 +152,42 @@ BundleMap BundleMap::load()
// Don't load this bundle if we've already loaded it.
if (res.find(id) != res.end()) { continue; }
// Fresh index should be in archive_dir, otherwise look for it in cache
fs::path idx_path (archive_dir / (id + ".idx"));
if (!boost::filesystem::exists(idx_path)) {
BOOST_LOG_TRIVIAL(warning) << format("Missing index %1% when loading bundle %2%.", idx_path.string(), id);
idx_path = fs::path(cache_dir / (id + ".idx"));
}
if (!boost::filesystem::exists(idx_path)) {
BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to missing index %2%.", id, idx_path.string());
continue;
}
Slic3r::GUI::Config::Index index;
try {
index.load(idx_path);
}
catch (const std::exception& /* err */) {
BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to invalid index %2%.", id, idx_path.string());
continue;
}
const auto recommended_it = index.recommended();
if (recommended_it == index.end()) {
BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to no recommended version in index %2%.", id, idx_path.string());
continue;
}
const auto recommended = recommended_it->config_version;
VendorProfile vp;
try {
vp = VendorProfile::from_ini(dir_entry, true);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to corrupted profile file %2%. Message: %3%", id, dir_entry.path().string(), e.what());
continue;
}
// Don't load
if (vp.config_version > recommended)
continue;
Bundle bundle;
if (bundle.load(dir_entry.path(), dir.second))
res.emplace(std::move(id), std::move(bundle));
@ -3286,6 +3323,8 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
: DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, p(new priv(this))
{
wxBusyCursor wait;
this->SetFont(wxGetApp().normal_font());
p->load_vendors();

View File

@ -4066,69 +4066,58 @@ void GCodeViewer::render_legend(float& legend_height)
}
};
auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) {
ImGuiIO& io = ImGui::GetIO();
const ImTextureID tex_id = io.Fonts->TexID;
const float tex_w = static_cast<float>(io.Fonts->TexWidth);
const float tex_h = static_cast<float>(io.Fonts->TexHeight);
const ImFontAtlas::CustomRect* const rect = imgui.GetTextureCustomRect(icon_id);
const ImVec2 uv0 = { static_cast<float>(rect->X) / tex_w, static_cast<float>(rect->Y) / tex_h };
const ImVec2 uv1 = { static_cast<float>(rect->X + rect->Width) / tex_w, static_cast<float>(rect->Y + rect->Height) / tex_h };
window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
};
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::Spacing();
toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendTravel);
toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendTravel);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendWipe);
toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendWipe);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendRetract);
toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendRetract);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendDeretract);
toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendDeretract);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendSeams);
toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendSeams);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendToolChanges);
toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendToolChanges);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendColorChanges);
toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendColorChanges);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendPausePrints);
toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendPausePrints);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendCustomGCodes);
toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendCustomGCodes);
});
ImGui::SameLine();
toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendCOG);
toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendCOG);
});
ImGui::SameLine();
if (!wxGetApp().is_gcode_viewer()) {
toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendShells);
toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendShells);
});
ImGui::SameLine();
}
toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) {
image_icon(window, pos, size, ImGui::LegendToolMarker);
toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
imgui.draw_icon(window, pos, size, ImGui::LegendToolMarker);
});
bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth();

View File

@ -1070,6 +1070,250 @@ void GLCanvas3D::load_arrange_settings()
m_arrange_settings_fff_seq_print.alignment = arr_alignment ;
}
static std::vector<int> processed_objects_idxs(const Model& model, const SLAPrint& sla_print, const GLVolumePtrs& volumes)
{
std::vector<int> ret;
GLVolumePtrs matching_volumes;
std::copy_if(volumes.begin(), volumes.end(), std::back_inserter(matching_volumes), [](GLVolume* v) {
return v->volume_idx() == -(int)slaposDrillHoles; });
for (const GLVolume* v : matching_volumes) {
const int mo_idx = v->object_idx();
const ModelObject* model_object = (mo_idx < (int)model.objects.size()) ? model.objects[mo_idx] : nullptr;
if (model_object != nullptr && model_object->instances[v->instance_idx()]->is_printable()) {
const SLAPrintObject* print_object = sla_print.get_print_object_by_model_object_id(model_object->id());
if (print_object != nullptr && print_object->get_parts_to_slice().size() > 1)
ret.push_back(mo_idx);
}
}
std::sort(ret.begin(), ret.end());
ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
return ret;
};
static bool composite_id_match(const GLVolume::CompositeID& id1, const GLVolume::CompositeID& id2)
{
return id1.object_id == id2.object_id && id1.instance_id == id2.instance_id;
}
static bool object_contains_negative_volumes(const Model& model, int obj_id) {
return (0 <= obj_id && obj_id < (int)model.objects.size()) ? model.objects[obj_id]->has_negative_volume_mesh() : false;
}
void GLCanvas3D::SLAView::detect_type_from_volumes(const GLVolumePtrs& volumes)
{
for (auto& [id, type] : m_instances_cache) {
type = ESLAViewType::Original;
}
for (const GLVolume* v : volumes) {
if (v->volume_idx() == -(int)slaposDrillHoles) {
if (object_contains_negative_volumes(*m_parent.get_model(), v->composite_id.object_id)) {
const InstancesCacheItem* instance = find_instance_item(v->composite_id);
assert(instance != nullptr);
set_type(instance->first, ESLAViewType::Processed);
}
}
}
}
void GLCanvas3D::SLAView::set_type(ESLAViewType new_type)
{
for (auto& [id, type] : m_instances_cache) {
type = new_type;
if (new_type == ESLAViewType::Processed)
select_full_instance(id);
}
}
void GLCanvas3D::SLAView::set_type(const GLVolume::CompositeID& id, ESLAViewType new_type)
{
InstancesCacheItem* instance = find_instance_item(id);
assert(instance != nullptr);
instance->second = new_type;
if (new_type == ESLAViewType::Processed)
select_full_instance(id);
}
void GLCanvas3D::SLAView::update_volumes_visibility(GLVolumePtrs& volumes)
{
const SLAPrint* sla_print = m_parent.sla_print();
const std::vector<int> mo_idxs = (sla_print != nullptr) ? processed_objects_idxs(*m_parent.get_model(), *sla_print, volumes) : std::vector<int>();
std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume);
for (GLVolume* v : volumes) {
const int obj_idx = v->object_idx();
bool active = std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end();
if (!active) {
const InstancesCacheItem* instance = find_instance_item(v->composite_id);
assert(instance != nullptr);
active = (instance->second == ESLAViewType::Processed) ? v->volume_idx() < 0 : v->volume_idx() != -(int)slaposDrillHoles;
}
v->is_active = active;
auto it = std::find_if(raycasters->begin(), raycasters->end(), [v](std::shared_ptr<SceneRaycasterItem> item) { return item->get_raycaster() == v->mesh_raycaster.get(); });
if (it != raycasters->end())
(*it)->set_active(v->is_active);
}
}
void GLCanvas3D::SLAView::update_instances_cache(const std::vector<std::pair<GLVolume::CompositeID, GLVolume::CompositeID>>& new_to_old_ids_map)
{
// First, extract current instances list from the volumes
const GLVolumePtrs& volumes = m_parent.get_volumes().volumes;
std::vector<InstancesCacheItem> new_instances_cache;
for (const GLVolume* v : volumes) {
new_instances_cache.emplace_back(v->composite_id, ESLAViewType::Original);
}
std::sort(new_instances_cache.begin(), new_instances_cache.end(),
[](const InstancesCacheItem& i1, const InstancesCacheItem& i2) {
return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); });
new_instances_cache.erase(std::unique(new_instances_cache.begin(), new_instances_cache.end(),
[](const InstancesCacheItem& i1, const InstancesCacheItem& i2) {
return composite_id_match(i1.first, i2.first); }), new_instances_cache.end());
// Second, update instances type from previous state
for (auto& inst_type : new_instances_cache) {
const auto map_to_old_it = std::find_if(new_to_old_ids_map.begin(), new_to_old_ids_map.end(), [&inst_type](const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& item) {
return composite_id_match(inst_type.first, item.first); });
const GLVolume::CompositeID old_inst_id = (map_to_old_it != new_to_old_ids_map.end()) ? map_to_old_it->second : inst_type.first;
const InstancesCacheItem* old_instance = find_instance_item(old_inst_id);
if (old_instance != nullptr)
inst_type.second = old_instance->second;
}
m_instances_cache = new_instances_cache;
}
void GLCanvas3D::SLAView::render_switch_button()
{
const SLAPrint* sla_print = m_parent.sla_print();
if (sla_print == nullptr)
return;
const std::vector<int> mo_idxs = processed_objects_idxs(*m_parent.get_model(), *sla_print, m_parent.get_volumes().volumes);
if (mo_idxs.empty())
return;
Selection& selection = m_parent.get_selection();
const int obj_idx = selection.get_object_idx();
if (std::find(mo_idxs.begin(), mo_idxs.end(), obj_idx) == mo_idxs.end())
return;
if (!object_contains_negative_volumes(*m_parent.get_model(), obj_idx))
return;
const int inst_idx = selection.get_instance_idx();
if (inst_idx < 0)
return;
const GLVolume::CompositeID composite_id(obj_idx, 0, inst_idx);
const InstancesCacheItem* sel_instance = find_instance_item(composite_id);
if (sel_instance == nullptr)
return;
const ESLAViewType type = sel_instance->second;
BoundingBoxf ss_box;
if (m_use_instance_bbox) {
const Selection::EMode mode = selection.get_mode();
if (obj_idx >= 0 && inst_idx >= 0) {
const Selection::IndicesList selected_idxs = selection.get_volume_idxs();
std::vector<unsigned int> idxs_as_vector;
idxs_as_vector.assign(selected_idxs.begin(), selected_idxs.end());
selection.add_instance(obj_idx, inst_idx, true);
ss_box = selection.get_screen_space_bounding_box();
selection.add_volumes(mode, idxs_as_vector, true);
}
}
if (!ss_box.defined)
ss_box = selection.get_screen_space_bounding_box();
assert(ss_box.defined);
ImGuiWrapper& imgui = *wxGetApp().imgui();
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::SetNextWindowPos(ImVec2((float)ss_box.max.x(), (float)ss_box.center().y()), ImGuiCond_Always, ImVec2(0.0, 0.5));
imgui.begin(std::string("SLAViewSwitch"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
const float icon_size = 1.5 * ImGui::GetTextLineHeight();
if (imgui.draw_radio_button(_u8L("SLA view"), 1.5f * icon_size, true,
[this, &imgui, sel_instance](ImGuiWindow& window, const ImVec2& pos, float size) {
const wchar_t icon_id = (sel_instance->second == ESLAViewType::Original) ? ImGui::SlaViewProcessed : ImGui::SlaViewOriginal;
imgui.draw_icon(window, pos, size, icon_id);
})) {
switch (sel_instance->second)
{
case ESLAViewType::Original: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Processed); break; }
case ESLAViewType::Processed: { m_parent.set_sla_view_type(sel_instance->first, ESLAViewType::Original); break; }
default: { assert(false); break; }
}
}
if (ImGui::IsItemHovered()) {
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
ImGui::BeginTooltip();
wxString tooltip;
switch (type)
{
case ESLAViewType::Original: { tooltip = _L("Show as processed"); break; }
case ESLAViewType::Processed: { tooltip = _L("Show as original"); break; }
default: { assert(false); break; }
}
imgui.text(tooltip);
ImGui::EndTooltip();
ImGui::PopStyleColor();
}
imgui.end();
ImGui::PopStyleColor(2);
}
#if ENABLE_SLA_VIEW_DEBUG_WINDOW
void GLCanvas3D::SLAView::render_debug_window()
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.begin(std::string("SLAView"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
for (const auto& [id, type] : m_instances_cache) {
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "(" + std::to_string(id.object_id) + ", " + std::to_string(id.instance_id) + ")");
ImGui::SameLine();
imgui.text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), (type == ESLAViewType::Original) ? "Original" : "Processed");
}
if (!m_instances_cache.empty())
ImGui::Separator();
imgui.checkbox("Use instance bounding box", m_use_instance_bbox);
imgui.end();
}
#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
GLCanvas3D::SLAView::InstancesCacheItem* GLCanvas3D::SLAView::find_instance_item(const GLVolume::CompositeID& id)
{
auto it = std::find_if(m_instances_cache.begin(), m_instances_cache.end(),
[&id](const InstancesCacheItem& item) { return composite_id_match(item.first, id); });
return (it == m_instances_cache.end()) ? nullptr : &(*it);
}
void GLCanvas3D::SLAView::select_full_instance(const GLVolume::CompositeID& id)
{
bool extended_selection = false;
Selection& selection = m_parent.get_selection();
const Selection::ObjectIdxsToInstanceIdxsMap& sel_cache = selection.get_content();
auto obj_it = sel_cache.find(id.object_id);
if (obj_it != sel_cache.end()) {
auto inst_it = std::find(obj_it->second.begin(), obj_it->second.end(), id.instance_id);
if (inst_it != obj_it->second.end()) {
selection.add_instance(id.object_id, id.instance_id);
extended_selection = true;
}
}
if (extended_selection)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
}
PrinterTechnology GLCanvas3D::current_printer_technology() const
{
return m_process->current_printer_technology();
@ -1111,6 +1355,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
, m_render_sla_auxiliaries(true)
, m_labels(*this)
, m_slope(m_volumes)
, m_sla_view(*this)
{
if (m_canvas != nullptr) {
m_timer.SetOwner(m_canvas);
@ -1643,6 +1888,15 @@ void GLCanvas3D::render()
wxGetApp().obj_manipul()->render_debug_window();
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() == ptSLA) {
const GLGizmosManager::EType type = m_gizmos.get_current_type();
if (type == GLGizmosManager::EType::Undefined)
m_sla_view.render_switch_button();
#if ENABLE_SLA_VIEW_DEBUG_WINDOW
m_sla_view.render_debug_window();
#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
}
std::string tooltip;
// Negative coordinate means out of the window, likely because the window was deactivated.
@ -1882,6 +2136,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
size_t volume_idx;
};
std::vector<std::pair<GLVolume::CompositeID, GLVolume::CompositeID>> new_to_old_ids_map;
// SLA steps to pull the preview meshes for.
typedef std::array<SLAPrintObjectStep, 3> SLASteps;
SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad };
@ -2047,12 +2303,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx);
m_volumes.volumes.back()->geometry_id = key.geometry_id;
update_object_list = true;
} else {
}
else {
// Recycling an old GLVolume.
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
assert(existing_volume.geometry_id == key.geometry_id);
// Update the Object/Volume/Instance indices into the current Model.
if (existing_volume.composite_id != it->composite_id) {
new_to_old_ids_map.push_back(std::make_pair(it->composite_id, existing_volume.composite_id));
existing_volume.composite_id = it->composite_id;
update_object_list = true;
}
@ -2106,7 +2364,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
}
else {
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
const GLVolume::CompositeID new_id(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
new_to_old_ids_map.push_back(std::make_pair(new_id, m_volumes.volumes[it->volume_idx]->composite_id));
m_volumes.volumes[it->volume_idx]->composite_id = new_id;
m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation());
}
}
@ -2174,6 +2434,24 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
else
m_selection.volumes_changed(map_glvolume_old_to_new);
if (printer_technology == ptSLA) {
std::sort(new_to_old_ids_map.begin(), new_to_old_ids_map.end(),
[](const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i1, const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i2) {
return i1.first.object_id < i2.first.object_id || (i1.first.object_id == i2.first.object_id && i1.first.instance_id < i2.first.instance_id); });
new_to_old_ids_map.erase(std::unique(new_to_old_ids_map.begin(), new_to_old_ids_map.end(),
[](const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i1, const std::pair<GLVolume::CompositeID, GLVolume::CompositeID>& i2) {
return composite_id_match(i1.first, i2.first); }), new_to_old_ids_map.end());
m_sla_view.update_instances_cache(new_to_old_ids_map);
if (m_sla_view_type_detection_active) {
m_sla_view.detect_type_from_volumes(m_volumes.volumes);
m_sla_view_type_detection_active = false;
}
m_sla_view.update_volumes_visibility(m_volumes.volumes);
update_object_list = true;
}
// @Enrico suggest this solution to preven accessing pointer on caster without data
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume);
m_gizmos.update_data();
@ -4204,6 +4482,20 @@ std::pair<SlicingParameters, const std::vector<double>> GLCanvas3D::get_layers_h
return ret;
}
void GLCanvas3D::set_sla_view_type(ESLAViewType type)
{
m_sla_view.set_type(type);
m_sla_view.update_volumes_visibility(m_volumes.volumes);
m_dirty = true;
}
void GLCanvas3D::set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type)
{
m_sla_view.set_type(id, type);
m_sla_view.update_volumes_visibility(m_volumes.volumes);
m_dirty = true;
}
bool GLCanvas3D::_is_shown_on_screen() const
{
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;

View File

@ -466,6 +466,12 @@ public:
int alignment = 0;
};
enum class ESLAViewType
{
Original,
Processed
};
private:
wxGLCanvas* m_canvas;
wxGLContext* m_context;
@ -545,11 +551,37 @@ private:
bool m_tooltip_enabled{ true };
Slope m_slope;
class SLAView
{
public:
explicit SLAView(GLCanvas3D& parent) : m_parent(parent) {}
void detect_type_from_volumes(const GLVolumePtrs& volumes);
void set_type(ESLAViewType type);
void set_type(const GLVolume::CompositeID& id, ESLAViewType type);
void update_volumes_visibility(GLVolumePtrs& volumes);
void update_instances_cache(const std::vector<std::pair<GLVolume::CompositeID, GLVolume::CompositeID>>& new_to_old_ids_map);
void render_switch_button();
#if ENABLE_SLA_VIEW_DEBUG_WINDOW
void render_debug_window();
#endif // ENABLE_SLA_VIEW_DEBUG_WINDOW
private:
GLCanvas3D& m_parent;
typedef std::pair<GLVolume::CompositeID, ESLAViewType> InstancesCacheItem;
std::vector<InstancesCacheItem> m_instances_cache;
bool m_use_instance_bbox{ true };
InstancesCacheItem* find_instance_item(const GLVolume::CompositeID& id);
void select_full_instance(const GLVolume::CompositeID& id);
};
SLAView m_sla_view;
bool m_sla_view_type_detection_active{ false };
ArrangeSettings m_arrange_settings_fff, m_arrange_settings_sla,
m_arrange_settings_fff_seq_print;
PrinterTechnology current_printer_technology() const;
bool is_arrange_alignment_enabled() const;
template<class Self>
@ -654,7 +686,7 @@ private:
GLModel m_background;
public:
explicit GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed);
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed);
~GLCanvas3D();
bool is_initialized() const { return m_initialized; }
@ -767,6 +799,8 @@ public:
void zoom_to_gcode();
void select_view(const std::string& direction);
PrinterTechnology current_printer_technology() const;
void update_volumes_colors_by_extruder();
bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); }
@ -954,6 +988,10 @@ public:
std::pair<SlicingParameters, const std::vector<double>> get_layers_height_data(int object_id);
void set_sla_view_type(ESLAViewType type);
void set_sla_view_type(const GLVolume::CompositeID& id, ESLAViewType type);
void enable_sla_view_type_detection() { m_sla_view_type_detection_active = true; }
private:
bool _is_shown_on_screen() const;

View File

@ -925,8 +925,8 @@ void GUI_App::init_app_config()
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
// SetAppName(SLIC3R_APP_KEY);
SetAppName(SLIC3R_APP_KEY "-alpha");
// SetAppName(SLIC3R_APP_KEY "-beta");
// SetAppName(SLIC3R_APP_KEY "-alpha");
SetAppName(SLIC3R_APP_KEY "-beta");
// SetAppDisplayName(SLIC3R_APP_NAME);
@ -3023,22 +3023,6 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
caption);
return false;
}
/*
if (model_has_multi_part_objects(model())) {
show_info(nullptr,
_L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" +
_L("Please check your object list before preset changing."),
caption);
return false;
}
if (model_has_connectors(model())) {
show_info(nullptr,
_L("SLA technology doesn't support cut with connectors") + "\n\n" +
_L("Please check your object list before preset changing."),
caption);
return false;
}
*/
return true;
}
@ -3057,6 +3041,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
auto wizard = new ConfigWizard(mainframe);
const bool res = wizard->run(reason, start_page);
// !!! Deallocate memory after close ConfigWizard.
// Note, that mainframe is a parent of ConfigWizard.
// So, wizard will be destroyed only during destroying of mainframe
// To avoid this state the wizard have to be disconnected from mainframe and Destroyed explicitly
mainframe->RemoveChild(wizard);
wizard->Destroy();
if (res) {
load_current_presets();

View File

@ -758,6 +758,10 @@ void ObjectList::selection_changed()
wxGetApp().obj_layers()->update_scene_from_editor_selection();
}
}
else if (type & itVolume) {
if (printer_technology() == ptSLA)
wxGetApp().plater()->canvas3D()->set_sla_view_type(scene_selection().get_first_volume()->composite_id, GLCanvas3D::ESLAViewType::Original);
}
}
part_selection_changed();

View File

@ -15,6 +15,8 @@
#include "MainFrame.hpp"
#include "MsgDialog.hpp"
#include <wx/glcanvas.h>
#include <boost/algorithm/string.hpp>
#include "slic3r/Utils/FixModelByWin10.hpp"
@ -519,19 +521,18 @@ void ObjectManipulation::UpdateAndShow(const bool show)
OG_Settings::UpdateAndShow(show);
}
void ObjectManipulation::Enable(const bool enadle)
void ObjectManipulation::Enable(const bool enable)
{
for (auto editor : m_editors)
editor->Enable(enadle);
m_is_enabled = m_is_enabled_size_and_scale = enable;
for (wxWindow* win : std::initializer_list<wxWindow*>{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt
, m_reset_skew_button })
win->Enable(enadle);
win->Enable(enable);
}
void ObjectManipulation::DisableScale()
{
for (auto editor : m_editors)
editor->Enable(editor->has_opt_key("scale") || editor->has_opt_key("size") ? false : true);
m_is_enabled = true;
m_is_enabled_size_and_scale = false;
for (wxWindow* win : std::initializer_list<wxWindow*>{ m_reset_scale_button, m_lock_bnt, m_reset_skew_button })
win->Enable(false);
}
@ -1229,6 +1230,12 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
this->SetSelection(-1, -1); //select all
event.Skip();
}));
this->Bind(wxEVT_UPDATE_UI, [parent, this](wxUpdateUIEvent& evt) {
const bool is_gizmo_in_editing_mode = wxGetApp().plater()->canvas3D()->get_gizmos_manager().is_in_editing_mode();
const bool is_enabled_editing = has_opt_key("scale") || has_opt_key("size") ? parent->is_enabled_size_and_scale() : true;
evt.Enable(!is_gizmo_in_editing_mode && parent->is_enabled() && is_enabled_editing);
});
}
void ManipulationEditor::msw_rescale()

View File

@ -165,6 +165,11 @@ private:
std::vector<ManipulationEditor*> m_editors;
// parameters for enabling/disabling of editors
bool m_is_enabled { true };
bool m_is_enabled_size_and_scale { true };
public:
ObjectManipulation(wxWindow* parent);
~ObjectManipulation() {}
@ -213,6 +218,9 @@ public:
static wxString coordinate_type_str(ECoordinatesType type);
bool is_enabled() const { return m_is_enabled; }
bool is_enabled_size_and_scale()const { return m_is_enabled_size_and_scale; }
#if ENABLE_OBJECT_MANIPULATION_DEBUG
void render_debug_window();
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG

View File

@ -250,7 +250,8 @@ std::string GLGizmoCut3D::get_tooltip() const
if (!m_dragging && m_hover_id == CutPlane)
return _u8L("Click to flip the cut plane\n"
"Drag to move the cut plane");
"Drag to move the cut plane\n"
"Right-click a part to assign it to the other side");
if (tooltip.empty() && (m_hover_id == X || m_hover_id == Y)) {
std::string axis = m_hover_id == X ? "X" : "Y";
@ -289,11 +290,28 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown());
}
else if (m_hover_id == CutPlane) {
if (mouse_event.LeftDown())
m_was_cut_plane_dragged = false;
else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged)
if (mouse_event.LeftDown()) {
m_was_cut_plane_dragged = m_was_contour_selected = false;
// disable / enable current contour
Vec3d pos;
Vec3d pos_world;
m_was_contour_selected = unproject_on_cut_plane(mouse_pos.cast<double>(), pos, pos_world, false);
if (m_was_contour_selected) {
// Following would inform the clipper about the mouse click, so it can
// toggle the respective contour as disabled.
//m_c->object_clipper()->pass_mouse_click(pos_world);
//process_contours();
return true;
}
}
else if (mouse_event.LeftUp() && !m_was_cut_plane_dragged && !m_was_contour_selected)
flip_cut_plane();
}
if (m_part_selection.valid())
m_parent.toggle_model_objects_visibility(false);
return true;
}
@ -334,6 +352,15 @@ bool GLGizmoCut3D::on_mouse(const wxMouseEvent &mouse_event)
return true;
}
else if (mouse_event.RightDown()) {
if (! m_connectors_editing && mouse_event.GetModifiers() == wxMOD_NONE) {
// Check the internal part raycasters.
if (! m_part_selection.valid())
process_contours();
m_part_selection.toggle_selection(mouse_pos);
check_and_update_connectors_state(); // after a contour is deactivated, its connectors are inside the object
return true;
}
if (m_parent.get_selection().get_object_idx() != -1 &&
gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) {
// we need to set the following right up as processed to avoid showing
@ -434,6 +461,7 @@ void GLGizmoCut3D::update_clipper()
void GLGizmoCut3D::set_center(const Vec3d& center, bool update_tbb /*=false*/)
{
set_center_pos(center, update_tbb);
check_and_update_connectors_state();
update_clipper();
}
@ -559,6 +587,8 @@ void GLGizmoCut3D::render_move_center_input(int axis)
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
set_center(move, true);
m_ar_plane_center = m_plane_center;
reset_cut_by_contours();
}
}
@ -865,6 +895,7 @@ void GLGizmoCut3D::on_set_state()
// initiate archived values
m_ar_plane_center = m_plane_center;
m_start_dragging_m = m_rotation_m;
reset_cut_by_contours();
m_parent.request_extra_frame();
}
@ -876,6 +907,13 @@ void GLGizmoCut3D::on_set_state()
m_selected.clear();
m_parent.set_use_color_clip_plane(false);
m_c->selection_info()->set_use_shift(false);
// Make sure that the part selection data are released when the gizmo is closed.
// The CallAfter is needed because in perform_cut, the gizmo is closed BEFORE
// the cut is performed (because of undo/redo snapshots), so the data would
// be deleted prematurely.
if (m_part_selection.valid())
wxGetApp().CallAfter([this]() { m_part_selection = PartSelection(); });
}
}
@ -1089,6 +1127,8 @@ void GLGizmoCut3D::dragging_grabber_z(const GLGizmoBase::UpdateData &data)
projection = m_snap_step * std::round(projection / m_snap_step);
const Vec3d shift = starting_vec * projection;
if (shift != Vec3d::Zero())
reset_cut_by_contours();
// move cut plane center
set_center(m_plane_center + shift, true);
@ -1126,6 +1166,9 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
if (m_hover_id == X)
theta += 0.5 * PI;
if (!is_approx(theta, 0.0))
reset_cut_by_contours();
Vec3d rotation = Vec3d::Zero();
rotation[m_hover_id] = theta;
@ -1166,6 +1209,7 @@ void GLGizmoCut3D::on_dragging(const UpdateData& data)
dragging_grabber_xy(data);
else if (m_hover_id >= m_connectors_group_id && m_connector_mode == CutConnectorMode::Manual)
dragging_connector(data);
check_and_update_connectors_state();
}
void GLGizmoCut3D::on_start_dragging()
@ -1191,6 +1235,7 @@ void GLGizmoCut3D::on_stop_dragging()
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move cut plane"), UndoRedo::SnapshotType::GizmoAction);
m_ar_plane_center = m_plane_center;
}
//check_and_update_connectors_state();
}
void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=false*/)
@ -1270,6 +1315,7 @@ void GLGizmoCut3D::update_bb()
m_bounding_box = box;
invalidate_cut_plane();
reset_cut_by_contours();
m_max_pos = box.max;
m_min_pos = box.min;
@ -1353,11 +1399,239 @@ void GLGizmoCut3D::render_clipper_cut()
{
if (! m_connectors_editing)
::glDisable(GL_DEPTH_TEST);
m_c->object_clipper()->render_cut();
GLboolean cull_face = GL_FALSE;
::glGetBooleanv(GL_CULL_FACE, &cull_face);
::glDisable(GL_CULL_FACE);
m_c->object_clipper()->render_cut(m_part_selection.get_ignored_contours_ptr());
if (cull_face)
::glEnable(GL_CULL_FACE);
if (! m_connectors_editing)
::glEnable(GL_DEPTH_TEST);
}
GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc)
{
m_model = Model();
m_model.add_object(*mo);
ModelObjectPtrs cut_part_ptrs = m_model.objects.front()->cut(instance_idx_in, cut_matrix,
ModelObjectCutAttribute::KeepUpper |
ModelObjectCutAttribute::KeepLower |
ModelObjectCutAttribute::KeepAsParts);
assert(cut_part_ptrs.size() == 1);
m_model = Model();
m_model.add_object(*cut_part_ptrs.front());
m_instance_idx = instance_idx_in;
const ModelVolumePtrs& volumes = model_object()->volumes;
// split to parts
for (int id = int(volumes.size())-1; id >= 0; id--)
if (volumes[id]->is_splittable())
volumes[id]->split(1);
m_parts.clear();
for (const ModelVolume* volume : volumes) {
assert(volume != nullptr);
m_parts.emplace_back(Part{GLModel(), MeshRaycaster(volume->mesh()), true});
m_parts.back().glmodel.set_color({ 0.f, 0.f, 1.f, 1.f });
m_parts.back().glmodel.init_from(volume->mesh());
// Now check whether this part is below or above the plane.
Transform3d tr = (model_object()->instances[m_instance_idx]->get_matrix() * volume->get_matrix()).inverse();
Vec3f pos = (tr * center).cast<float>();
Vec3f norm = (tr.linear().inverse().transpose() * normal).cast<float>();
for (const Vec3f& v : volume->mesh().its.vertices) {
double p = (v - pos).dot(norm);
if (std::abs(p) > EPSILON) {
m_parts.back().selected = p > 0.;
break;
}
}
}
// Now go through the contours and create a map from contours to parts.
m_contour_points.clear();
m_contour_to_parts.clear();
m_debug_pts = std::vector<std::vector<Vec3d>>(m_parts.size(), std::vector<Vec3d>());
if (std::vector<Vec3d> pts = oc.point_per_contour();! pts.empty()) {
m_contour_to_parts.resize(pts.size());
for (size_t pt_idx=0; pt_idx<pts.size(); ++pt_idx) {
const Vec3d& pt = pts[pt_idx];
const Vec3d dir = (center-pt).dot(normal) * normal;
m_contour_points.emplace_back(dir + pt); // the result is in world coordinates.
// Now, cast a ray from every contour point and see which volumes of the ones above
// the plane are hit from the inside.
for (size_t part_id=0; part_id<m_parts.size(); ++part_id) {
const AABBMesh& aabb = m_parts[part_id].raycaster.get_aabb_mesh();
const Transform3d& tr = (translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[part_id]->get_offset())).inverse();
for (double d : {-1., 1.}) {
const Vec3d dir_mesh = d * tr.linear().inverse().transpose() * normal;
const Vec3d src = tr * (m_contour_points[pt_idx] + d*0.01 * normal);
AABBMesh::hit_result hit = aabb.query_ray_hit(src, dir_mesh);
m_debug_pts[part_id].emplace_back(src);
if (hit.is_inside()) {
// This part belongs to this point.
if (d == 1.)
m_contour_to_parts[pt_idx].first.emplace_back(part_id);
else
m_contour_to_parts[pt_idx].second.emplace_back(part_id);
}
}
}
}
}
m_valid = true;
}
void GLGizmoCut3D::PartSelection::render(const Vec3d* normal, GLModel& sphere_model)
{
if (! valid())
return;
const Camera& camera = wxGetApp().plater()->get_camera();
if (GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light")) {
shader->start_using();
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("emission_factor", 0.f);
// FIXME: Cache the transforms.
const Vec3d inst_offset = model_object()->instances[m_instance_idx]->get_offset();
const Transform3d view_inst_matrix= camera.get_view_matrix() * translation_transform(inst_offset);
const bool is_looking_forward = normal && camera.get_dir_forward().dot(*normal) < 0.05;
for (size_t id=0; id<m_parts.size(); ++id) {
if (normal && (( is_looking_forward && m_parts[id].selected) ||
(!is_looking_forward && !m_parts[id].selected) ) )
continue;
const Vec3d volume_offset = model_object()->volumes[id]->get_offset();
shader->set_uniform("view_model_matrix", view_inst_matrix * translation_transform(volume_offset));
m_parts[id].glmodel.set_color(m_parts[id].selected ? UPPER_PART_COLOR : LOWER_PART_COLOR);
m_parts[id].glmodel.render();
}
shader->stop_using();
}
// { // Debugging render:
// static int idx = -1;
// ImGui::Begin("DEBUG");
// for (int i=0; i<m_parts.size(); ++i)
// if (ImGui::Button(std::to_string(i).c_str()))
// idx = i;
// if (idx >= m_parts.size())
// idx = -1;
// ImGui::End();
// ::glDisable(GL_DEPTH_TEST);
// if (valid()) {
// for (size_t i=0; i<m_contour_points.size(); ++i) {
// const Vec3d& pt = m_contour_points[i];
// ColorRGBA col = ColorRGBA::GREEN();
// bool red = false;
// bool yellow = false;
// for (size_t j=0; j<m_contour_to_parts[i].first.size(); ++j) {
// red |= m_parts[m_contour_to_parts[i].first[j]].selected;
// yellow |= m_parts[m_contour_to_parts[i].second[j]].selected;
// }
// if (red)
// col = ColorRGBA::RED();
// if (yellow)
// col = ColorRGBA::YELLOW();
// GLGizmoCut3D::render_model(sphere_model, col, camera.get_view_matrix() * translation_transform(pt));
// }
// }
// if (idx != -1) {
// render_model(m_parts[idx].glmodel, ColorRGBA::RED(), camera.get_view_matrix());
// for (const Vec3d& pt : m_debug_pts[idx]) {
// render_model(sphere_model, ColorRGBA::GREEN(), camera.get_view_matrix() * translation_transform(pt));
// }
// }
// ::glEnable(GL_DEPTH_TEST);
// }
}
bool GLGizmoCut3D::PartSelection::is_one_object() const
{
// In theory, the implementation could be just this:
// return m_contour_to_parts.size() == m_ignored_contours.size();
// However, this would require that the part-contour correspondence works
// flawlessly. Because it is currently not always so for self-intersecting
// objects, let's better check the parts itself:
if (m_parts.size() < 2)
return true;
return std::all_of(m_parts.begin(), m_parts.end(), [this](const Part& part) {
return part.selected == m_parts.front().selected;
});
}
void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos)
{
// FIXME: Cache the transforms.
const Camera& camera = wxGetApp().plater()->get_camera();
const Vec3d& camera_pos = camera.get_position();
Vec3f pos;
Vec3f normal;
std::vector<std::pair<size_t, double>> hits_id_and_sqdist;
for (size_t id=0; id<m_parts.size(); ++id) {
const Vec3d volume_offset = model_object()->volumes[id]->get_offset();
Transform3d tr = translation_transform(model_object()->instances[m_instance_idx]->get_offset()) * translation_transform(model_object()->volumes[id]->get_offset());
if (m_parts[id].raycaster.unproject_on_mesh(mouse_pos, tr, camera, pos, normal)) {
hits_id_and_sqdist.emplace_back(id, (camera_pos - tr*(pos.cast<double>())).squaredNorm());
}
}
if (! hits_id_and_sqdist.empty()) {
size_t id = std::min_element(hits_id_and_sqdist.begin(), hits_id_and_sqdist.end(),
[](const std::pair<size_t, double>& a, const std::pair<size_t, double>& b) { return a.second < b.second; })->first;
m_parts[id].selected = ! m_parts[id].selected;
// And now recalculate the contours which should be ignored.
m_ignored_contours.clear();
size_t cont_id = 0;
for (const auto& [parts_above, parts_below] : m_contour_to_parts) {
for (size_t upper : parts_above) {
bool upper_sel = m_parts[upper].selected;
if (std::find_if(parts_below.begin(), parts_below.end(), [this, &upper_sel](const size_t& i) { return m_parts[i].selected == upper_sel; }) != parts_below.end()) {
m_ignored_contours.emplace_back(cont_id);
break;
}
}
++cont_id;
}
}
}
void GLGizmoCut3D::PartSelection::turn_over_selection()
{
for (Part& part : m_parts)
part.selected = !part.selected;
}
void GLGizmoCut3D::on_render()
{
if (m_state == On) {
@ -1366,6 +1640,7 @@ void GLGizmoCut3D::on_render()
m_c->selection_info()->set_use_shift(true);
}
update_clipper();
init_picking_models();
@ -1374,6 +1649,11 @@ void GLGizmoCut3D::on_render()
render_connectors();
if (!m_connectors_editing)
m_part_selection.render(nullptr, m_sphere.model);
else
m_part_selection.render(&m_cut_normal, m_sphere.model);
render_clipper_cut();
if (!m_hide_cut_plane && !m_connectors_editing) {
@ -1483,7 +1763,7 @@ void GLGizmoCut3D::apply_selected_connectors(std::function<void(size_t idx)> app
for (size_t idx = 0; idx < m_selected.size(); idx++)
if (m_selected[idx])
apply_fn(idx);
check_and_update_connectors_state();
update_raycasters_for_picking_transform();
}
@ -1587,6 +1867,9 @@ void GLGizmoCut3D::reset_cut_plane()
set_center(m_bb_center);
m_start_dragging_m = m_rotation_m = Transform3d::Identity();
m_ar_plane_center = m_plane_center;
reset_cut_by_contours();
m_parent.request_extra_frame();
}
void GLGizmoCut3D::invalidate_cut_plane()
@ -1621,6 +1904,31 @@ void GLGizmoCut3D::flip_cut_plane()
m_start_dragging_m = m_rotation_m;
update_clipper();
m_part_selection.turn_over_selection();
}
void GLGizmoCut3D::reset_cut_by_contours()
{
m_part_selection = PartSelection();
const Selection& selection = m_parent.get_selection();
const ModelObjectPtrs& model_objects = selection.get_model()->objects;
m_parent.toggle_model_objects_visibility(true, model_objects[selection.get_object_idx()], selection.get_instance_idx());
}
void GLGizmoCut3D::process_contours()
{
reset_cut_by_contours();
const Selection& selection = m_parent.get_selection();
const ModelObjectPtrs& model_objects = selection.get_model()->objects;
wxBusyCursor wait;
const int instance_idx = selection.get_instance_idx();
const int object_idx = selection.get_object_idx();
m_part_selection = PartSelection(model_objects[object_idx], get_cut_matrix(selection), instance_idx, m_plane_center, m_cut_normal, *m_c->object_clipper());
m_parent.toggle_model_objects_visibility(false);
}
void GLGizmoCut3D::render_flip_plane_button(bool disable_pred /*=false*/)
@ -1701,7 +2009,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
add_vertical_scaled_interval(0.75f);
m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts);
m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower || m_keep_as_parts || (m_part_selection.valid() && m_part_selection.is_one_object()));
if (m_imgui->button(has_connectors ? _L("Edit connectors") : _L("Add connectors")))
set_connectors_editing(true);
m_imgui->disabled_end();
@ -1784,9 +2092,12 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
add_vertical_scaled_interval(0.75f);
m_imgui->disabled_begin(has_connectors);
m_imgui->disabled_begin(has_connectors || m_part_selection.valid());
ImGuiWrapper::text(_L("Cut into") + ":");
if (m_part_selection.valid())
m_keep_as_parts = false;
add_horizontal_scaled_interval(1.2f);
// TRN CutGizmo: RadioButton Cut into ...
if (m_imgui->radio_button(_L("Objects"), !m_keep_as_parts))
@ -1904,7 +2215,7 @@ void GLGizmoCut3D::render_input_window_warning() const
{
if (m_is_contour_changed)
return;
if (m_has_invalid_connector) {
if (! m_invalid_connectors_idxs.empty()) {
wxString out = wxString(ImGui::WarningMarkerSmall) + _L("Invalid connectors detected") + ":";
if (m_info_stats.outside_cut_contour > size_t(0))
out += "\n - " + format_wxstr(_L_PLURAL("%1$d connector is out of cut contour", "%1$d connectors are out of cut contour", m_info_stats.outside_cut_contour),
@ -1949,7 +2260,7 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit)
bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos)
{
// check if connector pos is out of clipping plane
if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) {
if (m_c->object_clipper() && m_c->object_clipper()->is_projection_inside_cut(cur_pos) == -1) {
m_info_stats.outside_cut_contour++;
return true;
}
@ -1974,12 +2285,21 @@ bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& co
}
its_transform(mesh, translation_transform(cur_pos) * m_rotation_m);
for (auto vertex : vertices) {
if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast<double>())) {
for (const Vec3f& vertex : vertices) {
if (m_c->object_clipper()) {
int contour_idx = m_c->object_clipper()->is_projection_inside_cut(vertex.cast<double>());
bool is_invalid = (contour_idx == -1);
if (m_part_selection.valid() && ! is_invalid) {
assert(contour_idx >= 0);
const std::vector<size_t>& ignored = *(m_part_selection.get_ignored_contours_ptr());
is_invalid = (std::find(ignored.begin(), ignored.end(), size_t(contour_idx)) != ignored.end());
}
if (is_invalid) {
m_info_stats.outside_cut_contour++;
return true;
}
}
}
return false;
}
@ -2016,6 +2336,27 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co
return false;
}
void GLGizmoCut3D::check_and_update_connectors_state()
{
m_info_stats.invalidate();
m_invalid_connectors_idxs.clear();
const ModelObject* mo = m_c->selection_info()->model_object();
auto inst_id = m_c->selection_info()->get_active_instance();
if (inst_id < 0)
return;
const CutConnectors& connectors = mo->cut_connectors;
const ModelInstance* mi = mo->instances[inst_id];
const Vec3d& instance_offset = mi->get_offset();
const double sla_shift = double(m_c->selection_info()->get_sla_shift());
for (size_t i = 0; i < connectors.size(); ++i) {
const CutConnector& connector = connectors[i];
Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // recalculate connector position to world position
if (is_conflict_for_connector(i, connectors, pos))
m_invalid_connectors_idxs.emplace_back(i);
}
}
void GLGizmoCut3D::render_connectors()
{
::glEnable(GL_DEPTH_TEST);
@ -2041,9 +2382,6 @@ void GLGizmoCut3D::render_connectors()
const Vec3d& instance_offset = mi->get_offset();
const double sla_shift = double(m_c->selection_info()->get_sla_shift());
m_has_invalid_connector = false;
m_info_stats.invalidate();
const bool looking_forward = is_looking_forward();
for (size_t i = 0; i < connectors.size(); ++i) {
@ -2054,11 +2392,10 @@ void GLGizmoCut3D::render_connectors()
Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ();
// First decide about the color of the point.
const bool conflict_connector = is_conflict_for_connector(i, connectors, pos);
if (conflict_connector) {
m_has_invalid_connector = true;
assert(std::is_sorted(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end()));
const bool conflict_connector = std::binary_search(m_invalid_connectors_idxs.begin(), m_invalid_connectors_idxs.end(), i);
if (conflict_connector)
render_color = CONNECTOR_ERR_COLOR;
}
else // default connector color
render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR;
@ -2098,8 +2435,10 @@ void GLGizmoCut3D::render_connectors()
bool GLGizmoCut3D::can_perform_cut() const
{
if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing)
if (! m_invalid_connectors_idxs.empty() || (!m_keep_upper && !m_keep_lower) || m_connectors_editing)
return false;
if (m_part_selection.valid())
return ! m_part_selection.is_one_object();
return true;// has_valid_contour();
}
@ -2132,6 +2471,24 @@ void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowel
}
}
Transform3d GLGizmoCut3D::get_cut_matrix(const Selection& selection)
{
const int instance_idx = selection.get_instance_idx();
const int object_idx = selection.get_object_idx();
ModelObject* mo = selection.get_model()->objects[object_idx];
if (!mo)
return Transform3d::Identity();
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z();
const Vec3d instance_offset = mo->instances[instance_idx]->get_offset();
Vec3d cut_center_offset = m_plane_center - instance_offset;
cut_center_offset[Z] -= sla_shift_z;
return translation_transform(cut_center_offset) * m_rotation_m;
}
void GLGizmoCut3D::perform_cut(const Selection& selection)
{
if (!can_perform_cut())
@ -2149,24 +2506,28 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
// deactivate CutGizmo and than perform a cut
m_parent.reset_all_gizmos();
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
const double sla_shift_z = selection.get_first_volume()->get_sla_shift_z();
const Vec3d instance_offset = mo->instances[instance_idx]->get_offset();
Vec3d cut_center_offset = m_plane_center - instance_offset;
cut_center_offset[Z] -= sla_shift_z;
// perform cut
{
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane"));
// This shall delete the part selection class and deallocate the memory.
ScopeGuard part_selection_killer([this]() { m_part_selection = PartSelection(); });
const bool cut_by_contour = m_part_selection.valid();
ModelObject* cut_mo = cut_by_contour ? m_part_selection.model_object() : nullptr;
if (cut_mo)
cut_mo->cut_connectors = mo->cut_connectors;
bool create_dowels_as_separate_object = false;
const bool has_connectors = !mo->cut_connectors.empty();
// update connectors pos as offset of its center before cut performing
apply_connectors_in_model(mo, create_dowels_as_separate_object);
apply_connectors_in_model(cut_mo ? cut_mo : mo , create_dowels_as_separate_object);
plater->cut(object_idx, instance_idx, translation_transform(cut_center_offset) * m_rotation_m,
only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
wxBusyCursor wait;
const Transform3d cut_matrix = get_cut_matrix(selection);
ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) |
only_if(m_place_on_cut_upper, ModelObjectCutAttribute::PlaceOnCutUpper) |
@ -2174,7 +2535,89 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
only_if(m_rotate_upper, ModelObjectCutAttribute::FlipUpper) |
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower) |
only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels) |
only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo));
only_if(!has_connectors, ModelObjectCutAttribute::InvalidateCutInfo);
ModelObjectPtrs cut_object_ptrs;
if (cut_by_contour) {
// apply cut attributes for object
if (m_keep_upper && m_keep_lower)
cut_mo->apply_cut_attributes(ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper |
only_if(create_dowels_as_separate_object, ModelObjectCutAttribute::CreateDowels));
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
if (m_keep_upper) cut_mo->clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (m_keep_lower) cut_mo->clone_for_cut(&lower);
auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower, bool invalidate_cut = true) {
if (upper && !upper->volumes.empty()) {
ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper);
if (invalidate_cut)
upper->invalidate_cut();
cut_objects.push_back(upper);
}
if (lower && !lower->volumes.empty()) {
ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower);
if (invalidate_cut)
lower->invalidate_cut();
cut_objects.push_back(lower);
}
};
const size_t cut_parts_cnt = m_part_selection.parts().size();
for (size_t id = 0; id < cut_parts_cnt; ++id) {
if (ModelObject* obj = (m_part_selection.parts()[id].selected ? upper : lower))
obj->add_volume(*(cut_mo->volumes[id]));
}
ModelVolumePtrs& volumes = cut_mo->volumes;
if (volumes.size() == cut_parts_cnt)
add_cut_objects(cut_object_ptrs, upper, lower);
else if (volumes.size() > cut_parts_cnt) {
for (size_t id = 0; id < cut_parts_cnt; id++)
delete *(volumes.begin() + id);
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes);
assert(create_dowels_as_separate_object ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
upper->add_volume(*volume, volume->type());
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
lower->add_volume(*volume, volume->type());
add_cut_objects(cut_object_ptrs, upper, lower, false);
if (cut_connectors_obj.size() >= 3)
for (size_t id = 2; id < cut_connectors_obj.size(); id++)
cut_object_ptrs.push_back(cut_connectors_obj[id]);
}
// Now merge all model parts together:
{
for (ModelObject* mo : cut_object_ptrs) {
TriangleMesh mesh;
for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part()) {
TriangleMesh m = mv->mesh();
m.transform(mv->get_matrix());
mesh.merge(m);
}
}
if (! mesh.empty()) {
ModelVolume* new_volume = mo->add_volume(mesh);
for (int i=int(mo->volumes.size())-2; i>=0; --i)
if (mo->volumes[i]->type() == ModelVolumeType::MODEL_PART)
mo->delete_volume(i);
}
}
}
}
else
cut_object_ptrs = mo->cut(instance_idx, cut_matrix, attributes);
plater->cut(object_idx, cut_object_ptrs);
}
}
@ -2182,7 +2625,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
// Return false if no intersection was found, true otherwise.
bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world)
bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour/* = true*/)
{
const float sla_shift = m_c->selection_info()->get_sla_shift();
@ -2204,8 +2647,33 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& po
} else
return false;
if (! m_c->object_clipper()->is_projection_inside_cut(hit))
// Now check if the hit is not obscured by a selected part on this side of the plane.
// FIXME: This would be better solved by remembering which contours are active. We will
// probably need that anyway because there is not other way to find out which contours
// to render. If you want to uncomment it, fix it first. It does not work yet.
/*for (size_t id = 0; id < m_part_selection.parts.size(); ++id) {
if (! m_part_selection.parts[id].selected) {
Vec3f pos, normal;
const ModelObject* model_object = m_part_selection.model_object;
const Vec3d volume_offset = m_part_selection.model_object->volumes[id]->get_offset();
Transform3d tr = model_object->instances[m_part_selection.instance_idx]->get_matrix() * model_object->volumes[id]->get_matrix();
if (m_part_selection.parts[id].raycaster.unproject_on_mesh(mouse_position, tr, camera, pos, normal))
return false;
}
}*/
{
// Do not react to clicks outside a contour (or inside a contour that is ignored)
int cont_id = m_c->object_clipper()->is_projection_inside_cut(hit);
if (cont_id == -1)
return false;
if (m_part_selection.valid()) {
const std::vector<size_t>& ign = *m_part_selection.get_ignored_contours_ptr();
if (std::find(ign.begin(), ign.end(), cont_id) != ign.end())
return false;
}
}
// recalculate hit to object's local position
Vec3d hit_d = hit;
@ -2230,6 +2698,7 @@ void GLGizmoCut3D::reset_connectors()
m_c->selection_info()->model_object()->cut_connectors.clear();
update_raycasters_for_picking();
clear_selection();
check_and_update_connectors_state();
}
void GLGizmoCut3D::init_connector_shapes()
@ -2284,6 +2753,8 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse
}
if (cut_line_processing()) {
reset_cut_by_contours();
m_line_end = pt;
if (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp) {
Vec3d line_dir = m_line_end - m_line_beg;
@ -2343,6 +2814,7 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p
assert(m_selected.size() == connectors.size());
update_raycasters_for_picking();
m_parent.set_as_dirty();
check_and_update_connectors_state();
return true;
}
@ -2368,6 +2840,7 @@ bool GLGizmoCut3D::delete_selected_connectors(CutConnectors& connectors)
assert(m_selected.size() == connectors.size());
update_raycasters_for_picking();
m_parent.set_as_dirty();
check_and_update_connectors_state();
return true;
}
@ -2430,20 +2903,8 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi
if (!m_keep_upper || !m_keep_lower)
return false;
if (!m_connectors_editing) {
if (0 && action == SLAGizmoEventType::LeftDown) {
// disable / enable current contour
Vec3d pos;
Vec3d pos_world;
if (unproject_on_cut_plane(mouse_position.cast<double>(), pos, pos_world)) {
// Following would inform the clipper about the mouse click, so it can
// toggle the respective contour as disabled.
m_c->object_clipper()->pass_mouse_click(pos_world);
return true;
}
}
if (!m_connectors_editing)
return false;
}
CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors;

View File

@ -20,6 +20,8 @@ class Selection;
enum class SLAGizmoEventType : unsigned char;
namespace CommonGizmosDataObjects { class ObjectClipper; }
class GLGizmoCut3D : public GLGizmoBase
{
enum GrabberID {
@ -133,8 +135,44 @@ class GLGizmoCut3D : public GLGizmoBase
GLSelectionRectangle m_selection_rectangle;
bool m_has_invalid_connector{ false };
std::vector<size_t> m_invalid_connectors_idxs;
bool m_was_cut_plane_dragged { false };
bool m_was_contour_selected { false };
class PartSelection {
public:
PartSelection() = default;
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
~PartSelection() { m_model.clear_objects(); }
struct Part {
GLModel glmodel;
MeshRaycaster raycaster;
bool selected;
};
void render(const Vec3d* normal, GLModel& sphere_model);
void toggle_selection(const Vec2d& mouse_pos);
void turn_over_selection();
ModelObject* model_object() { return m_model.objects.front(); }
bool valid() const { return m_valid; }
bool is_one_object() const;
const std::vector<Part>& parts() const { return m_parts; }
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
private:
Model m_model;
int m_instance_idx;
std::vector<Part> m_parts;
bool m_valid = false;
std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below
std::vector<size_t> m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object)
std::vector<Vec3d> m_contour_points; // Debugging
std::vector<std::vector<Vec3d>> m_debug_pts; // Debugging
};
PartSelection m_part_selection;
bool m_show_shortcuts{ false };
std::vector<std::pair<wxString, wxString>> m_shortcuts;
@ -176,7 +214,7 @@ public:
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
std::string get_tooltip() const override;
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world);
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
bool is_in_editing_mode() const override { return m_connectors_editing; }
@ -229,6 +267,8 @@ protected:
void reset_cut_plane();
void set_connectors_editing(bool connectors_editing);
void flip_cut_plane();
void process_contours();
void reset_cut_by_contours();
void render_flip_plane_button(bool disable_pred = false);
void add_vertical_scaled_interval(float interval);
void add_horizontal_scaled_interval(float interval);
@ -257,6 +297,7 @@ protected:
std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); }
void data_changed(bool is_serializing) override;
Transform3d get_cut_matrix(const Selection& selection);
private:
void set_center(const Vec3d& center, bool update_tbb = false);
@ -278,7 +319,7 @@ private:
void discard_cut_line_processing();
void render_cut_plane();
void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
@ -296,6 +337,7 @@ private:
void update_connector_shape();
void validate_connector_settings();
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
void check_and_update_connectors_state();
};
} // namespace GUI

View File

@ -17,6 +17,7 @@
#include <GL/glew.h>
#include <algorithm>
namespace Slic3r::GUI {
@ -521,6 +522,13 @@ void GLGizmoFdmSupports::auto_generate()
}
ModelObject *mo = m_c->selection_info()->model_object();
bool printable = std::any_of(mo->instances.begin(), mo->instances.end(), [](const ModelInstance *p) { return p->is_printable(); });
if (!printable) {
MessageDialog dlg(GUI::wxGetApp().plater(), _L("Automatic painting requires printable object."), _L("Warning"), wxOK);
dlg.ShowModal();
return;
}
bool not_painted = std::all_of(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume* vol){
return vol->type() != ModelVolumeType::MODEL_PART || vol->supported_facets.empty();
});

View File

@ -20,7 +20,14 @@ GLGizmoSlaBase::GLGizmoSlaBase(GLCanvas3D& parent, const std::string& icon_filen
void GLGizmoSlaBase::reslice_until_step(SLAPrintObjectStep step, bool postpone_error_messages)
{
wxGetApp().CallAfter([this, step, postpone_error_messages]() {
if (m_c->selection_info())
wxGetApp().plater()->reslice_SLA_until_step(step, *m_c->selection_info()->model_object(), postpone_error_messages);
else {
const Selection& selection = m_parent.get_selection();
const int object_idx = selection.get_object_idx();
if (object_idx >= 0 && !selection.is_wipe_tower())
wxGetApp().plater()->reslice_SLA_until_step(step, *wxGetApp().plater()->model().objects[object_idx], postpone_error_messages);
}
});
}

View File

@ -87,6 +87,8 @@ void GLGizmoSlaSupports::data_changed(bool is_serializing)
register_point_raycasters_for_picking();
else
update_point_raycasters_for_picking_transform();
m_c->instances_hider()->set_hide_full_scene(true);
}
// m_parent.toggle_model_objects_visibility(false);
@ -399,7 +401,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
if (action == SLAGizmoEventType::DiscardChanges) {
ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
ask_about_changes([this](){ editing_mode_apply_changes(); },
[this](){ editing_mode_discard_changes(); });
return true;
}
@ -816,39 +818,36 @@ std::string GLGizmoSlaSupports::on_get_name() const
return _u8L("SLA Support Points");
}
void GLGizmoSlaSupports::ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no)
bool GLGizmoSlaSupports::ask_about_changes(std::function<void()> on_yes, std::function<void()> on_no)
{
wxGetApp().CallAfter([on_yes, on_no]() {
// Following is called through CallAfter, because otherwise there was a problem
// on OSX with the wxMessageDialog being shown several times when clicked into.
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
"edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
int ret = dlg.ShowModal();
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually edited support points?") + "\n",
_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
const int ret = dlg.ShowModal();
if (ret == wxID_YES)
on_yes();
else if (ret == wxID_NO)
on_no();
});
}
else
return false;
return true;
}
void GLGizmoSlaSupports::on_set_state()
{
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
if (m_state == On) { // the gizmo was just turned on
// Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable();
if (will_ask) {
ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
[this](){ editing_mode_discard_changes(); });
// refuse to be turned off so the gizmo is active when the CallAfter is executed
m_state = m_old_state;
else {
if (m_editing_mode && unsaved_changes() && on_is_activable()) {
if (!ask_about_changes([this]() { editing_mode_apply_changes(); },
[this]() { editing_mode_discard_changes(); })) {
m_state = On;
return;
}
}
else {
// we are actually shutting down
@ -856,15 +855,11 @@ void GLGizmoSlaSupports::on_set_state()
m_old_mo_id = -1;
}
if (m_state == Off) {
m_c->instances_hider()->set_hide_full_scene(false);
m_c->selection_info()->set_use_shift(false); // see top of on_render for details
}
}
m_old_state = m_state;
}
}
}
void GLGizmoSlaSupports::on_start_dragging()
{

View File

@ -110,7 +110,6 @@ private:
bool m_wait_for_up_event = false;
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
@ -131,7 +130,9 @@ private:
void auto_generate();
void switch_to_editing_mode();
void disable_editing_mode();
void ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no);
// return false if Cancel was selected
bool ask_about_changes(std::function<void()> on_yes, std::function<void()> on_no);
protected:
void on_set_state() override;

View File

@ -370,27 +370,53 @@ void ObjectClipper::on_release()
}
void ObjectClipper::render_cut() const
void ObjectClipper::render_cut(const std::vector<size_t>* ignore_idxs) const
{
if (m_clp_ratio == 0.)
return;
const SelectionInfo* sel_info = get_pool()->selection_info();
const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation();
std::vector<size_t> ignore_idxs_local = ignore_idxs ? *ignore_idxs : std::vector<size_t>();
for (auto& clipper : m_clippers) {
Geometry::Transformation trafo = inst_trafo * clipper.second;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
clipper.first->set_plane(*m_clp);
clipper.first->set_transformation(trafo);
clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f });
clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f });
clipper.first->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }, &ignore_idxs_local);
clipper.first->render_contour({ 1.f, 1.f, 1.f, 1.f }, &ignore_idxs_local);
// Now update the ignore idxs. Find the first element belonging to the next clipper,
// and remove everything before it and decrement everything by current number of contours.
const int num_of_contours = clipper.first->get_number_of_contours();
ignore_idxs_local.erase(ignore_idxs_local.begin(), std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) { return idx >= num_of_contours; } ));
for (size_t& idx : ignore_idxs_local)
idx -= num_of_contours;
}
}
bool ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
int ObjectClipper::get_number_of_contours() const
{
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto& cl) { return cl.first->is_projection_inside_cut(point); });
int sum = 0;
for (const auto& [clipper, trafo] : m_clippers)
sum += clipper->get_number_of_contours();
return sum;
}
int ObjectClipper::is_projection_inside_cut(const Vec3d& point) const
{
if (m_clp_ratio == 0.)
return -1;
int idx_offset = 0;
for (const auto& [clipper, trafo] : m_clippers) {
if (int idx = clipper->is_projection_inside_cut(point); idx != -1)
return idx_offset + idx;
idx_offset += clipper->get_number_of_contours();
}
return -1;
}
bool ObjectClipper::has_valid_contour() const
@ -398,6 +424,18 @@ bool ObjectClipper::has_valid_contour() const
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto& cl) { return cl.first->has_valid_contour(); });
}
std::vector<Vec3d> ObjectClipper::point_per_contour() const
{
std::vector<Vec3d> pts;
for (const auto& clipper : m_clippers) {
const std::vector<Vec3d> pts_clipper = clipper.first->point_per_contour();
pts.insert(pts.end(), pts_clipper.begin(), pts_clipper.end());;
}
return pts;
}
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
@ -436,16 +474,6 @@ void ObjectClipper::set_behavior(bool hide_clipped, bool fill_cut, double contou
clipper.first->set_behaviour(fill_cut, contour_width);
}
void ObjectClipper::pass_mouse_click(const Vec3d& pt)
{
for (auto& clipper : m_clippers)
clipper.first->pass_mouse_click(pt);
}
std::vector<Vec3d> ObjectClipper::get_disabled_contours() const
{
return std::vector<Vec3d>();
}
void SupportsClipper::on_update()
{

View File

@ -242,15 +242,15 @@ public:
void set_normal(const Vec3d& dir);
double get_position() const { return m_clp_ratio; }
const ClippingPlane* get_clipping_plane(bool ignore_hide_clipped = false) const;
void render_cut() const;
void render_cut(const std::vector<size_t>* ignore_idxs = nullptr) const;
void set_position_by_ratio(double pos, bool keep_normal);
void set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos);
void set_behavior(bool hide_clipped, bool fill_cut, double contour_width);
void pass_mouse_click(const Vec3d& pt);
std::vector<Vec3d> get_disabled_contours() const;
int get_number_of_contours() const;
std::vector<Vec3d> point_per_contour() const;
bool is_projection_inside_cut(const Vec3d& point_in) const;
int is_projection_inside_cut(const Vec3d& point_in) const;
bool has_valid_contour() const;

View File

@ -173,7 +173,8 @@ void GLGizmosManager::reset_all_states()
const EType current = get_current_type();
if (current != Undefined)
// close any open gizmo
open_gizmo(current);
if (!open_gizmo(current))
return;
activate_gizmo(Undefined);
m_hover = Undefined;
@ -978,6 +979,9 @@ bool GLGizmosManager::activate_gizmo(EType type)
return false; // gizmo refused to be turned on.
}
if (m_parent.current_printer_technology() == ptSLA)
m_parent.set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
new_gizmo.register_raycasters_for_picking();
// sucessful activation of gizmo

View File

@ -104,6 +104,8 @@ static const std::map<const wchar_t, std::string> font_icons_large = {
{ImGui::PauseHoverButton , "notification_pause_hover" },
{ImGui::OpenButton , "notification_open" },
{ImGui::OpenHoverButton , "notification_open_hover" },
{ImGui::SlaViewOriginal , "sla_view_original" },
{ImGui::SlaViewProcessed , "sla_view_processed" },
};
static const std::map<const wchar_t, std::string> font_icons_extra_large = {
@ -490,6 +492,18 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active)
return ImGui::RadioButton(label_utf8.c_str(), active);
}
void ImGuiWrapper::draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id)
{
ImGuiIO& io = ImGui::GetIO();
const ImTextureID tex_id = io.Fonts->TexID;
const float tex_w = static_cast<float>(io.Fonts->TexWidth);
const float tex_h = static_cast<float>(io.Fonts->TexHeight);
const ImFontAtlas::CustomRect* const rect = GetTextureCustomRect(icon_id);
const ImVec2 uv0 = { static_cast<float>(rect->X) / tex_w, static_cast<float>(rect->Y) / tex_h };
const ImVec2 uv1 = { static_cast<float>(rect->X + rect->Width) / tex_w, static_cast<float>(rect->Y + rect->Height) / tex_h };
window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }));
}
bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active,
std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback)
{

View File

@ -95,6 +95,7 @@ public:
bool button(const wxString& label, float width, float height);
bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f)
bool radio_button(const wxString &label, bool active);
void draw_icon(ImGuiWindow& window, const ImVec2& pos, float size, wchar_t icon_id);
bool draw_radio_button(const std::string& name, float size, bool active, std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback);
bool checkbox(const wxString &label, bool &value);
static void text(const char *label);

View File

@ -25,7 +25,8 @@ void FillBedJob::prepare()
return;
ModelObject *model_object = m_plater->model().objects[m_object_idx];
if (model_object->instances.empty()) return;
if (model_object->instances.empty())
return;
m_selected.reserve(model_object->instances.size());
for (ModelInstance *inst : model_object->instances)
@ -37,7 +38,8 @@ void FillBedJob::prepare()
m_selected.emplace_back(ap);
}
if (m_selected.empty()) return;
if (m_selected.empty())
return;
m_bedpts = get_bed_shape(*m_plater->config());
@ -85,9 +87,11 @@ void FillBedJob::prepare()
ArrangePolygon ap = template_ap;
ap.poly = m_selected.front().poly;
ap.bed_idx = arrangement::UNARRANGED;
ap.setter = [this, mi](const ArrangePolygon &p) {
auto m = mi->get_transformation();
ap.setter = [this, mi, m](const ArrangePolygon &p) {
ModelObject *mo = m_plater->model().objects[m_object_idx];
ModelInstance *inst = mo->add_instance(*mi);
inst->set_transformation(m);
inst->apply_arrange_result(p.translation.cast<double>(), p.rotation);
};
m_selected.emplace_back(ap);
@ -166,10 +170,12 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
if (canceled || eptr)
return;
if (m_object_idx == -1) return;
if (m_object_idx == -1)
return;
ModelObject *model_object = m_plater->model().objects[m_object_idx];
if (model_object->instances.empty()) return;
if (model_object->instances.empty())
return;
size_t inst_cnt = model_object->instances.size();
@ -188,7 +194,8 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
m_plater->update();
// FIXME: somebody explain why this is needed for increase_object_instances
if (inst_cnt == 1) added_cnt++;
if (inst_cnt == 1)
added_cnt++;
m_plater->sidebar()
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));

View File

@ -11,6 +11,7 @@
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Format/SLAArchiveReader.hpp"
#include "libslic3r/Format/SLAArchiveFormatRegistry.hpp"
#include "slic3r/GUI/I18N.hpp"
@ -29,11 +30,16 @@ std::string get_readers_wildcard()
{
std::string ret;
for (const char *archtype : SLAArchiveReader::registered_archives()) {
ret += into_u8(_(SLAArchiveReader::get_description(archtype)));
auto registry = registered_sla_archives();
for (const ArchiveEntry &entry : registry) {
if (!entry.rdfactoryfn)
continue;
ret += into_u8(_(entry.desc));
ret += " (";
auto extensions = SLAArchiveReader::get_extensions(archtype);
for (const char * ext : extensions) {
std::vector<std::string> extensions = get_extensions(entry);
for (const std::string &ext : extensions) {
ret += "*.";
ret += ext;
ret += ", ";

View File

@ -94,7 +94,7 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
}
}
void MeshClipper::render_cut(const ColorRGBA& color)
void MeshClipper::render_cut(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs)
{
if (! m_result)
recalculate_triangles();
@ -108,7 +108,10 @@ void MeshClipper::render_cut(const ColorRGBA& color)
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
for (CutIsland& isl : m_result->cut_islands) {
for (size_t i=0; i<m_result->cut_islands.size(); ++i) {
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i))
continue;
CutIsland& isl = m_result->cut_islands[i];
isl.model.set_color(isl.disabled ? ColorRGBA(0.5f, 0.5f, 0.5f, 1.f) : color);
isl.model.render();
}
@ -120,7 +123,7 @@ void MeshClipper::render_cut(const ColorRGBA& color)
}
void MeshClipper::render_contour(const ColorRGBA& color)
void MeshClipper::render_contour(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs)
{
if (! m_result)
recalculate_triangles();
@ -135,7 +138,10 @@ void MeshClipper::render_contour(const ColorRGBA& color)
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
for (CutIsland& isl : m_result->cut_islands) {
for (size_t i=0; i<m_result->cut_islands.size(); ++i) {
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i))
continue;
CutIsland& isl = m_result->cut_islands[i];
isl.model_expanded.set_color(isl.disabled ? ColorRGBA(1.f, 0.f, 0.f, 1.f) : color);
isl.model_expanded.render();
}
@ -146,18 +152,19 @@ void MeshClipper::render_contour(const ColorRGBA& color)
curr_shader->start_using();
}
bool MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const
int MeshClipper::is_projection_inside_cut(const Vec3d& point_in) const
{
if (!m_result || m_result->cut_islands.empty())
return false;
return -1;
Vec3d point = m_result->trafo.inverse() * point_in;
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
for (const CutIsland& isl : m_result->cut_islands) {
for (int i=0; i<int(m_result->cut_islands.size()); ++i) {
const CutIsland& isl = m_result->cut_islands[i];
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
return !isl.disabled;
return i; // TODO: handle intersecting contours
}
return false;
return -1;
}
bool MeshClipper::has_valid_contour() const
@ -165,19 +172,47 @@ bool MeshClipper::has_valid_contour() const
return m_result && std::any_of(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& isl) { return !isl.expoly.empty(); });
}
void MeshClipper::pass_mouse_click(const Vec3d& point_in)
std::vector<Vec3d> MeshClipper::point_per_contour() const
{
if (! m_result || m_result->cut_islands.empty())
return;
Vec3d point = m_result->trafo.inverse() * point_in;
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
assert(m_result);
std::vector<Vec3d> out;
for (CutIsland& isl : m_result->cut_islands) {
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
isl.disabled = ! isl.disabled;
for (const CutIsland& isl : m_result->cut_islands) {
assert(isl.expoly.contour.size() > 2);
// Now return a point lying inside the contour but not in a hole.
// We do this by taking a point lying close to the edge, repeating
// this several times for different edges and distances from them.
// (We prefer point not extremely close to the border.
bool done = false;
Vec2d p;
size_t i = 1;
while (i < isl.expoly.contour.size()) {
const Vec2d& a = unscale(isl.expoly.contour.points[i-1]);
const Vec2d& b = unscale(isl.expoly.contour.points[i]);
Vec2d n = (b-a).normalized();
std::swap(n.x(), n.y());
n.x() = -1 * n.x();
double f = 10.;
while (f > 0.05) {
p = (0.5*(b+a)) + f * n;
if (isl.expoly.contains(Point::new_scale(p))) {
done = true;
break;
}
f = f/10.;
}
if (done)
break;
i += std::max(size_t(2), isl.expoly.contour.size() / 5);
}
// If the above failed, just return the centroid, regardless of whether
// it is inside the contour or in a hole (we must return something).
Vec2d c = done ? p : unscale(isl.expoly.contour.centroid());
out.emplace_back(m_result->trafo * Vec3d(c.x(), c.y(), 0.));
}
return out;
}
void MeshClipper::recalculate_triangles()
{
@ -357,8 +392,18 @@ void MeshClipper::recalculate_triangles()
}
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(exp);
isl.expoly_bb = get_extents(isl.expoly);
Point centroid_scaled = isl.expoly.contour.centroid();
Vec3d centroid_world = m_result->trafo * Vec3d(unscale(centroid_scaled).x(), unscale(centroid_scaled).y(), 0.);
isl.hash = isl.expoly.contour.size() + size_t(std::abs(100.*centroid_world.x())) + size_t(std::abs(100.*centroid_world.y())) + size_t(std::abs(100.*centroid_world.z()));
}
// Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes
// flips the normal of the cut, in which case the contours stay the same but their order may change.
std::sort(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland& a, const CutIsland& b) {
return a.hash < b.hash;
});
}

View File

@ -115,13 +115,14 @@ public:
// Render the triangulated cut. Transformation matrices should
// be set in world coords.
void render_cut(const ColorRGBA& color);
void render_contour(const ColorRGBA& color);
void render_cut(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs = nullptr);
void render_contour(const ColorRGBA& color, const std::vector<size_t>* ignore_idxs = nullptr);
void pass_mouse_click(const Vec3d& pt);
bool is_projection_inside_cut(const Vec3d& point) const;
// Returns index of the contour which was clicked, -1 otherwise.
int is_projection_inside_cut(const Vec3d& point) const;
bool has_valid_contour() const;
int get_number_of_contours() const { return m_result ? m_result->cut_islands.size() : 0; }
std::vector<Vec3d> point_per_contour() const;
private:
void recalculate_triangles();
@ -140,6 +141,7 @@ private:
ExPolygon expoly;
BoundingBox expoly_bb;
bool disabled = false;
size_t hash;
};
struct ClipResult {
std::vector<CutIsland> cut_islands;

View File

@ -1068,12 +1068,6 @@ void Sidebar::update_presets(Preset::Type preset_type)
dynamic_cast<ConfigOptionFloats*>(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
const size_t filament_cnt = p->combos_filament.size() > extruder_cnt ? extruder_cnt : p->combos_filament.size();
if (filament_cnt == 1) {
// Single filament printer, synchronize the filament presets.
const std::string &name = preset_bundle.filaments.get_selected_preset_name();
preset_bundle.set_filament_preset(0, name);
}
for (size_t i = 0; i < filament_cnt; i++)
p->combos_filament[i]->update();
@ -1417,27 +1411,25 @@ void Sidebar::update_sliced_info_sizer()
new_label = _L("Used Filament (g)");
info_text = wxString::Format("%.2f", ps.total_weight);
const std::vector<std::string>& filament_presets = wxGetApp().preset_bundle->filament_presets;
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
if (ps.filament_stats.size() > 1)
new_label += ":";
for (auto filament : ps.filament_stats) {
const Preset* filament_preset = filaments.find_preset(filament_presets[filament.first], false);
if (filament_preset) {
const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments;
for (const auto& [filament_id, filament_vol] : ps.filament_stats) {
assert(filament_id < extruders_filaments.size());
if (const Preset* preset = extruders_filaments[filament_id].get_selected_preset()) {
double filament_weight;
if (ps.filament_stats.size() == 1)
filament_weight = ps.total_weight;
else {
double filament_density = filament_preset->config.opt_float("filament_density", 0);
filament_weight = filament.second * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter;
double filament_density = preset->config.opt_float("filament_density", 0);
filament_weight = filament_vol * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter;
new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1);
new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament_id + 1);
info_text += wxString::Format("\n%.2f", filament_weight);
}
double spool_weight = filament_preset->config.opt_float("filament_spool_weight", 0);
double spool_weight = preset->config.opt_float("filament_spool_weight", 0);
if (spool_weight != 0.0) {
new_label += "\n " + _L("(including spool)");
info_text += wxString::Format(" (%.2f)\n", filament_weight + spool_weight);
@ -2405,8 +2397,8 @@ void Plater::check_selected_presets_visibility(PrinterTechnology loaded_printer_
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (loaded_printer_technology == ptFFF) {
update_selected_preset_visibility(preset_bundle->prints, names);
for (const std::string& filament : preset_bundle->filament_presets) {
Preset* preset = preset_bundle->filaments.find_preset(filament);
for (const auto& extruder_filaments : preset_bundle->extruders_filaments) {
Preset* preset = preset_bundle->filaments.find_preset(extruder_filaments.get_selected_preset_name());
if (preset && !preset->is_visible) {
preset->is_visible = true;
names.emplace_back(preset->name);
@ -4018,20 +4010,26 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
//! combo->GetStringSelection().ToUTF8().data());
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) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
}
Preset::remove_suffix_modified(into_u8(combo->GetString(selection))), idx);
std::string last_selected_ph_printer_name = combo->get_selected_ph_printer_name();
bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
// TODO: ?
if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
// Only update the plater UI for the 2nd and other filaments.
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
TabFilament* tab = dynamic_cast<TabFilament*>(wxGetApp().get_tab(Preset::TYPE_FILAMENT));
if (tab && combo->get_extruder_idx() == tab->get_active_extruder() && !tab->select_preset(preset_name)) {
// revert previously selection
const std::string& old_name = wxGetApp().preset_bundle->filaments.get_edited_preset().name;
wxGetApp().preset_bundle->set_filament_preset(idx, old_name);
combo->update();
}
else
// Synchronize config.ini with the current selections.
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
}
else if (select_preset) {
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
wxGetApp().get_tab(preset_type)->select_preset(preset_name, false, last_selected_ph_printer_name);
@ -4080,8 +4078,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
// If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
if (view3D->is_dragging())
delayed_scene_refresh = true;
else
else {
view3D->get_canvas3d()->enable_sla_view_type_detection();
this->update_sla_scene();
}
break;
default: break;
}
@ -6264,7 +6264,11 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat
wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
cut(obj_idx, new_objects);
}
void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects)
{
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);
@ -6282,6 +6286,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_mat
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
selection.add_object((unsigned int)(last_id - i), i == 0);
arrange();
}
void Plater::export_gcode(bool prefer_removable)
@ -6884,6 +6890,8 @@ void Plater::on_extruders_change(size_t num_extruders)
if (num_extruders == choices.size())
return;
dynamic_cast<TabFilament*>(wxGetApp().get_tab(Preset::TYPE_FILAMENT))->update_extruder_combobox();
wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/);
size_t i = choices.size();
@ -6910,16 +6918,16 @@ bool Plater::update_filament_colors_in_full_config()
// There is a case, when we use filament_color instead of extruder_color (when extruder_color == "").
// Thus plater config option "filament_colour" should be filled with filament_presets values.
// Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "".
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
if (filament_presets.size() == 1 || !p->config->has("filament_colour"))
const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments;
if (extruders_filaments.size() == 1 || !p->config->has("filament_colour"))
return false;
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
filament_colors.reserve(filament_presets.size());
filament_colors.reserve(extruders_filaments.size());
for (const std::string& filament_preset : filament_presets)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
for (const auto& extr_filaments : extruders_filaments)
filament_colors.push_back(filaments.find_preset(extr_filaments.get_selected_preset_name(), true)->config.opt_string("filament_colour", (unsigned)0));
p->config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
return true;
@ -6944,10 +6952,12 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") {
this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key));
const PrinterTechnology printer_technology = config.opt_enum<PrinterTechnology>(opt_key);
this->set_printer_technology(printer_technology);
p->sidebar->show_sliced_info_sizer(false);
p->reset_gcode_toolpaths();
p->view3D->get_canvas3d()->reset_sequential_print_clearance();
p->view3D->get_canvas3d()->set_sla_view_type(GLCanvas3D::ESLAViewType::Original);
}
else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
bed_shape_changed = true;
@ -7012,16 +7022,17 @@ void Plater::force_filament_colors_update()
{
bool update_scheduled = false;
DynamicPrintConfig* config = p->config;
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
if (filament_presets.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
const auto& extruders_filaments = wxGetApp().preset_bundle->extruders_filaments;
if (extruders_filaments.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == extruders_filaments.size())
{
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
filament_colors.reserve(filament_presets.size());
filament_colors.reserve(extruders_filaments.size());
for (const std::string& filament_preset : filament_presets)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
for (const auto& extr_filaments : extruders_filaments)
filament_colors.push_back(extr_filaments.get_selected_preset()->config.opt_string("filament_colour", (unsigned)0));
if (config->option<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
@ -7038,6 +7049,20 @@ void Plater::force_filament_colors_update()
this->p->schedule_background_process();
}
void Plater::force_filament_cb_update()
{
// Update visibility for templates presets according to app_config
PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
AppConfig& config = *wxGetApp().app_config;
for (Preset& preset : filaments)
preset.set_visible_from_appconfig(config);
wxGetApp().preset_bundle->update_compatible(PresetSelectCompatibleType::Never, PresetSelectCompatibleType::OnlyIfWasCompatible);
// Update preset comboboxes on sidebar and filaments tab
p->sidebar->update_presets(Preset::TYPE_FILAMENT);
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filaments.get_selected_preset_name());
}
void Plater::force_print_bed_update()
{
// Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed

View File

@ -260,6 +260,7 @@ public:
void toggle_layers_editing(bool enable);
void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
void export_gcode(bool prefer_removable);
void export_stl_obj(bool extended = false, bool selection_only = false);
@ -308,6 +309,7 @@ public:
bool update_filament_colors_in_full_config();
void on_config_change(const DynamicPrintConfig &config);
void force_filament_colors_update();
void force_filament_cb_update();
void force_print_bed_update();
// On activating the parent window.
void on_activate();

View File

@ -694,8 +694,6 @@ void PreferencesDialog::accept(wxEvent&)
#endif // __linux__
}
bool update_filament_sidebar = (m_values.find("no_templates") != m_values.end());
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled", "font_size" };
for (const std::string& option : options_to_recreate_GUI) {
@ -762,11 +760,11 @@ void PreferencesDialog::accept(wxEvent&)
#endif //_MSW_DARK_MODE
#endif // _WIN32
if (m_values.find("no_templates") != m_values.end())
wxGetApp().plater()->force_filament_cb_update();
wxGetApp().update_ui_from_settings();
clear_cache();
if (update_filament_sidebar)
wxGetApp().plater()->sidebar().update_presets(Preset::Type::TYPE_FILAMENT);
}
void PreferencesDialog::revert(wxEvent&)

View File

@ -585,10 +585,10 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
if (m_type == Preset::TYPE_FILAMENT)
{
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
const Filament* selected_filament = m_preset_bundle->extruders_filaments[m_extruder_idx].get_selected_filament();
// Wide icons are shown if the currently selected preset is not compatible with the current printer,
// and red flag is drown in front of the selected preset.
bool wide_icons = selected_preset && !selected_preset->is_compatible;
const bool wide_icons = selected_filament && !selected_filament->is_compatible;
float scale = m_em_unit*0.1f;
int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0;
@ -686,22 +686,16 @@ void PlaterPresetComboBox::switch_to_tab()
if (int page_id = wxGetApp().tab_panel()->FindPage(tab); page_id != wxNOT_FOUND)
{
//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 &&
!dynamic_cast<TabFilament*>(wxGetApp().get_tab(m_type))->set_active_extruder(m_extruder_idx))
// do nothing, if we can't set new extruder and select new preset
return;
wxGetApp().tab_panel()->SetSelection(page_id);
// Switch to Settings NotePad
wxGetApp().mainframe->select_tab();
//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)
{
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
// Call select_preset() only if there is new preset and not just modified
if (!boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()))
{
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
wxGetApp().get_tab(m_type)->select_preset(preset_name);
}
}
}
}
@ -808,7 +802,7 @@ void PlaterPresetComboBox::update()
{
if (m_type == Preset::TYPE_FILAMENT &&
(m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA ||
m_preset_bundle->filament_presets.size() <= (size_t)m_extruder_idx) )
m_preset_bundle->extruders_filaments.size() <= (size_t)m_extruder_idx) )
return;
// Otherwise fill in the list from scratch.
@ -816,6 +810,8 @@ void PlaterPresetComboBox::update()
this->Clear();
invalidate_selection();
const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_extruder_idx >= 0 ? m_extruder_idx : 0];
const Preset* selected_filament_preset = nullptr;
std::string extruder_color;
if (m_type == Preset::TYPE_FILAMENT) {
@ -823,21 +819,23 @@ void PlaterPresetComboBox::update()
if (!can_decode_color(extruder_color))
// Extruder color is not defined.
extruder_color.clear();
selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]);
selected_filament_preset = extruder_filaments.get_selected_preset();
assert(selected_filament_preset);
}
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;
// 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;
bool wide_icons = m_type == Preset::TYPE_FILAMENT ?
extruder_filaments.get_selected_filament() && !extruder_filaments.get_selected_filament()->is_compatible :
m_collection->get_selected_idx() != size_t(-1) && !m_collection->get_selected_preset().is_compatible;
null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width;
std::map<wxString, wxBitmapBundle*> nonsys_presets;
std::map<wxString, wxBitmapBundle*> template_presets;
const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates");
wxString selected_user_preset;
wxString tooltip;
const std::deque<Preset>& presets = m_collection->get_presets();
@ -848,13 +846,15 @@ 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 ?
m_preset_bundle->filament_presets[m_extruder_idx] == preset.name :
const bool is_selected = m_type == Preset::TYPE_FILAMENT ?
selected_filament_preset->name == preset.name :
// The case, when some physical printer is selected
m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false :
i == m_collection->get_selected_idx();
if (!preset.is_visible || (!preset.is_compatible && !is_selected))
const bool is_compatible = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.filament(i).is_compatible : preset.is_compatible;
if (!preset.is_visible || (!is_compatible && !is_selected))
continue;
std::string bitmap_key, filament_rgb, extruder_rgb, material_rgb;
@ -878,18 +878,20 @@ void PlaterPresetComboBox::update()
}
auto bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name,
preset.is_compatible, preset.is_system || preset.is_default,
is_compatible, preset.is_system || preset.is_default,
single_bar, filament_rgb, extruder_rgb, material_rgb);
assert(bmp);
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
if (preset.is_default || preset.is_system) {
if (preset.vendor && preset.vendor->templates_profile) {
if (allow_templates) {
template_presets.emplace(get_preset_name(preset), bmp);
if (is_selected) {
selected_user_preset = get_preset_name(preset);
tooltip = from_u8(preset.name);
}
}
} else {
Append(get_preset_name(preset), *bmp);
validate_selection(is_selected);
@ -919,8 +921,7 @@ void PlaterPresetComboBox::update()
}
}
const AppConfig* app_config = wxGetApp().app_config;
if (!template_presets.empty() && app_config->get("no_templates") == "0") {
if (!template_presets.empty()) {
set_label_marker(Append(separator(L("Template presets")), wxNullBitmap));
for (std::map<wxString, wxBitmapBundle*>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) {
Append(it->first, *it->second);
@ -1063,15 +1064,19 @@ void TabPresetComboBox::update()
Clear();
invalidate_selection();
const ExtruderFilaments& extruder_filaments = m_preset_bundle->extruders_filaments[m_active_extruder_idx];
const std::deque<Preset>& presets = m_collection->get_presets();
std::map<wxString, std::pair<wxBitmapBundle*, bool>> nonsys_presets;
std::map<wxString, std::pair<wxBitmapBundle*, bool>> template_presets;
const bool allow_templates = !wxGetApp().app_config->get_bool("no_templates");
wxString selected = "";
if (!presets.front().is_visible)
set_label_marker(Append(separator(L("System presets")), NullBitmapBndl()));
size_t idx_selected = m_collection->get_selected_idx();
size_t idx_selected = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.get_selected_idx() : m_collection->get_selected_idx();
if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) {
std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name();
@ -1083,7 +1088,10 @@ void TabPresetComboBox::update()
for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i)
{
const Preset& preset = presets[i];
if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected))
const bool is_compatible = m_type == Preset::TYPE_FILAMENT ? extruder_filaments.filament(i).is_compatible : preset.is_compatible;
if (!preset.is_visible || (!show_incompatible && !is_compatible && i != idx_selected))
continue;
// marker used for disable incompatible printer models for the selected physical printer
@ -1097,14 +1105,16 @@ void TabPresetComboBox::update()
}
std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default);
auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, is_compatible, preset.is_system || preset.is_default);
assert(bmp);
if (preset.is_default || preset.is_system) {
if (preset.vendor && preset.vendor->templates_profile) {
if (allow_templates) {
template_presets.emplace(get_preset_name(preset), std::pair<wxBitmapBundle*, bool>(bmp, is_enabled));
if (i == idx_selected)
selected = get_preset_name(preset);
}
} else {
int item_id = Append(get_preset_name(preset), *bmp);
if (!is_enabled)
@ -1137,8 +1147,7 @@ void TabPresetComboBox::update()
}
}
const AppConfig* app_config = wxGetApp().app_config;
if (!template_presets.empty() && app_config->get("no_templates") == "0") {
if (!template_presets.empty()) {
set_label_marker(Append(separator(L("Template presets")), wxNullBitmap));
for (std::map<wxString, std::pair<wxBitmapBundle*, bool>>::iterator it = template_presets.begin(); it != template_presets.end(); ++it) {
int item_id = Append(it->first, *it->second.first);

View File

@ -179,6 +179,8 @@ class TabPresetComboBox : public PresetComboBox
{
bool show_incompatible {false};
bool m_enable_all {false};
// This parameter is used by FilamentSettings tab to show filament setting related to the active extruder
int m_active_extruder_idx {0};
public:
TabPresetComboBox(wxWindow *parent, Preset::Type preset_type);
@ -197,6 +199,9 @@ public:
PresetCollection* presets() const { return m_collection; }
Preset::Type type() const { return m_type; }
// used by Filaments tab to update preset list according to the particular extruder
void set_active_extruder(int extruder_idx) { m_active_extruder_idx = extruder_idx; }
};
} // namespace GUI

View File

@ -75,9 +75,9 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle
{
// Find out, to which nozzle index is the current filament profile assigned.
int idx_extruder = 0;
int num_extruders = (int)preset_bundle.filament_presets.size();
int num_extruders = (int)preset_bundle.extruders_filaments.size();
for (; idx_extruder < num_extruders; ++ idx_extruder)
if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset_name())
if (preset_bundle.extruders_filaments[idx_extruder].get_selected_preset_name() == preset_bundle.filaments.get_selected_preset_name())
break;
if (idx_extruder == num_extruders)
// The current filament preset is not active for any extruder.

View File

@ -858,6 +858,43 @@ std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_s
return { out_box, out_trafo.get_matrix_no_scaling_factor() };
}
BoundingBoxf Selection::get_screen_space_bounding_box()
{
BoundingBoxf ss_box;
if (!is_empty()) {
const auto& [box, box_trafo] = get_bounding_box_in_current_reference_system();
// vertices
std::vector<Vec3d> vertices = {
{ box.min.x(), box.min.y(), box.min.z() },
{ box.max.x(), box.min.y(), box.min.z() },
{ box.max.x(), box.max.y(), box.min.z() },
{ box.min.x(), box.max.y(), box.min.z() },
{ box.min.x(), box.min.y(), box.max.z() },
{ box.max.x(), box.min.y(), box.max.z() },
{ box.max.x(), box.max.y(), box.max.z() },
{ box.min.x(), box.max.y(), box.max.z() }
};
const Camera& camera = wxGetApp().plater()->get_camera();
const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix();
const std::array<int, 4>& viewport = camera.get_viewport();
const double half_w = 0.5 * double(viewport[2]);
const double h = double(viewport[3]);
const double half_h = 0.5 * h;
for (const Vec3d& v : vertices) {
const Vec3d world = box_trafo * v;
const Vec4d clip = projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0);
const Vec3d ndc = Vec3d(clip.x(), clip.y(), clip.z()) / clip.w();
const Vec2d ss = Vec2d(half_w * ndc.x() + double(viewport[0]) + half_w, h - (half_h * ndc.y() + double(viewport[1]) + half_h));
ss_box.merge(ss);
}
}
return ss_box;
}
void Selection::setup_cache()
{
if (!m_valid)
@ -1717,8 +1754,7 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_volume(unsigned int ob
{
std::vector<unsigned int> idxs;
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
const GLVolume* v = (*m_volumes)[i];
if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) {
if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx)

View File

@ -319,6 +319,9 @@ public:
// and the transform to place and orient it in world coordinates
std::pair<BoundingBoxf3, Transform3d> get_bounding_box_in_reference_system(ECoordinatesType type) const;
// Returns the screen space bounding box
BoundingBoxf get_screen_space_bounding_box();
void setup_cache();
void translate(const Vec3d& displacement, TransformationType transformation_type);

View File

@ -1236,6 +1236,7 @@ void Tab::on_presets_changed()
m_dependent_tabs.clear();
// Update Project dirty state, update application title bar.
if (wxGetApp().mainframe)
wxGetApp().plater()->update_project_dirty_from_presets();
}
@ -1933,8 +1934,64 @@ void TabFilament::update_filament_overrides_page()
}
}
void TabFilament::create_extruder_combobox()
{
m_extruders_cb = new BitmapComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(12 * m_em_unit, -1), 0, nullptr, wxCB_READONLY);
m_extruders_cb->Hide();
m_extruders_cb->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) {
set_active_extruder(m_extruders_cb->GetSelection());
});
m_h_buttons_sizer->AddSpacer(3*em_unit(this));
m_h_buttons_sizer->Add(m_extruders_cb, 0, wxALIGN_CENTER_VERTICAL);
}
void TabFilament::update_extruder_combobox()
{
const size_t extruder_cnt = static_cast<const ConfigOptionFloats*>(m_preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"))->values.size();
m_extruders_cb->Show(extruder_cnt > 1);
if (extruder_cnt != m_extruders_cb->GetCount()) {
m_extruders_cb->Clear();
for (size_t id = 1; id <= extruder_cnt; id++)
m_extruders_cb->Append(format_wxstr("%1% %2%", _L("Extruder"), id), *get_bmp_bundle("funnel"));
}
if (m_active_extruder >= int(extruder_cnt))
m_active_extruder = 0;
m_extruders_cb->SetSelection(m_active_extruder);
}
bool TabFilament::set_active_extruder(int new_selected_extruder)
{
if (m_active_extruder == new_selected_extruder)
return true;
const int old_extruder_id = m_active_extruder;
m_active_extruder = new_selected_extruder;
m_presets_choice->set_active_extruder(m_active_extruder);
if (!select_preset(m_preset_bundle->extruders_filaments[m_active_extruder].get_selected_preset_name())) {
m_active_extruder = old_extruder_id;
m_presets_choice->set_active_extruder(m_active_extruder);
m_extruders_cb->SetSelection(m_active_extruder);
return false;
}
if (m_active_extruder != m_extruders_cb->GetSelection())
m_extruders_cb->Select(m_active_extruder);
return true;
}
void TabFilament::build()
{
// add extruder combobox
create_extruder_combobox();
m_presets = &m_preset_bundle->filaments;
load_initial_data();
@ -2189,7 +2246,7 @@ void TabFilament::update()
m_update_cnt--;
if (m_update_cnt == 0)
if (m_update_cnt == 0 && wxGetApp().mainframe)
wxGetApp().mainframe->on_config_changed(m_config);
}
@ -2210,6 +2267,44 @@ void TabFilament::msw_rescale()
Tab::msw_rescale();
}
void TabFilament::load_current_preset()
{
assert(m_active_extruder >= 0 && m_active_extruder < m_preset_bundle->extruders_filaments.size());
const std::string& selected_extr_filament_name = m_preset_bundle->extruders_filaments[m_active_extruder].get_selected_preset_name();
const std::string& selected_filament_name = m_presets->get_selected_preset_name();
if (selected_extr_filament_name != selected_filament_name)
m_presets->select_preset_by_name(selected_extr_filament_name, false);
Tab::load_current_preset();
}
bool TabFilament::select_preset_by_name(const std::string &name_w_suffix, bool force)
{
const bool is_selected_filament = Tab::select_preset_by_name(name_w_suffix, force);
const bool is_selected_extr_filament = m_preset_bundle->extruders_filaments[m_active_extruder].select_filament(name_w_suffix, force);
return is_selected_filament && is_selected_extr_filament;
}
bool TabFilament::save_current_preset(const std::string &new_name, bool detach)
{
m_preset_bundle->cache_extruder_filaments_names();
const bool is_saved = Tab::save_current_preset(new_name, detach);
if (is_saved) {
m_preset_bundle->reset_extruder_filaments();
m_preset_bundle->extruders_filaments[m_active_extruder].select_filament(m_presets->get_idx_selected());
}
return is_saved;
}
bool TabFilament::delete_current_preset()
{
m_preset_bundle->cache_extruder_filaments_names();
const bool is_deleted = Tab::delete_current_preset();
if (is_deleted)
m_preset_bundle->reset_extruder_filaments();
return is_deleted;
}
wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText, wxString text /*= wxEmptyString*/)
{
*StaticText = new ogStaticText(parent, text);
@ -2339,7 +2434,6 @@ void TabPrinter::build_fff()
const wxString msg_text = _(L("Single Extruder Multi Material is selected, \n"
"and all extruders must have the same diameter.\n"
"Do you want to change the diameter for all extruders to first extruder nozzle diameter value?"));
//wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO);
MessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO);
DynamicPrintConfig new_conf = *m_config;
@ -2357,6 +2451,14 @@ void TabPrinter::build_fff()
}
}
}
m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
// Upadte related comboboxes on Sidebar and Tabs
Sidebar& sidebar = wxGetApp().plater()->sidebar();
for (const Preset::Type& type : {Preset::TYPE_PRINT, Preset::TYPE_FILAMENT}) {
sidebar.update_presets(type);
wxGetApp().get_tab(type)->update_tab_ui();
}
}
}
else {
@ -2625,15 +2727,21 @@ void TabPrinter::build_sla()
void TabPrinter::extruders_count_changed(size_t extruders_count)
{
bool is_count_changed = false;
bool is_updated_mm_filament_presets = false;
if (m_extruders_count != extruders_count) {
m_extruders_count = extruders_count;
m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count);
m_preset_bundle->update_multi_material_filament_presets();
is_count_changed = true;
is_count_changed = is_updated_mm_filament_presets = true;
}
else if (m_extruders_count == 1 &&
m_preset_bundle->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size()>1)
m_preset_bundle->project_config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size()>1) {
is_updated_mm_filament_presets = true;
}
if (is_updated_mm_filament_presets) {
m_preset_bundle->update_multi_material_filament_presets();
m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::OnlyIfWasCompatible);
}
/* This function should be call in any case because of correct updating/rebuilding
* of unregular pages of a Printer Settings
@ -2768,7 +2876,10 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key&opt_key, boost::any value)
{
if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos)
const bool is_single_extruder_MM = m_config->opt_bool("single_extruder_multi_material");
const bool is_nozzle_diameter_changed = opt_key.find_first_of("nozzle_diameter") != std::string::npos;
if (is_single_extruder_MM && m_extruders_count > 1 && is_nozzle_diameter_changed)
{
SuppressBackgroundProcessingUpdate sbpu;
const double new_nd = boost::any_cast<double>(value);
@ -2798,6 +2909,15 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders)
}
}
if (is_nozzle_diameter_changed) {
if (extruder_idx == 0)
// Mark the print & filament enabled if they are compatible with the currently selected 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.
m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
else
m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::Never, extruder_idx);
}
update_dirty();
update();
};
@ -3040,6 +3160,7 @@ void TabPrinter::update_pages()
if (m_extruders_count > 1)
{
m_preset_bundle->update_multi_material_filament_presets();
m_preset_bundle->update_filaments_compatible(PresetSelectCompatibleType::OnlyIfWasCompatible);
on_value_change("extruders_count", m_extruders_count);
}
}
@ -3392,7 +3513,7 @@ void Tab::update_preset_choice()
// Called by the UI combo box when the user switches profiles, and also to delete the current profile.
// Select a preset by a name.If !defined(name), then the default preset is selected.
// If the current profile is modified, user is asked to save the changes.
void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/)
bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/)
{
if (preset_name.empty()) {
if (delete_current) {
@ -3492,7 +3613,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
// It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable.
// The 'external' presets will only be removed from the preset list, their files will not be deleted.
try {
m_presets->delete_current_preset();
// cache previously selected names
delete_current_preset();
} catch (const std::exception & /* e */) {
//FIXME add some error reporting!
canceled = true;
@ -3513,7 +3635,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
}
}
update_tab_ui();
// update_tab_ui(); //! ysFIXME delete after testing
// Trigger the on_presets_changed event so that we also restore the previous value in the plater selector,
// if this action was initiated from the plater.
@ -3522,7 +3644,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
if (current_dirty)
m_presets->discard_current_changes();
const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current;
const bool is_selected = select_preset_by_name(preset_name, false) || delete_current;
assert(m_presets->get_edited_preset().name == preset_name || ! is_selected);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
// The following method should not discard changes of current print or filament presets on change of a printer profile,
@ -3565,6 +3687,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
if (technology_changed)
wxGetApp().mainframe->technology_changed();
return !canceled;
}
// If the current preset is dirty, the user is asked whether the changes may be discarded.
@ -3812,16 +3936,12 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
// focus currently.is there anything better than this ?
//! m_treectrl->OnSetFocus();
auto& old_preset = m_presets->get_edited_preset();
Preset& edited_preset = m_presets->get_edited_preset();
bool from_template = false;
std::string edited_printer;
if (m_type == Preset::TYPE_FILAMENT && old_preset.vendor && old_preset.vendor->templates_profile)
{
//TODO: is this really the best way to get "printer_model" option of currently edited printer?
edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt<ConfigOptionString>("printer_model")->serialize();
if (!edited_printer.empty())
from_template = true;
if (m_type == Preset::TYPE_FILAMENT && edited_preset.vendor && edited_preset.vendor->templates_profile) {
edited_printer = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_model");
from_template = !edited_printer.empty();
}
if (name.empty()) {
@ -3836,24 +3956,21 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach)
if (detach && m_type == Preset::TYPE_PRINTER)
m_config->opt_string("printer_model", true) = "";
// Update compatible printers
if (from_template && !edited_printer.empty()) {
std::string cond = edited_preset.compatible_printers_condition();
if (!cond.empty())
cond += " and ";
cond += "printer_model == \"" + edited_printer + "\"";
edited_preset.config.opt_string("compatible_printers_condition") = cond;
}
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
m_presets->save_current_preset(name, detach);
save_current_preset(name, detach);
if (detach && m_type == Preset::TYPE_PRINTER)
wxGetApp().mainframe->on_config_changed(m_config);
// Update compatible printers
if (from_template && !edited_printer.empty()) {
auto& new_preset = m_presets->get_edited_preset();
std::string cond = new_preset.compatible_printers_condition();
if (!cond.empty())
cond += " and ";
cond += "printer_model == \""+edited_printer+"\"";
new_preset.config.set("compatible_printers_condition", cond);
new_preset.save();
m_presets->save_current_preset(name, detach);
load_current_preset();
}
// Mark the print & filament enabled if they are compatible with the currently selected 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.
@ -3980,7 +4097,7 @@ void Tab::rename_preset()
// sort presets after renaming
std::sort(m_presets->begin(), m_presets->end());
// update selection
m_presets->select_preset_by_name(new_name, true);
select_preset_by_name(new_name, true);
m_presets_choice->update();
on_presets_changed();
@ -4675,6 +4792,21 @@ void Tab::set_tooltips_text()
"Click to reset current value to the last saved preset."));
}
bool Tab::select_preset_by_name(const std::string &name_w_suffix, bool force)
{
return m_presets->select_preset_by_name(name_w_suffix, force);
}
bool Tab::save_current_preset(const std::string& new_name, bool detach)
{
return m_presets->save_current_preset(new_name, detach);
}
bool Tab::delete_current_preset()
{
return m_presets->delete_current_preset();
}
Page::Page(wxWindow* parent, const wxString& title, int iconID) :
m_parent(parent),
m_title(title),

View File

@ -307,12 +307,13 @@ public:
long style = wxBU_EXACTFIT | wxNO_BORDER);
void add_scaled_bitmap(wxWindow* parent, ScalableBitmap& btn, const std::string& icon_name);
void update_ui_items_related_on_parent_preset(const Preset* selected_preset_parent);
void load_current_preset();
virtual void load_current_preset();
void rebuild_page_tree();
void update_btns_enabling();
void update_preset_choice();
// Select a new preset, possibly delete the current one.
void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = "");
// return false, if action was canceled
bool select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = "");
bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
virtual void clear_pages();
@ -402,6 +403,10 @@ protected:
void fill_icon_descriptions();
void set_tooltips_text();
virtual bool select_preset_by_name(const std::string& name_w_suffix, bool force);
virtual bool save_current_preset(const std::string& new_name, bool detach);
virtual bool delete_current_preset();
ConfigManipulation m_config_manipulation;
ConfigManipulation get_config_manipulation();
};
@ -432,7 +437,8 @@ private:
class TabFilament : public Tab
{
private:
BitmapComboBox* m_extruders_cb {nullptr};
int m_active_extruder {0};
ogStaticText* m_volumetric_speed_description_line {nullptr};
ogStaticText* m_cooling_description_line {nullptr};
@ -440,6 +446,7 @@ private:
void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0, bool is_checked = true);
void add_filament_overrides_page();
void update_filament_overrides_page();
void create_extruder_combobox();
void update_volumetric_flow_preset_hints();
std::map<std::string, wxCheckBox*> m_overrides_options;
@ -455,6 +462,18 @@ public:
void clear_pages() override;
void msw_rescale() override;
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
void load_current_preset() override;
// set actiev extruder and update preset combobox if needed
// return false, if new preset wasn't selected
bool set_active_extruder(int new_selected_extruder);
void update_extruder_combobox();
int get_active_extruder() const { return m_active_extruder; }
protected:
bool select_preset_by_name(const std::string& name_w_suffix, bool force) override;
bool save_current_preset(const std::string& new_name, bool detach) override;
bool delete_current_preset() override;
};
class TabPrinter : public Tab

View File

@ -961,7 +961,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
BOOST_LOG_TRIVIAL(error) << format("Cannot load the installed index at `%1%`: %2%", bundle_path_idx, err.what());
}
}
#if 0
// Check if the update is already present in a snapshot
if(!current_not_supported)
{
@ -974,7 +974,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
continue;
}
}
#endif // 0
updates.updates.emplace_back(std::move(new_update));
// 'Install' the index in the vendor directory. This is used to memoize
// offered updates and to not offer the same update again if it was cancelled by the user.
@ -1320,7 +1320,35 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector<std::string
bool is_in_rsrc = fs::exists(path_in_rsrc);
bool is_in_cache_vendor = fs::exists(path_in_cache_vendor) && !fs::is_empty(path_in_cache_vendor);
// find if in cache vendor is newer version than in resources
// Find if in cache vendor is newer version than in resources.
// But we also need to mind too new versions - have to read index.
// Fresh index should be in archive_dir, otherwise look for it in cache
fs::path idx_path (path_in_cache_vendor);
idx_path.replace_extension(".idx");
if (!boost::filesystem::exists(idx_path)) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Couldn't locate idx file %1% when performing updates.", idx_path.string());
idx_path = fs::path(p->cache_path / idx_path.filename());
}
if (!boost::filesystem::exists(idx_path)) {
std::string msg = GUI::format(_L("Couldn't locate index file for vendor %1% when performing updates. The profile will not be installed."), bundle);
BOOST_LOG_TRIVIAL(error) << msg;
GUI::show_error(nullptr, msg);
continue;
}
Slic3r::GUI::Config::Index index;
try {
index.load(idx_path);
}
catch (const std::exception& /* err */) {
std::string msg = GUI::format(_L("Couldn't load index file for vendor %1% when performing updates. The profile will not be installed. Reason: Corrupted index file %2%."), bundle, idx_path.string());
BOOST_LOG_TRIVIAL(error) << msg;
GUI::show_error(nullptr, msg);
continue;
}
const auto recommended_it = index.recommended();
const auto recommended = recommended_it->config_version;
if (is_in_cache_vendor) {
Semver version_cache = Semver::zero();
try {
@ -1329,12 +1357,10 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector<std::string
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_cache_vendor, e.what());
// lets use file in resources
if (is_in_rsrc) {
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
continue;
version_cache = Semver::zero();
}
if (version_cache > recommended)
version_cache = Semver::zero();
Semver version_rsrc = Semver::zero();
try {
@ -1345,25 +1371,32 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector<std::string
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1%, message: %2%", path_in_rsrc, e.what());
continue;
//continue;
version_rsrc = Semver::zero();
}
// Should not happen!
if (version_rsrc > recommended)
version_rsrc = Semver::zero();
if (!is_in_rsrc || version_cache > version_rsrc) {
// in case we are installing from cache / vendor. we should also copy index to cache
// This needs to be done now bcs the current one would be missing this version on next start
// dk: Should we copy it to vendor dir too?
auto path_idx_cache_vendor(path_in_cache_vendor);
path_idx_cache_vendor.replace_extension(".idx");
auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx");
// DK: do this during perform_updates() too?
if (fs::exists(path_idx_cache_vendor))
copy_file_fix(path_idx_cache_vendor, path_idx_cache);
else // Should we dialog this?
BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string());
if (version_cache == Semver::zero() && version_rsrc == Semver::zero()) {
std::string msg = GUI::format(_L("Couldn't open profile file for vendor %1% when performing updates. The profile will not be installed. This installation might be corrupted."), bundle);
BOOST_LOG_TRIVIAL(error) << msg;
GUI::show_error(nullptr, msg);
continue;
} else if (version_cache == Semver::zero()) {
// cache vendor cannot be used, use resources
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
} else if (version_rsrc == Semver::zero()) {
// resources cannto be used, use cache vendor
updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", "");
} else if (version_cache > version_rsrc) {
// in case we are installing from cache / vendor. we should also copy index to cache
// This needs to be done now bcs the current one would be missing this version on the next start
auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx");
if (idx_path != path_idx_cache)
copy_file_fix(idx_path, path_idx_cache);
updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", "");
} else {
if (is_in_rsrc)
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
} else {

View File

@ -260,7 +260,7 @@ SCENARIO("Cooling integration tests", "[Cooling]") {
});
THEN("slowdown_below_layer_time is honored") {
// Account for some inaccuracies.
const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("slowdown_below_layer_time")->values.front() - 0.2;
const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("slowdown_below_layer_time")->values.front() - 0.5;
size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(),
[slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; });
REQUIRE(minimum_time_honored == layer_times.size());

View File

@ -48,9 +48,11 @@ SCENARIO("Shells", "[Shells]") {
REQUIRE(! has_shells(i));
}
THEN("correct number of top solid layers") {
for (int i = 0; i < top_solid_layers; ++ i)
// NOTE: there is one additional layer with enusring line under the bridge layer, bridges would be otherwise anchored weakly to the perimeter.
size_t additional_ensuring_anchors = top_solid_layers > 0 ? 1 : 0;
for (int i = 0; i < top_solid_layers + additional_ensuring_anchors; ++ i)
REQUIRE(has_shells(int(zs.size()) - i - 1));
for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i)
for (int i = top_solid_layers + additional_ensuring_anchors; i < int(zs.size() / 2); ++ i)
REQUIRE(! has_shells(int(zs.size()) - i - 1));
}
if (top_solid_layers > 0) {
@ -144,7 +146,7 @@ SCENARIO("Shells (from Perl)", "[Shells]") {
for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed))
if (z <= 7.2)
++ n;
REQUIRE(n == 3);
REQUIRE(n == 3 + 1/*one additional layer with ensuring for bridge anchors*/);
}
}

View File

@ -3,6 +3,7 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Format/SLAArchiveFormatRegistry.hpp"
#include "libslic3r/Format/SLAArchiveWriter.hpp"
#include "libslic3r/Format/SLAArchiveReader.hpp"
@ -11,16 +12,18 @@
using namespace Slic3r;
TEST_CASE("Archive export test", "[sla_archives]") {
auto registry = registered_sla_archives();
for (const char * pname : {"20mm_cube", "extruder_idler"})
for (auto &archname : SLAArchiveWriter::registered_archives()) {
INFO(std::string("Testing archive type: ") + archname + " -- writing...");
for (const ArchiveEntry &entry : registry) {
INFO(std::string("Testing archive type: ") + entry.id + " -- writing...");
SLAPrint print;
SLAFullPrintConfig fullcfg;
auto m = Model::read_from_file(TEST_DATA_DIR PATH_SEPARATOR + std::string(pname) + ".obj", nullptr);
fullcfg.printer_technology.setInt(ptSLA); // FIXME this should be ensured
fullcfg.set("sla_archive_format", archname);
fullcfg.set("sla_archive_format", entry.id);
fullcfg.set("supports_enable", false);
fullcfg.set("pad_enable", false);
@ -32,7 +35,7 @@ TEST_CASE("Archive export test", "[sla_archives]") {
print.process();
ThumbnailsList thumbnails;
auto outputfname = std::string("output_") + pname + "." + SLAArchiveWriter::get_extension(archname);
auto outputfname = std::string("output_") + pname + "." + entry.ext;
print.export_print(outputfname, thumbnails, pname);
@ -41,12 +44,8 @@ TEST_CASE("Archive export test", "[sla_archives]") {
double vol_written = m.mesh().volume();
auto readable_formats = SLAArchiveReader::registered_archives();
if (std::any_of(readable_formats.begin(), readable_formats.end(),
[&archname](const std::string &a) { return a == archname; })) {
INFO(std::string("Testing archive type: ") + archname + " -- reading back...");
if (entry.rdfactoryfn) {
INFO(std::string("Testing archive type: ") + entry.id + " -- reading back...");
indexed_triangle_set its;
DynamicPrintConfig cfg;

View File

@ -3,7 +3,7 @@
set(SLIC3R_APP_NAME "PrusaSlicer")
set(SLIC3R_APP_KEY "PrusaSlicer")
set(SLIC3R_VERSION "2.6.0-alpha6")
set(SLIC3R_VERSION "2.6.0-beta2")
set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
set(SLIC3R_RC_VERSION "2,6,0,0")
set(SLIC3R_RC_VERSION_DOTS "2.6.0.0")