mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 02:55:55 +08:00
Merge branch 'master' into fs_dir_per_glyph
# Conflicts: # src/slic3r/GUI/GLCanvas3D.cpp
This commit is contained in:
commit
bca6b7811d
72
resources/icons/sla_view_original.svg
Normal file
72
resources/icons/sla_view_original.svg
Normal 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 |
56
resources/icons/sla_view_processed.svg
Normal file
56
resources/icons/sla_view_processed.svg
Normal 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 |
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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*
|
||||
|
@ -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;
|
||||
|
128
src/libslic3r/Algorithm/PathSorting.hpp
Normal file
128
src/libslic3r/Algorithm/PathSorting.hpp
Normal 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_*/
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 ¶ms);
|
||||
static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
static void connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms);
|
||||
|
@ -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 ¶ms)
|
||||
ThickPolylines make_fill_polylines(
|
||||
const Fill *fill, const Surface *surface, const FillParams ¶ms, 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
|
||||
|
@ -6,13 +6,19 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillEnsuring : public FillRectilinear
|
||||
ThickPolylines make_fill_polylines(
|
||||
const Fill *fill, const Surface *surface, const FillParams ¶ms, 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 ¶ms) override { return {}; };
|
||||
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override;
|
||||
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override
|
||||
{
|
||||
return make_fill_polylines(this, surface, params, true, true, true);
|
||||
};
|
||||
|
||||
protected:
|
||||
void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
148
src/libslic3r/Format/SLAArchiveFormatRegistry.cpp
Normal file
148
src/libslic3r/Format/SLAArchiveFormatRegistry.cpp
Normal 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
|
71
src/libslic3r/Format/SLAArchiveFormatRegistry.hpp
Normal file
71
src/libslic3r/Format/SLAArchiveFormatRegistry.hpp
Normal 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
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {};
|
||||
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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 §ion_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)
|
||||
|
@ -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_ */
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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>(®ion)), 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>(®ion)) + "_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);
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
/*!
|
||||
|
@ -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{
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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 += ", ";
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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&)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
|
@ -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*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user