Merge branch 'master' into dev
This commit is contained in:
commit
76ffecd897
2
deps/wxWidgets/wxWidgets.cmake
vendored
2
deps/wxWidgets/wxWidgets.cmake
vendored
@ -13,7 +13,7 @@ prusaslicer_add_cmake_project(wxWidgets
|
||||
# GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets"
|
||||
# GIT_TAG tm_cross_compile #${_wx_git_tag}
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/refs/heads/v3.1.4-patched.zip
|
||||
URL_HASH SHA256=21ed12eb5c215b00999f0374af652be0a6f785df10d18d0dfec8d81ed4abaea3
|
||||
URL_HASH SHA256=ed36a2159c781cce07b06378664e683ebd8cb2f51914aba9acd3bfca3d63d7d3
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG
|
||||
CMAKE_ARGS
|
||||
-DwxBUILD_PRECOMP=ON
|
||||
|
19
resources/icons/fdm_supports_.svg
Normal file
19
resources/icons/fdm_supports_.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="paint_x5F_supports">
|
||||
<path fill="#ED6B21" d="M88,38.93c-0.83,0-1.5,0.67-1.5,1.5V70.5h-5V45.14c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V49.8
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v20.7h-5V53.84c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V49.8
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v20.7h-5V45.14c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V40.43
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V72v10.99c0,3.59,2.92,6.51,6.51,6.51h2.98c0.67,0.01,6.51,0.24,6.51,6.5v16
|
||||
c0,3.29,1.99,9.5,9.5,9.5s9.5-6.21,9.5-9.5V96c0-6.26,5.84-6.49,6.5-6.5h3c3.59,0,6.5-2.92,6.5-6.5V72V40.43
|
||||
C89.5,39.6,88.83,38.93,88,38.93z M86.5,83c0,1.93-1.57,3.5-3.5,3.5h-3c-3.29,0-9.5,1.99-9.5,9.5v15.99
|
||||
c-0.01,0.67-0.24,6.51-6.5,6.51s-6.49-5.84-6.5-6.5V96c0-7.51-6.21-9.5-9.5-9.5h-2.99c-1.94,0-3.51-1.57-3.51-3.51V73.5h45V83z"/>
|
||||
<g>
|
||||
<path fill="#808080" d="M64,48.03c-0.26,0-0.52-0.07-0.75-0.2l-48-27.69c-0.46-0.27-0.75-0.76-0.75-1.3V8c0-0.83,0.67-1.5,1.5-1.5
|
||||
s1.5,0.67,1.5,1.5v9.98L64,44.8l46.5-26.83V8c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v10.84c0,0.54-0.29,1.03-0.75,1.3
|
||||
l-48,27.69C64.52,47.97,64.26,48.03,64,48.03z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
28
resources/icons/mmu_segmentation_.svg
Normal file
28
resources/icons/mmu_segmentation_.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<path fill="#808080" d="M52.87,108.38c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S58.11,108.38,52.87,108.38z
|
||||
M52.87,92.38c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S56.46,92.38,52.87,92.38z M29.82,83.59
|
||||
c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S35.06,83.59,29.82,83.59z M29.82,67.59c-3.58,0-6.5,2.92-6.5,6.5
|
||||
s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S33.4,67.59,29.82,67.59z M34,49.86c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5
|
||||
S39.24,49.86,34,49.86z M34,33.86c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S37.59,33.86,34,33.86z M64,35.21
|
||||
c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S69.24,35.21,64,35.21z M64,19.21c-3.58,0-6.5,2.92-6.5,6.5
|
||||
s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S67.58,19.21,64,19.21z M96.1,52.24c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5
|
||||
S101.34,52.24,96.1,52.24z M96.1,36.24c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S99.69,36.24,96.1,36.24z
|
||||
M72.54,120.87c2.6-0.39,4.78-2.06,5.81-4.46c1.06-2.47,0.77-5.29-0.8-7.52c-3.1-4.43-4.49-9.87-3.92-15.31
|
||||
c0.26-2.47,0.94-4.89,2.03-7.17c0.36-0.75,0.04-1.64-0.71-2c-0.75-0.36-1.64-0.04-2,0.71c-1.23,2.6-2.01,5.34-2.3,8.15
|
||||
c-0.64,6.16,0.94,12.32,4.45,17.34c0.96,1.38,1.15,3.11,0.5,4.62c-0.63,1.47-1.91,2.44-3.5,2.68c-3.29,0.49-6.66,0.68-10.01,0.57
|
||||
c-28.61-0.99-51.7-24.18-52.56-52.79c-0.46-15.2,5.2-29.48,15.94-40.21S50.49,9.07,65.68,9.53c28.62,0.86,51.8,23.94,52.79,52.56
|
||||
c0.11,3.25-0.06,6.51-0.52,9.69c-0.24,1.66-1.31,3.06-2.87,3.73c-1.52,0.66-3.16,0.5-4.49-0.42c-3.29-2.3-7.17-3.8-11.21-4.34
|
||||
c-0.83-0.11-1.58,0.47-1.68,1.29c-0.11,0.82,0.47,1.58,1.29,1.68c3.57,0.47,6.99,1.79,9.89,3.82c2.17,1.52,4.94,1.78,7.4,0.72
|
||||
c2.52-1.09,4.26-3.36,4.65-6.06c0.48-3.36,0.67-6.8,0.55-10.22c-1.04-30.19-25.5-54.55-55.7-55.45
|
||||
c-16.02-0.48-31.1,5.49-42.42,16.81C12.02,34.66,6.05,49.73,6.53,65.77c0.9,30.19,25.26,54.66,55.45,55.7
|
||||
c0.67,0.02,1.34,0.04,2.01,0.04C66.86,121.5,69.73,121.29,72.54,120.87z"/>
|
||||
<path fill="#ED6B21" d="M115.41,105.01l-27.66-38.8c7.76-12.6-22.89-18.09-27.92-23.34c-2.48-2.6-0.44,35.31,15.58,32.26
|
||||
l29.74,37.59c0.54,0.91,3.45,5.54,7.39,6.36c0.39,0.08,0.78,0.12,1.16,0.12c1.26,0,2.48-0.42,3.58-1.25
|
||||
c1.36-1.02,2.14-2.41,2.27-4.01C119.87,109.95,116.14,105.79,115.41,105.01z M78.44,74.13c1.24-0.57,2.54-1.37,3.92-2.42
|
||||
c1.39-1.05,2.53-2.06,3.45-3.04l6.94,9.73l-6.85,5.15L78.44,74.13z M116.56,113.69c-0.06,0.76-0.4,1.35-1.08,1.85
|
||||
c-0.77,0.58-1.51,0.76-2.33,0.6c-2.38-0.49-4.75-3.78-5.46-5.01c-0.04-0.06-0.08-0.12-0.12-0.18L87.76,85.91l6.73-5.06l18.53,25.99
|
||||
c0.04,0.06,0.09,0.12,0.14,0.17C114.11,107.98,116.75,111.3,116.56,113.69z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
35
resources/icons/seam_.svg
Normal file
35
resources/icons/seam_.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="paint_x5F_seams_2_">
|
||||
|
||||
<polyline fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
120,32 64,8 8,32 8,96 64,120 "/>
|
||||
<path fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M120,96"/>
|
||||
|
||||
<polyline fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
8,32 64,56 64,120 "/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="64" y1="56" x2="120" y2="32"/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="64" y1="120" x2="120" y2="96"/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="120" y1="96" x2="120" y2="32"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="53.69" x2="95.96" y2="50.3"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="95.96" y1="58.3" x2="103.99" y2="54.86"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="69.69" x2="95.96" y2="66.3"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="80.05" y1="81.12" x2="88.05" y2="77.69"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="71.94" y1="92.6" x2="80.05" y2="89.12"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="80.05" y1="97.12" x2="88.05" y2="93.69"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="101.69" x2="96.13" y2="98.23"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
17
resources/icons/shape_gallery.svg
Normal file
17
resources/icons/shape_gallery.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="shape_x5F_gallery_x5F_2">
|
||||
<path fill="#808080" d="M11,6.72c0.14,0,0.25,0.11,0.25,0.25V14c0,0.14-0.11,0.25-0.25,0.25H2c-0.14,0-0.25-0.11-0.25-0.25V6.97
|
||||
c0-0.14,0.11-0.25,0.25-0.25H11 M11,5.97H2c-0.55,0-1,0.45-1,1V14c0,0.55,0.45,1,1,1h9c0.55,0,1-0.45,1-1V6.97
|
||||
C12,6.42,11.55,5.97,11,5.97L11,5.97z"/>
|
||||
<path fill="#808080" d="M14,2H5C4.45,2,4,2.45,4,3v1h0.75V3c0-0.14,0.11-0.25,0.25-0.25h9c0.14,0,0.25,0.11,0.25,0.25v8
|
||||
c0,0.14-0.11,0.25-0.25,0.25h-0.5V12H14c0.55,0,1-0.45,1-1V3C15,2.45,14.55,2,14,2z"/>
|
||||
<path fill="#808080" d="M12.5,4h-9c-0.55,0-1,0.45-1,1v0.97h0.75V5c0-0.14,0.11-0.25,0.25-0.25h9c0.14,0,0.25,0.11,0.25,0.25v7.5
|
||||
c0,0.14-0.11,0.25-0.25,0.25H12v0.75h0.5c0.55,0,1-0.45,1-1V5C13.5,4.45,13.05,4,12.5,4z"/>
|
||||
<path fill="#ED6B21" d="M9.07,11.7V9.3c0-0.18-0.1-0.34-0.25-0.43l-2.07-1.2c-0.15-0.09-0.35-0.09-0.5,0l-2.07,1.2
|
||||
C4.02,8.96,3.93,9.13,3.93,9.3v2.39c0,0.18,0.1,0.34,0.25,0.43l2.07,1.2c0.15,0.09,0.35,0.09,0.5,0l2.07-1.2
|
||||
C8.98,12.04,9.07,11.87,9.07,11.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
18
resources/icons/sinking.svg
Normal file
18
resources/icons/sinking.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="sinking">
|
||||
<g>
|
||||
<path fill="#808080" d="M15,10.5H1c-0.28,0-0.5-0.22-0.5-0.5S0.72,9.5,1,9.5h14c0.28,0,0.5,0.22,0.5,0.5S15.28,10.5,15,10.5z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M14.78,5.47L5.97,1.05C5.88,1.01,5.78,1,5.69,1.03c-0.09,0.03-0.17,0.1-0.22,0.19L2.15,7.83
|
||||
C2.09,7.95,2.1,8.09,2.17,8.2c0.07,0.11,0.19,0.18,0.32,0.18h11.02c0.14,0,0.27-0.08,0.33-0.21l1.1-2.19
|
||||
C15.04,5.79,14.97,5.56,14.78,5.47z"/>
|
||||
<path fill="#ED6B21" d="M11.82,11.8c-0.07-0.11-0.19-0.18-0.32-0.18H4.99c-0.17,0-0.32,0.12-0.36,0.29
|
||||
c-0.04,0.17,0.04,0.34,0.2,0.42l5.21,2.61c0.05,0.03,0.11,0.04,0.17,0.04c0.04,0,0.08-0.01,0.12-0.02
|
||||
c0.09-0.03,0.17-0.1,0.22-0.19l1.31-2.61C11.9,12.05,11.89,11.91,11.82,11.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
min_slic3r_version = 2.4.0-beta0
|
||||
min_slic3r_version = 2.4.0-beta2
|
||||
1.4.0-beta3 Added material profiles for Prusament Resins.
|
||||
1.4.0-beta2 Added SLA material colors. Updated BASF filament profiles.
|
||||
min_slic3r_version = 2.4.0-beta0
|
||||
1.4.0-beta1 Updated pad wall slope angle for SLA printers. Updated Filatech Filacarbon profile for Prusa MINI.
|
||||
1.4.0-beta0 Added multiple Filatech and BASF filament profiles. Added material profiles for SL1S.
|
||||
min_slic3r_version = 2.4.0-alpha0
|
||||
@ -16,6 +18,8 @@ min_slic3r_version = 2.4.0-alpha0
|
||||
1.3.0-alpha1 Added Prusament PCCF. Increased travel acceleration for Prusa MINI. Updated start g-code for Prusa MINI. Added multiple add:north and Extrudr filament profiles. Updated Z travel speed values.
|
||||
1.3.0-alpha0 Disabled thick bridges, updated support settings.
|
||||
min_slic3r_version = 2.3.2-alpha0
|
||||
1.3.5 Added material profiles for Prusament Resins.
|
||||
1.3.4 Added material profiles for new Prusament Resins. Added profiles for multiple BASF filaments.
|
||||
1.3.3 Added multiple profiles for Filatech filaments. Added material profiles for SL1S SPEED. Updated SLA print settings.
|
||||
1.3.2 Added material profiles for Prusament Resin.
|
||||
1.3.1 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S).
|
||||
|
@ -5,7 +5,7 @@
|
||||
name = Prusa Research
|
||||
# Configuration version of this file. Config file will only be installed, if the config_version differs.
|
||||
# This means, the server may force the PrusaSlicer configuration to be downgraded.
|
||||
config_version = 1.4.0-beta2
|
||||
config_version = 1.4.0-beta3
|
||||
# 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%
|
||||
@ -2925,7 +2925,6 @@ bridge_fan_speed = 100
|
||||
filament_type = PET
|
||||
disable_fan_first_layers = 1
|
||||
full_fan_speed_layer = 3
|
||||
|
||||
filament_notes = "BASF Forward AM Ultrafuse PET\nMaterial profile version 1.0\n\nMaterial Description\nUltrafuse PET is made from a premium PET and prints as easy as PLA, but is much stronger. The filament has a large operating window for printing (temperature vs. speed), so it can be used on every 3D-printer. PET will give you outstanding printing results: a good layer adhesion, a high resolution and it is easy to handle. Ultrafuse PET can be 100% recycled, is watertight and has great colors and finish.\n\nPrinting Recommendations:\nUltrafuse PET can be printed directly onto a clean build plate. For challenging prints, use 3dLac to improve adhesion.\n"
|
||||
filament_retract_length = 2
|
||||
filament_retract_speed = 40
|
||||
@ -4835,7 +4834,47 @@ initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #808080
|
||||
|
||||
|
||||
[sla_material:Prusament Resin Tough Sandstone Model @0.025]
|
||||
inherits = *common 0.025*
|
||||
exposure_time = 4
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F7D190
|
||||
|
||||
[sla_material:Prusament Resin Tough Terra Brown @0.025]
|
||||
inherits = *common 0.025*
|
||||
exposure_time = 4
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #7A5C45
|
||||
|
||||
[sla_material:Prusament Resin Tough Brick Red @0.025]
|
||||
inherits = *common 0.025*
|
||||
exposure_time = 4
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #B46056
|
||||
|
||||
[sla_material:Prusament Resin Tough Grass Green @0.025]
|
||||
inherits = *common 0.025*
|
||||
exposure_time = 4
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #37823F
|
||||
|
||||
[sla_material:Prusament Resin Tough Bright Yellow @0.025]
|
||||
inherits = *common 0.025*
|
||||
exposure_time = 4
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F9DB4C
|
||||
|
||||
## Prusa 0.025
|
||||
|
||||
[sla_material:Prusa Orange Tough @0.025]
|
||||
@ -5649,6 +5688,46 @@ material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #C0C0C0
|
||||
|
||||
[sla_material:Prusament Resin Tough Sandstone Model @0.05]
|
||||
inherits = *common 0.05*
|
||||
exposure_time = 6
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F7D190
|
||||
|
||||
[sla_material:Prusament Resin Tough Terra Brown @0.05]
|
||||
inherits = *common 0.05*
|
||||
exposure_time = 6
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #7A5C45
|
||||
|
||||
[sla_material:Prusament Resin Tough Brick Red @0.05]
|
||||
inherits = *common 0.05*
|
||||
exposure_time = 6
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #B46056
|
||||
|
||||
[sla_material:Prusament Resin Tough Grass Green @0.05]
|
||||
inherits = *common 0.05*
|
||||
exposure_time = 6
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #37823F
|
||||
|
||||
[sla_material:Prusament Resin Tough Bright Yellow @0.05]
|
||||
inherits = *common 0.05*
|
||||
exposure_time = 6
|
||||
initial_exposure_time = 35
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F9DB4C
|
||||
|
||||
## Prusa 0.05
|
||||
|
||||
[sla_material:Prusa Beige Tough @0.05]
|
||||
@ -5965,6 +6044,46 @@ material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #808080
|
||||
|
||||
[sla_material:Prusament Resin Tough Sandstone Model @0.1]
|
||||
inherits = *common 0.1*
|
||||
exposure_time = 13
|
||||
initial_exposure_time = 45
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F7D190
|
||||
|
||||
[sla_material:Prusament Resin Tough Terra Brown @0.1]
|
||||
inherits = *common 0.1*
|
||||
exposure_time = 13
|
||||
initial_exposure_time = 45
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #7A5C45
|
||||
|
||||
[sla_material:Prusament Resin Tough Brick Red @0.1]
|
||||
inherits = *common 0.1*
|
||||
exposure_time = 13
|
||||
initial_exposure_time = 45
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #B46056
|
||||
|
||||
[sla_material:Prusament Resin Tough Grass Green @0.1]
|
||||
inherits = *common 0.1*
|
||||
exposure_time = 13
|
||||
initial_exposure_time = 45
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #37823F
|
||||
|
||||
[sla_material:Prusament Resin Tough Bright Yellow @0.1]
|
||||
inherits = *common 0.1*
|
||||
exposure_time = 13
|
||||
initial_exposure_time = 45
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F9DB4C
|
||||
|
||||
## Prusa 0.1
|
||||
|
||||
[sla_material:Prusa Orange Tough @0.1]
|
||||
@ -6085,6 +6204,46 @@ material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #808080
|
||||
|
||||
[sla_material:Prusament Resin Tough Sandstone Model @0.025 SL1S]
|
||||
inherits = *0.025_sl1s*
|
||||
exposure_time = 2
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F7D190
|
||||
|
||||
[sla_material:Prusament Resin Tough Terra Brown @0.025 SL1S]
|
||||
inherits = *0.025_sl1s*
|
||||
exposure_time = 1.8
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #7A5C45
|
||||
|
||||
[sla_material:Prusament Resin Tough Brick Red @0.025 SL1S]
|
||||
inherits = *0.025_sl1s*
|
||||
exposure_time = 1.8
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #B46056
|
||||
|
||||
[sla_material:Prusament Resin Tough Grass Green @0.025 SL1S]
|
||||
inherits = *0.025_sl1s*
|
||||
exposure_time = 1.8
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #37823F
|
||||
|
||||
[sla_material:Prusament Resin Tough Bright Yellow @0.025 SL1S]
|
||||
inherits = *0.025_sl1s*
|
||||
exposure_time = 1.8
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F9DB4C
|
||||
|
||||
## Made for Prusa 0.025
|
||||
|
||||
[sla_material:Prusa Orange Tough @0.025 SL1S]
|
||||
@ -6331,6 +6490,46 @@ material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #808080
|
||||
|
||||
[sla_material:Prusament Resin Tough Sandstone Model @0.05 SL1S]
|
||||
inherits = *0.05_sl1s*
|
||||
exposure_time = 2.4
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F7D190
|
||||
|
||||
[sla_material:Prusament Resin Tough Terra Brown @0.05 SL1S]
|
||||
inherits = *0.05_sl1s*
|
||||
exposure_time = 2
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #7A5C45
|
||||
|
||||
[sla_material:Prusament Resin Tough Brick Red @0.05 SL1S]
|
||||
inherits = *0.05_sl1s*
|
||||
exposure_time = 2
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #B46056
|
||||
|
||||
[sla_material:Prusament Resin Tough Grass Green @0.05 SL1S]
|
||||
inherits = *0.05_sl1s*
|
||||
exposure_time = 2
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #37823F
|
||||
|
||||
[sla_material:Prusament Resin Tough Bright Yellow @0.05 SL1S]
|
||||
inherits = *0.05_sl1s*
|
||||
exposure_time = 2
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F9DB4C
|
||||
|
||||
## Made for Prusa 0.05
|
||||
|
||||
[sla_material:Prusa Orange Tough @0.05 SL1S]
|
||||
@ -6577,6 +6776,46 @@ material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #808080
|
||||
|
||||
[sla_material:Prusament Resin Tough Sandstone Model @0.1 SL1S]
|
||||
inherits = *0.1_sl1s*
|
||||
exposure_time = 3
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F7D190
|
||||
|
||||
[sla_material:Prusament Resin Tough Terra Brown @0.1 SL1S]
|
||||
inherits = *0.1_sl1s*
|
||||
exposure_time = 2.6
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #7A5C45
|
||||
|
||||
[sla_material:Prusament Resin Tough Brick Red @0.1 SL1S]
|
||||
inherits = *0.1_sl1s*
|
||||
exposure_time = 2.6
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #B46056
|
||||
|
||||
[sla_material:Prusament Resin Tough Grass Green @0.1 SL1S]
|
||||
inherits = *0.1_sl1s*
|
||||
exposure_time = 2.6
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #37823F
|
||||
|
||||
[sla_material:Prusament Resin Tough Bright Yellow @0.1 SL1S]
|
||||
inherits = *0.1_sl1s*
|
||||
exposure_time = 2.6
|
||||
initial_exposure_time = 25
|
||||
material_type = Tough
|
||||
material_vendor = Prusa Polymers
|
||||
material_colour = #F9DB4C
|
||||
|
||||
## Made for Prusa 0.1
|
||||
|
||||
[sla_material:Prusa Orange Tough @0.1 SL1S]
|
||||
|
@ -1,26 +1,7 @@
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const vec3 GREEN = vec3(0.0, 0.7, 0.0);
|
||||
const vec3 YELLOW = vec3(0.5, 0.7, 0.0);
|
||||
const vec3 RED = vec3(0.7, 0.0, 0.0);
|
||||
const vec3 WHITE = vec3(1.0, 1.0, 1.0);
|
||||
const float EPSILON = 0.0001;
|
||||
const float BANDS_WIDTH = 10.0;
|
||||
|
||||
struct PrintVolumeDetection
|
||||
{
|
||||
|
@ -595,6 +595,19 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
if (start_gui) {
|
||||
#ifdef SLIC3R_GUI
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
// likely some linux / unix system
|
||||
const char *display = boost::nowide::getenv("DISPLAY");
|
||||
// const char *wayland_display = boost::nowide::getenv("WAYLAND_DISPLAY");
|
||||
//if (! ((display && *display) || (wayland_display && *wayland_display))) {
|
||||
if (! (display && *display)) {
|
||||
// DISPLAY not set.
|
||||
boost::nowide::cerr << "DISPLAY not set, GUI mode not available." << std::endl << std::endl;
|
||||
this->print_help(false);
|
||||
// Indicate an error.
|
||||
return 1;
|
||||
}
|
||||
#endif // some linux / unix system
|
||||
Slic3r::GUI::GUI_InitParams params;
|
||||
params.argc = argc;
|
||||
params.argv = argv;
|
||||
|
@ -174,6 +174,9 @@ void AppConfig::set_defaults()
|
||||
if (get("show_hints").empty())
|
||||
set("show_hints", "1");
|
||||
|
||||
if (get("allow_ip_resolve").empty())
|
||||
set("allow_ip_resolve", "1");
|
||||
|
||||
#ifdef _WIN32
|
||||
if (get("use_legacy_3DConnexion").empty())
|
||||
set("use_legacy_3DConnexion", "0");
|
||||
|
@ -126,7 +126,7 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print,
|
||||
return top_level_objects_with_brim;
|
||||
}
|
||||
|
||||
static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_level_objects_with_brim)
|
||||
static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_level_objects_with_brim, const double scaled_resolution)
|
||||
{
|
||||
Polygons islands;
|
||||
for (const PrintObject *object : top_level_objects_with_brim) {
|
||||
@ -139,7 +139,7 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
|
||||
for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) {
|
||||
Polygons contour_offset = offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare);
|
||||
for (Polygon &poly : contour_offset)
|
||||
poly.douglas_peucker(SCALED_RESOLUTION);
|
||||
poly.douglas_peucker(scaled_resolution);
|
||||
|
||||
polygons_append(islands_object, std::move(contour_offset));
|
||||
}
|
||||
@ -359,13 +359,14 @@ static void make_inner_brim(const Print &print,
|
||||
ExtrusionEntityCollection &brim)
|
||||
{
|
||||
assert(print.objects().size() == bottom_layers_expolygons.size());
|
||||
const auto scaled_resolution = scaled<double>(print.config().gcode_resolution.value);
|
||||
Flow flow = print.brim_flow();
|
||||
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
|
||||
Polygons loops;
|
||||
islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), ClipperLib::jtSquare);
|
||||
for (size_t i = 0; !islands_ex.empty(); ++i) {
|
||||
for (ExPolygon &poly_ex : islands_ex)
|
||||
poly_ex.douglas_peucker(SCALED_RESOLUTION);
|
||||
poly_ex.douglas_peucker(scaled_resolution);
|
||||
polygons_append(loops, to_polygons(islands_ex));
|
||||
islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), ClipperLib::jtSquare);
|
||||
}
|
||||
@ -380,10 +381,11 @@ static void make_inner_brim(const Print &print,
|
||||
// Collect islands_area to be merged into the final 1st layer convex hull.
|
||||
ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area)
|
||||
{
|
||||
const auto scaled_resolution = scaled<double>(print.config().gcode_resolution.value);
|
||||
Flow flow = print.brim_flow();
|
||||
std::vector<ExPolygons> bottom_layers_expolygons = get_print_bottom_layers_expolygons(print);
|
||||
ConstPrintObjectPtrs top_level_objects_with_brim = get_top_level_objects_with_brim(print, bottom_layers_expolygons);
|
||||
Polygons islands = top_level_outer_brim_islands(top_level_objects_with_brim);
|
||||
Polygons islands = top_level_outer_brim_islands(top_level_objects_with_brim, scaled_resolution);
|
||||
ExPolygons islands_area_ex = top_level_outer_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
|
||||
islands_area = to_polygons(islands_area_ex);
|
||||
|
||||
@ -393,7 +395,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
try_cancel();
|
||||
islands = expand(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
|
||||
for (Polygon &poly : islands)
|
||||
poly.douglas_peucker(SCALED_RESOLUTION);
|
||||
poly.douglas_peucker(scaled_resolution);
|
||||
polygons_append(loops, shrink(islands, 0.5f * float(flow.scaled_spacing())));
|
||||
}
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
|
@ -269,15 +269,17 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con
|
||||
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed) const
|
||||
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom) const
|
||||
{
|
||||
switch (m_type) {
|
||||
case Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||
if (m_max_print_height == 0)
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
||||
//FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||
@ -286,14 +288,14 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &i
|
||||
case Type::Circle:
|
||||
{
|
||||
Geometry::Circlef circle { unscaled<float>(m_circle.center), unscaled<float>(m_circle.radius + SceneEpsilon) };
|
||||
return m_max_print_height == 0 ?
|
||||
return m_max_print_height == 0.0 ?
|
||||
object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) :
|
||||
object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
|
||||
}
|
||||
case Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case Type::Custom:
|
||||
return m_max_print_height == 0 ?
|
||||
return m_max_print_height == 0.0 ?
|
||||
object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
|
||||
object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
|
||||
case Type::Invalid:
|
||||
@ -302,18 +304,20 @@ BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set &i
|
||||
}
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3 &volume_bbox) const
|
||||
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const
|
||||
{
|
||||
assert(m_type == Type::Rectangle);
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
if (m_max_print_height == 0)
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below :
|
||||
build_volume.contains(volume_bbox) ? ObjectState::Inside :
|
||||
build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
|
||||
}
|
||||
|
||||
bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const BoundingBoxf3 &paths_bbox) const
|
||||
bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const
|
||||
{
|
||||
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) {
|
||||
return move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.f && move.height != 0.f;
|
||||
@ -324,8 +328,10 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const Boun
|
||||
case Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(epsilon);
|
||||
if (m_max_print_height == 0)
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox);
|
||||
}
|
||||
case Type::Circle:
|
||||
@ -333,7 +339,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const Boun
|
||||
const Vec2f c = unscaled<float>(m_circle.center);
|
||||
const float r = unscaled<double>(m_circle.radius) + epsilon;
|
||||
const float r2 = sqr(r);
|
||||
return m_max_print_height == 0 ?
|
||||
return m_max_print_height == 0.0 ?
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) :
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move)
|
||||
@ -342,7 +348,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult &paths, const Boun
|
||||
case Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case Type::Custom:
|
||||
return m_max_print_height == 0 ?
|
||||
return m_max_print_height == 0.0 ?
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()); }) :
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move)
|
||||
@ -364,7 +370,7 @@ inline bool all_inside_vertices_normals_interleaved(const std::vector<float> &pa
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::vector<float> &paths, const Eigen::AlignedBox<float, 3> &paths_bbox) const
|
||||
bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::vector<float>& paths, const Eigen::AlignedBox<float, 3>& paths_bbox, bool ignore_bottom) const
|
||||
{
|
||||
assert(paths.size() % 6 == 0);
|
||||
static constexpr const double epsilon = BedEpsilon;
|
||||
@ -372,8 +378,10 @@ bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::v
|
||||
case Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(epsilon);
|
||||
if (m_max_print_height == 0)
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox.min().cast<double>()) && build_volume.contains(paths_bbox.max().cast<double>());
|
||||
}
|
||||
case Type::Circle:
|
||||
@ -381,14 +389,14 @@ bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::v
|
||||
const Vec2f c = unscaled<float>(m_circle.center);
|
||||
const float r = unscaled<double>(m_circle.radius) + float(epsilon);
|
||||
const float r2 = sqr(r);
|
||||
return m_max_print_height == 0 ?
|
||||
return m_max_print_height == 0.0 ?
|
||||
all_inside_vertices_normals_interleaved(paths, [c, r2](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2; }) :
|
||||
all_inside_vertices_normals_interleaved(paths, [c, r2, z = m_max_print_height + epsilon](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; });
|
||||
}
|
||||
case Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case Type::Custom:
|
||||
return m_max_print_height == 0 ?
|
||||
return m_max_print_height == 0.0 ?
|
||||
all_inside_vertices_normals_interleaved(paths, [this](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast<double>()); }) :
|
||||
all_inside_vertices_normals_interleaved(paths, [this, z = m_max_print_height + epsilon](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast<double>()) && p.z() <= z; });
|
||||
default:
|
||||
|
@ -80,19 +80,19 @@ public:
|
||||
// Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing.
|
||||
// Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state()
|
||||
// Using SceneEpsilon
|
||||
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed) const;
|
||||
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, bool ignore_bottom = true) const;
|
||||
// Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example.
|
||||
// Called for a rectangular bed:
|
||||
ObjectState volume_state_bbox(const BoundingBoxf3 &volume_bbox) const;
|
||||
ObjectState volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom = true) const;
|
||||
|
||||
// 2) Test called on G-code paths.
|
||||
// Using BedEpsilon for all tests.
|
||||
static constexpr const double BedEpsilon = 3. * EPSILON;
|
||||
// Called on final G-code paths.
|
||||
//FIXME The test does not take the thickness of the extrudates into account!
|
||||
bool all_paths_inside(const GCodeProcessorResult &paths, const BoundingBoxf3 &paths_bbox) const;
|
||||
bool all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom = true) const;
|
||||
// Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices.
|
||||
bool all_paths_inside_vertices_and_normals_interleaved(const std::vector<float> &paths, const Eigen::AlignedBox<float, 3> &bbox) const;
|
||||
bool all_paths_inside_vertices_and_normals_interleaved(const std::vector<float>& paths, const Eigen::AlignedBox<float, 3>& bbox, bool ignore_bottom = true) const;
|
||||
|
||||
private:
|
||||
// Source definition of the print bed geometry (PrintConfig::bed_shape)
|
||||
|
@ -67,6 +67,16 @@ add_library(libslic3r STATIC
|
||||
Fill/FillPlanePath.hpp
|
||||
Fill/FillLine.cpp
|
||||
Fill/FillLine.hpp
|
||||
Fill/FillLightning.cpp
|
||||
Fill/FillLightning.hpp
|
||||
Fill/Lightning/DistanceField.cpp
|
||||
Fill/Lightning/DistanceField.hpp
|
||||
Fill/Lightning/Generator.cpp
|
||||
Fill/Lightning/Generator.hpp
|
||||
Fill/Lightning/Layer.cpp
|
||||
Fill/Lightning/Layer.hpp
|
||||
Fill/Lightning/TreeNode.cpp
|
||||
Fill/Lightning/TreeNode.hpp
|
||||
Fill/FillRectilinear.cpp
|
||||
Fill/FillRectilinear.hpp
|
||||
Flow.cpp
|
||||
@ -288,7 +298,7 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL 4.13.2 REQUIRED)
|
||||
find_package(CGAL REQUIRED)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
|
||||
|
@ -740,11 +740,7 @@ ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, Fo
|
||||
}
|
||||
|
||||
// Load the config keys from the given string.
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
size_t ConfigBase::load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions)
|
||||
#else
|
||||
static inline size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions)
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
{
|
||||
if (str == nullptr)
|
||||
return 0;
|
||||
|
@ -2017,9 +2017,7 @@ public:
|
||||
// Set all the nullable values to nils.
|
||||
void null_nullables();
|
||||
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions);
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
|
||||
private:
|
||||
// Set a configuration value from a string.
|
||||
|
@ -329,7 +329,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this);
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
const auto resolution = this->object()->print()->config().gcode_resolution.value;
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
@ -371,6 +372,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.dont_adjust = false; // surface_fill.params.dont_adjust;
|
||||
params.anchor_length = surface_fill.params.anchor_length;
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
@ -537,7 +539,7 @@ void Layer::make_ironing()
|
||||
fill_params.density = 1.;
|
||||
fill_params.monotonic = true;
|
||||
|
||||
for (size_t i = 0; i < by_extruder.size(); ++ i) {
|
||||
for (size_t i = 0; i < by_extruder.size();) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
IroningParams &ironing_params = by_extruder[i];
|
||||
size_t j = i;
|
||||
@ -587,14 +589,17 @@ void Layer::make_ironing()
|
||||
polygons_append(infills, surface.expolygon);
|
||||
}
|
||||
}
|
||||
|
||||
if (! infills.empty() || j > i + 1) {
|
||||
// Ironing over more than a single region or over solid internal infill.
|
||||
if (! infills.empty())
|
||||
// For IroningType::AllSolid only:
|
||||
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
|
||||
append(polys, std::move(infills));
|
||||
polys = union_safety_offset(polys);
|
||||
}
|
||||
// Trim the top surfaces with half the nozzle diameter.
|
||||
ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
|
||||
if (! infills.empty()) {
|
||||
// For IroningType::AllSolid only:
|
||||
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
|
||||
append(infills, to_polygons(std::move(ironing_areas)));
|
||||
ironing_areas = union_safety_offset_ex(infills);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
@ -624,6 +629,9 @@ void Layer::make_ironing()
|
||||
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
|
||||
}
|
||||
}
|
||||
|
||||
// Regions up to j were processed.
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "FillLine.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillAdaptive.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
|
||||
// #define INFILL_DEBUG_OUTPUT
|
||||
|
||||
@ -45,6 +46,9 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
||||
case ipAdaptiveCubic: return new FillAdaptive::Filler();
|
||||
case ipSupportCubic: return new FillAdaptive::Filler();
|
||||
case ipSupportBase: return new FillSupportBase();
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
case ipLightning: return new FillLightning::Filler();
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
default: throw Slic3r::InvalidArgument("unknown type");
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,10 @@
|
||||
#include "../BoundingBox.hpp"
|
||||
#include "../Exception.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
class Surface;
|
||||
enum InfillPattern : int;
|
||||
|
||||
@ -44,6 +44,9 @@ struct FillParams
|
||||
float anchor_length { 1000.f };
|
||||
float anchor_length_max { 1000.f };
|
||||
|
||||
// G-code resolution.
|
||||
double resolution { 0.0125 };
|
||||
|
||||
// Don't adjust spacing to fill the space evenly.
|
||||
bool dont_adjust { true };
|
||||
|
||||
|
29
src/libslic3r/Fill/FillLightning.cpp
Normal file
29
src/libslic3r/Fill/FillLightning.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "../Print.hpp"
|
||||
|
||||
#include "FillLightning.hpp"
|
||||
#include "Lightning/Generator.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
Polylines Filler::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
||||
return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width());
|
||||
}
|
||||
|
||||
void GeneratorDeleter::operator()(Generator *p) {
|
||||
delete p;
|
||||
}
|
||||
|
||||
GeneratorPtr build_generator(const PrintObject &print_object)
|
||||
{
|
||||
return GeneratorPtr(new Generator(print_object));
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillAdaptive
|
36
src/libslic3r/Fill/FillLightning.hpp
Normal file
36
src/libslic3r/Fill/FillLightning.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef slic3r_FillLightning_hpp_
|
||||
#define slic3r_FillLightning_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
namespace FillLightning {
|
||||
|
||||
class Generator;
|
||||
// To keep the definition of Octree opaque, we have to define a custom deleter.
|
||||
struct GeneratorDeleter { void operator()(Generator *p); };
|
||||
using GeneratorPtr = std::unique_ptr<Generator, GeneratorDeleter>;
|
||||
|
||||
GeneratorPtr build_generator(const PrintObject &print_object);
|
||||
|
||||
class Filler : public Slic3r::Fill
|
||||
{
|
||||
public:
|
||||
~Filler() override = default;
|
||||
|
||||
Generator *generator { nullptr };
|
||||
protected:
|
||||
Fill* clone() const override { return new Filler(*this); }
|
||||
// Perform the fill.
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
// Let the G-code export reoder the infill lines.
|
||||
bool no_sort() const override { return false; }
|
||||
};
|
||||
|
||||
} // namespace FillAdaptive
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillLightning_hpp_
|
@ -31,7 +31,8 @@ void FillPlanePath::_fill_surface_single(
|
||||
coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)));
|
||||
coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)),
|
||||
params.resolution);
|
||||
|
||||
if (pts.size() >= 2) {
|
||||
// Convert points to a polyline, upscale.
|
||||
@ -58,7 +59,7 @@ void FillPlanePath::_fill_surface_single(
|
||||
}
|
||||
|
||||
// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta
|
||||
Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y)
|
||||
Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
@ -72,8 +73,8 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m
|
||||
out.emplace_back(0, 0);
|
||||
out.emplace_back(1, 0);
|
||||
while (r < rmax) {
|
||||
// Discretization angle to achieve a discretization error lower than RESOLUTION.
|
||||
theta += 2. * acos(1. - RESOLUTION / r);
|
||||
// Discretization angle to achieve a discretization error lower than resolution.
|
||||
theta += 2. * acos(1. - resolution / r);
|
||||
r = a + b * theta;
|
||||
out.emplace_back(r * cos(theta), r * sin(theta));
|
||||
}
|
||||
@ -125,7 +126,7 @@ static inline Point hilbert_n_to_xy(const size_t n)
|
||||
return Point(x, y);
|
||||
}
|
||||
|
||||
Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y)
|
||||
Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
{
|
||||
// Minimum power of two square to fit the domain.
|
||||
size_t sz = 2;
|
||||
@ -148,7 +149,7 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x,
|
||||
return line;
|
||||
}
|
||||
|
||||
Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y)
|
||||
Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
|
@ -28,7 +28,7 @@ protected:
|
||||
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
virtual bool _centered() const = 0;
|
||||
virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) = 0;
|
||||
virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0;
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
@ -39,7 +39,7 @@ public:
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) override;
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
};
|
||||
|
||||
class FillHilbertCurve : public FillPlanePath
|
||||
@ -50,7 +50,7 @@ public:
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return false; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) override;
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
@ -61,7 +61,7 @@ public:
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) override;
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -3041,5 +3041,40 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams
|
||||
return polylines_out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
|
||||
{
|
||||
ExPolygonWithOffset poly_with_offset(expolygon, 0, 0, 0);
|
||||
BoundingBox bounding_box = poly_with_offset.bounding_box_src();
|
||||
std::vector<SegmentedIntersectionLine> segs = slice_region_by_vertical_lines(
|
||||
poly_with_offset,
|
||||
(bounding_box.max.x() - bounding_box.min.x() + spacing - 1) / spacing,
|
||||
bounding_box.min.x(),
|
||||
spacing);
|
||||
|
||||
Points out;
|
||||
for (const SegmentedIntersectionLine &sil : segs) {
|
||||
for (size_t i = 0; i < sil.intersections.size(); i += 2) {
|
||||
coord_t a = sil.intersections[i].pos();
|
||||
coord_t b = sil.intersections[i + 1].pos();
|
||||
for (coord_t y = a - (a % spacing) - spacing; y < b; y += spacing)
|
||||
if (y > a)
|
||||
out.emplace_back(sil.pos, y);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing)
|
||||
{
|
||||
Points out;
|
||||
for (const ExPolygon &expoly : expolygons)
|
||||
append(out, sample_grid_pattern(expoly, spacing));
|
||||
return out;
|
||||
}
|
||||
|
||||
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing)
|
||||
{
|
||||
return sample_grid_pattern(union_ex(polygons), spacing);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -109,6 +109,10 @@ protected:
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
|
||||
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
|
||||
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillRectilinear_hpp_
|
||||
|
99
src/libslic3r/Fill/Lightning/DistanceField.cpp
Normal file
99
src/libslic3r/Fill/Lightning/DistanceField.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "DistanceField.hpp" //Class we're implementing.
|
||||
#include "../FillRectilinear.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient.
|
||||
|
||||
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
|
||||
m_cell_size(radius / radius_per_cell_size),
|
||||
m_supporting_radius(radius)
|
||||
{
|
||||
m_supporting_radius2 = double(radius) * double(radius);
|
||||
// Sample source polygons with a regular grid sampling pattern.
|
||||
for (const ExPolygon &expoly : union_ex(current_outline)) {
|
||||
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
|
||||
// Find a squared distance to the source expolygon boundary.
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
|
||||
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
|
||||
prev = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_unsupported_points.emplace_back(p, sqrt(d2));
|
||||
}
|
||||
}
|
||||
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||
constexpr coord_t prime_for_hash = 191;
|
||||
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
|
||||
a.dist_to_boundary < b.dist_to_boundary :
|
||||
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
|
||||
});
|
||||
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
|
||||
UnsupportedCell& cell = *it;
|
||||
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
|
||||
}
|
||||
}
|
||||
|
||||
void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
||||
{
|
||||
Vec2d v = (added_leaf - to_node).cast<double>();
|
||||
auto l2 = v.squaredNorm();
|
||||
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
|
||||
|
||||
BoundingBox grid;
|
||||
{
|
||||
Point diagonal(m_supporting_radius, m_supporting_radius);
|
||||
Point iextent(extent.cast<coord_t>());
|
||||
grid = BoundingBox(added_leaf - diagonal, added_leaf + diagonal);
|
||||
grid.merge(to_node - iextent);
|
||||
grid.merge(to_node + iextent);
|
||||
grid.merge(added_leaf - iextent);
|
||||
grid.merge(added_leaf + iextent);
|
||||
grid.min /= m_cell_size;
|
||||
grid.max /= m_cell_size;
|
||||
}
|
||||
|
||||
Point grid_loc;
|
||||
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
|
||||
grid_loc.y() = row * m_cell_size;
|
||||
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
|
||||
grid_loc.x() = col * m_cell_size;
|
||||
// Test inside a circle at the new leaf.
|
||||
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
|
||||
// Not inside a circle at the end of the new leaf.
|
||||
// Test inside a rotated rectangle.
|
||||
Vec2d vx = (grid_loc - to_node).cast<double>();
|
||||
double d = v.dot(vx);
|
||||
if (d >= 0 && d <= l2) {
|
||||
d = extent.dot(vx);
|
||||
if (d < -1. || d > 1.)
|
||||
// Not inside a rotated rectangle.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
|
||||
// Remove unsupported leafs at this grid location.
|
||||
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
|
||||
std::list<UnsupportedCell>::iterator& list_it = it->second;
|
||||
UnsupportedCell& cell = *list_it;
|
||||
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
|
||||
m_unsupported_points.erase(list_it);
|
||||
m_unsupported_points_grid.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
100
src/libslic3r/Fill/Lightning/DistanceField.hpp
Normal file
100
src/libslic3r/Fill/Lightning/DistanceField.hpp
Normal file
@ -0,0 +1,100 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_DISTANCE_FIELD_H
|
||||
#define LIGHTNING_DISTANCE_FIELD_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
/*!
|
||||
* 2D field that maintains locations which need to be supported for Lightning
|
||||
* Infill.
|
||||
*
|
||||
* This field contains a set of "cells", spaced out in a grid. Each cell
|
||||
* maintains how far it is removed from the edge, which is used to determine
|
||||
* how it gets supported by Lightning Infill.
|
||||
*/
|
||||
class DistanceField
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Construct a new field to calculate Lightning Infill with.
|
||||
* \param radius The radius of influence that an infill line is expected to
|
||||
* support in the layer above.
|
||||
* \param current_outline The total infill area on this layer.
|
||||
* \param current_overhang The overhang that needs to be supported on this
|
||||
* layer.
|
||||
*/
|
||||
DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
|
||||
|
||||
/*!
|
||||
* Gets the next unsupported location to be supported by a new branch.
|
||||
* \param p Output variable for the next point to support.
|
||||
* \return ``true`` if successful, or ``false`` if there are no more points
|
||||
* to consider.
|
||||
*/
|
||||
bool tryGetNextPoint(Point* p) const {
|
||||
if (m_unsupported_points.empty())
|
||||
return false;
|
||||
*p = m_unsupported_points.front().loc;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Update the distance field with a newly added branch.
|
||||
*
|
||||
* The branch is a line extending from \p to_node to \p added_leaf . This
|
||||
* function updates the grid cells so that the distance field knows how far
|
||||
* off it is from being supported by the current pattern. Grid points are
|
||||
* updated with sampling points spaced out by the supporting radius along
|
||||
* the line.
|
||||
* \param to_node The node endpoint of the newly added branch.
|
||||
* \param added_leaf The location of the leaf of the newly added branch,
|
||||
* drawing a straight line to the node.
|
||||
*/
|
||||
void update(const Point& to_node, const Point& added_leaf);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Spacing between grid points to consider supporting.
|
||||
*/
|
||||
coord_t m_cell_size;
|
||||
|
||||
/*!
|
||||
* The radius of the area of the layer above supported by a point on a
|
||||
* branch of a tree.
|
||||
*/
|
||||
coord_t m_supporting_radius;
|
||||
double m_supporting_radius2;
|
||||
|
||||
/*!
|
||||
* Represents a small discrete area of infill that needs to be supported.
|
||||
*/
|
||||
struct UnsupportedCell
|
||||
{
|
||||
UnsupportedCell(Point loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
|
||||
// The position of the center of this cell.
|
||||
Point loc;
|
||||
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
|
||||
coord_t dist_to_boundary;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Cells which still need to be supported at some point.
|
||||
*/
|
||||
std::list<UnsupportedCell> m_unsupported_points;
|
||||
|
||||
/*!
|
||||
* Links the unsupported points to a grid point, so that we can quickly look
|
||||
* up the cell belonging to a certain position in the grid.
|
||||
*/
|
||||
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
#endif //LIGHTNING_DISTANCE_FIELD_H
|
127
src/libslic3r/Fill/Lightning/Generator.cpp
Normal file
127
src/libslic3r/Fill/Lightning/Generator.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "Generator.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../ClipperUtils.hpp"
|
||||
#include "../../Layer.hpp"
|
||||
#include "../../Print.hpp"
|
||||
#include "../../Surface.hpp"
|
||||
|
||||
/* Possible future tasks/optimizations,etc.:
|
||||
* - Improve connecting heuristic to favor connecting to shorter trees
|
||||
* - Change which node of a tree is the root when that would be better in reconnectRoots.
|
||||
* - (For implementation in Infill classes & elsewhere): Outline offset, infill-overlap & perimeter gaps.
|
||||
* - Allow for polylines, i.e. merge Tims PR about polyline fixes
|
||||
* - Unit Tests?
|
||||
* - Optimization: let the square grid store the closest point on boundary
|
||||
* - Optimization: only compute the closest dist to / point on boundary for the outer cells and flood-fill the rest
|
||||
* - Make a pass with Arachne over the output. Somehow.
|
||||
* - Generate all to-be-supported points at once instead of sequentially: See branch interlocking_gen PolygonUtils::spreadDots (Or work with sparse grids.)
|
||||
* - Lots of magic values ... to many to parameterize. But are they the best?
|
||||
* - Move more complex computations from Generator constructor to elsewhere.
|
||||
*/
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
Generator::Generator(const PrintObject &print_object)
|
||||
{
|
||||
const PrintConfig &print_config = print_object.print()->config();
|
||||
const PrintObjectConfig &object_config = print_object.config();
|
||||
const PrintRegionConfig ®ion_config = print_object.shared_regions()->all_regions.front()->config();
|
||||
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
|
||||
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
// const int infill_extruder = region_config.infill_extruder.value;
|
||||
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
|
||||
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
|
||||
const double layer_thickness = object_config.layer_height;
|
||||
|
||||
m_infill_extrusion_width = scaled<float>(region_config.infill_extrusion_width.percent ? default_infill_extrusion_width * 0.01 * region_config.infill_extrusion_width : region_config.infill_extrusion_width);
|
||||
m_supporting_radius = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.fill_density);
|
||||
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
m_wall_supporting_radius = layer_thickness * std::tan(lightning_infill_overhang_angle);
|
||||
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
|
||||
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
|
||||
|
||||
generateInitialInternalOverhangs(print_object);
|
||||
generateTrees(print_object);
|
||||
}
|
||||
|
||||
void Generator::generateInitialInternalOverhangs(const PrintObject &print_object)
|
||||
{
|
||||
m_overhang_per_layer.resize(print_object.layers().size());
|
||||
const float infill_wall_offset = - m_infill_extrusion_width;
|
||||
|
||||
Polygons infill_area_above;
|
||||
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
|
||||
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
|
||||
Polygons infill_area_here;
|
||||
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
|
||||
for (const Surface& surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
|
||||
|
||||
//Remove the part of the infill area that is already supported by the walls.
|
||||
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
|
||||
|
||||
m_overhang_per_layer[layer_nr] = overhang;
|
||||
infill_area_above = std::move(infill_area_here);
|
||||
}
|
||||
}
|
||||
|
||||
const Layer& Generator::getTreesForLayer(const size_t& layer_id) const
|
||||
{
|
||||
assert(layer_id < m_lightning_layers.size());
|
||||
return m_lightning_layers[layer_id];
|
||||
}
|
||||
|
||||
void Generator::generateTrees(const PrintObject &print_object)
|
||||
{
|
||||
m_lightning_layers.resize(print_object.layers().size());
|
||||
const coord_t infill_wall_offset = - m_infill_extrusion_width;
|
||||
|
||||
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
|
||||
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
|
||||
for (const Surface &surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
|
||||
|
||||
// For various operations its beneficial to quickly locate nearby features on the polygon:
|
||||
const size_t top_layer_id = print_object.layers().size() - 1;
|
||||
EdgeGrid::Grid outlines_locator(get_extents(infill_outlines[top_layer_id]).inflated(SCALED_EPSILON));
|
||||
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
|
||||
{
|
||||
Layer& current_lightning_layer = m_lightning_layers[layer_id];
|
||||
Polygons& current_outlines = infill_outlines[layer_id];
|
||||
|
||||
// register all trees propagated from the previous layer as to-be-reconnected
|
||||
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
|
||||
|
||||
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
|
||||
// Initialize trees for next lower layer from the current one.
|
||||
if (layer_id == 0)
|
||||
return;
|
||||
|
||||
const Polygons& below_outlines = infill_outlines[layer_id - 1];
|
||||
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
|
||||
outlines_locator.create(below_outlines, locator_cell_size);
|
||||
|
||||
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
|
||||
for (auto& tree : current_lightning_layer.tree_roots)
|
||||
tree->propagateToNextLayer(lower_trees, below_outlines, outlines_locator, m_prune_length, m_straightening_max_distance, locator_cell_size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
132
src/libslic3r/Fill/Lightning/Generator.hpp
Normal file
132
src/libslic3r/Fill/Lightning/Generator.hpp
Normal file
@ -0,0 +1,132 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_GENERATOR_H
|
||||
#define LIGHTNING_GENERATOR_H
|
||||
|
||||
#include "Layer.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
class PrintObject;
|
||||
|
||||
namespace FillLightning
|
||||
{
|
||||
|
||||
/*!
|
||||
* Generates the Lightning Infill pattern.
|
||||
*
|
||||
* The lightning infill pattern is designed to use a minimal amount of material
|
||||
* to support the top skin of the print, while still printing with reasonably
|
||||
* consistently flowing lines. It sacrifices strength completely in favour of
|
||||
* top surface quality and reduced print time / material usage.
|
||||
*
|
||||
* Lightning Infill is so named because the patterns it creates resemble a
|
||||
* forked path with one main path and many small lines on the side. These paths
|
||||
* grow out from the sides of the model just below where the top surface needs
|
||||
* to be supported from the inside, so that minimal material is needed.
|
||||
*
|
||||
* This pattern is based on a paper called "Ribbed Support Vaults for 3D
|
||||
* Printing of Hollowed Objects" by Tricard, Claux and Lefebvre:
|
||||
* https://www.researchgate.net/publication/333808588_Ribbed_Support_Vaults_for_3D_Printing_of_Hollowed_Objects
|
||||
*/
|
||||
class Generator // "Just like Nicola used to make!"
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Create a generator to fill a certain mesh with infill.
|
||||
*
|
||||
* This generator will pre-compute things in preparation of generating
|
||||
* Lightning Infill for the infill areas in that mesh. The infill areas must
|
||||
* already be calculated at this point.
|
||||
* \param mesh The mesh to generate infill for.
|
||||
*/
|
||||
Generator(const PrintObject &print_object);
|
||||
|
||||
/*!
|
||||
* Get a tree of paths generated for a certain layer of the mesh.
|
||||
*
|
||||
* This tree represents the paths that must be traced to print the infill.
|
||||
* \param layer_id The layer number to get the path tree for. This is within
|
||||
* the range of layers of the mesh (not the global layer numbers).
|
||||
* \return A tree structure representing paths to print to create the
|
||||
* Lightning Infill pattern.
|
||||
*/
|
||||
const Layer& getTreesForLayer(const size_t& layer_id) const;
|
||||
|
||||
float infilll_extrusion_width() const { return m_infill_extrusion_width; }
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Calculate the overhangs above the infill areas that need to be supported
|
||||
* by infill.
|
||||
*
|
||||
* Normally, overhangs are only generated for the outside of the model and
|
||||
* only when support is generated. For this pattern, we also need to
|
||||
* generate overhang areas for the inside of the model.
|
||||
*/
|
||||
void generateInitialInternalOverhangs(const PrintObject &print_object);
|
||||
|
||||
/*!
|
||||
* Calculate the tree structure of all layers.
|
||||
*/
|
||||
void generateTrees(const PrintObject &print_object);
|
||||
|
||||
float m_infill_extrusion_width;
|
||||
|
||||
/*!
|
||||
* How far each piece of infill can support skin in the layer above.
|
||||
*/
|
||||
coord_t m_supporting_radius;
|
||||
|
||||
/*!
|
||||
* How far a wall can support the wall above it. If a wall completely
|
||||
* supports the wall above it, no infill needs to support that.
|
||||
*
|
||||
* This is similar to the overhang distance calculated for support. It is
|
||||
* determined by the lightning_infill_overhang_angle setting.
|
||||
*/
|
||||
coord_t m_wall_supporting_radius;
|
||||
|
||||
/*!
|
||||
* How far each piece of infill can support other infill in the layer above.
|
||||
*
|
||||
* This may be different than \ref supporting_radius, because the infill is
|
||||
* printed with one end floating in mid-air. This endpoint will sag more, so
|
||||
* an infill line may need to be supported more than a skin line.
|
||||
*/
|
||||
coord_t m_prune_length;
|
||||
|
||||
/*!
|
||||
* How far a line may be shifted in order to straighten the line out.
|
||||
*
|
||||
* Straightening the line reduces material and time usage and reduces
|
||||
* accelerations needed to print the pattern. However it makes the infill
|
||||
* weak if lines are partially suspended next to the line on the previous
|
||||
* layer.
|
||||
*/
|
||||
coord_t m_straightening_max_distance;
|
||||
|
||||
/*!
|
||||
* For each layer, the overhang that needs to be supported by the pattern.
|
||||
*
|
||||
* This is generated by \ref generateInitialInternalOverhangs.
|
||||
*/
|
||||
std::vector<Polygons> m_overhang_per_layer;
|
||||
|
||||
/*!
|
||||
* For each layer, the generated lightning paths.
|
||||
*
|
||||
* This is generated by \ref generateTrees.
|
||||
*/
|
||||
std::vector<Layer> m_lightning_layers;
|
||||
};
|
||||
|
||||
} // namespace FillLightning
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // LIGHTNING_GENERATOR_H
|
410
src/libslic3r/Fill/Lightning/Layer.cpp
Normal file
410
src/libslic3r/Fill/Lightning/Layer.cpp
Normal file
@ -0,0 +1,410 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "Layer.hpp" //The class we're implementing.
|
||||
|
||||
#include <iterator> // advance
|
||||
|
||||
#include "DistanceField.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../Geometry.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location)
|
||||
{
|
||||
return coord_t((boundary_loc - unsupported_location).cast<double>().norm());
|
||||
}
|
||||
|
||||
Point GroundingLocation::p() const
|
||||
{
|
||||
assert(tree_node || boundary_location);
|
||||
return tree_node ? tree_node->getLocation() : *boundary_location;
|
||||
}
|
||||
|
||||
void Layer::fillLocator(SparseNodeGrid &tree_node_locator)
|
||||
{
|
||||
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator](NodeSPtr node) {
|
||||
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
|
||||
};
|
||||
for (auto& tree : tree_roots)
|
||||
tree->visitNodes(add_node_to_locator_func);
|
||||
}
|
||||
|
||||
void Layer::generateNewTrees
|
||||
(
|
||||
const Polygons& current_overhang,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outlines_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
)
|
||||
{
|
||||
DistanceField distance_field(supporting_radius, current_outlines, current_overhang);
|
||||
|
||||
SparseNodeGrid tree_node_locator;
|
||||
fillLocator(tree_node_locator);
|
||||
|
||||
// Until no more points need to be added to support all:
|
||||
// Determine next point from tree/outline areas via distance-field
|
||||
Point unsupported_location;
|
||||
while (distance_field.tryGetNextPoint(&unsupported_location)) {
|
||||
GroundingLocation grounding_loc = getBestGroundingLocation(
|
||||
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
|
||||
|
||||
NodeSPtr new_parent;
|
||||
NodeSPtr new_child;
|
||||
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
|
||||
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
|
||||
if (new_parent)
|
||||
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
|
||||
// update distance field
|
||||
distance_field.update(grounding_loc.p(), unsupported_location);
|
||||
}
|
||||
}
|
||||
|
||||
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
|
||||
{
|
||||
struct Visitor {
|
||||
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
if (Geometry::segments_intersect(segment.first, segment.second, line.a, line.b)) {
|
||||
this->intersect = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid& grid;
|
||||
Line line;
|
||||
bool intersect = false;
|
||||
} visitor(loc_to_line);
|
||||
|
||||
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
|
||||
return visitor.intersect;
|
||||
}
|
||||
|
||||
GroundingLocation Layer::getBestGroundingLocation
|
||||
(
|
||||
const Point& unsupported_location,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius,
|
||||
const SparseNodeGrid& tree_node_locator,
|
||||
const NodeSPtr& exclude_tree
|
||||
)
|
||||
{
|
||||
// Closest point on current_outlines to unsupported_location:
|
||||
Point node_location;
|
||||
{
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (const Polygon &contour : current_outlines)
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
|
||||
d2 = d;
|
||||
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
|
||||
}
|
||||
prev = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
|
||||
|
||||
NodeSPtr sub_tree{ nullptr };
|
||||
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
|
||||
const coord_t search_radius = std::min(current_dist, within_dist);
|
||||
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
|
||||
region.min /= locator_cell_size;
|
||||
region.max /= locator_cell_size;
|
||||
Point grid_addr;
|
||||
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
|
||||
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
|
||||
auto it_range = tree_node_locator.equal_range(grid_addr);
|
||||
for (auto it = it_range.first; it != it_range.second; ++ it) {
|
||||
auto candidate_sub_tree = it->second.lock();
|
||||
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
|
||||
if (candidate_dist < current_dist) {
|
||||
current_dist = candidate_dist;
|
||||
sub_tree = candidate_sub_tree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ! sub_tree ?
|
||||
GroundingLocation{ nullptr, node_location } :
|
||||
GroundingLocation{ sub_tree, std::optional<Point>() };
|
||||
}
|
||||
|
||||
bool Layer::attach(
|
||||
const Point& unsupported_location,
|
||||
const GroundingLocation& grounding_loc,
|
||||
NodeSPtr& new_child,
|
||||
NodeSPtr& new_root)
|
||||
{
|
||||
// Update trees & distance fields.
|
||||
if (grounding_loc.boundary_location) {
|
||||
new_root = Node::create(grounding_loc.p(), std::make_optional(grounding_loc.p()));
|
||||
new_child = new_root->addChild(unsupported_location);
|
||||
tree_roots.push_back(new_root);
|
||||
return true;
|
||||
} else {
|
||||
new_child = grounding_loc.tree_node->addChild(unsupported_location);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Layer::reconnectRoots
|
||||
(
|
||||
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
)
|
||||
{
|
||||
constexpr coord_t tree_connecting_ignore_offset = 100;
|
||||
|
||||
SparseNodeGrid tree_node_locator;
|
||||
fillLocator(tree_node_locator);
|
||||
|
||||
const coord_t within_max_dist = outline_locator.resolution() * 2;
|
||||
for (auto root_ptr : to_be_reconnected_tree_roots)
|
||||
{
|
||||
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
|
||||
|
||||
if (root_ptr->getLastGroundingLocation())
|
||||
{
|
||||
const Point& ground_loc = *root_ptr->getLastGroundingLocation();
|
||||
if (ground_loc != root_ptr->getLocation())
|
||||
{
|
||||
Point new_root_pt;
|
||||
// Find an intersection of the line segment from root_ptr->getLocation() to ground_loc, at within_max_dist from ground_loc.
|
||||
if (lineSegmentPolygonsIntersection(root_ptr->getLocation(), ground_loc, outline_locator, new_root_pt, within_max_dist)) {
|
||||
auto new_root = Node::create(new_root_pt, new_root_pt);
|
||||
root_ptr->addChild(new_root);
|
||||
new_root->reroot();
|
||||
|
||||
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
|
||||
|
||||
*old_root_it = std::move(new_root); // replace old root with new root
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const coord_t tree_connecting_ignore_width = wall_supporting_radius - tree_connecting_ignore_offset; // Ideally, the boundary size in which the valence rule is ignored would be configurable.
|
||||
GroundingLocation ground =
|
||||
getBestGroundingLocation
|
||||
(
|
||||
root_ptr->getLocation(),
|
||||
current_outlines,
|
||||
outline_locator,
|
||||
supporting_radius,
|
||||
tree_connecting_ignore_width,
|
||||
tree_node_locator,
|
||||
root_ptr
|
||||
);
|
||||
if (ground.boundary_location)
|
||||
{
|
||||
if (*ground.boundary_location == root_ptr->getLocation())
|
||||
continue; // Already on the boundary.
|
||||
|
||||
auto new_root = Node::create(ground.p(), ground.p());
|
||||
auto attach_ptr = root_ptr->closestNode(new_root->getLocation());
|
||||
attach_ptr->reroot();
|
||||
|
||||
new_root->addChild(attach_ptr);
|
||||
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
|
||||
|
||||
*old_root_it = std::move(new_root); // replace old root with new root
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ground.tree_node);
|
||||
assert(ground.tree_node != root_ptr);
|
||||
assert(!root_ptr->hasOffspring(ground.tree_node));
|
||||
assert(!ground.tree_node->hasOffspring(root_ptr));
|
||||
|
||||
auto attach_ptr = root_ptr->closestNode(ground.tree_node->getLocation());
|
||||
attach_ptr->reroot();
|
||||
|
||||
ground.tree_node->addChild(attach_ptr);
|
||||
|
||||
// remove old root
|
||||
*old_root_it = std::move(tree_roots.back());
|
||||
tree_roots.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||
*/
|
||||
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
Point ret = from;
|
||||
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||
unsigned int bestPoly = static_cast<unsigned int>(-1);
|
||||
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
const Polygon &poly = polygons[poly_idx];
|
||||
if (poly.size() < 2)
|
||||
continue;
|
||||
Point p0 = poly[poly.size() - 2];
|
||||
Point p1 = poly.back();
|
||||
// because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
|
||||
// to avoid integer rounding edge cases
|
||||
bool projected_p_beyond_prev_segment = (p1 - p0).cast<int64_t>().dot((from - p0).cast<int64_t>()) >= (p1 - p0).cast<int64_t>().squaredNorm();
|
||||
for (const Point& p2 : poly)
|
||||
{
|
||||
// X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
|
||||
// = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
|
||||
// X = P projected on AB
|
||||
const Point& a = p1;
|
||||
const Point& b = p2;
|
||||
const Point& p = from;
|
||||
Point ab = b - a;
|
||||
Point ap = p - a;
|
||||
int64_t ab_length2 = ab.cast<int64_t>().squaredNorm();
|
||||
if (ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
|
||||
{
|
||||
p1 = p2; //Skip only one of the points.
|
||||
continue;
|
||||
}
|
||||
int64_t dot_prod = ab.cast<int64_t>().dot(ap.cast<int64_t>());
|
||||
if (dot_prod <= 0) // x is projected to before ab
|
||||
{
|
||||
if (projected_p_beyond_prev_segment)
|
||||
{ // case which looks like: > .
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point& x = p1;
|
||||
|
||||
int64_t dist2 = (x - p).cast<int64_t>().squaredNorm();
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
bestPoly = poly_idx;
|
||||
if (distance == 0) {
|
||||
ret = x;
|
||||
} else {
|
||||
// inward direction irrespective of sign of [distance]
|
||||
Point inward_dir = perp((ab.cast<double>().normalized() * scaled<double>(10.0) + (p1 - p0).cast<double>().normalized() * scaled<double>(10.0)).eval()).cast<coord_t>();
|
||||
// MM2INT(10.0) to retain precision for the eventual normalization
|
||||
ret = x + (inward_dir.cast<double>().normalized() * distance).cast<coord_t>();
|
||||
is_already_on_correct_side_of_boundary = inward_dir.cast<int64_t>().dot((p - x).cast<int64_t>()) * distance >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
projected_p_beyond_prev_segment = false;
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (dot_prod >= ab_length2) // x is projected to beyond ab
|
||||
{
|
||||
projected_p_beyond_prev_segment = true;
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point x = a + ab * dot_prod / ab_length2;
|
||||
|
||||
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
bestPoly = poly_idx;
|
||||
if (distance == 0) { ret = x; }
|
||||
else
|
||||
{
|
||||
// inward or outward depending on the sign of [distance]
|
||||
Vec2d inward_dir = perp((ab.cast<double>().normalized() * distance).eval());
|
||||
ret = x + inward_dir.cast<coord_t>();
|
||||
is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast<double>()) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
|
||||
{
|
||||
if (bestDist2 < distance * distance)
|
||||
{
|
||||
from = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// from = from; // original point stays unaltered. It is already inside by enough distance
|
||||
}
|
||||
return bestPoly;
|
||||
}
|
||||
else if (bestDist2 < maxDist2)
|
||||
{
|
||||
from = ret;
|
||||
return bestPoly;
|
||||
}
|
||||
return static_cast<unsigned int>(-1);
|
||||
}
|
||||
|
||||
// Returns 'added someting'.
|
||||
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
|
||||
{
|
||||
if (tree_roots.empty())
|
||||
return {};
|
||||
|
||||
Polygons result_lines;
|
||||
for (const auto& tree : tree_roots) {
|
||||
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
|
||||
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
|
||||
constexpr coord_t epsilon = 5;
|
||||
Point should_be_inside = tree->getLocation();
|
||||
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
|
||||
if (inside(limit_to_outline, should_be_inside))
|
||||
tree->convertToPolylines(result_lines, line_width);
|
||||
}
|
||||
|
||||
// TODO: allow for polylines!
|
||||
Polylines split_lines;
|
||||
for (Polygon &line : result_lines) {
|
||||
if (line.size() <= 1)
|
||||
continue;
|
||||
Point last = line[0];
|
||||
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
|
||||
Point here = line[point_idx];
|
||||
split_lines.push_back({ last, here });
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
|
||||
return split_lines;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Lightning
|
88
src/libslic3r/Fill/Lightning/Layer.hpp
Normal file
88
src/libslic3r/Fill/Lightning/Layer.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_LAYER_H
|
||||
#define LIGHTNING_LAYER_H
|
||||
|
||||
#include "../../EdgeGrid.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
class Node;
|
||||
using NodeSPtr = std::shared_ptr<Node>;
|
||||
using SparseNodeGrid = std::unordered_multimap<Point, std::weak_ptr<Node>, PointHash>;
|
||||
|
||||
struct GroundingLocation
|
||||
{
|
||||
NodeSPtr tree_node; //!< not null if the gounding location is on a tree
|
||||
std::optional<Point> boundary_location; //!< in case the gounding location is on the boundary
|
||||
Point p() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* A layer of the lightning fill.
|
||||
*
|
||||
* Contains the trees to be printed and propagated to the next layer below.
|
||||
*/
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::vector<NodeSPtr> tree_roots;
|
||||
|
||||
void generateNewTrees
|
||||
(
|
||||
const Polygons& current_overhang,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
);
|
||||
|
||||
/*! Determine & connect to connection point in tree/outline.
|
||||
* \param min_dist_from_boundary_for_tree If the unsupported point is closer to the boundary than this then don't consider connecting it to a tree
|
||||
*/
|
||||
GroundingLocation getBestGroundingLocation
|
||||
(
|
||||
const Point& unsupported_location,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius,
|
||||
const SparseNodeGrid& tree_node_locator,
|
||||
const NodeSPtr& exclude_tree = nullptr
|
||||
);
|
||||
|
||||
/*!
|
||||
* \param[out] new_child The new child node introduced
|
||||
* \param[out] new_root The new root node if one had been made
|
||||
* \return Whether a new root was added
|
||||
*/
|
||||
bool attach(const Point& unsupported_location, const GroundingLocation& ground, NodeSPtr& new_child, NodeSPtr& new_root);
|
||||
|
||||
void reconnectRoots
|
||||
(
|
||||
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
);
|
||||
|
||||
Polylines convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
|
||||
|
||||
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
|
||||
|
||||
void fillLocator(SparseNodeGrid& tree_node_locator);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
#endif // LIGHTNING_LAYER_H
|
411
src/libslic3r/Fill/Lightning/TreeNode.cpp
Normal file
411
src/libslic3r/Fill/Lightning/TreeNode.cpp
Normal file
@ -0,0 +1,411 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../Geometry.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
coord_t Node::getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const
|
||||
{
|
||||
constexpr coord_t min_valence_for_boost = 0;
|
||||
constexpr coord_t max_valence_for_boost = 4;
|
||||
constexpr coord_t valence_boost_multiplier = 4;
|
||||
|
||||
const size_t valence = (!m_is_root) + m_children.size();
|
||||
const coord_t valence_boost = (min_valence_for_boost < valence && valence < max_valence_for_boost) ? valence_boost_multiplier * supporting_radius : 0;
|
||||
const auto dist_here = coord_t((getLocation() - unsupported_location).cast<double>().norm());
|
||||
return dist_here - valence_boost;
|
||||
}
|
||||
|
||||
bool Node::hasOffspring(const NodeSPtr& to_be_checked) const
|
||||
{
|
||||
if (to_be_checked == shared_from_this())
|
||||
return true;
|
||||
|
||||
for (auto& child_ptr : m_children)
|
||||
if (child_ptr->hasOffspring(to_be_checked))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NodeSPtr Node::addChild(const Point& child_loc)
|
||||
{
|
||||
assert(m_p != child_loc);
|
||||
NodeSPtr child = Node::create(child_loc);
|
||||
return addChild(child);
|
||||
}
|
||||
|
||||
NodeSPtr Node::addChild(NodeSPtr& new_child)
|
||||
{
|
||||
assert(new_child != shared_from_this());
|
||||
//assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio.
|
||||
m_children.push_back(new_child);
|
||||
new_child->m_parent = shared_from_this();
|
||||
new_child->m_is_root = false;
|
||||
return new_child;
|
||||
}
|
||||
|
||||
void Node::propagateToNextLayer(
|
||||
std::vector<NodeSPtr>& next_trees,
|
||||
const Polygons& next_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t prune_distance,
|
||||
const coord_t smooth_magnitude,
|
||||
const coord_t max_remove_colinear_dist) const
|
||||
{
|
||||
auto tree_below = deepCopy();
|
||||
tree_below->prune(prune_distance);
|
||||
tree_below->straighten(smooth_magnitude, max_remove_colinear_dist);
|
||||
if (tree_below->realign(next_outlines, outline_locator, next_trees))
|
||||
next_trees.push_back(tree_below);
|
||||
}
|
||||
|
||||
// NOTE: Depth-first, as currently implemented.
|
||||
// Skips the root (because that has no root itself), but all initial nodes will have the root point anyway.
|
||||
void Node::visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const
|
||||
{
|
||||
for (const auto& node : m_children) {
|
||||
assert(node->m_parent.lock() == shared_from_this());
|
||||
visitor(m_p, node->m_p);
|
||||
node->visitBranches(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Depth-first, as currently implemented.
|
||||
void Node::visitNodes(const std::function<void(NodeSPtr)>& visitor)
|
||||
{
|
||||
visitor(shared_from_this());
|
||||
for (const auto& node : m_children) {
|
||||
assert(node->m_parent.lock() == shared_from_this());
|
||||
node->visitNodes(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
Node::Node(const Point& p, const std::optional<Point>& last_grounding_location /*= std::nullopt*/) :
|
||||
m_is_root(true), m_p(p), m_last_grounding_location(last_grounding_location)
|
||||
{}
|
||||
|
||||
NodeSPtr Node::deepCopy() const
|
||||
{
|
||||
NodeSPtr local_root = Node::create(m_p);
|
||||
local_root->m_is_root = m_is_root;
|
||||
if (m_is_root)
|
||||
{
|
||||
local_root->m_last_grounding_location = m_last_grounding_location.value_or(m_p);
|
||||
}
|
||||
local_root->m_children.reserve(m_children.size());
|
||||
for (const auto& node : m_children)
|
||||
{
|
||||
NodeSPtr child = node->deepCopy();
|
||||
child->m_parent = local_root;
|
||||
local_root->m_children.push_back(child);
|
||||
}
|
||||
return local_root;
|
||||
}
|
||||
|
||||
void Node::reroot(NodeSPtr new_parent /*= nullptr*/)
|
||||
{
|
||||
if (! m_is_root) {
|
||||
auto old_parent = m_parent.lock();
|
||||
old_parent->reroot(shared_from_this());
|
||||
m_children.push_back(old_parent);
|
||||
}
|
||||
|
||||
if (new_parent) {
|
||||
m_children.erase(std::remove(m_children.begin(), m_children.end(), new_parent), m_children.end());
|
||||
m_is_root = false;
|
||||
m_parent = new_parent;
|
||||
} else {
|
||||
m_is_root = true;
|
||||
m_parent.reset();
|
||||
}
|
||||
}
|
||||
|
||||
NodeSPtr Node::closestNode(const Point& loc)
|
||||
{
|
||||
NodeSPtr result = shared_from_this();
|
||||
auto closest_dist2 = coord_t((m_p - loc).cast<double>().norm());
|
||||
|
||||
for (const auto& child : m_children) {
|
||||
NodeSPtr candidate_node = child->closestNode(loc);
|
||||
const auto child_dist2 = coord_t((candidate_node->m_p - loc).cast<double>().norm());
|
||||
if (child_dist2 < closest_dist2) {
|
||||
closest_dist2 = child_dist2;
|
||||
result = candidate_node;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool inside(const Polygons &polygons, const Point p)
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const Polygon &poly : polygons) {
|
||||
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
|
||||
if (is_inside_this_poly == -1)
|
||||
return true;
|
||||
poly_count_inside += is_inside_this_poly;
|
||||
}
|
||||
return (poly_count_inside % 2) == 1;
|
||||
}
|
||||
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist)
|
||||
{
|
||||
struct Visitor {
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
if (Vec2d ip; Geometry::segment_segment_intersection(segment.first.cast<double>(), segment.second.cast<double>(), this->line_a, this->line_b, ip))
|
||||
if (double d = (this->intersection_pt - this->line_b).squaredNorm(); d < d2min) {
|
||||
this->d2min = d;
|
||||
this->intersection_pt = ip;
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid& grid;
|
||||
Vec2d line_a;
|
||||
Vec2d line_b;
|
||||
Vec2d intersection_pt;
|
||||
double d2min { std::numeric_limits<double>::max() };
|
||||
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
|
||||
|
||||
outline_locator.visit_cells_intersecting_line(a, b, visitor);
|
||||
return visitor.d2min < within_max_dist * within_max_dist;
|
||||
}
|
||||
|
||||
bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts)
|
||||
{
|
||||
if (outlines.empty())
|
||||
return false;
|
||||
|
||||
if (inside(outlines, m_p)) {
|
||||
// Only keep children that have an unbroken connection to here, realign will put the rest in rerooted parts due to recursion:
|
||||
Point coll;
|
||||
bool reground_me = false;
|
||||
m_children.erase(std::remove_if(m_children.begin(), m_children.end(), [&](const NodeSPtr &child) {
|
||||
bool connect_branch = child->realign(outlines, outline_locator, rerooted_parts);
|
||||
// Find an intersection of the line segment from p to child->p, at maximum outline_locator.resolution() * 2 distance from p.
|
||||
if (connect_branch && lineSegmentPolygonsIntersection(child->m_p, m_p, outline_locator, coll, outline_locator.resolution() * 2)) {
|
||||
child->m_last_grounding_location.reset();
|
||||
child->m_parent.reset();
|
||||
child->m_is_root = true;
|
||||
rerooted_parts.push_back(child);
|
||||
reground_me = true;
|
||||
connect_branch = false;
|
||||
}
|
||||
return ! connect_branch;
|
||||
}), m_children.end());
|
||||
if (reground_me)
|
||||
m_last_grounding_location.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'Lift' any decendants out of this tree:
|
||||
for (auto& child : m_children)
|
||||
if (child->realign(outlines, outline_locator, rerooted_parts)) {
|
||||
child->m_last_grounding_location = m_p;
|
||||
child->m_parent.reset();
|
||||
child->m_is_root = true;
|
||||
rerooted_parts.push_back(child);
|
||||
}
|
||||
|
||||
m_children.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Node::straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist)
|
||||
{
|
||||
straighten(magnitude, m_p, 0, max_remove_colinear_dist * max_remove_colinear_dist);
|
||||
}
|
||||
|
||||
Node::RectilinearJunction Node::straighten(
|
||||
const coord_t magnitude,
|
||||
const Point& junction_above,
|
||||
const coord_t accumulated_dist,
|
||||
const coord_t max_remove_colinear_dist2)
|
||||
{
|
||||
constexpr coord_t junction_magnitude_factor_numerator = 3;
|
||||
constexpr coord_t junction_magnitude_factor_denominator = 4;
|
||||
|
||||
const coord_t junction_magnitude = magnitude * junction_magnitude_factor_numerator / junction_magnitude_factor_denominator;
|
||||
if (m_children.size() == 1)
|
||||
{
|
||||
auto child_p = m_children.front();
|
||||
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
|
||||
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
|
||||
Point a = junction_above;
|
||||
Point b = junction_below.junction_loc;
|
||||
if (a != b) // should always be true!
|
||||
{
|
||||
Point ab = b - a;
|
||||
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
|
||||
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
|
||||
m_p = destination;
|
||||
else
|
||||
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
|
||||
}
|
||||
{ // remove nodes on linear segments
|
||||
constexpr coord_t close_enough = 10;
|
||||
|
||||
child_p = m_children.front(); //recursive call to straighten might have removed the child
|
||||
const NodeSPtr& parent_node = m_parent.lock();
|
||||
if (parent_node &&
|
||||
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
|
||||
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
|
||||
child_p->m_parent = m_parent;
|
||||
for (auto& sibling : parent_node->m_children)
|
||||
{ // find this node among siblings
|
||||
if (sibling == shared_from_this())
|
||||
{
|
||||
sibling = child_p; // replace this node by child
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return junction_below;
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr coord_t weight = 1000;
|
||||
Point junction_moving_dir = ((junction_above - m_p).cast<double>().normalized() * weight).cast<coord_t>();
|
||||
bool prevent_junction_moving = false;
|
||||
for (auto& child_p : m_children)
|
||||
{
|
||||
const auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||
RectilinearJunction below = child_p->straighten(magnitude, m_p, child_dist, max_remove_colinear_dist2);
|
||||
|
||||
junction_moving_dir += ((below.junction_loc - m_p).cast<double>().normalized() * weight).cast<coord_t>();
|
||||
if (below.total_recti_dist < magnitude) // TODO: make configurable?
|
||||
{
|
||||
prevent_junction_moving = true; // prevent flipflopping in branches due to straightening and junctoin moving clashing
|
||||
}
|
||||
}
|
||||
if (junction_moving_dir != Point(0, 0) && ! m_children.empty() && ! m_is_root && ! prevent_junction_moving)
|
||||
{
|
||||
auto junction_moving_dir_len = coord_t(junction_moving_dir.norm());
|
||||
if (junction_moving_dir_len > junction_magnitude)
|
||||
{
|
||||
junction_moving_dir = junction_moving_dir * junction_magnitude / junction_moving_dir_len;
|
||||
}
|
||||
m_p += junction_moving_dir;
|
||||
}
|
||||
return RectilinearJunction{ accumulated_dist, m_p };
|
||||
}
|
||||
}
|
||||
|
||||
// Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||
coord_t Node::prune(const coord_t& pruning_distance)
|
||||
{
|
||||
if (pruning_distance <= 0)
|
||||
return 0;
|
||||
|
||||
coord_t max_distance_pruned = 0;
|
||||
for (auto child_it = m_children.begin(); child_it != m_children.end(); ) {
|
||||
auto& child = *child_it;
|
||||
coord_t dist_pruned_child = child->prune(pruning_distance);
|
||||
if (dist_pruned_child >= pruning_distance)
|
||||
{ // pruning is finished for child; dont modify further
|
||||
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child);
|
||||
++child_it;
|
||||
} else {
|
||||
const Point a = getLocation();
|
||||
const Point b = child->getLocation();
|
||||
const Point ba = a - b;
|
||||
const auto ab_len = coord_t(ba.cast<double>().norm());
|
||||
if (dist_pruned_child + ab_len <= pruning_distance) {
|
||||
// we're still in the process of pruning
|
||||
assert(child->m_children.empty() && "when pruning away a node all it's children must already have been pruned away");
|
||||
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child + ab_len);
|
||||
child_it = m_children.erase(child_it);
|
||||
} else {
|
||||
// pruning stops in between this node and the child
|
||||
const Point n = b + (ba.cast<double>().normalized() * (pruning_distance - dist_pruned_child)).cast<coord_t>();
|
||||
assert(std::abs((n - b).cast<double>().norm() + dist_pruned_child - pruning_distance) < 10 && "total pruned distance must be equal to the pruning_distance");
|
||||
max_distance_pruned = std::max(max_distance_pruned, pruning_distance);
|
||||
child->setLocation(n);
|
||||
++child_it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max_distance_pruned;
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
|
||||
{
|
||||
Polygons result;
|
||||
output.emplace_back();
|
||||
convertToPolylines(0, result);
|
||||
removeJunctionOverlap(result, line_width);
|
||||
append(output, std::move(result));
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
||||
{
|
||||
if (m_children.empty()) {
|
||||
output[long_line_idx].points.push_back(m_p);
|
||||
return;
|
||||
}
|
||||
size_t first_child_idx = rand() % m_children.size();
|
||||
m_children[first_child_idx]->convertToPolylines(long_line_idx, output);
|
||||
output[long_line_idx].points.push_back(m_p);
|
||||
|
||||
for (size_t idx_offset = 1; idx_offset < m_children.size(); idx_offset++) {
|
||||
size_t child_idx = (first_child_idx + idx_offset) % m_children.size();
|
||||
const Node& child = *m_children[child_idx];
|
||||
output.emplace_back();
|
||||
size_t child_line_idx = output.size() - 1;
|
||||
child.convertToPolylines(child_line_idx, output);
|
||||
output[child_line_idx].points.emplace_back(m_p);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
|
||||
{
|
||||
const coord_t reduction = line_width / 2; // TODO make configurable?
|
||||
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
|
||||
Polygon &polyline = *poly_it;
|
||||
if (polyline.size() <= 1) {
|
||||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
coord_t to_be_reduced = reduction;
|
||||
Point a = polyline.back();
|
||||
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
|
||||
const Point b = polyline[point_idx];
|
||||
const Point ab = b - a;
|
||||
const auto ab_len = coord_t(ab.cast<double>().norm());
|
||||
if (ab_len >= to_be_reduced) {
|
||||
polyline.points.back() = a + (ab.cast<double>() * (double(to_be_reduced) / ab_len)).cast<coord_t>();
|
||||
break;
|
||||
} else {
|
||||
to_be_reduced -= ab_len;
|
||||
polyline.points.pop_back();
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
|
||||
if (polyline.size() <= 1) {
|
||||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
} else
|
||||
++ poly_it;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
275
src/libslic3r/Fill/Lightning/TreeNode.hpp
Normal file
275
src/libslic3r/Fill/Lightning/TreeNode.hpp
Normal file
@ -0,0 +1,275 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_TREE_NODE_H
|
||||
#define LIGHTNING_TREE_NODE_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "../../EdgeGrid.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
constexpr auto locator_cell_size = scaled<coord_t>(4.);
|
||||
|
||||
class Node;
|
||||
|
||||
using NodeSPtr = std::shared_ptr<Node>;
|
||||
|
||||
// NOTE: As written, this struct will only be valid for a single layer, will have to be updated for the next.
|
||||
// NOTE: Reasons for implementing this with some separate closures:
|
||||
// - keep clear deliniation during development
|
||||
// - possibility of multiple distance field strategies
|
||||
|
||||
/*!
|
||||
* A single vertex of a Lightning Tree, the structure that determines the paths
|
||||
* to be printed to form Lightning Infill.
|
||||
*
|
||||
* In essence these vertices are just a position linked to other positions in
|
||||
* 2D. The nodes have a hierarchical structure of parents and children, forming
|
||||
* a tree. The class also has some helper functions specific to Lightning Infill
|
||||
* e.g. to straighten the paths around this node.
|
||||
*/
|
||||
class Node : public std::enable_shared_from_this<Node>
|
||||
{
|
||||
public:
|
||||
// Workaround for private/protected constructors and 'make_shared': https://stackoverflow.com/a/27832765
|
||||
template<typename ...Arg> NodeSPtr static create(Arg&&...arg)
|
||||
{
|
||||
struct EnableMakeShared : public Node
|
||||
{
|
||||
EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
|
||||
};
|
||||
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the position on this layer that this node represents, a vertex of the
|
||||
* path to print.
|
||||
* \return The position that this node represents.
|
||||
*/
|
||||
const Point& getLocation() const { return m_p; }
|
||||
|
||||
/*!
|
||||
* Change the position on this layer that the node represents.
|
||||
* \param p The position that the node needs to represent.
|
||||
*/
|
||||
void setLocation(const Point& p) { m_p = p; }
|
||||
|
||||
/*!
|
||||
* Construct a new ``Node`` instance and add it as a child of
|
||||
* this node.
|
||||
* \param p The location of the new node.
|
||||
* \return A shared pointer to the new node.
|
||||
*/
|
||||
NodeSPtr addChild(const Point& p);
|
||||
|
||||
/*!
|
||||
* Add an existing ``Node`` as a child of this node.
|
||||
* \param new_child The node that must be added as a child.
|
||||
* \return Always returns \p new_child.
|
||||
*/
|
||||
NodeSPtr addChild(NodeSPtr& new_child);
|
||||
|
||||
/*!
|
||||
* Propagate this node's sub-tree to the next layer.
|
||||
*
|
||||
* Creates a copy of this tree, realign it to the new layer boundaries
|
||||
* \p next_outlines and reduce (i.e. prune and straighten) it. A copy of
|
||||
* this node and all of its descendant nodes will be added to the
|
||||
* \p next_trees vector.
|
||||
* \param next_trees A collection of tree nodes to use for the next layer.
|
||||
* \param next_outlines The shape of the layer below, to make sure that the
|
||||
* tree stays within the bounds of the infill area.
|
||||
* \param prune_distance The maximum distance that a leaf node may be moved
|
||||
* such that it still supports the current node.
|
||||
* \param smooth_magnitude The maximum distance that a line may be shifted
|
||||
* to straighten the tree's paths, such that it still supports the current
|
||||
* paths.
|
||||
* \param max_remove_colinear_dist The maximum distance of a line-segment
|
||||
* from which straightening may remove a colinear point.
|
||||
*/
|
||||
void propagateToNextLayer
|
||||
(
|
||||
std::vector<NodeSPtr>& next_trees,
|
||||
const Polygons& next_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t prune_distance,
|
||||
const coord_t smooth_magnitude,
|
||||
const coord_t max_remove_colinear_dist
|
||||
) const;
|
||||
|
||||
/*!
|
||||
* Executes a given function for every line segment in this node's sub-tree.
|
||||
*
|
||||
* The function takes two `Point` arguments. These arguments will be filled
|
||||
* in with the higher-order node (closer to the root) first, and the
|
||||
* downtree node (closer to the leaves) as the second argument. The segment
|
||||
* from this node's parent to this node itself is not included.
|
||||
* The order in which the segments are visited is depth-first.
|
||||
* \param visitor A function to execute for every branch in the node's sub-
|
||||
* tree.
|
||||
*/
|
||||
void visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const;
|
||||
|
||||
/*!
|
||||
* Execute a given function for every node in this node's sub-tree.
|
||||
*
|
||||
* The visitor function takes a node as input. This node is not const, so
|
||||
* this can be used to change the tree.
|
||||
* Nodes are visited in depth-first order. This node itself is visited as
|
||||
* well (pre-order).
|
||||
* \param visitor A function to execute for every node in this node's sub-
|
||||
* tree.
|
||||
*/
|
||||
void visitNodes(const std::function<void(NodeSPtr)>& visitor);
|
||||
|
||||
/*!
|
||||
* Get a weighted distance from an unsupported point to this node (given the current supporting radius).
|
||||
*
|
||||
* When attaching a unsupported location to a node, not all nodes have the same priority.
|
||||
* (Eucludian) closer nodes are prioritised, but that's not the whole story.
|
||||
* For instance, we give some nodes a 'valence boost' depending on the nr. of branches.
|
||||
* \param unsupported_location The (unsuppported) location of which the weighted distance needs to be calculated.
|
||||
* \param supporting_radius The maximum distance which can be bridged without (infill) supporting it.
|
||||
* \return The weighted distance.
|
||||
*/
|
||||
coord_t getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const;
|
||||
|
||||
/*!
|
||||
* Returns whether this node is the root of a lightning tree. It is the root
|
||||
* if it has no parents.
|
||||
* \return ``true`` if this node is the root (no parents) or ``false`` if it
|
||||
* is a child node of some other node.
|
||||
*/
|
||||
bool isRoot() const { return m_is_root; }
|
||||
|
||||
/*!
|
||||
* Reverse the parent-child relationship all the way to the root, from this node onward.
|
||||
* This has the effect of 're-rooting' the tree at the current node if no immediate parent is given as argument.
|
||||
* That is, the current node will become the root, it's (former) parent if any, will become one of it's children.
|
||||
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
|
||||
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
|
||||
*/
|
||||
void reroot(NodeSPtr new_parent = nullptr);
|
||||
|
||||
/*!
|
||||
* Retrieves the closest node to the specified location.
|
||||
* \param loc The specified location.
|
||||
* \result The branch that starts at the position closest to the location within this tree.
|
||||
*/
|
||||
NodeSPtr closestNode(const Point& loc);
|
||||
|
||||
/*!
|
||||
* Returns whether the given tree node is a descendant of this node.
|
||||
*
|
||||
* If this node itself is given, it is also considered to be a descendant.
|
||||
* \param to_be_checked A node to find out whether it is a descendant of
|
||||
* this node.
|
||||
* \return ``true`` if the given node is a descendant or this node itself,
|
||||
* or ``false`` if it is not in the sub-tree.
|
||||
*/
|
||||
bool hasOffspring(const NodeSPtr& to_be_checked) const;
|
||||
|
||||
protected:
|
||||
Node() = delete; // Don't allow empty contruction
|
||||
|
||||
/*!
|
||||
* Construct a new node, either for insertion in a tree or as root.
|
||||
* \param p The physical location in the 2D layer that this node represents.
|
||||
* Connecting other nodes to this node indicates that a line segment should
|
||||
* be drawn between those two physical positions.
|
||||
*/
|
||||
Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
|
||||
|
||||
/*!
|
||||
* Copy this node and its entire sub-tree.
|
||||
* \return The equivalent of this node in the copy (the root of the new sub-
|
||||
* tree).
|
||||
*/
|
||||
NodeSPtr deepCopy() const;
|
||||
|
||||
/*! Reconnect trees from the layer above to the new outlines of the lower layer.
|
||||
* \return Wether or not the root is kept (false is no, true is yes).
|
||||
*/
|
||||
bool realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts);
|
||||
|
||||
struct RectilinearJunction
|
||||
{
|
||||
coord_t total_recti_dist; //!< rectilinear distance along the tree from the last junction above to the junction below
|
||||
Point junction_loc; //!< junction location below
|
||||
};
|
||||
|
||||
/*!
|
||||
* Smoothen the tree to make it a bit more printable, while still supporting
|
||||
* the trees above.
|
||||
* \param magnitude The maximum allowed distance to move the node.
|
||||
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
|
||||
*/
|
||||
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
|
||||
|
||||
/*! Recursive part of \ref straighten(.)
|
||||
* \param junction_above The last seen junction with multiple children above
|
||||
* \param accumulated_dist The distance along the tree from the last seen junction to this node
|
||||
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
|
||||
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
|
||||
*/
|
||||
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
|
||||
|
||||
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
|
||||
*/
|
||||
coord_t prune(const coord_t& distance);
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Convert the tree into polylines
|
||||
*
|
||||
* At each junction one line is chosen at random to continue
|
||||
*
|
||||
* The lines start at a leaf and end in a junction
|
||||
*
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(Polygons& output, const coord_t line_width) const;
|
||||
|
||||
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
|
||||
*
|
||||
* This needs to be known when roots are reconnected, so that the last (higher) layer is supported by the next one.
|
||||
*/
|
||||
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Convert the tree into polylines
|
||||
*
|
||||
* At each junction one line is chosen at random to continue
|
||||
*
|
||||
* The lines start at a leaf and end in a junction
|
||||
*
|
||||
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
|
||||
|
||||
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
|
||||
|
||||
bool m_is_root;
|
||||
Point m_p;
|
||||
std::weak_ptr<Node> m_parent;
|
||||
std::vector<NodeSPtr> m_children;
|
||||
|
||||
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
|
||||
};
|
||||
|
||||
bool inside(const Polygons &polygons, const Point p);
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
#endif // LIGHTNING_TREE_NODE_H
|
@ -2710,16 +2710,10 @@ namespace Slic3r {
|
||||
|
||||
bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items)
|
||||
{
|
||||
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
// This happens for empty projects
|
||||
#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
if (build_items.size() == 0) {
|
||||
add_error("No build item found");
|
||||
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
}
|
||||
|
||||
stream << " <" << BUILD_TAG << ">\n";
|
||||
|
@ -1544,30 +1544,37 @@ void GCode::process_layers(
|
||||
const size_t single_object_idx,
|
||||
GCodeOutputStream &output_stream)
|
||||
{
|
||||
// The pipeline is fixed: Neither wipe tower nor vase mode are implemented for sequential print.
|
||||
// The pipeline is variable: The vase mode filter is optional.
|
||||
size_t layer_to_print_idx = 0;
|
||||
tbb::parallel_pipeline(12,
|
||||
tbb::make_filter<void, GCode::LayerResult>(
|
||||
tbb::filter::serial_in_order,
|
||||
[this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> GCode::LayerResult {
|
||||
if (layer_to_print_idx == layers_to_print.size()) {
|
||||
fc.stop();
|
||||
return {};
|
||||
} else {
|
||||
LayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
|
||||
print.throw_if_canceled();
|
||||
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx);
|
||||
}
|
||||
}) &
|
||||
tbb::make_filter<GCode::LayerResult, std::string>(
|
||||
tbb::filter::serial_in_order,
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string {
|
||||
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
|
||||
}) &
|
||||
tbb::make_filter<std::string, void>(
|
||||
tbb::filter::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); }
|
||||
));
|
||||
const auto generator = tbb::make_filter<void, GCode::LayerResult>(tbb::filter::serial_in_order,
|
||||
[this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> GCode::LayerResult {
|
||||
if (layer_to_print_idx == layers_to_print.size()) {
|
||||
fc.stop();
|
||||
return {};
|
||||
} else {
|
||||
LayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
|
||||
print.throw_if_canceled();
|
||||
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx);
|
||||
}
|
||||
});
|
||||
const auto spiral_vase = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(tbb::filter::serial_in_order,
|
||||
[&spiral_vase = *this->m_spiral_vase.get()](GCode::LayerResult in)->GCode::LayerResult {
|
||||
spiral_vase.enable(in.spiral_vase_enable);
|
||||
return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
|
||||
});
|
||||
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(tbb::filter::serial_in_order,
|
||||
[&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string {
|
||||
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
|
||||
});
|
||||
const auto output = tbb::make_filter<std::string, void>(tbb::filter::serial_in_order,
|
||||
[&output_stream](std::string s) { output_stream.write(s); }
|
||||
);
|
||||
|
||||
// The pipeline elements are joined using const references, thus no copying is performed.
|
||||
if (m_spiral_vase)
|
||||
tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output);
|
||||
else
|
||||
tbb::parallel_pipeline(12, generator & cooling & output);
|
||||
}
|
||||
|
||||
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
|
||||
@ -1991,13 +1998,19 @@ GCode::LayerResult GCode::process_layer(
|
||||
// Either printing all copies of all objects, or just a single copy of a single object.
|
||||
assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
|
||||
|
||||
// First object, support and raft layer, if available.
|
||||
const Layer *object_layer = nullptr;
|
||||
const SupportLayer *support_layer = nullptr;
|
||||
const SupportLayer *raft_layer = nullptr;
|
||||
for (const LayerToPrint &l : layers) {
|
||||
if (l.object_layer != nullptr && object_layer == nullptr)
|
||||
if (l.object_layer && ! object_layer)
|
||||
object_layer = l.object_layer;
|
||||
if (l.support_layer != nullptr && support_layer == nullptr)
|
||||
support_layer = l.support_layer;
|
||||
if (l.support_layer) {
|
||||
if (! support_layer)
|
||||
support_layer = l.support_layer;
|
||||
if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers())
|
||||
raft_layer = support_layer;
|
||||
}
|
||||
}
|
||||
const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
|
||||
GCode::LayerResult result { {}, layer.id(), false, last_layer };
|
||||
@ -2399,7 +2412,7 @@ GCode::LayerResult GCode::process_layer(
|
||||
log_memory_info();
|
||||
|
||||
result.gcode = std::move(gcode);
|
||||
result.cooling_buffer_flush = object_layer || last_layer;
|
||||
result.cooling_buffer_flush = object_layer || raft_layer || last_layer;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2407,6 +2420,7 @@ void GCode::apply_print_config(const PrintConfig &print_config)
|
||||
{
|
||||
m_writer.apply_print_config(print_config);
|
||||
m_config.apply(print_config);
|
||||
m_scaled_resolution = scaled<double>(print_config.gcode_resolution.value);
|
||||
}
|
||||
|
||||
void GCode::append_full_config(const Print &print, std::string &str)
|
||||
@ -2558,7 +2572,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
|
||||
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
|
||||
// description += ExtrusionLoop::role_to_string(loop.loop_role());
|
||||
// description += ExtrusionEntity::role_to_string(path->role);
|
||||
path->simplify(SCALED_RESOLUTION);
|
||||
path->simplify(m_scaled_resolution);
|
||||
gcode += this->_extrude(*path, description, speed);
|
||||
}
|
||||
|
||||
@ -2612,7 +2626,7 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
|
||||
for (ExtrusionPath path : multipath.paths) {
|
||||
// description += ExtrusionLoop::role_to_string(loop.loop_role());
|
||||
// description += ExtrusionEntity::role_to_string(path->role);
|
||||
path.simplify(SCALED_RESOLUTION);
|
||||
path.simplify(m_scaled_resolution);
|
||||
gcode += this->_extrude(path, description, speed);
|
||||
}
|
||||
if (m_wipe.enable) {
|
||||
@ -2640,7 +2654,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
|
||||
std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)
|
||||
{
|
||||
// description += ExtrusionEntity::role_to_string(path.role());
|
||||
path.simplify(SCALED_RESOLUTION);
|
||||
path.simplify(m_scaled_resolution);
|
||||
std::string gcode = this->_extrude(path, description, speed);
|
||||
if (m_wipe.enable) {
|
||||
m_wipe.path = std::move(path.polyline);
|
||||
|
@ -345,6 +345,8 @@ private:
|
||||
methods. */
|
||||
Vec2d m_origin;
|
||||
FullPrintConfig m_config;
|
||||
// scaled G-code resolution
|
||||
double m_scaled_resolution;
|
||||
GCodeWriter m_writer;
|
||||
PlaceholderParser m_placeholder_parser;
|
||||
// For random number generator etc.
|
||||
|
@ -254,7 +254,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
|
||||
};
|
||||
|
||||
std::vector<Intersection> new_intersections = intersections;
|
||||
if (!intersections.empty() && !start_lines.empty()) {
|
||||
if (!new_intersections.empty() && !start_lines.empty()) {
|
||||
size_t cl_start_idx = get_closer(start_lines, new_intersections.front(), start);
|
||||
if (cl_start_idx != std::numeric_limits<size_t>::max()) {
|
||||
// If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine.
|
||||
@ -265,11 +265,13 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
|
||||
// vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which
|
||||
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
|
||||
// use the first one, which is the closest one to the start point.
|
||||
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, intersections, true);
|
||||
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, new_intersections, true);
|
||||
const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits<size_t>::max()) ? start_lines[start_closest_lines_idx] : start_lines.front();
|
||||
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
|
||||
}
|
||||
} else if (!intersections.empty() && !end_lines.empty()) {
|
||||
}
|
||||
|
||||
if (!new_intersections.empty() && !end_lines.empty()) {
|
||||
size_t cl_end_idx = get_closer(end_lines, new_intersections.back(), end);
|
||||
if (cl_end_idx != std::numeric_limits<size_t>::max()) {
|
||||
// If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine.
|
||||
@ -280,7 +282,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
|
||||
// vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which
|
||||
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
|
||||
// use the first one, which is the closest one to the end point.
|
||||
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, intersections, false);
|
||||
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, new_intersections, false);
|
||||
const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits<size_t>::max()) ? end_lines[end_closest_lines_idx] : end_lines.front();
|
||||
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
|
||||
}
|
||||
|
@ -748,9 +748,7 @@ const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProces
|
||||
{ EProducer::PrusaSlicer, "generated by PrusaSlicer" },
|
||||
{ EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" },
|
||||
{ EProducer::Slic3r, "generated by Slic3r" },
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
{ EProducer::SuperSlicer, "generated by SuperSlicer" },
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
{ EProducer::Cura, "Cura_SteamEngine" },
|
||||
{ EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" },
|
||||
{ EProducer::CraftWare, "CraftWare" },
|
||||
@ -813,9 +811,7 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i
|
||||
}
|
||||
|
||||
GCodeProcessor::GCodeProcessor()
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
: m_options_z_corrector(m_result)
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
{
|
||||
reset();
|
||||
m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n";
|
||||
@ -1179,9 +1175,7 @@ void GCodeProcessor::reset()
|
||||
m_use_volumetric_e = false;
|
||||
m_last_default_color_id = 0;
|
||||
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
m_options_z_corrector.reset();
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_mm3_per_mm_compare.reset();
|
||||
@ -1238,10 +1232,8 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
||||
}
|
||||
else if (m_producer == EProducer::Simplify3D)
|
||||
apply_config_simplify3d(filename);
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
else if (m_producer == EProducer::SuperSlicer)
|
||||
apply_config_superslicer(filename);
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
}
|
||||
|
||||
// process gcode
|
||||
@ -1375,7 +1367,6 @@ std::vector<std::pair<ExtrusionRole, float>> GCodeProcessor::get_roles_time(Prin
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
ConfigSubstitutions load_from_superslicer_gcode_file(const std::string& filename, DynamicPrintConfig& config, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// for reference, see: ConfigBase::load_from_gcode_file()
|
||||
@ -1408,7 +1399,6 @@ void GCodeProcessor::apply_config_superslicer(const std::string& filename)
|
||||
load_from_superslicer_gcode_file(filename, config, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
apply_config(config);
|
||||
}
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
|
||||
std::vector<float> GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const
|
||||
{
|
||||
@ -1845,9 +1835,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
store_move_vertex(EMoveType::Color_change);
|
||||
CustomGCode::Item item = { static_cast<double>(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" };
|
||||
m_result.custom_gcode_per_print_z.emplace_back(item);
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
m_options_z_corrector.set();
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
process_custom_gcode_time(CustomGCode::ColorChange);
|
||||
process_filaments(CustomGCode::ColorChange);
|
||||
}
|
||||
@ -1860,9 +1848,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
store_move_vertex(EMoveType::Pause_Print);
|
||||
CustomGCode::Item item = { static_cast<double>(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" };
|
||||
m_result.custom_gcode_per_print_z.emplace_back(item);
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
m_options_z_corrector.set();
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
process_custom_gcode_time(CustomGCode::PausePrint);
|
||||
return;
|
||||
}
|
||||
@ -1872,9 +1858,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
store_move_vertex(EMoveType::Custom_GCode);
|
||||
CustomGCode::Item item = { static_cast<double>(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" };
|
||||
m_result.custom_gcode_per_print_z.emplace_back(item);
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
m_options_z_corrector.set();
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1900,9 +1884,7 @@ bool GCodeProcessor::process_producers_tags(const std::string_view comment)
|
||||
{
|
||||
case EProducer::Slic3rPE:
|
||||
case EProducer::Slic3r:
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
case EProducer::SuperSlicer:
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); }
|
||||
case EProducer::Cura: { return process_cura_tags(comment); }
|
||||
case EProducer::Simplify3D: { return process_simplify3d_tags(comment); }
|
||||
@ -2455,12 +2437,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
if (m_forced_height > 0.0f)
|
||||
m_height = m_forced_height;
|
||||
else {
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON) {
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON)
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
#if !ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
#endif // !ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
}
|
||||
}
|
||||
|
||||
if (m_height == 0.0f)
|
||||
@ -2469,10 +2447,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
if (m_end_position[Z] == 0.0f)
|
||||
m_end_position[Z] = m_height;
|
||||
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
m_options_z_corrector.update(m_height);
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_height_compare.update(m_height, m_extrusion_role);
|
||||
|
@ -389,7 +389,6 @@ namespace Slic3r {
|
||||
bool has_first_vertex() const { return m_first_vertex.has_value(); }
|
||||
};
|
||||
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
// Helper class used to fix the z for color change, pause print and
|
||||
// custom gcode markes
|
||||
class OptionsZCorrector
|
||||
@ -426,7 +425,6 @@ namespace Slic3r {
|
||||
m_custom_gcode_per_print_z_id.reset();
|
||||
}
|
||||
};
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
struct DataChecker
|
||||
@ -532,9 +530,7 @@ namespace Slic3r {
|
||||
CpColor m_cp_color;
|
||||
bool m_use_volumetric_e;
|
||||
SeamsDetector m_seams_detector;
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
OptionsZCorrector m_options_z_corrector;
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
size_t m_last_default_color_id;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
|
||||
@ -546,9 +542,7 @@ namespace Slic3r {
|
||||
PrusaSlicer,
|
||||
Slic3rPE,
|
||||
Slic3r,
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
SuperSlicer,
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
Cura,
|
||||
Simplify3D,
|
||||
CraftWare,
|
||||
@ -605,9 +599,7 @@ namespace Slic3r {
|
||||
private:
|
||||
void apply_config(const DynamicPrintConfig& config);
|
||||
void apply_config_simplify3d(const std::string& filename);
|
||||
#if ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
void apply_config_superslicer(const std::string& filename);
|
||||
#endif // ENABLE_FIX_SUPERSLICER_GCODE_IMPORT
|
||||
void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled);
|
||||
|
||||
// Process tags embedded into comments
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@ -184,6 +185,11 @@ static int run_script(const std::string &script, const std::string &gcode, std::
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) (s)
|
||||
#define _(s) Slic3r::I18N::translate(s)
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
@ -278,6 +284,15 @@ bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::
|
||||
delete_copy();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
if (! boost::filesystem::exists(gcode_file)) {
|
||||
const std::string msg = (boost::format(_(L(
|
||||
"Post-processing script %1% failed.\n\n"
|
||||
"The post-processing script is expected to change the G-code file %2% in place, but the G-code file was deleted and likely saved under a new name.\n"
|
||||
"Please adjust the post-processing script to change the G-code in place and consult the manual on how to optionally rename the post-processed G-code file.\n")))
|
||||
% script % path).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boost::filesystem::exists(path_output_name)) {
|
||||
|
@ -45,7 +45,7 @@ void Layer::make_slices()
|
||||
Polygons slices_p;
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
polygons_append(slices_p, to_polygons(layerm->slices.surfaces));
|
||||
slices = union_ex(slices_p);
|
||||
slices = union_safety_offset_ex(slices_p);
|
||||
}
|
||||
|
||||
this->lslices.clear();
|
||||
|
@ -148,7 +148,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
void make_perimeters();
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); };
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree);
|
||||
void make_ironing();
|
||||
|
||||
|
@ -160,13 +160,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||
if (!result)
|
||||
throw Slic3r::RuntimeError("Loading of a model file failed.");
|
||||
|
||||
#if !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
if (model.objects.empty())
|
||||
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
|
||||
#endif // !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
{
|
||||
for (ModelObject *o : model.objects) {
|
||||
// if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
// {
|
||||
// // we remove the .zip part of the extension to avoid it be added to filenames when exporting
|
||||
|
@ -322,7 +322,7 @@ void PerimeterGenerator::process()
|
||||
for (const Surface &surface : this->slices->surfaces) {
|
||||
// detect how many perimeters must be generated for this island
|
||||
int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(m_scaled_resolution));
|
||||
ExPolygons gaps;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
@ -533,7 +533,7 @@ void PerimeterGenerator::process()
|
||||
// simplify infill contours according to resolution
|
||||
Polygons pp;
|
||||
for (ExPolygon &ex : last)
|
||||
ex.simplify_p(SCALED_RESOLUTION, &pp);
|
||||
ex.simplify_p(m_scaled_resolution, &pp);
|
||||
// collapse too narrow infill areas
|
||||
coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
||||
// append infill areas to fill_surfaces
|
||||
|
@ -50,6 +50,7 @@ public:
|
||||
overhang_flow(flow), solid_infill_flow(flow),
|
||||
config(config), object_config(object_config), print_config(print_config),
|
||||
m_spiral_vase(spiral_vase),
|
||||
m_scaled_resolution(scaled<double>(print_config->gcode_resolution.value)),
|
||||
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
|
||||
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1)
|
||||
{}
|
||||
@ -63,6 +64,7 @@ public:
|
||||
|
||||
private:
|
||||
bool m_spiral_vase;
|
||||
double m_scaled_resolution;
|
||||
double m_ext_mm3_per_mm;
|
||||
double m_mm3_per_mm;
|
||||
double m_mm3_per_mm_overhang;
|
||||
|
@ -255,7 +255,7 @@ namespace int128 {
|
||||
// To be used by std::unordered_map, std::unordered_multimap and friends.
|
||||
struct PointHash {
|
||||
size_t operator()(const Vec2crd &pt) const {
|
||||
return std::hash<coord_t>()(pt.x()) ^ std::hash<coord_t>()(pt.y());
|
||||
return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -444,7 +444,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
|
||||
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
|
@ -214,7 +214,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
} else if (
|
||||
opt_key == "first_layer_extrusion_width"
|
||||
|| opt_key == "min_layer_height"
|
||||
|| opt_key == "max_layer_height") {
|
||||
|| opt_key == "max_layer_height"
|
||||
|| opt_key == "gcode_resolution") {
|
||||
osteps.emplace_back(posPerimeters);
|
||||
osteps.emplace_back(posInfill);
|
||||
osteps.emplace_back(posSupportMaterial);
|
||||
|
@ -613,13 +613,25 @@ const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegi
|
||||
}
|
||||
|
||||
// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id.
|
||||
const PrintObjectRegions::BoundingBox* find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id)
|
||||
PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id)
|
||||
{
|
||||
// Find the top-most printable volume of this modifier, or the printable volume itself.
|
||||
int parent_region_id = this_region_id;
|
||||
for (; ! layer_range.volume_regions[parent_region_id].model_volume->is_model_part(); parent_region_id = layer_range.volume_regions[parent_region_id].parent)
|
||||
assert(parent_region_id >= 0);
|
||||
return find_volume_extents(layer_range, *layer_range.volume_regions[parent_region_id].model_volume);
|
||||
const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id];
|
||||
const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume);
|
||||
assert(this_extents);
|
||||
PrintObjectRegions::BoundingBox out { *this_extents };
|
||||
if (! this_region.model_volume->is_model_part())
|
||||
for (int parent_region_id = this_region.parent;;) {
|
||||
assert(parent_region_id >= 0);
|
||||
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume);
|
||||
assert(parent_extents);
|
||||
out.extend(*parent_extents);
|
||||
if (parent_region.model_volume->is_model_part())
|
||||
break;
|
||||
parent_region_id = parent_region.parent;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders);
|
||||
@ -662,11 +674,10 @@ bool verify_update_print_object_regions(
|
||||
// if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed,
|
||||
// which may require new regions to be created.
|
||||
it_model_volume_modifier_last = it_model_volume;
|
||||
int this_region_id = int(®ion - layer_range.volume_regions.data());
|
||||
int next_region_id = this_region_id + 1;
|
||||
int next_region_id = int(®ion - layer_range.volume_regions.data());
|
||||
const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume);
|
||||
assert(bbox);
|
||||
for (int parent_region_id = this_region_id - 1; parent_region_id >= 0; -- parent_region_id) {
|
||||
for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) {
|
||||
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
assert(parent_region.model_volume != region.model_volume);
|
||||
if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
|
||||
@ -680,17 +691,13 @@ bool verify_update_print_object_regions(
|
||||
layer_range.volume_regions[next_region_id].parent == parent_region_id) {
|
||||
// A parent region is already overridden.
|
||||
++ next_region_id;
|
||||
} else {
|
||||
} else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox))
|
||||
// Such parent region does not exist. If it is needed, then we need to reslice.
|
||||
const PrintObjectRegions::BoundingBox *parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id);
|
||||
assert(parent_bbox != nullptr);
|
||||
if (parent_bbox->intersects(*bbox))
|
||||
// Only create new region for a modifier, which actually modifies config of it's parent.
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders);
|
||||
config != parent_region.region->config())
|
||||
// This modifier newly overrides a region, which it did not before. We need to reslice.
|
||||
return false;
|
||||
}
|
||||
// Only create new region for a modifier, which actually modifies config of it's parent.
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders);
|
||||
config != parent_region.region->config())
|
||||
// This modifier newly overrides a region, which it did not before. We need to reslice.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -912,10 +919,8 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) {
|
||||
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
const ModelVolume &parent_volume = *parent_region.model_volume;
|
||||
if (parent_volume.is_model_part() || parent_volume.is_modifier()) {
|
||||
const PrintObjectRegions::BoundingBox *parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id);
|
||||
assert(parent_bbox != nullptr);
|
||||
if (parent_bbox->intersects(*bbox))
|
||||
if (parent_volume.is_model_part() || parent_volume.is_modifier())
|
||||
if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) {
|
||||
// Only create new region for a modifier, which actually modifies config of it's parent.
|
||||
if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders);
|
||||
config != parent_region.region->config()) {
|
||||
@ -923,7 +928,7 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox });
|
||||
} else if (parent_model_part_id == -1 && parent_volume.is_model_part())
|
||||
parent_model_part_id = parent_region_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! added && parent_model_part_id >= 0)
|
||||
// This modifier does not override any printable volume's configuration, however it may in the future.
|
||||
|
@ -72,7 +72,8 @@ static t_config_enum_values s_keys_map_PrintHostType {
|
||||
{ "duet", htDuet },
|
||||
{ "flashair", htFlashAir },
|
||||
{ "astrobox", htAstroBox },
|
||||
{ "repetier", htRepetier }
|
||||
{ "repetier", htRepetier },
|
||||
{ "mks", htMKS }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
|
||||
|
||||
@ -106,7 +107,10 @@ static t_config_enum_values s_keys_map_InfillPattern {
|
||||
{ "archimedeanchords", ipArchimedeanChords },
|
||||
{ "octagramspiral", ipOctagramSpiral },
|
||||
{ "adaptivecubic", ipAdaptiveCubic },
|
||||
{ "supportcubic", ipSupportCubic }
|
||||
{ "supportcubic", ipSupportCubic },
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
{ "lightning", ipLightning }
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern)
|
||||
|
||||
@ -1134,6 +1138,9 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_values.push_back("octagramspiral");
|
||||
def->enum_values.push_back("adaptivecubic");
|
||||
def->enum_values.push_back("supportcubic");
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
def->enum_values.push_back("lightning");
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Aligned Rectilinear"));
|
||||
def->enum_labels.push_back(L("Grid"));
|
||||
@ -1150,6 +1157,9 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
def->enum_labels.push_back(L("Adaptive Cubic"));
|
||||
def->enum_labels.push_back(L("Support Cubic"));
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
def->enum_labels.push_back(L("Lightning"));
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
|
||||
|
||||
def = this->add("first_layer_acceleration", coFloat);
|
||||
@ -1854,12 +1864,14 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_values.push_back("flashair");
|
||||
def->enum_values.push_back("astrobox");
|
||||
def->enum_values.push_back("repetier");
|
||||
def->enum_values.push_back("mks");
|
||||
def->enum_labels.push_back("PrusaLink");
|
||||
def->enum_labels.push_back("OctoPrint");
|
||||
def->enum_labels.push_back("Duet");
|
||||
def->enum_labels.push_back("FlashAir");
|
||||
def->enum_labels.push_back("AstroBox");
|
||||
def->enum_labels.push_back("Repetier");
|
||||
def->enum_labels.push_back("MKS");
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
||||
@ -2069,7 +2081,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionInt(0));
|
||||
|
||||
def = this->add("resolution", coFloat);
|
||||
def->label = L("Resolution");
|
||||
def->label = L("Slice resolution");
|
||||
def->tooltip = L("Minimum detail resolution, used to simplify the input file for speeding up "
|
||||
"the slicing job and reducing memory usage. High-resolution models often carry "
|
||||
"more detail than printers can render. Set to zero to disable any simplification "
|
||||
@ -2079,6 +2091,18 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
def = this->add("gcode_resolution", coFloat);
|
||||
def->label = L("G-code resolution");
|
||||
def->tooltip = L("Maximum deviation of exported G-code paths from their full resolution counterparts. "
|
||||
"Very high resolution G-code requires huge amount of RAM to slice and preview, "
|
||||
"also a 3D printer may stutter not being able to process a high resolution G-code in a timely manner. "
|
||||
"On the other hand, a low resolution G-code will produce a low poly effect and because "
|
||||
"the G-code reduction is performed at each layer independently, visible artifacts may be produced.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.0125));
|
||||
|
||||
def = this->add("retract_before_travel", coFloats);
|
||||
def->label = L("Minimum travel after retraction");
|
||||
def->tooltip = L("Retraction is not triggered when travel moves are shorter than this length.");
|
||||
@ -2688,7 +2712,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->enum_labels.push_back(L("Concentric"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear));
|
||||
def->set_default_value(new ConfigOptionEnum<SupportMaterialInterfacePattern>(smipRectilinear));
|
||||
|
||||
def = this->add("support_material_spacing", coFloat);
|
||||
def->label = L("Pattern spacing");
|
||||
@ -3164,7 +3188,7 @@ void PrintConfigDef::init_sla_params()
|
||||
|
||||
def = this->add("relative_correction_y", coFloat);
|
||||
def->label = L("Printer scaling correction in Y axis");
|
||||
def->full_label = L("Printer scaling X axis correction");
|
||||
def->full_label = L("Printer scaling Y axis correction");
|
||||
def->tooltip = L("Printer scaling correction in Y axis");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
@ -3172,7 +3196,7 @@ void PrintConfigDef::init_sla_params()
|
||||
|
||||
def = this->add("relative_correction_z", coFloat);
|
||||
def->label = L("Printer scaling correction in Z axis");
|
||||
def->full_label = L("Printer scaling X axis correction");
|
||||
def->full_label = L("Printer scaling Z axis correction");
|
||||
def->tooltip = L("Printer scaling correction in Z axis");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
@ -3937,6 +3961,10 @@ void DynamicPrintConfig::normalize_fdm()
|
||||
this->opt<ConfigOptionPercent>("fill_density", true)->value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto *opt_gcode_resolution = this->opt<ConfigOptionFloat>("gcode_resolution", false); opt_gcode_resolution)
|
||||
// Resolution will be above 1um.
|
||||
opt_gcode_resolution->value = std::max(opt_gcode_resolution->value, 0.001);
|
||||
}
|
||||
|
||||
void handle_legacy_sla(DynamicPrintConfig &config)
|
||||
|
@ -44,7 +44,7 @@ enum class MachineLimitsUsage {
|
||||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
|
||||
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
@ -57,9 +57,15 @@ enum class FuzzySkinType {
|
||||
All,
|
||||
};
|
||||
|
||||
#define HAS_LIGHTNING_INFILL 0
|
||||
|
||||
enum InfillPattern : int {
|
||||
ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipCount,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
|
||||
#if HAS_LIGHTNING_INFILL
|
||||
ipLightning,
|
||||
#endif // HAS_LIGHTNING_INFILL
|
||||
ipCount,
|
||||
};
|
||||
|
||||
enum class IroningType {
|
||||
@ -734,6 +740,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionString, printer_model))
|
||||
((ConfigOptionString, printer_notes))
|
||||
((ConfigOptionFloat, resolution))
|
||||
((ConfigOptionFloat, gcode_resolution))
|
||||
((ConfigOptionFloats, retract_before_travel))
|
||||
((ConfigOptionBools, retract_layer_change))
|
||||
((ConfigOptionFloat, skirt_distance))
|
||||
|
@ -379,11 +379,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
||||
int j = i;
|
||||
bool merged = false;
|
||||
ExPolygons &expolygons = temp_slices[i].expolygons;
|
||||
for (++ j;
|
||||
j < int(temp_slices.size()) &&
|
||||
temp_slices[i].region_id == temp_slices[j].region_id &&
|
||||
(clip_multipart_objects || temp_slices[i].volume_id == temp_slices[j].volume_id);
|
||||
++ j)
|
||||
for (++ j; j < int(temp_slices.size()) && temp_slices[i].region_id == temp_slices[j].region_id; ++ j)
|
||||
if (ExPolygons &expolygons2 = temp_slices[j].expolygons; ! expolygons2.empty()) {
|
||||
if (expolygons.empty()) {
|
||||
expolygons = std::move(expolygons2);
|
||||
@ -392,7 +388,10 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
if (merged)
|
||||
// Don't unite the regions if ! clip_multipart_objects. In that case it is user's responsibility
|
||||
// to handle region overlaps. Indeed, one may intentionally let the regions overlap to produce crossing perimeters
|
||||
// for example.
|
||||
if (merged && clip_multipart_objects)
|
||||
expolygons = closing_ex(expolygons, float(scale_(EPSILON)));
|
||||
slices_by_region[temp_slices[i].region_id][z_idx] = std::move(expolygons);
|
||||
i = j;
|
||||
|
@ -1480,7 +1480,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
|
||||
overhang_polygons = to_polygons(layer.lslices);
|
||||
#endif
|
||||
// Expand for better stability.
|
||||
contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
|
||||
contact_polygons = object_config.raft_expansion.value > 0 ? expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value)) : overhang_polygons;
|
||||
}
|
||||
else if (! layer.regions().empty())
|
||||
{
|
||||
@ -2354,8 +2354,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
||||
Polygons &layer_support_area = layer_support_areas[layer_id];
|
||||
Polygons *layer_buildplate_covered = buildplate_covered.empty() ? nullptr : &buildplate_covered[layer_id];
|
||||
// Filtering the propagated support columns to two extrusions, overlapping by maximum 20%.
|
||||
float column_propagation_filtering_radius = scaled<float>(0.8 * 0.5 * (m_support_params.support_material_flow.spacing() + m_support_params.support_material_flow.width()));
|
||||
task_group.run([&grid_params, &overhangs_projection, &overhangs_projection_raw, &layer, &layer_support_area, layer_buildplate_covered, column_propagation_filtering_radius
|
||||
// float column_propagation_filtering_radius = scaled<float>(0.8 * 0.5 * (m_support_params.support_material_flow.spacing() + m_support_params.support_material_flow.width()));
|
||||
task_group.run([&grid_params, &overhangs_projection, &overhangs_projection_raw, &layer, &layer_support_area, layer_buildplate_covered /* , column_propagation_filtering_radius */
|
||||
#ifdef SLIC3R_DEBUG
|
||||
, iRun, layer_id
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
@ -36,43 +36,6 @@
|
||||
#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS 1
|
||||
|
||||
|
||||
//====================
|
||||
// 2.4.0.alpha1 techs
|
||||
//====================
|
||||
#define ENABLE_2_4_0_ALPHA1 1
|
||||
|
||||
// Enable implementation of retract acceleration in gcode processor
|
||||
#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA1)
|
||||
// Enable rendering seams (and other options) in preview using models
|
||||
#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA1)
|
||||
// Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects
|
||||
#define ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED (1 && ENABLE_2_4_0_ALPHA1)
|
||||
|
||||
|
||||
//====================
|
||||
// 2.4.0.alpha2 techs
|
||||
//====================
|
||||
#define ENABLE_2_4_0_ALPHA2 1
|
||||
|
||||
// Enable rendering seams (and other options) in preview using batched models on systems not supporting OpenGL 3.3
|
||||
#define ENABLE_SEAMS_USING_BATCHED_MODELS (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_2_4_0_ALPHA2)
|
||||
// Enable fixing the z position of color change, pause print and custom gcode markers in preview
|
||||
#define ENABLE_FIX_PREVIEW_OPTIONS_Z (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_2_4_0_ALPHA2)
|
||||
// Enable replacing a missing file during reload from disk command
|
||||
#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA2)
|
||||
// Enable fixing the synchronization of seams with the horizontal slider in preview
|
||||
#define ENABLE_FIX_SEAMS_SYNCH (1 && ENABLE_2_4_0_ALPHA2)
|
||||
|
||||
|
||||
//====================
|
||||
// 2.4.0.alpha3 techs
|
||||
//====================
|
||||
#define ENABLE_2_4_0_ALPHA3 1
|
||||
|
||||
// Enable fixing loading of gcode files generated with SuperSlicer in GCodeViewer
|
||||
#define ENABLE_FIX_SUPERSLICER_GCODE_IMPORT (1 && ENABLE_2_4_0_ALPHA3)
|
||||
|
||||
|
||||
//====================
|
||||
// 2.4.0.beta1 techs
|
||||
//====================
|
||||
@ -91,5 +54,8 @@
|
||||
// an additional button can be used to set the keyboard focus into the slider
|
||||
// to allow the user to type in the desired value
|
||||
#define ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT (1 && ENABLE_2_4_0_BETA2)
|
||||
// Enable fit print volume command for circular printbeds
|
||||
#define ENABLE_ENHANCED_PRINT_VOLUME_FIT (1 && ENABLE_2_4_0_BETA2)
|
||||
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
@ -1090,7 +1090,7 @@ indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
|
||||
centroid += pt;
|
||||
centroid /= float(pts.size());
|
||||
#endif // NDEBUG
|
||||
for (const orgQhull::QhullFacet facet : qhull.facetList()) {
|
||||
for (const orgQhull::QhullFacet &facet : qhull.facetList()) {
|
||||
// Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID.
|
||||
Vec3i indices;
|
||||
int cnt = 0;
|
||||
|
@ -560,9 +560,9 @@ void slice_facet_with_slabs(
|
||||
// Save the open edge for sure.
|
||||
type = FacetSliceType::Slicing;
|
||||
} else {
|
||||
#ifndef NDEBUG
|
||||
const stl_triangle_vertex_indices &neighbor = mesh_triangles[neighbor_idx];
|
||||
float z = *it;
|
||||
#ifndef NDEBUG
|
||||
int num_on_plane = (mesh_vertices[neighbor(0)].z() == z) + (mesh_vertices[neighbor(1)].z() == z) + (mesh_vertices[neighbor(2)].z() == z);
|
||||
assert(num_on_plane == 2 || num_on_plane == 3);
|
||||
#endif // NDEBUG
|
||||
@ -1866,9 +1866,13 @@ std::vector<ExPolygons> slice_mesh_ex(
|
||||
//FIXME simplify
|
||||
if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour)
|
||||
keep_largest_contour_only(expolygons);
|
||||
if (resolution != 0.)
|
||||
for (ExPolygon &ex : expolygons)
|
||||
ex.simplify(resolution);
|
||||
if (resolution != 0.) {
|
||||
ExPolygons simplified;
|
||||
simplified.reserve(expolygons.size());
|
||||
for (const ExPolygon &ex : expolygons)
|
||||
append(simplified, ex.simplify(resolution));
|
||||
expolygons = std::move(simplified);
|
||||
}
|
||||
}
|
||||
});
|
||||
// BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end";
|
||||
|
@ -9,6 +9,112 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Check if the line is whole inside the sphere, or it is partially inside (intersecting) the sphere.
|
||||
// Inspired by Christer Ericson's Real-Time Collision Detection, pp. 177-179.
|
||||
static bool test_line_inside_sphere(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &sphere_p, const float sphere_radius)
|
||||
{
|
||||
const float sphere_radius_sqr = Slic3r::sqr(sphere_radius);
|
||||
const Vec3f line_dir = line_b - line_a; // n
|
||||
const Vec3f origins_diff = line_a - sphere_p; // m
|
||||
|
||||
const float m_dot_m = origins_diff.dot(origins_diff);
|
||||
// Check if any of the end-points of the line is inside the sphere.
|
||||
if (m_dot_m <= sphere_radius_sqr || (line_b - sphere_p).squaredNorm() <= sphere_radius_sqr)
|
||||
return true;
|
||||
|
||||
// Check if the infinite line is going through the sphere.
|
||||
const float n_dot_n = line_dir.dot(line_dir);
|
||||
const float m_dot_n = origins_diff.dot(line_dir);
|
||||
|
||||
const float eq_a = n_dot_n;
|
||||
const float eq_b = m_dot_n;
|
||||
const float eq_c = m_dot_m - sphere_radius_sqr;
|
||||
|
||||
const float discr = eq_b * eq_b - eq_a * eq_c;
|
||||
// A negative discriminant corresponds to the infinite line infinite not going through the sphere.
|
||||
if (discr < 0.f)
|
||||
return false;
|
||||
|
||||
// Check if the finite line is going through the sphere.
|
||||
const float discr_sqrt = std::sqrt(discr);
|
||||
const float t1 = (-eq_b - discr_sqrt) / eq_a;
|
||||
if (0.f <= t1 && t1 <= 1.f)
|
||||
return true;
|
||||
|
||||
const float t2 = (-eq_b + discr_sqrt) / eq_a;
|
||||
if (0.f <= t2 && t2 <= 1.f && discr_sqrt > 0.f)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the line is whole inside the finite cylinder, or it is partially inside (intersecting) the finite cylinder.
|
||||
// Inspired by Christer Ericson's Real-Time Collision Detection, pp. 194-198.
|
||||
static bool test_line_inside_cylinder(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &cylinder_P, const Vec3f &cylinder_Q, const float cylinder_radius)
|
||||
{
|
||||
assert(cylinder_P != cylinder_Q);
|
||||
const Vec3f cylinder_dir = cylinder_Q - cylinder_P; // d
|
||||
auto is_point_inside_finite_cylinder = [&cylinder_P, &cylinder_Q, &cylinder_radius, &cylinder_dir](const Vec3f &pt) {
|
||||
const Vec3f first_center_diff = cylinder_P - pt;
|
||||
const Vec3f second_center_diff = cylinder_Q - pt;
|
||||
// First, check if the point pt is laying between planes defined by cylinder_p and cylinder_q.
|
||||
// Then check if it is inside the cylinder between cylinder_p and cylinder_q.
|
||||
return first_center_diff.dot(cylinder_dir) <= 0 && second_center_diff.dot(cylinder_dir) >= 0 &&
|
||||
(first_center_diff.cross(cylinder_dir).norm() / cylinder_dir.norm()) <= cylinder_radius;
|
||||
};
|
||||
|
||||
// Check if any of the end-points of the line is inside the cylinder.
|
||||
if (is_point_inside_finite_cylinder(line_a) || is_point_inside_finite_cylinder(line_b))
|
||||
return true;
|
||||
|
||||
// Check if the line is going through the cylinder.
|
||||
const Vec3f origins_diff = line_a - cylinder_P; // m
|
||||
const Vec3f line_dir = line_b - line_a; // n
|
||||
|
||||
const float m_dot_d = origins_diff.dot(cylinder_dir);
|
||||
const float n_dot_d = line_dir.dot(cylinder_dir);
|
||||
const float d_dot_d = cylinder_dir.dot(cylinder_dir);
|
||||
|
||||
const float n_dot_n = line_dir.dot(line_dir);
|
||||
const float m_dot_n = origins_diff.dot(line_dir);
|
||||
const float m_dot_m = origins_diff.dot(origins_diff);
|
||||
|
||||
const float eq_a = d_dot_d * n_dot_n - n_dot_d * n_dot_d;
|
||||
const float eq_b = d_dot_d * m_dot_n - n_dot_d * m_dot_d;
|
||||
const float eq_c = d_dot_d * (m_dot_m - Slic3r::sqr(cylinder_radius)) - m_dot_d * m_dot_d;
|
||||
|
||||
const float discr = eq_b * eq_b - eq_a * eq_c;
|
||||
// A negative discriminant corresponds to the infinite line not going through the infinite cylinder.
|
||||
if (discr < 0.0f)
|
||||
return false;
|
||||
|
||||
// Check if the finite line is going through the finite cylinder.
|
||||
const float discr_sqrt = std::sqrt(discr);
|
||||
const float t1 = (-eq_b - discr_sqrt) / eq_a;
|
||||
if (0.f <= t1 && t1 <= 1.f)
|
||||
if (const float cylinder_endcap_t1 = m_dot_d + t1 * n_dot_d; 0.f <= cylinder_endcap_t1 && cylinder_endcap_t1 <= d_dot_d)
|
||||
return true;
|
||||
|
||||
const float t2 = (-eq_b + discr_sqrt) / eq_a;
|
||||
if (0.f <= t2 && t2 <= 1.f)
|
||||
if (const float cylinder_endcap_t2 = (m_dot_d + t2 * n_dot_d); 0.f <= cylinder_endcap_t2 && cylinder_endcap_t2 <= d_dot_d)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the line is whole inside the capsule, or it is partially inside (intersecting) the capsule.
|
||||
static bool test_line_inside_capsule(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &capsule_p, const Vec3f &capsule_q, const float capsule_radius) {
|
||||
assert(capsule_p != capsule_q);
|
||||
|
||||
// Check if the line intersect any of the spheres forming the capsule.
|
||||
if (test_line_inside_sphere(line_a, line_b, capsule_p, capsule_radius) || test_line_inside_sphere(line_a, line_b, capsule_q, capsule_radius))
|
||||
return true;
|
||||
|
||||
// Check if the line intersects the cylinder between the centers of the spheres.
|
||||
return test_line_inside_cylinder(line_a, line_b, capsule_p, capsule_q, capsule_radius);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const
|
||||
{
|
||||
@ -124,24 +230,20 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
|
||||
return this->select_unsplit_triangle(hit, facet_idx, neighbors);
|
||||
}
|
||||
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, float radius,
|
||||
CursorType cursor_type, EnforcerBlockerType new_state,
|
||||
const Transform3d& trafo, const Transform3d& trafo_no_translate,
|
||||
bool triangle_splitting, const ClippingPlane &clp, float highlight_by_angle_deg)
|
||||
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
|
||||
// Save current cursor center, squared radius and camera direction, so we don't
|
||||
// have to pass it around.
|
||||
m_cursor = Cursor(hit, source, radius, cursor_type, trafo, clp);
|
||||
m_cursor = std::move(cursor);
|
||||
|
||||
// In case user changed cursor size since last time, update triangle edge limit.
|
||||
// It is necessary to compare the internal radius in m_cursor! radius is in
|
||||
// world coords and does not change after scaling.
|
||||
if (m_old_cursor_radius_sqr != m_cursor.radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor.radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor.radius_sqr;
|
||||
if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor->radius_sqr;
|
||||
}
|
||||
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
@ -163,7 +265,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
if (select_triangle(facet, new_state, triangle_splitting)) {
|
||||
// add neighboring facets to list to be processed later
|
||||
for (int neighbor_idx : m_neighbors[facet])
|
||||
if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx)))
|
||||
if (neighbor_idx >= 0 && m_cursor->is_facet_visible(neighbor_idx, m_face_normals))
|
||||
facets_to_check.push_back(neighbor_idx);
|
||||
}
|
||||
}
|
||||
@ -788,11 +890,11 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
|
||||
|
||||
assert(this->verify_triangle_neighbors(*tr, neighbors));
|
||||
|
||||
int num_of_inside_vertices = vertices_inside(facet_idx);
|
||||
int num_of_inside_vertices = m_cursor->vertices_inside(*tr, m_vertices);
|
||||
|
||||
if (num_of_inside_vertices == 0
|
||||
&& ! is_pointer_in_triangle(facet_idx)
|
||||
&& ! is_edge_inside_cursor(facet_idx))
|
||||
&& ! m_cursor->is_pointer_in_triangle(*tr, m_vertices)
|
||||
&& ! m_cursor->is_edge_inside_cursor(*tr, m_vertices))
|
||||
return false;
|
||||
|
||||
if (num_of_inside_vertices == 3) {
|
||||
@ -840,7 +942,7 @@ void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state)
|
||||
}
|
||||
|
||||
// called by select_patch()->select_triangle()...select_triangle()
|
||||
// to decide which sides of the traingle to split and to actually split it calling set_division() and perform_split().
|
||||
// to decide which sides of the triangle to split and to actually split it calling set_division() and perform_split().
|
||||
void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
{
|
||||
if (m_triangles[facet_idx].is_split()) {
|
||||
@ -864,9 +966,9 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
|
||||
// In case the object is non-uniformly scaled, transform the
|
||||
// points to world coords.
|
||||
if (! m_cursor.uniform_scaling) {
|
||||
if (! m_cursor->uniform_scaling) {
|
||||
for (size_t i=0; i<pts.size(); ++i) {
|
||||
pts_transformed[i] = m_cursor.trafo * (*pts[i]);
|
||||
pts_transformed[i] = m_cursor->trafo * (*pts[i]);
|
||||
pts[i] = &pts_transformed[i];
|
||||
}
|
||||
}
|
||||
@ -897,71 +999,80 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
perform_split(facet_idx, neighbors, old_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Is pointer in a triangle?
|
||||
bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const
|
||||
{
|
||||
const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v;
|
||||
const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v;
|
||||
const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v;
|
||||
return m_cursor.is_pointer_in_triangle(p1, p2, p3);
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const {
|
||||
const Vec3f& p1 = vertices[tr.verts_idxs[0]].v;
|
||||
const Vec3f& p2 = vertices[tr.verts_idxs[1]].v;
|
||||
const Vec3f& p3 = vertices[tr.verts_idxs[2]].v;
|
||||
return this->is_pointer_in_triangle(p1, p2, p3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Determine whether this facet is potentially visible (still can be obscured).
|
||||
bool TriangleSelector::faces_camera(int facet) const
|
||||
bool TriangleSelector::Cursor::is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals)
|
||||
{
|
||||
assert(facet < m_orig_size_indices);
|
||||
Vec3f n = m_face_normals[facet];
|
||||
if (! m_cursor.uniform_scaling)
|
||||
n = m_cursor.trafo_normal * n;
|
||||
return n.dot(m_cursor.dir) < 0.;
|
||||
assert(facet_idx < int(face_normals.size()));
|
||||
Vec3f n = face_normals[facet_idx];
|
||||
if (!cursor.uniform_scaling)
|
||||
n = cursor.trafo_normal * n;
|
||||
return n.dot(cursor.dir) < 0.f;
|
||||
}
|
||||
|
||||
|
||||
// How many vertices of a triangle are inside the circle?
|
||||
int TriangleSelector::vertices_inside(int facet_idx) const
|
||||
int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
int inside = 0;
|
||||
for (size_t i=0; i<3; ++i) {
|
||||
if (m_cursor.is_mesh_point_inside(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
if (this->is_mesh_point_inside(vertices[tr.verts_idxs[i]].v))
|
||||
++inside;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
|
||||
// Is any edge inside Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i=0; i<3; ++i) {
|
||||
pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v;
|
||||
if (! m_cursor.uniform_scaling)
|
||||
pts[i] = m_cursor.trafo * pts[i];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f& p = m_cursor.center;
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f& a = pts[side];
|
||||
const Vec3f& b = pts[side<2 ? side+1 : 0];
|
||||
Vec3f s = (b-a).normalized();
|
||||
float t = (p-a).dot(s);
|
||||
Vec3f vector = a+t*s - p;
|
||||
|
||||
// vector is 3D vector from center to the intersection. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f);
|
||||
if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm())
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
if (test_line_inside_sphere(edge_a, edge_b, this->center, this->radius))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f &p = this->center;
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &a = pts[side];
|
||||
const Vec3f &b = pts[side < 2 ? side + 1 : 0];
|
||||
Vec3f s = (b - a).normalized();
|
||||
float t = (p - a).dot(s);
|
||||
Vec3f vector = a + t * s - p;
|
||||
|
||||
// vector is 3D vector from center to the intersection. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(this->dir), 2.f);
|
||||
if (dist_sqr < this->radius_sqr && t >= 0.f && t <= (b - a).norm())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively remove all subtriangles.
|
||||
void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
@ -1002,7 +1113,6 @@ void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
{
|
||||
// Check that all children are leafs of the same type. If not, try to
|
||||
@ -1041,8 +1151,6 @@ void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
tr.set_state(first_child_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::garbage_collect()
|
||||
{
|
||||
// First make a map from old to new triangle indices.
|
||||
@ -1103,7 +1211,6 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh)
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::reset()
|
||||
{
|
||||
m_vertices.clear();
|
||||
@ -1124,17 +1231,11 @@ void TriangleSelector::reset()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::set_edge_limit(float edge_limit)
|
||||
{
|
||||
m_edge_limit_sqr = std::pow(edge_limit, 2.f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state)
|
||||
{
|
||||
for (int i : {a, b, c}) {
|
||||
@ -1693,54 +1794,132 @@ void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_stat
|
||||
}
|
||||
}
|
||||
|
||||
TriangleSelector::Cursor::Cursor(
|
||||
const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
: center{center_},
|
||||
source{source_},
|
||||
type{type_},
|
||||
trafo{trafo_.cast<float>()},
|
||||
clipping_plane(clipping_plane_)
|
||||
TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: source{source_}, trafo{trafo_.cast<float>()}, clipping_plane{clipping_plane_}
|
||||
{
|
||||
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
|
||||
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
|
||||
radius_sqr = float(std::pow(radius_world / sf(0), 2));
|
||||
radius = float(radius_world / sf(0));
|
||||
radius_sqr = float(Slic3r::sqr(radius_world / sf(0)));
|
||||
uniform_scaling = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// First transform center, source and dir in world coords and remember
|
||||
// that we did this.
|
||||
center = trafo * center;
|
||||
source = trafo * source;
|
||||
// First transform source in world coords and remember that we did this.
|
||||
source = trafo * source;
|
||||
uniform_scaling = false;
|
||||
radius_sqr = radius_world * radius_world;
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
radius = radius_world;
|
||||
radius_sqr = Slic3r::sqr(radius_world);
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
}
|
||||
}
|
||||
|
||||
TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
: center{center_}, Cursor(source_, radius_world, trafo_, clipping_plane_)
|
||||
{
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// Because of the center is transformed.
|
||||
if (!uniform_scaling)
|
||||
center = trafo * center;
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (center - source).normalized();
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a cursor?
|
||||
bool TriangleSelector::Cursor::is_mesh_point_inside(const Vec3f &point) const
|
||||
TriangleSelector::DoublePointCursor::DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: first_center{first_center_}, second_center{second_center_}, Cursor(source_, radius_world, trafo_, clipping_plane_)
|
||||
{
|
||||
if (!uniform_scaling) {
|
||||
first_center = trafo * first_center_;
|
||||
second_center = trafo * second_center_;
|
||||
}
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (first_center - source).normalized();
|
||||
}
|
||||
|
||||
// Returns true if clipping plane is not active or if the point not clipped by clipping plane.
|
||||
inline static bool is_mesh_point_not_clipped(const Vec3f &point, const TriangleSelector::ClippingPlane &clipping_plane)
|
||||
{
|
||||
return !clipping_plane.is_active() || !clipping_plane.is_mesh_point_clipped(point);
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
if ((center - transformed_point).squaredNorm() < radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Circle cursor?
|
||||
bool TriangleSelector::Circle::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f diff = center - transformed_point;
|
||||
const bool is_point_inside = (type == CIRCLE ? (diff - diff.dot(dir) * dir).squaredNorm() : diff.squaredNorm()) < radius_sqr;
|
||||
|
||||
if (is_point_inside && clipping_plane.is_active())
|
||||
return !clipping_plane.is_mesh_point_clipped(point);
|
||||
if ((diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return is_point_inside;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Capsule3D cursor?
|
||||
bool TriangleSelector::Capsule3D::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f first_center_diff = this->first_center - transformed_point;
|
||||
const Vec3f second_center_diff = this->second_center - transformed_point;
|
||||
if (first_center_diff.squaredNorm() < this->radius_sqr || second_center_diff.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
// First, check if the point pt is laying between planes defined by first_center and second_center.
|
||||
// Then check if it is inside the cylinder between first_center and second_center.
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
if (first_center_diff.dot(centers_diff) <= 0.f && second_center_diff.dot(centers_diff) >= 0.f && (first_center_diff.cross(centers_diff).norm() / centers_diff.norm()) <= this->radius)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Capsule2D cursor?
|
||||
bool TriangleSelector::Capsule2D::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f first_center_diff = this->first_center - transformed_point;
|
||||
const Vec3f first_center_diff_projected = first_center_diff - first_center_diff.dot(this->dir) * this->dir;
|
||||
if (first_center_diff_projected.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
const Vec3f second_center_diff = this->second_center - transformed_point;
|
||||
const Vec3f second_center_diff_projected = second_center_diff - second_center_diff.dot(this->dir) * this->dir;
|
||||
if (second_center_diff_projected.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
const Vec3f centers_diff_projected = centers_diff - centers_diff.dot(this->dir) * this->dir;
|
||||
|
||||
// First, check if the point is laying between first_center and second_center.
|
||||
if (first_center_diff_projected.dot(centers_diff_projected) <= 0.f && second_center_diff_projected.dot(centers_diff_projected) >= 0.f) {
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
// Vector pointing from first_center to the point 'A' of the rectangle.
|
||||
const Vec3f first_center_rectangle_a_diff = rectangle_da_dir.normalized() * this->radius;
|
||||
const Vec3f rectangle_a = this->first_center - first_center_rectangle_a_diff;
|
||||
const Vec3f rectangle_d = this->first_center + first_center_rectangle_a_diff;
|
||||
// Now check if the point is laying inside the rectangle between circles with centers in first_center and second_center.
|
||||
if ((rectangle_a - transformed_point).dot(rectangle_da_dir) <= 0.f && (rectangle_d - transformed_point).dot(rectangle_da_dir) >= 0.f)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
const Vec3f& p2_,
|
||||
const Vec3f& p3_) const
|
||||
{
|
||||
static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_, const Vec3f ¢er, const Vec3f &dir, const bool uniform_scaling, const Transform3f &trafo) {
|
||||
const Vec3f& q1 = center + dir;
|
||||
const Vec3f& q2 = center - dir;
|
||||
|
||||
@ -1761,4 +1940,108 @@ bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
return signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos;
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::SinglePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const
|
||||
{
|
||||
return is_circle_pointer_inside_triangle(p1_, p2_, p3_, center, dir, uniform_scaling, trafo);
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::DoublePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const
|
||||
{
|
||||
return is_circle_pointer_inside_triangle(p1_, p2_, p3_, first_center, dir, uniform_scaling, trafo) ||
|
||||
is_circle_pointer_inside_triangle(p1_, p2_, p3_, second_center, dir, uniform_scaling, trafo);
|
||||
}
|
||||
|
||||
bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &plane_origin, const Vec3f &plane_normal, Vec3f &out_intersection)
|
||||
{
|
||||
Vec3f line_dir = line_b - line_a;
|
||||
float t_denominator = plane_normal.dot(line_dir);
|
||||
if (t_denominator == 0.f)
|
||||
return false;
|
||||
|
||||
// Compute 'd' in plane equation by using some point (origin) on the plane
|
||||
float plane_d = plane_normal.dot(plane_origin);
|
||||
if (float t = (plane_d - plane_normal.dot(line_a)) / t_denominator; t >= 0.f && t <= 1.f) {
|
||||
out_intersection = line_a + t * line_dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
if (test_line_inside_capsule(edge_a, edge_b, this->first_center, this->second_center, this->radius))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
// Vector pointing from first_center to the point 'A' of the rectangle.
|
||||
const Vec3f first_center_rectangle_a_diff = rectangle_da_dir.normalized() * this->radius;
|
||||
const Vec3f rectangle_a = this->first_center - first_center_rectangle_a_diff;
|
||||
const Vec3f rectangle_d = this->first_center + first_center_rectangle_a_diff;
|
||||
|
||||
auto edge_inside_rectangle = [&self = std::as_const(*this), ¢ers_diff](const Vec3f &edge_a, const Vec3f &edge_b, const Vec3f &plane_origin, const Vec3f &plane_normal) -> bool {
|
||||
Vec3f intersection(-1.f, -1.f, -1.f);
|
||||
if (line_plane_intersection(edge_a, edge_b, plane_origin, plane_normal, intersection)) {
|
||||
// Now check if the intersection point is inside the rectangle. That means it is between 'first_center' and 'second_center', resp. between 'A' and 'B'.
|
||||
if (self.first_center.dot(centers_diff) <= intersection.dot(centers_diff) && intersection.dot(centers_diff) <= self.second_center.dot(centers_diff))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
const Vec3f edge_dir = edge_b - edge_a;
|
||||
const Vec3f edge_dir_n = edge_dir.normalized();
|
||||
|
||||
float t1 = (this->first_center - edge_a).dot(edge_dir_n);
|
||||
float t2 = (this->second_center - edge_a).dot(edge_dir_n);
|
||||
Vec3f vector1 = edge_a + t1 * edge_dir_n - this->first_center;
|
||||
Vec3f vector2 = edge_a + t2 * edge_dir_n - this->second_center;
|
||||
|
||||
// Vectors vector1 and vector2 are 3D vector from centers to the intersections. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
if (float dist = vector1.squaredNorm() - std::pow(vector1.dot(this->dir), 2.f); dist < this->radius_sqr && t1 >= 0.f && t1 <= edge_dir.norm())
|
||||
return true;
|
||||
|
||||
if (float dist = vector2.squaredNorm() - std::pow(vector2.dot(this->dir), 2.f); dist < this->radius_sqr && t2 >= 0.f && t2 <= edge_dir.norm())
|
||||
return true;
|
||||
|
||||
// Check if the edge is passing through the rectangle between first_center and second_center.
|
||||
if (edge_inside_rectangle(edge_a, edge_b, rectangle_a, (rectangle_d - rectangle_a)) || edge_inside_rectangle(edge_a, edge_b, rectangle_d, (rectangle_a - rectangle_d)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -15,7 +15,12 @@ enum class EnforcerBlockerType : int8_t;
|
||||
|
||||
// Following class holds information about selected triangles. It also has power
|
||||
// to recursively subdivide the triangles and make the selection finer.
|
||||
class TriangleSelector {
|
||||
class TriangleSelector
|
||||
{
|
||||
protected:
|
||||
class Triangle;
|
||||
struct Vertex;
|
||||
|
||||
public:
|
||||
enum CursorType {
|
||||
CIRCLE,
|
||||
@ -35,6 +40,146 @@ public:
|
||||
bool is_mesh_point_clipped(const Vec3f &point) const { return normal.dot(point) - offset > 0.f; }
|
||||
};
|
||||
|
||||
class Cursor
|
||||
{
|
||||
public:
|
||||
Cursor() = delete;
|
||||
virtual ~Cursor() = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
|
||||
virtual bool is_mesh_point_inside(const Vec3f &point) const = 0;
|
||||
virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0;
|
||||
virtual int vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
|
||||
virtual bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const = 0;
|
||||
|
||||
static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals);
|
||||
|
||||
protected:
|
||||
explicit Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Transform3f trafo;
|
||||
Vec3f source;
|
||||
|
||||
bool uniform_scaling;
|
||||
Transform3f trafo_normal;
|
||||
float radius;
|
||||
float radius_sqr;
|
||||
Vec3f dir = Vec3f(0.f, 0.f, 0.f);
|
||||
|
||||
ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only
|
||||
|
||||
friend TriangleSelector;
|
||||
};
|
||||
|
||||
class SinglePointCursor : public Cursor
|
||||
{
|
||||
public:
|
||||
SinglePointCursor() = delete;
|
||||
~SinglePointCursor() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override;
|
||||
|
||||
static std::unique_ptr<Cursor> cursor_factory(const Vec3f ¢er, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane)
|
||||
{
|
||||
assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
if (cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
return std::make_unique<TriangleSelector::Sphere>(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
else
|
||||
return std::make_unique<TriangleSelector::Circle>(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit SinglePointCursor(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Vec3f center;
|
||||
};
|
||||
|
||||
class DoublePointCursor : public Cursor
|
||||
{
|
||||
public:
|
||||
DoublePointCursor() = delete;
|
||||
~DoublePointCursor() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override;
|
||||
|
||||
static std::unique_ptr<Cursor> cursor_factory(const Vec3f &first_center, const Vec3f &second_center, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane)
|
||||
{
|
||||
assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
if (cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
return std::make_unique<TriangleSelector::Capsule3D>(first_center, second_center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
else
|
||||
return std::make_unique<TriangleSelector::Capsule2D>(first_center, second_center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Vec3f first_center;
|
||||
Vec3f second_center;
|
||||
};
|
||||
|
||||
class Sphere : public SinglePointCursor
|
||||
{
|
||||
public:
|
||||
Sphere() = delete;
|
||||
explicit Sphere(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){};
|
||||
~Sphere() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
class Circle : public SinglePointCursor
|
||||
{
|
||||
public:
|
||||
Circle() = delete;
|
||||
explicit Circle(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){};
|
||||
~Circle() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
class Capsule3D : public DoublePointCursor
|
||||
{
|
||||
public:
|
||||
Capsule3D() = delete;
|
||||
explicit Capsule3D(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: TriangleSelector::DoublePointCursor(first_center_, second_center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
{}
|
||||
~Capsule3D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
class Capsule2D : public DoublePointCursor
|
||||
{
|
||||
public:
|
||||
Capsule2D() = delete;
|
||||
explicit Capsule2D(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: TriangleSelector::DoublePointCursor(first_center_, second_center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
{}
|
||||
~Capsule2D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<std::vector<Vec3i>, std::vector<Vec3i>> precompute_all_neighbors() const;
|
||||
void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_normal_out) const;
|
||||
|
||||
@ -51,17 +196,12 @@ public:
|
||||
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const;
|
||||
|
||||
// Select all triangles fully inside the circle, subdivide where needed.
|
||||
void select_patch(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const Vec3f &source, // camera position (mesh coords)
|
||||
float radius, // radius of the cursor
|
||||
CursorType type, // current type of cursor
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d &trafo, // matrix to get from mesh to world
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
void select_patch(int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
std::unique_ptr<Cursor> &&cursor, // Cursor containing information about the point where to start, camera position (mesh coords), matrix to get from mesh to world, and its shape and type.
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
|
||||
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
@ -195,39 +335,16 @@ protected:
|
||||
int m_orig_size_vertices = 0;
|
||||
int m_orig_size_indices = 0;
|
||||
|
||||
// Cache for cursor position, radius and direction.
|
||||
struct Cursor {
|
||||
Cursor() = default;
|
||||
Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_);
|
||||
bool is_mesh_point_inside(const Vec3f &pt) const;
|
||||
bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const;
|
||||
|
||||
Vec3f center;
|
||||
Vec3f source;
|
||||
Vec3f dir;
|
||||
float radius_sqr;
|
||||
CursorType type;
|
||||
Transform3f trafo;
|
||||
Transform3f trafo_normal;
|
||||
bool uniform_scaling;
|
||||
ClippingPlane clipping_plane;
|
||||
};
|
||||
|
||||
Cursor m_cursor;
|
||||
std::unique_ptr<Cursor> m_cursor;
|
||||
float m_old_cursor_radius_sqr;
|
||||
|
||||
// Private functions:
|
||||
private:
|
||||
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting);
|
||||
bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting);
|
||||
int vertices_inside(int facet_idx) const;
|
||||
bool faces_camera(int facet) const;
|
||||
void undivide_triangle(int facet_idx);
|
||||
void split_triangle(int facet_idx, const Vec3i &neighbors);
|
||||
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
|
||||
bool is_pointer_in_triangle(int facet_idx) const;
|
||||
bool is_edge_inside_cursor(int facet_idx) const;
|
||||
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
|
||||
int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0});
|
||||
void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state);
|
||||
|
@ -47,9 +47,6 @@ static constexpr double EPSILON = 1e-4;
|
||||
// int32_t fits an interval of (-2147.48mm, +2147.48mm)
|
||||
// with int64_t we don't have to worry anymore about the size of the int.
|
||||
static constexpr double SCALING_FACTOR = 0.000001;
|
||||
// RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm.
|
||||
static constexpr double RESOLUTION = 0.0125;
|
||||
#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR)
|
||||
static constexpr double PI = 3.141592653589793238;
|
||||
// When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam.
|
||||
static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15;
|
||||
|
@ -139,7 +139,7 @@ Code flags --
|
||||
REALfloat = 1 all numbers are 'float' type
|
||||
= 0 all numbers are 'double' type
|
||||
*/
|
||||
#define REALfloat 1
|
||||
#define REALfloat 0
|
||||
|
||||
#if (REALfloat == 1)
|
||||
#define realT float
|
||||
|
@ -237,6 +237,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/UndoRedo.hpp
|
||||
Utils/HexFile.cpp
|
||||
Utils/HexFile.hpp
|
||||
Utils/TCPConsole.cpp
|
||||
Utils/TCPConsole.hpp
|
||||
Utils/MKS.cpp
|
||||
Utils/MKS.hpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
@ -250,7 +254,6 @@ if (APPLE)
|
||||
GUI/InstanceCheckMac.h
|
||||
)
|
||||
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
|
||||
|
||||
endif ()
|
||||
|
||||
add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
|
||||
@ -259,8 +262,12 @@ encoding_check(libslic3r_gui)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES})
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (MSVC)
|
||||
target_link_libraries(libslic3r_gui Setupapi.lib)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES})
|
||||
elseif (APPLE)
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
|
||||
endif()
|
||||
|
||||
if (SLIC3R_STATIC)
|
||||
@ -269,10 +276,6 @@ if (SLIC3R_STATIC)
|
||||
target_compile_definitions(libslic3r_gui PUBLIC -DwxDEBUG_LEVEL=0)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
|
||||
endif()
|
||||
|
||||
if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL AND UNIX AND NOT APPLE)
|
||||
target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE)
|
||||
endif ()
|
||||
|
@ -72,15 +72,10 @@ namespace Slic3r {
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& normals)
|
||||
{
|
||||
mesh.repair();
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
std::vector<stl_normal> face_normals(mesh.stl.stats.number_of_facets);
|
||||
for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
|
||||
face_normals[i] = mesh.stl.facet_start[i].normal;
|
||||
}
|
||||
std::vector<Vec3f> face_normals = its_face_normals(mesh.its);
|
||||
|
||||
Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
|
||||
Eigen::Index(mesh.its.vertices.size()), 3).cast<double>();
|
||||
@ -102,8 +97,6 @@ static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& n
|
||||
|
||||
static void smooth_normals_vertex(TriangleMesh& mesh, std::vector<stl_normal>& normals)
|
||||
{
|
||||
mesh.repair();
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
|
@ -167,7 +167,12 @@ int BitmapComboBox::Append(const wxString& item)
|
||||
//3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct
|
||||
|
||||
wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1));
|
||||
bitmap.SetWidth(0);
|
||||
{
|
||||
// bitmap.SetWidth(0); is depricated now
|
||||
// so, use next code
|
||||
bitmap.UnShare();// AllocExclusive();
|
||||
bitmap.GetGDIImageData()->m_width = 0;
|
||||
}
|
||||
|
||||
OnAddBitmap(bitmap);
|
||||
const int n = wxComboBox::Append(item);
|
||||
|
@ -162,8 +162,8 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
|
||||
if (config->opt_bool("support_material")) {
|
||||
// Ask only once.
|
||||
if (!support_material_overhangs_queried) {
|
||||
support_material_overhangs_queried = true;
|
||||
if (!m_support_material_overhangs_queried) {
|
||||
m_support_material_overhangs_queried = true;
|
||||
if (!config->opt_bool("overhangs")/* != 1*/) {
|
||||
wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n"
|
||||
"- Detect bridging perimeters"));
|
||||
@ -182,7 +182,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
}
|
||||
}
|
||||
else {
|
||||
support_material_overhangs_queried = false;
|
||||
m_support_material_overhangs_queried = false;
|
||||
}
|
||||
|
||||
if (config->option<ConfigOptionPercent>("fill_density")->value == 100) {
|
||||
|
@ -17,15 +17,12 @@ class ModelConfig;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
// This variable have to be static because of use its value from Preset configuration
|
||||
// and from object/parts configuration from the Settings in sidebar
|
||||
static bool support_material_overhangs_queried {false};
|
||||
|
||||
class ConfigManipulation
|
||||
{
|
||||
bool is_msg_dlg_already_exist{ false };
|
||||
|
||||
bool m_is_initialized_support_material_overhangs_queried{ false };
|
||||
bool m_support_material_overhangs_queried{ false };
|
||||
|
||||
// function to loading of changed configuration
|
||||
std::function<void()> load_config = nullptr;
|
||||
@ -66,7 +63,7 @@ public:
|
||||
void initialize_support_material_overhangs_queried(bool queried)
|
||||
{
|
||||
m_is_initialized_support_material_overhangs_queried = true;
|
||||
support_material_overhangs_queried = queried;
|
||||
m_support_material_overhangs_queried = queried;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2722,7 +2722,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
// if unsaved changes was not cheched till this moment
|
||||
if (!check_unsaved_preset_changes) {
|
||||
if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) {
|
||||
header = format_wxstr(_L("A new %1% was installed and it will be activated."), !first_added_filament.empty() ? _L("Filament") : _L("SLA material"));
|
||||
header = !first_added_filament.empty() ?
|
||||
_L("A new filament was installed and it will be activated.") :
|
||||
_L("A new SLA material was installed and it will be activated.");
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
}
|
||||
@ -2730,7 +2732,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
bool is_filaments_changed = app_config->get_section(AppConfig::SECTION_FILAMENTS) != appconfig_new.get_section(AppConfig::SECTION_FILAMENTS);
|
||||
bool is_sla_materials_changed = app_config->get_section(AppConfig::SECTION_MATERIALS) != appconfig_new.get_section(AppConfig::SECTION_MATERIALS);
|
||||
if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
|
||||
header = format_wxstr(_L("Some %1% were uninstalled."), is_filaments_changed ? _L("Filaments") : _L("SLA materials"));
|
||||
header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled.");
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <random>
|
||||
#include "Field.hpp"
|
||||
#include "format.hpp"
|
||||
@ -1406,8 +1407,8 @@ wxString Control::get_tooltip(int tick/*=-1*/)
|
||||
if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist
|
||||
{
|
||||
if (m_draw_mode == dmSequentialFffPrint)
|
||||
return _L("The sequential print is on.\n"
|
||||
"It's impossible to apply any custom G-code for objects printing sequentually.\n");
|
||||
return (_L("The sequential print is on.\n"
|
||||
"It's impossible to apply any custom G-code for objects printing sequentually.") + "\n");
|
||||
|
||||
// Show mode as a first string of tooltop
|
||||
tooltip = " " + _L("Print mode") + ": ";
|
||||
@ -1446,6 +1447,18 @@ wxString Control::get_tooltip(int tick/*=-1*/)
|
||||
std::string space = " ";
|
||||
tooltip = space;
|
||||
auto format_gcode = [space](std::string gcode) {
|
||||
// when the tooltip is too long, it starts to flicker, see: https://github.com/prusa3d/PrusaSlicer/issues/7368
|
||||
// so we limit the number of lines shown
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, gcode, boost::is_any_of("\n"), boost::token_compress_off);
|
||||
static const size_t MAX_LINES = 10;
|
||||
if (lines.size() > MAX_LINES) {
|
||||
gcode = lines.front() + '\n';
|
||||
for (size_t i = 1; i < MAX_LINES; ++i) {
|
||||
gcode += lines[i] + '\n';
|
||||
}
|
||||
gcode += "[" + into_u8(_L("continue")) + "]\n";
|
||||
}
|
||||
boost::replace_all(gcode, "\n", "\n" + space);
|
||||
return gcode;
|
||||
};
|
||||
|
@ -115,7 +115,7 @@ class TickCodeInfo
|
||||
bool m_suppress_plus = false;
|
||||
bool m_suppress_minus = false;
|
||||
bool m_use_default_colors= false;
|
||||
int m_default_color_idx = 0;
|
||||
// int m_default_color_idx = 0;
|
||||
|
||||
std::vector<std::string>* m_colors {nullptr};
|
||||
|
||||
|
@ -223,7 +223,8 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value
|
||||
if (!text_editor || text_editor->GetValue().IsEmpty())
|
||||
return false;
|
||||
|
||||
if (m_was_unusable_symbol = Slic3r::GUI::Plater::has_illegal_filename_characters(text_editor->GetValue()))
|
||||
m_was_unusable_symbol = Slic3r::GUI::Plater::has_illegal_filename_characters(text_editor->GetValue());
|
||||
if (m_was_unusable_symbol)
|
||||
return false;
|
||||
|
||||
// The icon can't be edited so get its old value and reuse it.
|
||||
|
@ -316,7 +316,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
|
||||
}
|
||||
else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) ||
|
||||
(m_opt.sidetext.rfind("mm ") != std::string::npos && val > /*1*/m_opt.max_literal)) &&
|
||||
(m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast<std::string>(m_value)))
|
||||
(m_value.empty() || into_u8(str) != boost::any_cast<std::string>(m_value)))
|
||||
{
|
||||
if (!check_value) {
|
||||
m_value.clear();
|
||||
@ -340,7 +340,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
|
||||
}
|
||||
}
|
||||
|
||||
m_value = std::string(str.ToUTF8().data());
|
||||
m_value = into_u8(str);
|
||||
break; }
|
||||
|
||||
case coPoints: {
|
||||
@ -1286,7 +1286,7 @@ void Choice::msw_rescale()
|
||||
size_t counter = 0;
|
||||
bool labels = ! m_opt.enum_labels.empty();
|
||||
for (const std::string &el : labels ? m_opt.enum_labels : m_opt.enum_values) {
|
||||
wxString text = labels ? _(el) : wxString::FromUTF8(el.c_str());
|
||||
wxString text = labels ? _(el) : from_u8(el);
|
||||
field->Append(text);
|
||||
if (text == selection)
|
||||
idx = counter;
|
||||
@ -1574,7 +1574,7 @@ void StaticText::BUILD()
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
|
||||
|
||||
const wxString legend = wxString::FromUTF8(m_opt.get_default_value<ConfigOptionString>()->value.c_str());
|
||||
const wxString legend = from_u8(m_opt.get_default_value<ConfigOptionString>()->value);
|
||||
auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size, wxST_ELLIPSIZE_MIDDLE);
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
@ -89,7 +89,6 @@ void GCodeViewer::VBuffer::reset()
|
||||
count = 0;
|
||||
}
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void GCodeViewer::InstanceVBuffer::Ranges::reset()
|
||||
{
|
||||
for (Range& range : ranges) {
|
||||
@ -107,7 +106,6 @@ void GCodeViewer::InstanceVBuffer::reset()
|
||||
buffer.clear();
|
||||
render_ranges.reset();
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
void GCodeViewer::IBuffer::reset()
|
||||
{
|
||||
@ -150,12 +148,10 @@ bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) co
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void GCodeViewer::TBuffer::Model::reset()
|
||||
{
|
||||
instances.reset();
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
void GCodeViewer::TBuffer::reset()
|
||||
{
|
||||
@ -167,9 +163,7 @@ void GCodeViewer::TBuffer::reset()
|
||||
indices.clear();
|
||||
paths.clear();
|
||||
render_paths.clear();
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
model.reset();
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id)
|
||||
@ -270,12 +264,8 @@ void GCodeViewer::SequentialView::Marker::render() const
|
||||
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":");
|
||||
ImGui::SameLine();
|
||||
char buf[1024];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
const Vec3f position = m_world_position + m_world_offset;
|
||||
sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z());
|
||||
#else
|
||||
sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", m_world_position.x(), m_world_position.y(), m_world_position.z());
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
imgui.text(std::string(buf));
|
||||
|
||||
// force extra frame to automatically update window size
|
||||
@ -567,48 +557,11 @@ const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0
|
||||
|
||||
GCodeViewer::GCodeViewer()
|
||||
{
|
||||
#if !ENABLE_SEAMS_USING_MODELS
|
||||
// initializes non OpenGL data of TBuffers
|
||||
// OpenGL data are initialized into render().init_gl_data()
|
||||
for (size_t i = 0; i < m_buffers.size(); ++i) {
|
||||
TBuffer& buffer = m_buffers[i];
|
||||
switch (buffer_type(i))
|
||||
{
|
||||
default: { break; }
|
||||
case EMoveType::Tool_change:
|
||||
case EMoveType::Color_change:
|
||||
case EMoveType::Pause_Print:
|
||||
case EMoveType::Custom_GCode:
|
||||
case EMoveType::Retract:
|
||||
case EMoveType::Unretract:
|
||||
case EMoveType::Seam: {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
|
||||
buffer.vertices.format = VBuffer::EFormat::Position;
|
||||
break;
|
||||
}
|
||||
case EMoveType::Wipe:
|
||||
case EMoveType::Extrude: {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
|
||||
buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
|
||||
break;
|
||||
}
|
||||
case EMoveType::Travel: {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
|
||||
buffer.vertices.format = VBuffer::EFormat::PositionNormal1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_toolpath_move_type_visible(EMoveType::Extrude, true);
|
||||
#endif // !ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
m_extrusions.reset_role_visibility_flags();
|
||||
|
||||
// m_sequential_view.skip_invisible_moves = true;
|
||||
}
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void GCodeViewer::init()
|
||||
{
|
||||
if (m_gl_data_initialized)
|
||||
@ -628,7 +581,6 @@ void GCodeViewer::init()
|
||||
case EMoveType::Retract:
|
||||
case EMoveType::Unretract:
|
||||
case EMoveType::Seam: {
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel;
|
||||
buffer.shader = "gouraud_light_instanced";
|
||||
@ -646,20 +598,6 @@ void GCodeViewer::init()
|
||||
buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Model;
|
||||
buffer.shader = "gouraud_light_instanced";
|
||||
buffer.model.model.init_from(diamond(16));
|
||||
buffer.model.color = option_color(type);
|
||||
}
|
||||
else {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
|
||||
buffer.vertices.format = VBuffer::EFormat::Position;
|
||||
buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110";
|
||||
}
|
||||
break;
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
}
|
||||
case EMoveType::Wipe:
|
||||
case EMoveType::Extrude: {
|
||||
@ -689,7 +627,6 @@ void GCodeViewer::init()
|
||||
|
||||
m_gl_data_initialized = true;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized)
|
||||
{
|
||||
@ -879,72 +816,11 @@ void GCodeViewer::reset()
|
||||
|
||||
void GCodeViewer::render()
|
||||
{
|
||||
#if !ENABLE_SEAMS_USING_MODELS
|
||||
auto init_gl_data = [this]() {
|
||||
// initializes opengl data of TBuffers
|
||||
for (size_t i = 0; i < m_buffers.size(); ++i) {
|
||||
TBuffer& buffer = m_buffers[i];
|
||||
EMoveType type = buffer_type(i);
|
||||
switch (type)
|
||||
{
|
||||
default: { break; }
|
||||
case EMoveType::Tool_change:
|
||||
case EMoveType::Color_change:
|
||||
case EMoveType::Pause_Print:
|
||||
case EMoveType::Custom_GCode:
|
||||
case EMoveType::Retract:
|
||||
case EMoveType::Unretract:
|
||||
case EMoveType::Seam: {
|
||||
if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Model;
|
||||
buffer.shader = "gouraud_light_instanced";
|
||||
buffer.model.model.init_from(diamond(16));
|
||||
buffer.model.color = option_color(type);
|
||||
}
|
||||
else {
|
||||
buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point;
|
||||
buffer.vertices.format = VBuffer::EFormat::Position;
|
||||
buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EMoveType::Wipe:
|
||||
case EMoveType::Extrude: {
|
||||
buffer.shader = "gouraud_light";
|
||||
break;
|
||||
}
|
||||
case EMoveType::Travel: {
|
||||
buffer.shader = "toolpaths_lines";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initializes tool marker
|
||||
m_sequential_view.marker.init();
|
||||
|
||||
// initializes point sizes
|
||||
std::array<int, 2> point_sizes;
|
||||
::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data());
|
||||
m_detected_point_sizes = { static_cast<float>(point_sizes[0]), static_cast<float>(point_sizes[1]) };
|
||||
m_gl_data_initialized = true;
|
||||
};
|
||||
#endif // !ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
m_statistics.reset_opengl();
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
m_statistics.total_instances_gpu_size = 0;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
#if !ENABLE_SEAMS_USING_MODELS
|
||||
// OpenGL data must be initialized after the glContext has been created.
|
||||
// This is ensured when this method is called by GLCanvas3D::_render_gcode().
|
||||
if (!m_gl_data_initialized)
|
||||
init_gl_data();
|
||||
#endif // !ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
if (m_roles.empty())
|
||||
return;
|
||||
|
||||
@ -955,9 +831,7 @@ void GCodeViewer::render()
|
||||
render_legend(legend_height);
|
||||
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
|
||||
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.render(legend_height);
|
||||
}
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -1316,7 +1190,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
vertices.push_back(normal.z());
|
||||
};
|
||||
|
||||
if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
|
||||
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
|
||||
buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1);
|
||||
buffer.paths.back().sub_paths.back().first.position = prev.position;
|
||||
}
|
||||
@ -1407,7 +1281,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]);
|
||||
};
|
||||
|
||||
if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
|
||||
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
|
||||
buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
|
||||
buffer.paths.back().sub_paths.back().first.position = prev.position;
|
||||
}
|
||||
@ -1501,7 +1375,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
sq_prev_length = sq_length;
|
||||
};
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
// format data into the buffers to be rendered as instanced model
|
||||
auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) {
|
||||
// append position
|
||||
@ -1517,7 +1390,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
instances_ids.push_back(move_id);
|
||||
};
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
// format data into the buffers to be rendered as batched model
|
||||
auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) {
|
||||
const double width = static_cast<double>(1.5f * curr.width);
|
||||
@ -1558,8 +1430,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
@ -1599,46 +1469,32 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
if (wxGetApp().is_editor())
|
||||
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.gcode_ids.clear();
|
||||
for (size_t i = 0; i < gcode_result.moves.size(); ++i) {
|
||||
const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i];
|
||||
if (move.type != EMoveType::Seam)
|
||||
m_sequential_view.gcode_ids.push_back(move.gcode_id);
|
||||
}
|
||||
#else
|
||||
for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) {
|
||||
m_sequential_view.gcode_ids.push_back(move.gcode_id);
|
||||
}
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
std::vector<MultiVertexBuffer> vertices(m_buffers.size());
|
||||
std::vector<MultiIndexBuffer> indices(m_buffers.size());
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
std::vector<InstanceBuffer> instances(m_buffers.size());
|
||||
std::vector<InstanceIdBuffer> instances_ids(m_buffers.size());
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
std::vector<InstancesOffsets> instances_offsets(m_buffers.size());
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
std::vector<float> options_zs;
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
size_t seams_count = 0;
|
||||
std::vector<size_t> seams_ids;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// toolpaths data -> extract vertices from result
|
||||
for (size_t i = 0; i < m_moves_count; ++i) {
|
||||
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (curr.type == EMoveType::Seam) {
|
||||
++seams_count;
|
||||
seams_ids.push_back(i);
|
||||
}
|
||||
|
||||
size_t move_id = i - seams_count;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// skip first vertex
|
||||
if (i == 0)
|
||||
@ -1658,13 +1514,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
const unsigned char id = buffer_id(curr.type);
|
||||
TBuffer& t_buffer = m_buffers[id];
|
||||
MultiVertexBuffer& v_multibuffer = vertices[id];
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
InstanceBuffer& inst_buffer = instances[id];
|
||||
InstanceIdBuffer& inst_id_buffer = instances_ids[id];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
InstancesOffsets& inst_offsets = instances_offsets[id];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// ensure there is at least one vertex buffer
|
||||
if (v_multibuffer.empty())
|
||||
@ -1672,21 +1524,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
|
||||
// if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
|
||||
// add another vertex buffer
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes();
|
||||
if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) {
|
||||
#else
|
||||
if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - t_buffer.max_vertices_per_segment_size_bytes()) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
v_multibuffer.push_back(VertexBuffer());
|
||||
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
|
||||
Path& last_path = t_buffer.paths.back();
|
||||
if (prev.type == curr.type && last_path.matches(curr))
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, move_id - 1);
|
||||
#else
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, i - 1);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -1696,21 +1540,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
{
|
||||
case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; }
|
||||
case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; }
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, move_id); break; }
|
||||
#else
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, i); break; }
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
case TBuffer::ERenderPrimitiveType::InstancedModel:
|
||||
{
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, move_id);
|
||||
inst_offsets.push_back(prev.position - curr.position);
|
||||
#else
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.instances_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -1718,32 +1552,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::BatchedModel:
|
||||
{
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id);
|
||||
inst_offsets.push_back(prev.position - curr.position);
|
||||
#else
|
||||
add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.batched_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
break;
|
||||
}
|
||||
#else
|
||||
case TBuffer::ERenderPrimitiveType::Model:
|
||||
{
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, move_id);
|
||||
#else
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.instances_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
break;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
}
|
||||
|
||||
// collect options zs for later use
|
||||
@ -1755,11 +1570,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
|
||||
// smooth toolpaths corners for the given TBuffer using triangles
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
auto smooth_triangle_toolpaths_corners = [&gcode_result, &seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) {
|
||||
#else
|
||||
auto smooth_triangle_toolpaths_corners = [&gcode_result](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) {
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) {
|
||||
return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]);
|
||||
};
|
||||
@ -1833,7 +1644,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
auto extract_move_id = [&seams_ids](size_t id) {
|
||||
for (int i = seams_ids.size() - 1; i >= 0; --i) {
|
||||
if (seams_ids[i] < id + i + 1)
|
||||
@ -1841,7 +1651,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
return id;
|
||||
};
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats();
|
||||
for (const Path& path : t_buffer.paths) {
|
||||
@ -1853,16 +1662,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
const float half_width = 0.5f * path.width;
|
||||
for (size_t j = 1; j < path_vertices_count - 1; ++j) {
|
||||
size_t curr_s_id = path.sub_paths.front().first.s_id + j;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
size_t move_id = extract_move_id(curr_s_id);
|
||||
const Vec3f& prev = gcode_result.moves[move_id - 1].position;
|
||||
const Vec3f& curr = gcode_result.moves[move_id].position;
|
||||
const Vec3f& next = gcode_result.moves[move_id + 1].position;
|
||||
#else
|
||||
const Vec3f& prev = gcode_result.moves[curr_s_id - 1].position;
|
||||
const Vec3f& curr = gcode_result.moves[curr_s_id].position;
|
||||
const Vec3f& next = gcode_result.moves[curr_s_id + 1].position;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// select the subpaths which contains the previous/next segments
|
||||
if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id))
|
||||
@ -1929,10 +1732,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
// dismiss, no more needed
|
||||
std::vector<size_t>().swap(seams_ids);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
for (MultiVertexBuffer& v_multibuffer : vertices) {
|
||||
for (VertexBuffer& v_buffer : v_multibuffer) {
|
||||
@ -1951,16 +1752,12 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
// send vertices data to gpu, where needed
|
||||
for (size_t i = 0; i < m_buffers.size(); ++i) {
|
||||
TBuffer& t_buffer = m_buffers[i];
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
|
||||
const InstanceBuffer& inst_buffer = instances[i];
|
||||
if (!inst_buffer.empty()) {
|
||||
t_buffer.model.instances.buffer = inst_buffer;
|
||||
t_buffer.model.instances.s_ids = instances_ids[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
t_buffer.model.instances.offsets = instances_offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1969,25 +1766,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
if (!inst_buffer.empty()) {
|
||||
t_buffer.model.instances.buffer = inst_buffer;
|
||||
t_buffer.model.instances.s_ids = instances_ids[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
t_buffer.model.instances.offsets = instances_offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) {
|
||||
const InstanceBuffer& inst_buffer = instances[i];
|
||||
if (!inst_buffer.empty()) {
|
||||
t_buffer.model.instances.buffer = inst_buffer;
|
||||
t_buffer.model.instances.s_ids = instances_ids[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
t_buffer.model.instances.offsets = instances_offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
else {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
const MultiVertexBuffer& v_multibuffer = vertices[i];
|
||||
for (const VertexBuffer& v_buffer : v_multibuffer) {
|
||||
const size_t size_elements = v_buffer.size();
|
||||
@ -2010,9 +1791,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
t_buffer.vertices.vbos.push_back(static_cast<unsigned int>(id));
|
||||
t_buffer.vertices.sizes.push_back(size_bytes);
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -2023,10 +1802,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
|
||||
// dismiss vertices data, no more needed
|
||||
std::vector<MultiVertexBuffer>().swap(vertices);
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
std::vector<InstanceBuffer>().swap(instances);
|
||||
std::vector<InstanceIdBuffer>().swap(instances_ids);
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// toolpaths data -> extract indices from result
|
||||
// paths may have been filled while extracting vertices,
|
||||
@ -2043,18 +1820,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
using VboIndexList = std::vector<unsigned int>;
|
||||
std::vector<VboIndexList> vbo_indices(m_buffers.size());
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
seams_count = 0;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
for (size_t i = 0; i < m_moves_count; ++i) {
|
||||
const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (curr.type == EMoveType::Seam)
|
||||
++seams_count;
|
||||
|
||||
size_t move_id = i - seams_count;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// skip first vertex
|
||||
if (i == 0)
|
||||
@ -2082,63 +1855,37 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
// ensure there is at least one index buffer
|
||||
if (i_multibuffer.empty()) {
|
||||
i_multibuffer.push_back(IndexBuffer());
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
if (!t_buffer.vertices.vbos.empty())
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
|
||||
}
|
||||
|
||||
// if adding the indices for the current segment exceeds the threshold size of the current index buffer
|
||||
// create another index buffer
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes();
|
||||
if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) {
|
||||
#else
|
||||
if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.max_indices_per_segment_size_bytes()) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
i_multibuffer.push_back(IndexBuffer());
|
||||
vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point &&
|
||||
t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) {
|
||||
#else
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
Path& last_path = t_buffer.paths.back();
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
|
||||
#else
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, i - 1);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
// if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
|
||||
// create another index buffer
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes();
|
||||
if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) {
|
||||
#else
|
||||
if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - t_buffer.max_vertices_per_segment_size_bytes()) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
i_multibuffer.push_back(IndexBuffer());
|
||||
|
||||
++curr_vertex_buffer.first;
|
||||
curr_vertex_buffer.second = 0;
|
||||
vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point &&
|
||||
t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) {
|
||||
#else
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
Path& last_path = t_buffer.paths.back();
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
|
||||
#else
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, i - 1);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -2147,38 +1894,24 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
switch (t_buffer.render_primitive_type)
|
||||
{
|
||||
case TBuffer::ERenderPrimitiveType::Point: {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_indices_as_point(curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
|
||||
#else
|
||||
add_indices_as_point(curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
|
||||
break;
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::Line: {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
|
||||
#else
|
||||
add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
|
||||
break;
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
|
||||
#else
|
||||
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
break;
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
case TBuffer::ERenderPrimitiveType::BatchedModel: {
|
||||
add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second);
|
||||
curr_vertex_buffer.second += t_buffer.model.data.vertices_count();
|
||||
break;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
default: { break; }
|
||||
}
|
||||
}
|
||||
@ -2192,13 +1925,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
// toolpaths data -> send indices data to gpu
|
||||
for (size_t i = 0; i < m_buffers.size(); ++i) {
|
||||
TBuffer& t_buffer = m_buffers[i];
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) {
|
||||
#else
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Model) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
const MultiIndexBuffer& i_multibuffer = indices[i];
|
||||
for (const IndexBuffer& i_buffer : i_multibuffer) {
|
||||
const size_t size_elements = i_buffer.size();
|
||||
@ -2221,9 +1948,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
if (progress_dialog != nullptr) {
|
||||
@ -2264,34 +1989,22 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
|
||||
// layers zs / roles / extruder ids -> extract from result
|
||||
size_t last_travel_s_id = 0;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
seams_count = 0;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
for (size_t i = 0; i < m_moves_count; ++i) {
|
||||
const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (move.type == EMoveType::Seam)
|
||||
++seams_count;
|
||||
|
||||
size_t move_id = i - seams_count;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
if (move.type == EMoveType::Extrude) {
|
||||
// layers zs
|
||||
const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back();
|
||||
const double z = static_cast<double>(move.position.z());
|
||||
if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z)
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_layers.append(z, { last_travel_s_id, move_id });
|
||||
#else
|
||||
m_layers.append(z, { last_travel_s_id, i });
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
else
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_layers.get_endpoints().back().last = move_id;
|
||||
#else
|
||||
m_layers.get_endpoints().back().last = i;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
// extruder ids
|
||||
m_extruder_ids.emplace_back(move.extruder_id);
|
||||
// roles
|
||||
@ -2299,17 +2012,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
m_roles.emplace_back(move.extrusion_role);
|
||||
}
|
||||
else if (move.type == EMoveType::Travel) {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (move_id - last_travel_s_id > 1 && !m_layers.empty())
|
||||
m_layers.get_endpoints().back().last = move_id;
|
||||
|
||||
last_travel_s_id = move_id;
|
||||
#else
|
||||
if (i - last_travel_s_id > 1 && !m_layers.empty())
|
||||
m_layers.get_endpoints().back().last = i;
|
||||
|
||||
last_travel_s_id = i;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -2361,8 +2067,19 @@ void GCodeViewer::load_shells(const Print& print, bool initialized)
|
||||
instance_ids[i] = i;
|
||||
}
|
||||
|
||||
size_t current_volumes_count = m_shells.volumes.volumes.size();
|
||||
m_shells.volumes.load_object(model_obj, object_id, instance_ids, "object", initialized);
|
||||
|
||||
// adjust shells' z if raft is present
|
||||
const SlicingParameters& slicing_parameters = obj->slicing_parameters();
|
||||
if (slicing_parameters.object_print_z_min != 0.0) {
|
||||
const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ();
|
||||
for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) {
|
||||
GLVolume* v = m_shells.volumes.volumes[i];
|
||||
v->set_volume_offset(v->get_volume_offset() + z_offset);
|
||||
}
|
||||
}
|
||||
|
||||
++object_id;
|
||||
}
|
||||
|
||||
@ -2474,9 +2191,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
Statistics* statistics = const_cast<Statistics*>(&m_statistics);
|
||||
statistics->render_paths_size = 0;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
statistics->models_instances_size = 0;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
const bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1";
|
||||
@ -2497,13 +2212,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
if (!buffer.visible)
|
||||
continue;
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel ||
|
||||
buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
|
||||
#else
|
||||
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
for (size_t id : buffer.model.instances.s_ids) {
|
||||
if (id < m_layers.get_endpoints_at(m_layers_z_range[0]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id)
|
||||
continue;
|
||||
@ -2521,7 +2231,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
}
|
||||
}
|
||||
else {
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
for (size_t i = 0; i < buffer.paths.size(); ++i) {
|
||||
const Path& path = buffer.paths[i];
|
||||
if (path.type == EMoveType::Travel) {
|
||||
@ -2555,9 +2264,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
}
|
||||
}
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
// update current sequential position
|
||||
@ -2567,30 +2274,21 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
// get the world position from the vertex buffer
|
||||
bool found = false;
|
||||
for (const TBuffer& buffer : m_buffers) {
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel ||
|
||||
buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
|
||||
#else
|
||||
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) {
|
||||
if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) {
|
||||
size_t offset = i * buffer.model.instances.instance_size_floats();
|
||||
sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0];
|
||||
sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1];
|
||||
sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
sequential_view->current_offset = buffer.model.instances.offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
// searches the path containing the current position
|
||||
for (const Path& path : buffer.paths) {
|
||||
if (path.contains(m_sequential_view.current.last)) {
|
||||
@ -2622,18 +2320,13 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast<GLintptr>(index * buffer.vertices.vertex_size_bytes()), static_cast<GLsizeiptr>(3 * sizeof(float)), static_cast<void*>(sequential_view->current_position.data())));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
sequential_view->current_offset = Vec3f::Zero();
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
if (found)
|
||||
break;
|
||||
@ -2740,20 +2433,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
// second pass: for buffers using instanced and batched models, update the instances render ranges
|
||||
#else
|
||||
// second pass: for buffers using instanced models, update the instances render ranges
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
for (size_t b = 0; b < m_buffers.size(); ++b) {
|
||||
TBuffer& buffer = const_cast<TBuffer&>(m_buffers[b]);
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel &&
|
||||
buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel)
|
||||
#else
|
||||
if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Model)
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
continue;
|
||||
|
||||
buffer.model.instances.render_ranges.reset();
|
||||
@ -2769,11 +2453,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) {
|
||||
for (size_t id : buffer.model.instances.s_ids) {
|
||||
if (has_second_range) {
|
||||
#if ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
if (id < m_sequential_view.endpoints.first) {
|
||||
#else
|
||||
if (id <= m_sequential_view.endpoints.first) {
|
||||
#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z
|
||||
++buffer.model.instances.render_ranges.ranges.front().offset;
|
||||
if (id <= m_sequential_view.current.first)
|
||||
++buffer.model.instances.render_ranges.ranges.back().offset;
|
||||
@ -2796,14 +2476,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// set sequential data to their final value
|
||||
sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints;
|
||||
sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
sequential_view->global = global_endpoints;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// updates sequential range caps
|
||||
std::array<SequentialRangeCap, 2>* sequential_range_caps = const_cast<std::array<SequentialRangeCap, 2>*>(&m_sequential_range_caps);
|
||||
@ -2925,11 +2602,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int);
|
||||
statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t);
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float);
|
||||
statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t);
|
||||
statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range);
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
statistics->refresh_paths_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -3018,7 +2693,6 @@ void GCodeViewer::render_toolpaths()
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_instanced_model = [this]
|
||||
#else
|
||||
@ -3044,7 +2718,6 @@ void GCodeViewer::render_toolpaths()
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) {
|
||||
#else
|
||||
@ -3102,8 +2775,6 @@ void GCodeViewer::render_toolpaths()
|
||||
buffer_range.first = buffer_range.last;
|
||||
}
|
||||
};
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
auto line_width = [](double zoom) {
|
||||
return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0));
|
||||
@ -3113,11 +2784,7 @@ void GCodeViewer::render_toolpaths()
|
||||
unsigned char end_id = buffer_id(EMoveType::Count);
|
||||
|
||||
for (unsigned char i = begin_id; i < end_id; ++i) {
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
TBuffer& buffer = m_buffers[i];
|
||||
#else
|
||||
const TBuffer& buffer = m_buffers[i];
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
if (!buffer.visible || !buffer.has_data())
|
||||
continue;
|
||||
|
||||
@ -3125,25 +2792,17 @@ void GCodeViewer::render_toolpaths()
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
|
||||
#else
|
||||
if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
shader->set_uniform("emission_factor", 0.25f);
|
||||
render_as_instanced_model(buffer, *shader);
|
||||
shader->set_uniform("emission_factor", 0.0f);
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
|
||||
shader->set_uniform("emission_factor", 0.25f);
|
||||
render_as_batched_model(buffer, *shader);
|
||||
shader->set_uniform("emission_factor", 0.0f);
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
else {
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
for (size_t j = 0; j < buffer.indices.size(); ++j) {
|
||||
const IBuffer& i_buffer = buffer.indices[j];
|
||||
|
||||
@ -3184,9 +2843,7 @@ void GCodeViewer::render_toolpaths()
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
@ -3297,11 +2954,7 @@ void GCodeViewer::render_legend(float& legend_height)
|
||||
|
||||
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label,
|
||||
#else
|
||||
auto append_item = [this, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label,
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f },
|
||||
double used_filament_m = 0.0, double used_filament_g = 0.0,
|
||||
std::function<void()> callback = nullptr) {
|
||||
@ -3319,19 +2972,7 @@ void GCodeViewer::render_legend(float& legend_height)
|
||||
}
|
||||
case EItemType::Circle: {
|
||||
ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
|
||||
#if !ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") {
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size,
|
||||
ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
|
||||
float radius = 0.5f * icon_size;
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
radius = 0.5f * icon_size * 0.01f * 33.0f;
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16);
|
||||
}
|
||||
else
|
||||
#endif // !ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
|
||||
draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16);
|
||||
break;
|
||||
}
|
||||
case EItemType::Hexagon: {
|
||||
@ -3971,11 +3612,7 @@ void GCodeViewer::render_legend(float& legend_height)
|
||||
auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) {
|
||||
const TBuffer& buffer = m_buffers[buffer_id(move_type)];
|
||||
if (buffer.visible && buffer.has_data())
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
append_item(EItemType::Circle, Options_Colors[static_cast<unsigned int>(color)], text);
|
||||
#else
|
||||
append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast<unsigned int>(color)], text);
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
};
|
||||
|
||||
// options section
|
||||
@ -4198,13 +3835,9 @@ void GCodeViewer::render_statistics()
|
||||
add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count);
|
||||
add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count);
|
||||
add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count);
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
ImGui::Separator();
|
||||
add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count);
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count);
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("CPU memory")) {
|
||||
@ -4213,17 +3846,13 @@ void GCodeViewer::render_statistics()
|
||||
ImGui::Separator();
|
||||
add_memory(std::string("Paths:"), m_statistics.paths_size);
|
||||
add_memory(std::string("Render paths:"), m_statistics.render_paths_size);
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
add_memory(std::string("Models instances:"), m_statistics.models_instances_size);
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("GPU memory")) {
|
||||
add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size);
|
||||
add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size);
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size);
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
ImGui::Separator();
|
||||
add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size);
|
||||
add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size);
|
||||
@ -4233,12 +3862,8 @@ void GCodeViewer::render_statistics()
|
||||
add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count);
|
||||
add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count);
|
||||
add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count);
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
add_counter(std::string("Instances count:"), m_statistics.instances_count);
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
add_counter(std::string("Batched count:"), m_statistics.batched_count);
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
ImGui::Separator();
|
||||
add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count);
|
||||
add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count);
|
||||
|
@ -27,13 +27,9 @@ class GCodeViewer
|
||||
using MultiVertexBuffer = std::vector<VertexBuffer>;
|
||||
using IndexBuffer = std::vector<IBufferType>;
|
||||
using MultiIndexBuffer = std::vector<IndexBuffer>;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
using InstanceBuffer = std::vector<float>;
|
||||
using InstanceIdBuffer = std::vector<size_t>;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
using InstancesOffsets = std::vector<Vec3f>;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
static const std::vector<Color> Extrusion_Role_Colors;
|
||||
static const std::vector<Color> Options_Colors;
|
||||
@ -107,17 +103,10 @@ class GCodeViewer
|
||||
void reset();
|
||||
};
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
// buffer containing instances data used to render a toolpaths using instanced or batched models
|
||||
// instance record format:
|
||||
// instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced())
|
||||
// batched models: 3 floats -> position.x|position.y|position.z
|
||||
#else
|
||||
// buffer containing instances data used to render a toolpaths using instanced models
|
||||
// instance record format: 5 floats -> position.x|position.y|position.z|width|height
|
||||
// which is sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced()
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
struct InstanceVBuffer
|
||||
{
|
||||
// ranges used to render only subparts of the intances
|
||||
@ -140,7 +129,6 @@ class GCodeViewer
|
||||
void reset();
|
||||
};
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
enum class EFormat : unsigned char
|
||||
{
|
||||
InstancedModel,
|
||||
@ -148,21 +136,17 @@ class GCodeViewer
|
||||
};
|
||||
|
||||
EFormat format;
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
|
||||
// cpu-side buffer containing all instances data
|
||||
InstanceBuffer buffer;
|
||||
// indices of the moves for all instances
|
||||
std::vector<size_t> s_ids;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
// position offsets, used to show the correct value of the tool position
|
||||
InstancesOffsets offsets;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
Ranges render_ranges;
|
||||
|
||||
size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); }
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t instance_size_floats() const {
|
||||
switch (format)
|
||||
{
|
||||
@ -171,14 +155,10 @@ class GCodeViewer
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
#else
|
||||
size_t instance_size_floats() const { return 5; }
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); }
|
||||
|
||||
void reset();
|
||||
};
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type
|
||||
struct IBuffer
|
||||
@ -313,17 +293,9 @@ class GCodeViewer
|
||||
{
|
||||
Point,
|
||||
Line,
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
Triangle,
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
InstancedModel,
|
||||
BatchedModel
|
||||
#else
|
||||
Model
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#else
|
||||
Triangle
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
};
|
||||
|
||||
ERenderPrimitiveType render_primitive_type;
|
||||
@ -332,22 +304,18 @@ class GCodeViewer
|
||||
VBuffer vertices;
|
||||
std::vector<IBuffer> indices;
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
struct Model
|
||||
{
|
||||
GLModel model;
|
||||
Color color;
|
||||
InstanceVBuffer instances;
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
GLModel::InitializationData data;
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// contain the buffer for model primitive types
|
||||
Model model;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
std::string shader;
|
||||
std::vector<Path> paths;
|
||||
@ -396,7 +364,6 @@ class GCodeViewer
|
||||
}
|
||||
size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); }
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
bool has_data() const {
|
||||
switch (render_primitive_type)
|
||||
{
|
||||
@ -405,23 +372,14 @@ class GCodeViewer
|
||||
case ERenderPrimitiveType::Triangle: {
|
||||
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); }
|
||||
case ERenderPrimitiveType::BatchedModel: {
|
||||
return model.data.vertices_count() > 0 && model.data.indices_count() &&
|
||||
!vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
|
||||
}
|
||||
#else
|
||||
case ERenderPrimitiveType::Model: { return model.model.is_initialized() && !model.instances.buffer.empty(); }
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
default: { return false; }
|
||||
}
|
||||
}
|
||||
#else
|
||||
bool has_data() const {
|
||||
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
};
|
||||
|
||||
// helper to render shells
|
||||
@ -569,36 +527,24 @@ class GCodeViewer
|
||||
int64_t gl_multi_lines_calls_count{ 0 };
|
||||
int64_t gl_multi_triangles_calls_count{ 0 };
|
||||
int64_t gl_triangles_calls_count{ 0 };
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
int64_t gl_instanced_models_calls_count{ 0 };
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
int64_t gl_batched_models_calls_count{ 0 };
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
// memory
|
||||
int64_t results_size{ 0 };
|
||||
int64_t total_vertices_gpu_size{ 0 };
|
||||
int64_t total_indices_gpu_size{ 0 };
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
int64_t total_instances_gpu_size{ 0 };
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
int64_t max_vbuffer_gpu_size{ 0 };
|
||||
int64_t max_ibuffer_gpu_size{ 0 };
|
||||
int64_t paths_size{ 0 };
|
||||
int64_t render_paths_size{ 0 };
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
int64_t models_instances_size{ 0 };
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
// other
|
||||
int64_t travel_segments_count{ 0 };
|
||||
int64_t wipe_segments_count{ 0 };
|
||||
int64_t extrude_segments_count{ 0 };
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
int64_t instances_count{ 0 };
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
int64_t batched_count{ 0 };
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
int64_t vbuffers_count{ 0 };
|
||||
int64_t ibuffers_count{ 0 };
|
||||
|
||||
@ -624,40 +570,28 @@ class GCodeViewer
|
||||
gl_multi_lines_calls_count = 0;
|
||||
gl_multi_triangles_calls_count = 0;
|
||||
gl_triangles_calls_count = 0;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
gl_instanced_models_calls_count = 0;
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
gl_batched_models_calls_count = 0;
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
void reset_sizes() {
|
||||
results_size = 0;
|
||||
total_vertices_gpu_size = 0;
|
||||
total_indices_gpu_size = 0;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
total_instances_gpu_size = 0;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
max_vbuffer_gpu_size = 0;
|
||||
max_ibuffer_gpu_size = 0;
|
||||
paths_size = 0;
|
||||
render_paths_size = 0;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
models_instances_size = 0;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
|
||||
void reset_others() {
|
||||
travel_segments_count = 0;
|
||||
wipe_segments_count = 0;
|
||||
extrude_segments_count = 0;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
extrude_segments_count = 0;
|
||||
instances_count = 0;
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
batched_count = 0;
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
vbuffers_count = 0;
|
||||
ibuffers_count = 0;
|
||||
}
|
||||
@ -672,12 +606,10 @@ public:
|
||||
GLModel m_model;
|
||||
Vec3f m_world_position;
|
||||
Transform3f m_world_transform;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
// for seams, the position of the marker is on the last endpoint of the toolpath containing it
|
||||
// the offset is used to show the correct value of tool position in the "ToolPosition" window
|
||||
// see implementation of render() method
|
||||
Vec3f m_world_offset;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
float m_z_offset{ 0.5f };
|
||||
bool m_visible{ true };
|
||||
|
||||
@ -687,9 +619,7 @@ public:
|
||||
const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
|
||||
|
||||
void set_world_position(const Vec3f& position);
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
void set_world_offset(const Vec3f& offset) { m_world_offset = offset; }
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool visible) { m_visible = visible; }
|
||||
@ -743,13 +673,9 @@ public:
|
||||
Endpoints endpoints;
|
||||
Endpoints current;
|
||||
Endpoints last_current;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
Endpoints global;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
Vec3f current_position{ Vec3f::Zero() };
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
Vec3f current_offset{ Vec3f::Zero() };
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
Marker marker;
|
||||
GCodeWindow gcode_window;
|
||||
std::vector<unsigned int> gcode_ids;
|
||||
@ -811,9 +737,7 @@ public:
|
||||
GCodeViewer();
|
||||
~GCodeViewer() { reset(); }
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void init();
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// extract rendering data from the given parameters
|
||||
void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized);
|
||||
|
@ -95,6 +95,11 @@ RetinaHelper::~RetinaHelper() {}
|
||||
float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); }
|
||||
#endif // __WXGTK3__
|
||||
|
||||
// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0.
|
||||
#if defined(__linux__) && defined(Convex)
|
||||
#undef Convex
|
||||
#endif
|
||||
|
||||
Size::Size()
|
||||
: m_width(0)
|
||||
, m_height(0)
|
||||
@ -1417,10 +1422,8 @@ void GLCanvas3D::render()
|
||||
if (!is_initialized() && !init())
|
||||
return;
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
if (!m_main_toolbar.is_enabled())
|
||||
m_gcode_viewer.init();
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
if (! m_bed.build_volume().valid()) {
|
||||
// this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
|
||||
@ -4127,24 +4130,15 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
|
||||
}
|
||||
}
|
||||
|
||||
#if !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
if (visible_volumes.empty())
|
||||
return;
|
||||
#endif // !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
|
||||
BoundingBoxf3 volumes_box;
|
||||
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
if (!visible_volumes.empty()) {
|
||||
#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
for (const GLVolume* vol : visible_volumes) {
|
||||
volumes_box.merge(vol->transformed_bounding_box());
|
||||
}
|
||||
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
}
|
||||
else
|
||||
// This happens for empty projects
|
||||
volumes_box = m_bed.extended_bounding_box();
|
||||
#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
|
||||
Camera camera;
|
||||
camera.set_type(camera_type);
|
||||
|
@ -623,9 +623,7 @@ public:
|
||||
void reset_volumes();
|
||||
ModelInstanceEPrintVolumeState check_volumes_outside_state() const;
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void init_gcode_viewer() { m_gcode_viewer.init(); }
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
void reset_gcode_toolpaths() { m_gcode_viewer.reset(); }
|
||||
const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); }
|
||||
void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
|
||||
|
@ -17,7 +17,6 @@
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t GLModel::InitializationData::vertices_count() const
|
||||
{
|
||||
size_t ret = 0;
|
||||
@ -35,7 +34,6 @@ size_t GLModel::InitializationData::indices_count() const
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
|
||||
void GLModel::init_from(const InitializationData& data)
|
||||
{
|
||||
@ -231,7 +229,6 @@ void GLModel::render() const
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const
|
||||
{
|
||||
if (instances_vbo == 0)
|
||||
@ -308,7 +305,6 @@ void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instance
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
void GLModel::send_to_gpu(RenderData& data, const std::vector<float>& vertices, const std::vector<unsigned int>& indices)
|
||||
{
|
||||
|
@ -49,14 +49,12 @@ namespace GUI {
|
||||
|
||||
std::vector<Entity> entities;
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
size_t vertices_count() const;
|
||||
size_t vertices_size_floats() const { return vertices_count() * 6; }
|
||||
size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); }
|
||||
|
||||
size_t indices_count() const;
|
||||
size_t indices_size_bytes() const { return indices_count() * sizeof(unsigned int); }
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
};
|
||||
|
||||
private:
|
||||
@ -80,9 +78,7 @@ namespace GUI {
|
||||
|
||||
void reset();
|
||||
void render() const;
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
bool is_initialized() const { return !m_render_data.empty(); }
|
||||
|
||||
|
@ -33,31 +33,13 @@ std::pair<bool, std::string> GLShadersManager::init()
|
||||
|
||||
bool valid = true;
|
||||
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
// used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview
|
||||
#else
|
||||
// used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" });
|
||||
// used to render printbed
|
||||
valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" });
|
||||
// used to render options in gcode preview
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3))
|
||||
valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" });
|
||||
#else
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3))
|
||||
valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" });
|
||||
else {
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" });
|
||||
if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20))
|
||||
valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" });
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
}
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
// used to render extrusion and travel paths as lines in gcode preview
|
||||
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
|
||||
// used to render objects in 3d editor
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <exception>
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
@ -97,6 +98,8 @@
|
||||
#include <gtk/gtk.h>
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
@ -471,39 +474,82 @@ static bool run_updater_win()
|
||||
}
|
||||
#endif //_WIN32
|
||||
|
||||
struct FileWildcards {
|
||||
std::string_view title;
|
||||
std::vector<std::string_view> file_extensions;
|
||||
};
|
||||
|
||||
static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
|
||||
/* FT_STL */ { "STL files"sv, { ".stl"sv } },
|
||||
/* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } },
|
||||
/* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } },
|
||||
/* FT_3MF */ { "3MF files"sv, { ".3mf"sv } },
|
||||
/* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } },
|
||||
/* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } },
|
||||
/* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } },
|
||||
/* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } },
|
||||
|
||||
/* FT_INI */ { "INI files"sv, { ".ini"sv } },
|
||||
/* FT_SVG */ { "SVG files"sv, { ".svg"sv } },
|
||||
|
||||
/* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } },
|
||||
|
||||
/* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } },
|
||||
};
|
||||
|
||||
// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.
|
||||
// The function accepts a custom extension parameter. If the parameter is provided, the custom extension
|
||||
// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips
|
||||
// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template).
|
||||
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
||||
{
|
||||
static const std::string defaults[FT_SIZE] = {
|
||||
/* FT_STL */ "STL files (*.stl)|*.stl;*.STL",
|
||||
/* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ",
|
||||
/* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML",
|
||||
/* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;",
|
||||
/* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
|
||||
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF",
|
||||
/* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF",
|
||||
/* FT_GALLERY */ "Known files (*.stl, *.obj)|*.stl;*.STL;*.obj;*.OBJ",
|
||||
const FileWildcards& data = file_wildcards_by_type[file_type];
|
||||
std::string title;
|
||||
std::string mask;
|
||||
std::string custom_ext_lower;
|
||||
|
||||
/* FT_INI */ "INI files (*.ini)|*.ini;*.INI",
|
||||
/* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG",
|
||||
|
||||
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
|
||||
|
||||
/* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S",
|
||||
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
|
||||
/* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1",
|
||||
};
|
||||
|
||||
std::string out = defaults[file_type];
|
||||
if (! custom_extension.empty()) {
|
||||
// Find the custom extension in the template.
|
||||
if (out.find(std::string("*") + custom_extension + ",") == std::string::npos && out.find(std::string("*") + custom_extension + ")") == std::string::npos) {
|
||||
// The custom extension was not found in the template.
|
||||
// Append the custom extension to the wildcards, so that the file dialog would not add the default extension to it.
|
||||
boost::replace_first(out, ")|", std::string(", *") + custom_extension + ")|");
|
||||
out += std::string(";*") + custom_extension;
|
||||
// Generate an extension into the title mask and into the list of extensions.
|
||||
custom_ext_lower = boost::to_lower_copy(custom_extension);
|
||||
const std::string custom_ext_upper = boost::to_upper_copy(custom_extension);
|
||||
if (custom_ext_lower == custom_extension) {
|
||||
// Add a lower case version.
|
||||
title = std::string("*") + custom_ext_lower;
|
||||
mask = title;
|
||||
// Add an upper case version.
|
||||
mask += ";*";
|
||||
mask += custom_ext_upper;
|
||||
} else if (custom_ext_upper == custom_extension) {
|
||||
// Add an upper case version.
|
||||
title = std::string("*") + custom_ext_upper;
|
||||
mask = title;
|
||||
// Add a lower case version.
|
||||
mask += ";*";
|
||||
mask += custom_ext_lower;
|
||||
} else {
|
||||
// Add the mixed case version only.
|
||||
title = std::string("*") + custom_extension;
|
||||
mask = title;
|
||||
}
|
||||
}
|
||||
return from_u8(out);
|
||||
|
||||
for (const std::string_view &ext : data.file_extensions)
|
||||
// Only add an extension if it was not added first as the custom extension.
|
||||
if (ext != custom_ext_lower) {
|
||||
if (title.empty()) {
|
||||
title = "*";
|
||||
title += ext;
|
||||
mask = title;
|
||||
} else {
|
||||
title += ", *";
|
||||
title += ext;
|
||||
mask += ";*";
|
||||
mask += ext;
|
||||
}
|
||||
mask += ";*";
|
||||
mask += boost::to_upper_copy(std::string(ext));
|
||||
}
|
||||
return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask);
|
||||
}
|
||||
|
||||
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
|
||||
@ -733,8 +779,8 @@ void GUI_App::post_init()
|
||||
show_send_system_info_dialog_if_needed();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// Run external updater on Windows.
|
||||
if (! run_updater_win())
|
||||
// Run external updater on Windows if version check is enabled.
|
||||
if (this->preset_updater->version_check_enabled() && ! run_updater_win())
|
||||
// "prusaslicer-updater.exe" was not started, run our own update check.
|
||||
#endif // _WIN32
|
||||
this->preset_updater->slic3r_update_notify();
|
||||
@ -989,7 +1035,7 @@ bool GUI_App::on_init_inner()
|
||||
RichMessageDialog dlg(nullptr,
|
||||
_L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows."
|
||||
"\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system."
|
||||
"\nPlease download and install a 64 bit build of PrusaSlice from https://www.prusa3d.cz/prusaslicer/."
|
||||
"\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/."
|
||||
"\nDo you wish to continue?"),
|
||||
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
@ -1441,6 +1487,7 @@ void GUI_App::update_fonts(const MainFrame *main_frame)
|
||||
m_normal_font = main_frame->normal_font();
|
||||
m_small_font = m_normal_font;
|
||||
m_bold_font = main_frame->normal_font().Bold();
|
||||
m_link_font = m_bold_font.Underlined();
|
||||
m_em_unit = main_frame->em_unit();
|
||||
m_code_font.SetPointSize(m_normal_font.GetPointSize());
|
||||
}
|
||||
|
@ -66,13 +66,11 @@ enum FileType
|
||||
FT_TEX,
|
||||
|
||||
FT_SL1,
|
||||
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
|
||||
FT_SL1S,
|
||||
|
||||
FT_SIZE,
|
||||
};
|
||||
|
||||
extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string());
|
||||
extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string{});
|
||||
|
||||
enum ConfigMenuIDs {
|
||||
ConfigMenuWizard,
|
||||
@ -134,6 +132,7 @@ private:
|
||||
wxFont m_bold_font;
|
||||
wxFont m_normal_font;
|
||||
wxFont m_code_font;
|
||||
wxFont m_link_font;
|
||||
|
||||
int m_em_unit; // width of a "m"-symbol in pixels for current system font
|
||||
// Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls
|
||||
@ -219,6 +218,7 @@ public:
|
||||
const wxFont& bold_font() { return m_bold_font; }
|
||||
const wxFont& normal_font() { return m_normal_font; }
|
||||
const wxFont& code_font() { return m_code_font; }
|
||||
const wxFont& link_font() { return m_link_font; }
|
||||
int em_unit() const { return m_em_unit; }
|
||||
bool tabs_as_menu() const;
|
||||
wxSize get_min_size() const;
|
||||
|
@ -484,6 +484,10 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
|
||||
menu->Destroy(settings_id);
|
||||
}
|
||||
|
||||
// Update "Height range Modifier" item (delete old & create new)
|
||||
if (const auto range_id = menu->FindItem(_L("Height range Modifier")); range_id != wxNOT_FOUND)
|
||||
menu->Destroy(range_id);
|
||||
|
||||
const ConfigOptionMode mode = wxGetApp().get_mode();
|
||||
|
||||
if (mode == comAdvanced) {
|
||||
@ -513,6 +517,8 @@ void MenuFactory::append_menu_items_add_volume(wxMenu* menu)
|
||||
append_submenu(menu, sub_menu, wxID_ANY, _(item.first), "", item.second,
|
||||
[]() { return obj_list()->is_instance_or_object_selected(); }, m_parent);
|
||||
}
|
||||
|
||||
append_menu_item_layers_editing(menu);
|
||||
}
|
||||
|
||||
wxMenuItem* MenuFactory::append_menu_item_layers_editing(wxMenu* menu)
|
||||
@ -786,8 +792,14 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
|
||||
|
||||
void MenuFactory::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu)
|
||||
{
|
||||
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
|
||||
append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"),
|
||||
[](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu,
|
||||
[]() { return plater()->can_scale_to_print_volume(); }, m_parent);
|
||||
#else
|
||||
append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"),
|
||||
[](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu);
|
||||
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
|
||||
}
|
||||
|
||||
void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/)
|
||||
@ -938,11 +950,7 @@ void MenuFactory::create_object_menu()
|
||||
[]() { return plater()->can_split(true) && wxGetApp().get_mode() > comSimple; }, m_parent);
|
||||
m_object_menu.AppendSeparator();
|
||||
|
||||
// Layers Editing for object
|
||||
append_menu_item_layers_editing(&m_object_menu);
|
||||
m_object_menu.AppendSeparator();
|
||||
|
||||
// "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume()
|
||||
// "Height range Modifier" and "Add (volumes)" menu items will be added later in append_menu_items_add_volume()
|
||||
}
|
||||
|
||||
void MenuFactory::create_sla_object_menu()
|
||||
|
@ -97,7 +97,7 @@ public:
|
||||
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
|
||||
static const constexpr size_t EXTRUDERS_LIMIT = 16;
|
||||
|
||||
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
|
||||
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
|
||||
|
||||
protected:
|
||||
std::array<float, 4> get_cursor_sphere_left_button_color() const override;
|
||||
|
@ -13,7 +13,8 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@ -223,6 +224,126 @@ bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transfo
|
||||
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
|
||||
}
|
||||
|
||||
// Interpolate points between the previous and current mouse positions, which are then projected onto the object.
|
||||
// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector<GLGizmoPainterBase::ProjectedMousePosition>
|
||||
// with the same mesh_idx, but all items in std::vector<GLGizmoPainterBase::ProjectedMousePosition> always have the same mesh_idx.
|
||||
std::vector<std::vector<GLGizmoPainterBase::ProjectedMousePosition>> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector<Transform3d> &trafo_matrices) const
|
||||
{
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
if (m_last_mouse_click != Vec2d::Zero()) {
|
||||
// In case current mouse position is far from the last one,
|
||||
// add several positions from between into the list, so there
|
||||
// are no gaps in the painted region.
|
||||
if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) {
|
||||
const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1);
|
||||
for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx)
|
||||
mouse_positions.emplace_back(mouse_position + patch_idx * diff);
|
||||
mouse_positions.emplace_back(m_last_mouse_click);
|
||||
}
|
||||
}
|
||||
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
std::vector<ProjectedMousePosition> mesh_hit_points;
|
||||
mesh_hit_points.reserve(mouse_position.size());
|
||||
|
||||
// In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
|
||||
for (const Vec2d &mp : mouse_positions) {
|
||||
update_raycast_cache(mp, camera, trafo_matrices);
|
||||
mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet});
|
||||
if (m_rr.mesh_id == -1)
|
||||
break;
|
||||
}
|
||||
|
||||
// Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx.
|
||||
std::vector<std::vector<ProjectedMousePosition>> mesh_hit_points_by_mesh;
|
||||
for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) {
|
||||
size_t next_mesh_hit_point = curr_mesh_hit_point + 1;
|
||||
if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) {
|
||||
mesh_hit_points_by_mesh.emplace_back();
|
||||
mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point));
|
||||
prev_mesh_hit_point = next_mesh_hit_point;
|
||||
}
|
||||
}
|
||||
|
||||
auto on_same_facet = [](std::vector<ProjectedMousePosition> &hit_points) -> bool {
|
||||
for (const ProjectedMousePosition &mesh_hit_point : hit_points)
|
||||
if (mesh_hit_point.facet_idx != hit_points.front().facet_idx)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
struct Plane
|
||||
{
|
||||
Vec3d origin;
|
||||
Vec3d first_axis;
|
||||
Vec3d second_axis;
|
||||
};
|
||||
auto find_plane = [](std::vector<ProjectedMousePosition> &hit_points) -> std::optional<Plane> {
|
||||
assert(hit_points.size() >= 3);
|
||||
for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) {
|
||||
const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast<double>();
|
||||
const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast<double>();
|
||||
const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast<double>();
|
||||
|
||||
const Vec3d first_vec = first_point - second_point;
|
||||
const Vec3d second_vec = third_point - second_point;
|
||||
|
||||
// If three points aren't collinear, then there exists only one plane going through all points.
|
||||
if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) {
|
||||
const Vec3d first_axis_vec_n = first_vec.normalized();
|
||||
// Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process
|
||||
const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized();
|
||||
return Plane{second_point, first_axis_vec_n, second_axis_vec_n};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
for(std::vector<ProjectedMousePosition> &hit_points : mesh_hit_points_by_mesh) {
|
||||
assert(!hit_points.empty());
|
||||
if (hit_points.back().mesh_idx == -1)
|
||||
break;
|
||||
|
||||
if (hit_points.size() <= 2)
|
||||
continue;
|
||||
|
||||
if (on_same_facet(hit_points)) {
|
||||
hit_points = {hit_points.front(), hit_points.back()};
|
||||
} else if (std::optional<Plane> plane = find_plane(hit_points); plane) {
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(hit_points.size());
|
||||
// Project hit_points into its plane to simplified them in the next step.
|
||||
for (auto &hit_point : hit_points) {
|
||||
const Vec3d &point = hit_point.mesh_hit.cast<double>();
|
||||
const double x_cord = plane->first_axis.dot(point - plane->origin);
|
||||
const double y_cord = plane->second_axis.dot(point - plane->origin);
|
||||
polyline.points.emplace_back(scale_(x_cord), scale_(y_cord));
|
||||
}
|
||||
|
||||
polyline.simplify(scale_(m_cursor_radius) / 10.);
|
||||
|
||||
const int mesh_idx = hit_points.front().mesh_idx;
|
||||
std::vector<ProjectedMousePosition> new_hit_points;
|
||||
new_hit_points.reserve(polyline.points.size());
|
||||
// Project 2D simplified hit_points beck to 3D.
|
||||
for (const Point &point : polyline.points) {
|
||||
const double x_cord = unscale<double>(point.x());
|
||||
const double y_cord = unscale<double>(point.y());
|
||||
const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis;
|
||||
const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast<float>());
|
||||
new_hit_points.push_back({new_hit_point.cast<float>(), mesh_idx, size_t(facet_idx)});
|
||||
}
|
||||
|
||||
hit_points = new_hit_points;
|
||||
} else {
|
||||
hit_points = {hit_points.front(), hit_points.back()};
|
||||
}
|
||||
}
|
||||
|
||||
return mesh_hit_points_by_mesh;
|
||||
}
|
||||
|
||||
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
|
||||
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
|
||||
@ -295,28 +416,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
const Transform3d instance_trafo = mi->get_transformation().get_matrix();
|
||||
const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
|
||||
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
|
||||
// In case current mouse position is far from the last one,
|
||||
// add several positions from between into the list, so there
|
||||
// are no gaps in the painted region.
|
||||
{
|
||||
if (m_last_mouse_click == Vec2d::Zero())
|
||||
m_last_mouse_click = mouse_position;
|
||||
// resolution describes minimal distance limit using circle radius
|
||||
// as a unit (e.g., 2 would mean the patches will be touching).
|
||||
double resolution = 0.7;
|
||||
double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
|
||||
int patches_in_between = int(((mouse_position - m_last_mouse_click).norm() - diameter_px) / diameter_px);
|
||||
if (patches_in_between > 0) {
|
||||
Vec2d diff = (mouse_position - m_last_mouse_click)/(patches_in_between+1);
|
||||
for (int i=1; i<=patches_in_between; ++i)
|
||||
mouse_positions.emplace_back(m_last_mouse_click + i*diff);
|
||||
}
|
||||
}
|
||||
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
std::vector<Transform3d> trafo_matrices_not_translate;
|
||||
@ -326,50 +425,70 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
|
||||
}
|
||||
|
||||
// Now "click" into all the prepared points and spill paint around them.
|
||||
for (const Vec2d& mp : mouse_positions) {
|
||||
update_raycast_cache(mp, camera, trafo_matrices);
|
||||
std::vector<std::vector<ProjectedMousePosition>> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices);
|
||||
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||
|
||||
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||
for (const std::vector<ProjectedMousePosition> &projected_mouse_positions : projected_mouse_positions_by_mesh) {
|
||||
assert(!projected_mouse_positions.empty());
|
||||
const int mesh_idx = projected_mouse_positions.front().mesh_idx;
|
||||
const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||
|
||||
// The mouse button click detection is enabled when there is a valid hit.
|
||||
// Missing the object entirely
|
||||
// shall not capture the mouse.
|
||||
if (m_rr.mesh_id != -1) {
|
||||
if (mesh_idx != -1)
|
||||
if (m_button_down == Button::None)
|
||||
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
|
||||
}
|
||||
|
||||
if (m_rr.mesh_id == -1) {
|
||||
// In case we have no valid hit, we can return. The event will be stopped when
|
||||
// dragging while painting (to prevent scene rotations and moving the object)
|
||||
// In case we have no valid hit, we can return. The event will be stopped when
|
||||
// dragging while painting (to prevent scene rotations and moving the object)
|
||||
if (mesh_idx == -1)
|
||||
return dragging_while_painting;
|
||||
}
|
||||
|
||||
const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
|
||||
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
|
||||
const Transform3d &trafo_matrix = trafo_matrices[mesh_idx];
|
||||
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
|
||||
|
||||
// Calculate direction from camera to the hit (in mesh coords):
|
||||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
assert(mesh_idx < int(m_triangle_selectors.size()));
|
||||
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true, true);
|
||||
for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
|
||||
assert(projected_mouse_position.mesh_idx == mesh_idx);
|
||||
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
|
||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} else if (m_tool_type == ToolType::BRUSH)
|
||||
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
|
||||
new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, clp,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
}
|
||||
} else if (m_tool_type == ToolType::BRUSH) {
|
||||
assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
|
||||
if (projected_mouse_positions.size() == 1) {
|
||||
const ProjectedMousePosition &first_position = projected_mouse_positions.front();
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit,
|
||||
camera_pos, m_cursor_radius,
|
||||
m_cursor_type, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
|
||||
m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
} else {
|
||||
for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) {
|
||||
auto second_position_it = first_position_it + 1;
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_triangle_selectors[mesh_idx]->request_update_render_data();
|
||||
m_last_mouse_click = mouse_position;
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,13 @@ protected:
|
||||
SMART_FILL
|
||||
};
|
||||
|
||||
struct ProjectedMousePosition
|
||||
{
|
||||
Vec3f mesh_hit;
|
||||
int mesh_idx;
|
||||
size_t facet_idx;
|
||||
};
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_smart_fill_angle = 30.f;
|
||||
@ -188,6 +195,8 @@ protected:
|
||||
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
|
||||
|
||||
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||
void update_raycast_cache(const Vec2d& mouse_position,
|
||||
const Camera& camera,
|
||||
|
@ -553,9 +553,15 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||
auto btn_txt_sz = ImGui::CalcTextSize(btn_txt.c_str());
|
||||
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
|
||||
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
|
||||
|
||||
if (wxGetApp().plater()->is_any_job_running())
|
||||
imgui->disabled_begin(true);
|
||||
|
||||
if ( imgui->button(btn_txt) ) {
|
||||
wxGetApp().plater()->optimize_rotation();
|
||||
}
|
||||
|
||||
imgui->disabled_end();
|
||||
}
|
||||
|
||||
GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow()
|
||||
|
@ -109,7 +109,7 @@ void GLGizmoSimplify::add_simplify_suggestion_notification(
|
||||
if (big_ids.empty()) return;
|
||||
|
||||
for (size_t object_id : big_ids) {
|
||||
std::string t = GUI::format(_u8L(
|
||||
std::string t = GUI::format(_L(
|
||||
"Processing model '%1%' with more than 1M triangles "
|
||||
"could be slow. It is highly recommend to reduce "
|
||||
"amount of triangles."), objects[object_id]->name);
|
||||
@ -476,7 +476,7 @@ void GLGizmoSimplify::apply_simplify() {
|
||||
int object_idx = selection.get_object_idx();
|
||||
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
|
||||
plater->take_snapshot(GUI::format(_u8L("Simplify %1%"), m_volume->name));
|
||||
plater->clear_before_change_mesh(object_idx);
|
||||
|
||||
ModelVolume* mv = get_model_volume(selection, wxGetApp().model());
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user