Merge branch 'master' into fs_fix_for_Allura_script_font

This commit is contained in:
Filip Sykala - NTB T15p 2023-01-26 12:01:12 +01:00
commit 94982c758d
157 changed files with 18449 additions and 10810 deletions

View File

@ -1,4 +1,9 @@
# In PrusaSlicer 2.6.0 we switched from https://github.com/memononen/nanosvg to its fork https://github.com/fltk/nanosvg
# because this last implements the new function nsvgRasterizeXY() which we now use in GLTexture::load_from_svg()
# for rasterizing svg files from their original size to a squared power of two texture on Windows systems using
# AMD Radeon graphics cards
prusaslicer_add_cmake_project(NanoSVG
URL https://github.com/memononen/nanosvg/archive/4c8f0139b62c6e7faa3b67ce1fbe6e63590ed148.zip
URL_HASH SHA256=584e084af1a75bf633f79753ce2f6f6ec8686002ca27f35f1037c25675fecfb6
URL https://github.com/fltk/nanosvg/archive/abcd277ea45e9098bed752cf9c6875b533c0892f.zip
URL_HASH SHA256=e859938fbaee4b351bd8a8b3d3c7a75b40c36885ce00b73faa1ce0b98aa0ad34
)

View File

@ -1,219 +1,221 @@
min_slic3r_version = 2.5.0-alpha0
1.5.5 Added new Prusament Resin material profiles. Enabled g-code thumbnails for MK2.5 family printers.
1.5.4 Added material profiles for Prusament Resin BioBased60.
1.5.3 Added filament profiles for ColorFabb VarioShore TPU, FormFutura PP, NinjaTek NinjaFlex/Cheetah TPU and for multiple Eolas Prints filaments. Updated bridging settings in 50um and 70um profiles.
1.5.2 Added SLA material profiles.
1.5.1 Renamed filament type "NYLON" to "PA". Updated Adura X profile. Updated PETG fan settings for Prusa MINI (removed fan ramp up).
1.5.0 Updated arachne parameters. Added profiles for Jessie filaments.
1.5.0-alpha1 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments.
1.5.0-alpha0 Added parameters for Arachne perimeter generator. Changed default seam position. Updated output filename format.
min_slic3r_version = 2.4.0-rc
1.4.8 Added filament and SLA material profiles. Updated settings.
1.4.7 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments.
1.4.6 Added SLA materials. Updated filament profiles.
1.4.5 Added MMU2/S profiles for 0.25mm nozzle. Updated FW version. Enabled g-code thumbnails for MK3 family printers. Updated end g-code.
1.4.4 Added multiple Fiberlogy filament profiles. Updated Extrudr filament profiles.
1.4.3 Added new filament profiles and SLA materials.
1.4.2 Added SLA material profiles.
1.4.1 Updated firmware version.
1.4.0 Updated for the PrusaSlicer 2.4.0-rc release. Updated SLA material colors.
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
1.4.0-alpha8 Added material profiles for Prusament Resin. Detect bridging perimeters enabled by default.
1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles.
1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height).
1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S).
1.4.0-alpha4 Decreased Area Fill (SL1S).
1.4.0-alpha3 Updated SL1S tilt times.
1.4.0-alpha2 Updated Prusa MINI machine limits.
1.4.0-alpha1 Added new SL1S resin profiles.
1.4.0-alpha0 Bumped up config version.
1.3.0-alpha2 Added SL1S SPEED profiles.
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.7 Updated firmware version.
1.3.6 Updated firmware version.
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).
1.3.0 Added SL1S SPEED profiles.
min_slic3r_version = 2.3.0-rc1
1.2.12 Updated firmware version.
1.2.11 Updated firmware version.
1.2.10 Added multiple profiles for Filatech filaments. Updated SLA print settings (pad wall slope angle).
1.2.9 Added material profiles for Prusament Resin.
1.2.8 Added multiple add:north and Extrudr filament profiles.
1.2.7 Updated "Prusament PC Blend Carbon Fiber" profile for Prusa MINI.
1.2.6 Added filament profile for "Prusament PC Blend Carbon Fiber".
1.2.5 Updated firmware version. Added filament profiles. Various improvements.
1.2.4 Updated cost/density values in filament settings. Various changes in print settings.
1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles.
1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles.
1.2.1 Updated FW version for MK2.5 family printers.
1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version.
min_slic3r_version = 2.3.0-beta2
1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values.
min_slic3r_version = 2.3.0-beta0
1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights.
min_slic3r_version = 2.3.0-alpha4
1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles.
1.2.0-alpha0 Added filament spool weights
min_slic3r_version = 2.2.0-alpha3
1.1.16 Updated firmware version.
1.1.15 Updated firmware version.
1.1.14 Updated firmware version.
1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles.
1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles.
1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles.
1.1.10 Updated firmware version.
1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials.
1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles.
1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles.
1.1.6 Updated firmware version for MK2.5/S and MK3/S.
1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend
1.1.4 Added Prusament PC Blend filament profile.
1.1.3 Added SLA material and filament profile
1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET.
1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS.
1.1.1-beta Updated for PrusaSlicer 2.2.0-beta
1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc
1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer.
# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer,
# so they will see the print bed.
max_slic3r_version = 2.2.0-alpha2
min_slic3r_version = 2.2.0-alpha0
1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles.
1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0
min_slic3r_version = 2.1.1-beta0
1.0.12 Updated firmware version.
1.0.11 Updated firmware version.
1.0.10 Updated firmware version for MK2.5/S and MK3/S.
1.0.9 Updated firmware version for MK2.5/S and MK3/S.
1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog.
1.0.7 Updated layer height limits for MINI
1.0.6 Added Prusa MINI profiles
min_slic3r_version = 2.1.0-alpha0
1.0.5 Added SLA materials
1.0.4 Updated firmware version and 0.25mm nozzle profiles
1.0.3 Added filament profiles
1.0.2 Added SLA materials
1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers.
1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports.
1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament.
1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times
1.0.0-alpha2 Printer model and nozzle diameter check
1.0.0-alpha1 Added Prusament ASA profile
1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX
min_slic3r_version = 1.42.0-alpha6
0.8.11 Updated firmware version.
0.8.10 Updated firmware version.
0.8.9 Updated firmware version for MK2.5/S and MK3/S.
0.8.8 Updated firmware version for MK2.5/S and MK3/S.
0.8.7 Updated firmware version
0.8.6 Updated firmware version for MK2.5/S and MK3/S
0.8.5 Updated SL1 printer and material settings
0.8.4 Added Prusament ASA profile
0.8.3 FW version and SL1 materials update
0.8.2 FFF and SL1 settings update
0.8.1 Output settings and SLA materials update
0.8.0 Updated for the PrusaSlicer 2.0.0 final release
0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S
0.8.0-rc1 Updated SLA profiles
0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release
0.8.0-beta4 Updated SLA profiles
0.8.0-beta3 Updated SLA profiles
0.8.0-beta2 Updated SLA profiles
0.8.0-beta1 Updated SLA profiles
0.8.0-beta Updated SLA profiles
0.8.0-alpha9 Updated SLA and FFF profiles
0.8.0-alpha8 Updated SLA profiles
0.8.0-alpha7 Updated SLA profiles
0.8.0-alpha6 Updated SLA profiles
min_slic3r_version = 1.42.0-alpha
0.8.0-alpha Updated SLA profiles
0.4.0-alpha4 Updated SLA profiles
0.4.0-alpha3 Update of SLA profiles
0.4.0-alpha2 First SLA profiles
min_slic3r_version = 1.41.3-alpha
0.4.12 Updated firmware version for MK2.5/S and MK3/S.
0.4.11 Updated firmware version for MK2.5/S and MK3/S.
0.4.10 Updated firmware version
0.4.9 Updated firmware version for MK2.5/S and MK3/S
0.4.8 MK2.5/3/S FW update
0.4.7 MK2/S/MMU FW update
0.4.6 Updated firmware versions for MK2.5/S and MK3/S
0.4.5 Enabled remaining time support for MK2/S/MMU1
0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.4.1 New MK2.5S and MK3S FW versions
0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
min_slic3r_version = 1.41.1
0.3.11 Updated firmware version for MK2.5/S and MK3/S.
0.3.10 Updated firmware version
0.3.9 Updated firmware version for MK2.5/S and MK3/S
0.3.8 MK2.5/3/S FW update
0.3.7 MK2/S/MMU FW update
0.3.6 Updated firmware versions for MK2.5 and MK3
0.3.5 New MK2.5 and MK3 FW versions
0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.3.3 Prusament PETG released
0.3.2 New MK2.5 and MK3 FW versions
0.3.1 New MK2.5 and MK3 FW versions
0.3.0 New MK2.5 and MK3 FW version
min_slic3r_version = 1.41.0-alpha
0.2.9 New MK2.5 and MK3 FW versions
0.2.8 New MK2.5 and MK3 FW version
min_slic3r_version = 1.41.1
0.2.7 New MK2.5 and MK3 FW version
0.2.6 Added MMU2 MK2.5 settings
min_slic3r_version = 1.41.0-alpha
0.2.5 Prusament is out - added prusament settings
0.2.4 Added soluble support profiles for MMU2
0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit
0.2.2 Edited MMU2 Single mode purge line
0.2.1 Added PET and BVOH settings for MMU2
0.2.0-beta5 Fixed MMU1 ramming parameters
0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower
0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2
0.2.0-beta2 Edited first layer speed and wipe tower position
0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles
0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets.
0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references
0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version
0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2
0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers.
0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost
0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material
0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0
0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters
min_slic3r_version = 1.40.0
0.1.18 Updated firmware version
0.1.17 Updated firmware version for MK2.5/S and MK3/S
0.1.16 MK2.5/3/S FW update
0.1.15 MK2/S/MMU FW update
0.1.14 Updated firmware versions for MK2.5 and MK3
0.1.13 New MK2.5 and MK3 FW versions
0.1.12 New MK2.5 and MK3 FW versions
0.1.11 fw version changed to 3.3.1
0.1.10 MK3 jerk and acceleration update
0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles
0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes
0.1.7 Fixed errors in 0.25mm and 0.6mm profiles
0.1.6 Split the MK2.5 profile from the MK2S
min_slic3r_version = 1.40.0-beta
0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles
0.1.4 edited fw version, added z-raise after print
min_slic3r_version = 1.40.0-alpha
0.1.3 Fixed an incorrect position of the max_print_height parameter
0.1.2 Wipe tower changes
0.1.1 Minor print speed adjustments
0.1.0 Initial
min_slic3r_version = 2.6.0-alpha1
1.6.0-alpha0 Default top fill set to monotonic lines. Updated infill/perimeter overlap values. Updated output filename format. Enabled dynamic overhang speeds.
min_slic3r_version = 2.5.0-alpha0
1.5.5 Added new Prusament Resin material profiles. Enabled g-code thumbnails for MK2.5 family printers.
1.5.4 Added material profiles for Prusament Resin BioBased60.
1.5.3 Added filament profiles for ColorFabb VarioShore TPU, FormFutura PP, NinjaTek NinjaFlex/Cheetah TPU and for multiple Eolas Prints filaments. Updated bridging settings in 50um and 70um profiles.
1.5.2 Added SLA material profiles.
1.5.1 Renamed filament type "NYLON" to "PA". Updated Adura X profile. Updated PETG fan settings for Prusa MINI (removed fan ramp up).
1.5.0 Updated arachne parameters. Added profiles for Jessie filaments.
1.5.0-alpha1 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments.
1.5.0-alpha0 Added parameters for Arachne perimeter generator. Changed default seam position. Updated output filename format.
min_slic3r_version = 2.4.0-rc
1.4.8 Added filament and SLA material profiles. Updated settings.
1.4.7 Added filament profile for Prusament PA11 Carbon Fiber. Added profiles for multiple 3D-Fuel filaments.
1.4.6 Added SLA materials. Updated filament profiles.
1.4.5 Added MMU2/S profiles for 0.25mm nozzle. Updated FW version. Enabled g-code thumbnails for MK3 family printers. Updated end g-code.
1.4.4 Added multiple Fiberlogy filament profiles. Updated Extrudr filament profiles.
1.4.3 Added new filament profiles and SLA materials.
1.4.2 Added SLA material profiles.
1.4.1 Updated firmware version.
1.4.0 Updated for the PrusaSlicer 2.4.0-rc release. Updated SLA material colors.
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
1.4.0-alpha8 Added material profiles for Prusament Resin. Detect bridging perimeters enabled by default.
1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles.
1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height).
1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S).
1.4.0-alpha4 Decreased Area Fill (SL1S).
1.4.0-alpha3 Updated SL1S tilt times.
1.4.0-alpha2 Updated Prusa MINI machine limits.
1.4.0-alpha1 Added new SL1S resin profiles.
1.4.0-alpha0 Bumped up config version.
1.3.0-alpha2 Added SL1S SPEED profiles.
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.7 Updated firmware version.
1.3.6 Updated firmware version.
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).
1.3.0 Added SL1S SPEED profiles.
min_slic3r_version = 2.3.0-rc1
1.2.12 Updated firmware version.
1.2.11 Updated firmware version.
1.2.10 Added multiple profiles for Filatech filaments. Updated SLA print settings (pad wall slope angle).
1.2.9 Added material profiles for Prusament Resin.
1.2.8 Added multiple add:north and Extrudr filament profiles.
1.2.7 Updated "Prusament PC Blend Carbon Fiber" profile for Prusa MINI.
1.2.6 Added filament profile for "Prusament PC Blend Carbon Fiber".
1.2.5 Updated firmware version. Added filament profiles. Various improvements.
1.2.4 Updated cost/density values in filament settings. Various changes in print settings.
1.2.3 Updated firmware version. Updated end g-code in MMU2 printer profiles.
1.2.2 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles.
1.2.1 Updated FW version for MK2.5 family printers.
1.2.0 Added full_fan_speed_layer value for PETG. Increased support interface spacing for 0.6mm nozzle profiles. Updated firmware version.
min_slic3r_version = 2.3.0-beta2
1.2.0-beta1 Updated end g-code. Added full_fan_speed_layer values.
min_slic3r_version = 2.3.0-beta0
1.2.0-beta0 Adjusted infill anchor limits. Added filament spool weights.
min_slic3r_version = 2.3.0-alpha4
1.2.0-alpha1 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles.
1.2.0-alpha0 Added filament spool weights
min_slic3r_version = 2.2.0-alpha3
1.1.16 Updated firmware version.
1.1.15 Updated firmware version.
1.1.14 Updated firmware version.
1.1.13 Updated firmware version. Updated end g-code in MMU2 printer profiles.
1.1.12 Added Prusament PVB filament profile. Added 0.8mm nozzle profiles.
1.1.11 Renamed MK3S and MINI printer profiles. Updated end g-code (MINI). Added new SLA materials and filament profiles.
1.1.10 Updated firmware version.
1.1.9 Updated K values in filament profiles (linear advance). Added new filament profiles and SLA materials.
1.1.8 Updated start/end g-code scripts for MK3 family printer profiles (reduced extruder motor current for some print profiles). Added new filament and SLA material profiles.
1.1.7 Updated end g-code for MMU2 Single printer profiles. Added/updated filament and SLA material profiles.
1.1.6 Updated firmware version for MK2.5/S and MK3/S.
1.1.5 Updated MMU1 specific retraction settings for Prusament PC Blend
1.1.4 Added Prusament PC Blend filament profile.
1.1.3 Added SLA material and filament profile
1.1.2 Added renamed_from fields for PETG filaments to indicate that they were renamed from PET.
1.1.1 Added Verbatim and Fiberlogy PETG filament profiles. Updated auto cooling settings for ABS.
1.1.1-beta Updated for PrusaSlicer 2.2.0-beta
1.1.1-alpha4 Extended list of default filaments to be installed, top/bottom_solid_min_thickness defined, infill_acceleration changed etc
1.1.1-alpha3 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer.
# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer,
# so they will see the print bed.
max_slic3r_version = 2.2.0-alpha2
min_slic3r_version = 2.2.0-alpha0
1.1.1-alpha2 Bumped up config version, so our in house customer will get updated profiles.
1.1.0 Filament aliases, Creality profiles and other goodies for PrusaSlicer 2.2.0-alpha0
min_slic3r_version = 2.1.1-beta0
1.0.12 Updated firmware version.
1.0.11 Updated firmware version.
1.0.10 Updated firmware version for MK2.5/S and MK3/S.
1.0.9 Updated firmware version for MK2.5/S and MK3/S.
1.0.8 Various changes in FFF profiles, new filaments/materials added. See changelog.
1.0.7 Updated layer height limits for MINI
1.0.6 Added Prusa MINI profiles
min_slic3r_version = 2.1.0-alpha0
1.0.5 Added SLA materials
1.0.4 Updated firmware version and 0.25mm nozzle profiles
1.0.3 Added filament profiles
1.0.2 Added SLA materials
1.0.1 Updated MK3 firmware version check to 3.8.0, new soluble support profiles for 0.6mm nozzle diameter MMU2S printers.
1.0.0 Updated end G-code for the MMU2 profiles to lift the extruder at the end of print. Wipe tower bridging distance was made smaller for soluble supports.
1.0.0-beta1 Updated color for the ASA filaments to differ from the other filaments. Single extruder printers now have no extruder color assigned, obects and toolpaths will be colored with the color of the active filament.
1.0.0-beta0 Printer model checks in start G-codes, ASA filament profiles, limits on min / max SL1 exposition times
1.0.0-alpha2 Printer model and nozzle diameter check
1.0.0-alpha1 Added Prusament ASA profile
1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX
min_slic3r_version = 1.42.0-alpha6
0.8.11 Updated firmware version.
0.8.10 Updated firmware version.
0.8.9 Updated firmware version for MK2.5/S and MK3/S.
0.8.8 Updated firmware version for MK2.5/S and MK3/S.
0.8.7 Updated firmware version
0.8.6 Updated firmware version for MK2.5/S and MK3/S
0.8.5 Updated SL1 printer and material settings
0.8.4 Added Prusament ASA profile
0.8.3 FW version and SL1 materials update
0.8.2 FFF and SL1 settings update
0.8.1 Output settings and SLA materials update
0.8.0 Updated for the PrusaSlicer 2.0.0 final release
0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S
0.8.0-rc1 Updated SLA profiles
0.8.0-rc Updated for the PrusaSlicer 2.0.0-rc release
0.8.0-beta4 Updated SLA profiles
0.8.0-beta3 Updated SLA profiles
0.8.0-beta2 Updated SLA profiles
0.8.0-beta1 Updated SLA profiles
0.8.0-beta Updated SLA profiles
0.8.0-alpha9 Updated SLA and FFF profiles
0.8.0-alpha8 Updated SLA profiles
0.8.0-alpha7 Updated SLA profiles
0.8.0-alpha6 Updated SLA profiles
min_slic3r_version = 1.42.0-alpha
0.8.0-alpha Updated SLA profiles
0.4.0-alpha4 Updated SLA profiles
0.4.0-alpha3 Update of SLA profiles
0.4.0-alpha2 First SLA profiles
min_slic3r_version = 1.41.3-alpha
0.4.12 Updated firmware version for MK2.5/S and MK3/S.
0.4.11 Updated firmware version for MK2.5/S and MK3/S.
0.4.10 Updated firmware version
0.4.9 Updated firmware version for MK2.5/S and MK3/S
0.4.8 MK2.5/3/S FW update
0.4.7 MK2/S/MMU FW update
0.4.6 Updated firmware versions for MK2.5/S and MK3/S
0.4.5 Enabled remaining time support for MK2/S/MMU1
0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.4.1 New MK2.5S and MK3S FW versions
0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
min_slic3r_version = 1.41.1
0.3.11 Updated firmware version for MK2.5/S and MK3/S.
0.3.10 Updated firmware version
0.3.9 Updated firmware version for MK2.5/S and MK3/S
0.3.8 MK2.5/3/S FW update
0.3.7 MK2/S/MMU FW update
0.3.6 Updated firmware versions for MK2.5 and MK3
0.3.5 New MK2.5 and MK3 FW versions
0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt
0.3.3 Prusament PETG released
0.3.2 New MK2.5 and MK3 FW versions
0.3.1 New MK2.5 and MK3 FW versions
0.3.0 New MK2.5 and MK3 FW version
min_slic3r_version = 1.41.0-alpha
0.2.9 New MK2.5 and MK3 FW versions
0.2.8 New MK2.5 and MK3 FW version
min_slic3r_version = 1.41.1
0.2.7 New MK2.5 and MK3 FW version
0.2.6 Added MMU2 MK2.5 settings
min_slic3r_version = 1.41.0-alpha
0.2.5 Prusament is out - added prusament settings
0.2.4 Added soluble support profiles for MMU2
0.2.3 Added materials for MMU2 single mode, edited MK3 xy stealth feedrate limit
0.2.2 Edited MMU2 Single mode purge line
0.2.1 Added PET and BVOH settings for MMU2
0.2.0-beta5 Fixed MMU1 ramming parameters
0.2.0-beta4 Added filament loading speed at start, increased minimal purge on wipe tower
0.2.0-beta3 Edited ramming parameters and filament cooling moves for MMU2
0.2.0-beta2 Edited first layer speed and wipe tower position
0.2.0-beta Removed limit on the MK3MMU2 height, added legacy M204 S T format to the MK2 profiles
0.2.0-alpha8 Added filament_load/unload_time for the PLA/ABS MMU2 filament presets.
0.2.0-alpha7 Vojtech's fix the incorrect *MK3* references
0.2.0-alpha6 Jindra's way to fix the 0.2.0-alpha5 version
0.2.0-alpha5 Bumped up firmware versions for MK2.5/MK3 to 3.3.1, disabled priming areas for MK3MMU2
0.2.0-alpha4 Extended the custom start/end G-codes of the MMU2.0 printers for no priming towers.
0.2.0-alpha3 Adjusted machine limits for time estimates, added filament density and cost
0.2.0-alpha2 Renamed the key MK3SMMU to MK3MMU2, added a generic PLA MMU2 material
0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0
0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters
min_slic3r_version = 1.40.0
0.1.18 Updated firmware version
0.1.17 Updated firmware version for MK2.5/S and MK3/S
0.1.16 MK2.5/3/S FW update
0.1.15 MK2/S/MMU FW update
0.1.14 Updated firmware versions for MK2.5 and MK3
0.1.13 New MK2.5 and MK3 FW versions
0.1.12 New MK2.5 and MK3 FW versions
0.1.11 fw version changed to 3.3.1
0.1.10 MK3 jerk and acceleration update
0.1.9 edited support extrusion width for 0.25 and 0.6 nozzles
0.1.8 extrusion width for 0,25, 0.6 and variable layer height fixes
0.1.7 Fixed errors in 0.25mm and 0.6mm profiles
0.1.6 Split the MK2.5 profile from the MK2S
min_slic3r_version = 1.40.0-beta
0.1.5 fixed printer_variant fields for the i3 MK3 0.25 and 0.6mm nozzles
0.1.4 edited fw version, added z-raise after print
min_slic3r_version = 1.40.0-alpha
0.1.3 Fixed an incorrect position of the max_print_height parameter
0.1.2 Wipe tower changes
0.1.1 Minor print speed adjustments
0.1.0 Initial

View File

@ -5,7 +5,7 @@
name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.5.5
config_version = 1.6.0-alpha0
# 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%
@ -173,7 +173,7 @@ infill_extruder = 1
infill_extrusion_width = 0.45
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 25%
infill_overlap = 10%
interface_shells = 0
max_print_speed = 100
max_volumetric_extrusion_rate_slope_negative = 0
@ -184,7 +184,7 @@ notes =
overhangs = 1
only_retract_when_crossing_perimeters = 0
ooze_prevention = 0
output_filename_format = {input_filename_base}_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode
output_filename_format = {input_filename_base}_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode
perimeters = 2
perimeter_extruder = 1
perimeter_extrusion_width = 0.45
@ -248,6 +248,8 @@ wall_transition_filter_deviation = 25%
wall_transition_length = 0.4
wall_distribution_count = 1
min_bead_width = 85%
enable_dynamic_overhang_speeds = 1
top_fill_pattern = monotoniclines
[print:*MK3*]
fill_pattern = grid
@ -289,7 +291,7 @@ support_material_interface_spacing = 0.15
support_material_spacing = 1
support_material_xy_spacing = 150%
support_material_contact_distance = 0.1
output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode
output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode
thick_bridges = 0
bridge_flow_ratio = 1
bridge_speed = 20
@ -299,6 +301,8 @@ wall_transition_filter_deviation = 25%
wall_transition_length = 0.25
wall_distribution_count = 1
min_bead_width = 85%
infill_overlap = 10%
dynamic_overhang_speeds[0] = 20,20,15,15
[print:*0.25nozzleMK3*]
inherits = *0.25nozzle*
@ -340,7 +344,7 @@ support_material_extrusion_width = 0.55
support_material_contact_distance = 0.15
support_material_xy_spacing = 80%
support_material_interface_spacing = 0.3
output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode
output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode
infill_anchor_max = 15
top_solid_min_thickness = 0.9
bottom_solid_min_thickness = 0.6
@ -352,6 +356,7 @@ wall_transition_filter_deviation = 25%
wall_transition_length = 0.6
wall_distribution_count = 1
min_bead_width = 85%
infill_overlap = 15%
[print:*0.6nozzleMK3*]
inherits = *0.6nozzle*
@ -390,7 +395,7 @@ support_material_interface_speed = 100%
support_material_spacing = 2
support_material_xy_spacing = 80%
support_material_threshold = 50
output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{initial_filament_type}_{printer_model}_{print_time}.gcode
output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{printing_filament_types}_{printer_model}_{print_time}.gcode
fill_pattern = gyroid
fill_density = 15%
infill_anchor_max = 20
@ -399,7 +404,7 @@ bottom_solid_layers = 3
skirt_distance = 3
skirt_height = 2
first_layer_height = 0.3
infill_overlap = 30%
infill_overlap = 15%
bridge_speed = 22
gap_fill_speed = 30
bridge_flow_ratio = 0.9
@ -4494,6 +4499,8 @@ filament_soluble = 1
filament_type = PVB
filament_colour = #FFFF6F
filament_spool_weight = 201
bed_temperature = 75
first_layer_bed_temperature = 75
slowdown_below_layer_time = 20
filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6"
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.05{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0"

View File

@ -0,0 +1,2 @@
min_slic3r_version = 2.6.0-alpha0
1.0.0 Initial

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
#version 110
uniform vec4 uniform_color;
uniform float emission_factor;
// x = tainted, y = specular;
varying vec2 intensity;
varying float clipping_planes_dot;
void main()
{
if (clipping_planes_dot < 0.0)
discard;
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}

View File

@ -0,0 +1,54 @@
#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
uniform mat4 view_model_matrix;
uniform mat4 projection_matrix;
uniform mat3 view_normal_matrix;
uniform mat4 volume_world_matrix;
// Clipping plane - general orientation. Used by the SLA gizmo.
uniform vec4 clipping_plane;
attribute vec3 v_position;
attribute vec3 v_normal;
// x = tainted, y = specular;
varying vec2 intensity;
varying float clipping_planes_dot;
void main()
{
// First transform the normal into camera space and normalize the result.
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 eye_position = view_model_matrix * vec4(v_position, 1.0);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
gl_Position = projection_matrix * eye_position;
// Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded.
clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane);
}

View File

@ -0,0 +1,19 @@
#version 140
uniform vec4 uniform_color;
uniform float emission_factor;
// x = tainted, y = specular;
in vec2 intensity;
in float clipping_planes_dot;
out vec4 out_color;
void main()
{
if (clipping_planes_dot < 0.0)
discard;
out_color = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}

View File

@ -0,0 +1,54 @@
#version 140
#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
uniform mat4 view_model_matrix;
uniform mat4 projection_matrix;
uniform mat3 view_normal_matrix;
uniform mat4 volume_world_matrix;
// Clipping plane - general orientation. Used by the SLA gizmo.
uniform vec4 clipping_plane;
in vec3 v_position;
in vec3 v_normal;
// x = tainted, y = specular;
out vec2 intensity;
out float clipping_planes_dot;
void main()
{
// First transform the normal into camera space and normalize the result.
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 eye_position = view_model_matrix * vec4(v_position, 1.0);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
gl_Position = projection_matrix * eye_position;
// Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded.
clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane);
}

View File

@ -0,0 +1,19 @@
#version 100
precision highp float;
uniform vec4 uniform_color;
uniform float emission_factor;
// x = tainted, y = specular;
varying vec2 intensity;
varying float clipping_planes_dot;
void main()
{
if (clipping_planes_dot < 0.0)
discard;
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}

View File

@ -0,0 +1,54 @@
#version 100
#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
uniform mat4 view_model_matrix;
uniform mat4 projection_matrix;
uniform mat3 view_normal_matrix;
uniform mat4 volume_world_matrix;
// Clipping plane - general orientation. Used by the SLA gizmo.
uniform vec4 clipping_plane;
attribute vec3 v_position;
attribute vec3 v_normal;
// x = tainted, y = specular;
varying vec2 intensity;
varying float clipping_planes_dot;
void main()
{
// First transform the normal into camera space and normalize the result.
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 eye_position = view_model_matrix * vec4(v_position, 1.0);
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
gl_Position = projection_matrix * eye_position;
// Fill in the scalar for fragment shader clipping. Fragments with this value lower than zero are discarded.
clipping_planes_dot = dot(volume_world_matrix * vec4(v_position, 1.0), clipping_plane);
}

View File

@ -68,9 +68,6 @@ public:
template<class M> void AABBMesh::init(const M &mesh, bool calculate_epsilon)
{
BoundingBoxf3 bb = bounding_box(mesh);
m_ground_level += bb.min(Z);
// Build the AABB accelaration tree
m_aabb->init(*m_tm, calculate_epsilon);
}
@ -97,7 +94,6 @@ AABBMesh::~AABBMesh() {}
AABBMesh::AABBMesh(const AABBMesh &other)
: m_tm(other.m_tm)
, m_ground_level(other.m_ground_level)
, m_aabb(new AABBImpl(*other.m_aabb))
, m_vfidx{other.m_vfidx}
, m_fnidx{other.m_fnidx}
@ -106,7 +102,6 @@ AABBMesh::AABBMesh(const AABBMesh &other)
AABBMesh &AABBMesh::operator=(const AABBMesh &other)
{
m_tm = other.m_tm;
m_ground_level = other.m_ground_level;
m_aabb.reset(new AABBImpl(*other.m_aabb));
m_vfidx = other.m_vfidx;
m_fnidx = other.m_fnidx;

View File

@ -26,10 +26,9 @@ class TriangleMesh;
// casting and other higher level operations.
class AABBMesh {
class AABBImpl;
const indexed_triangle_set* m_tm;
double m_ground_level = 0/*, m_gnd_offset = 0*/;
std::unique_ptr<AABBImpl> m_aabb;
VertexFaceIndex m_vfidx; // vertex-face index
std::vector<Vec3i> m_fnidx; // face-neighbor index
@ -43,7 +42,7 @@ class AABBMesh {
template<class M> void init(const M &mesh, bool calculate_epsilon);
public:
// calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length.
// If set to false, a default epsilon is used, which works for "reasonable" meshes.
explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false);
@ -51,30 +50,26 @@ public:
AABBMesh(const AABBMesh& other);
AABBMesh& operator=(const AABBMesh&);
AABBMesh(AABBMesh &&other);
AABBMesh& operator=(AABBMesh &&other);
~AABBMesh();
inline double ground_level() const { return m_ground_level /*+ m_gnd_offset*/; }
// inline void ground_level_offset(double o) { m_gnd_offset = o; }
// inline double ground_level_offset() const { return m_gnd_offset; }
const std::vector<Vec3f>& vertices() const;
const std::vector<Vec3i>& indices() const;
const Vec3f& vertices(size_t idx) const;
const Vec3i& indices(size_t idx) const;
// Result of a raycast
class hit_result {
// m_t holds a distance from m_source to the intersection.
double m_t = infty();
int m_face_id = -1;
const AABBMesh *m_mesh = nullptr;
Vec3d m_dir;
Vec3d m_source;
Vec3d m_normal;
Vec3d m_dir = Vec3d::Zero();
Vec3d m_source = Vec3d::Zero();
Vec3d m_normal = Vec3d::Zero();
friend class AABBMesh;
// A valid object of this class can only be obtained from

130
src/libslic3r/AnyPtr.hpp Normal file
View File

@ -0,0 +1,130 @@
#ifndef ANYPTR_HPP
#define ANYPTR_HPP
#include <memory>
#include <type_traits>
#include <boost/variant.hpp>
namespace Slic3r {
// A general purpose pointer holder that can hold any type of smart pointer
// or raw pointer which can own or not own any object they point to.
// In case a raw pointer is stored, it is not destructed so ownership is
// assumed to be foreign.
//
// The stored pointer is not checked for being null when dereferenced.
//
// This is a movable only object due to the fact that it can possibly hold
// a unique_ptr which a non-copy.
template<class T>
class AnyPtr {
enum { RawPtr, UPtr, ShPtr, WkPtr };
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
template<class Self> static T *get_ptr(Self &&s)
{
switch (s.ptr.which()) {
case RawPtr: return boost::get<T *>(s.ptr);
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
case WkPtr: {
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
return shptr.get();
}
}
return nullptr;
}
public:
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(TT *p = nullptr) : ptr{p}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
{}
~AnyPtr() = default;
AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {}
AnyPtr(const AnyPtr &other) = delete;
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
AnyPtr &operator=(const AnyPtr &other) = delete;
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
const T &operator*() const { return *get_ptr(*this); }
T &operator*() { return *get_ptr(*this); }
T *operator->() { return get_ptr(*this); }
const T *operator->() const { return get_ptr(*this); }
T *get() { return get_ptr(*this); }
const T *get() const { return get_ptr(*this); }
operator bool() const
{
switch (ptr.which()) {
case RawPtr: return bool(boost::get<T *>(ptr));
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
case WkPtr: {
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
return bool(shptr);
}
}
return false;
}
// If the stored pointer is a shared or weak pointer, returns a reference
// counted copy. Empty shared pointer is returned otherwise.
std::shared_ptr<T> get_shared_cpy() const
{
std::shared_ptr<T> ret;
switch (ptr.which()) {
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
default:
;
}
return ret;
}
// If the underlying pointer is unique, convert to shared pointer
void convert_unique_to_shared()
{
if (ptr.which() == UPtr)
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
}
// Returns true if the data is owned by this AnyPtr instance
bool is_owned() const noexcept
{
return ptr.which() == UPtr || ptr.which() == ShPtr;
}
};
} // namespace Slic3r
#endif // ANYPTR_HPP

View File

@ -34,8 +34,13 @@ static const std::string MODEL_PREFIX = "model:";
// to show this notification. On the other hand, we would like PrusaSlicer 2.3.2 to show an update notification of the upcoming PrusaSlicer 2.4.0.
// Thus we will let PrusaSlicer 2.3.2 and couple of follow-up versions to download the version number from an alternate file until the PrusaSlicer 2.3.0/2.3.1
// are phased out, then we will revert to the original name.
//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2";
// For 2.6.0-alpha1 we have switched back to the original. The file should contain data for AppUpdater.cpp
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2";
// Url to index archive zip that contains latest indicies
static const std::string INDEX_ARCHIVE_URL= "https://files.prusa3d.com/wp-content/uploads/repository/vendor_indices.zip";
// Url to folder with vendor profile files. Used when downloading new profiles that are not in resources folder.
static const std::string PROFILE_FOLDER_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
@ -68,6 +73,8 @@ void AppConfig::set_defaults()
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty())
set("no_defaults", "1");
if (get("no_templates").empty())
set("no_templates", "0");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
@ -665,6 +672,26 @@ std::string AppConfig::version_check_url() const
return from_settings.empty() ? VERSION_CHECK_URL : from_settings;
}
std::string AppConfig::index_archive_url() const
{
#if 0
// this code is for debug & testing purposes only - changed url wont get trough inner checks anyway.
auto from_settings = get("index_archive_url");
return from_settings.empty() ? INDEX_ARCHIVE_URL : from_settings;
#endif
return INDEX_ARCHIVE_URL;
}
std::string AppConfig::profile_folder_url() const
{
#if 0
// this code is for debug & testing purposes only - changed url wont get trough inner checks anyway.
auto from_settings = get("profile_folder_url");
return from_settings.empty() ? PROFILE_FOLDER_URL : from_settings;
#endif
return PROFILE_FOLDER_URL;
}
bool AppConfig::exists()
{
return boost::filesystem::exists(config_path());

View File

@ -139,6 +139,11 @@ public:
// Get the Slic3r version check url.
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
std::string version_check_url() const;
// Get the Slic3r url to vendor index archive zip.
std::string index_archive_url() const;
// Get the Slic3r url to folder with vendor profile files.
std::string profile_folder_url() const;
// Returns the original Slic3r version found in the ini file before it was overwritten
// by the current version

View File

@ -5,7 +5,7 @@
#include <optional>
#include <algorithm>
#include "libslic3r/SLA/SupportTreeUtils.hpp"
#include "libslic3r/TriangleMesh.hpp"
namespace Slic3r { namespace branchingtree {
@ -76,18 +76,20 @@ void build_tree(PointCloud &nodes, Builder &builder)
switch (type) {
case BED: {
closest_node.weight = w;
if (closest_it->dst_branching > nodes.properties().max_branch_length()) {
auto hl_br_len = float(nodes.properties().max_branch_length()) / 2.f;
Node new_node {{node.pos.x(), node.pos.y(), node.pos.z() - hl_br_len}, node.Rmin};
new_node.id = int(nodes.next_junction_id());
new_node.weight = nodes.get(node_id).weight + hl_br_len;
new_node.left = node.id;
double max_br_len = nodes.properties().max_branch_length();
if (closest_it->dst_branching > max_br_len) {
std::optional<Vec3f> avo = builder.suggest_avoidance(node, max_br_len);
if (!avo)
break;
Node new_node {*avo, node.Rmin};
new_node.weight = nodes.get(node_id).weight + (node.pos - *avo).norm();
new_node.left = node.id;
if ((routed = builder.add_bridge(node, new_node))) {
size_t new_idx = nodes.insert_junction(new_node);
ptsqueue.push(new_idx);
}
}
else if ((routed = builder.add_ground_bridge(node, closest_node))) {
} else if ((routed = builder.add_ground_bridge(node, closest_node))) {
closest_node.left = closest_node.right = node_id;
nodes.get(closest_node_id) = closest_node;
nodes.mark_unreachable(closest_node_id);

View File

@ -5,7 +5,6 @@
#include <admesh/stl.h>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r { namespace branchingtree {
@ -21,6 +20,7 @@ class Properties
ExPolygons m_bed_shape;
public:
// Maximum slope for bridges of the tree
Properties &max_slope(double val) noexcept
{
@ -77,6 +77,11 @@ struct Node
{}
};
inline bool is_occupied(const Node &n)
{
return n.left != Node::ID_NONE && n.right != Node::ID_NONE;
}
// An output interface for the branching tree generator function. Consider each
// method as a callback and implement the actions that need to be done.
class Builder
@ -100,6 +105,12 @@ public:
// Add an anchor bridge to the model body
virtual bool add_mesh_bridge(const Node &from, const Node &to) = 0;
virtual std::optional<Vec3f> suggest_avoidance(const Node &from,
float max_bridge_len) const
{
return {};
}
// Report nodes that can not be routed to an endpoint (model or ground)
virtual void report_unroutable(const Node &j) = 0;

View File

@ -1,82 +1,15 @@
#include "PointCloud.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/SLA/SupportTreeUtils.hpp"
#include <igl/random_points_on_mesh.h>
namespace Slic3r { namespace branchingtree {
std::optional<Vec3f> find_merge_pt(const Vec3f &A,
const Vec3f &B,
float critical_angle)
std::optional<Vec3f> find_merge_pt(const Vec3f &A, const Vec3f &B, float max_slope)
{
// The idea is that A and B both have their support cones. But searching
// for the intersection of these support cones is difficult and its enough
// to reduce this problem to 2D and search for the intersection of two
// rays that merge somewhere between A and B. The 2D plane is a vertical
// slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and
// the 2D X axis is determined by the XY direction of the AB vector.
//
// Z^
// | A *
// | . . B *
// | . . . .
// | . . . .
// | . x .
// -------------------> XY
// Determine the transformation matrix for the 2D projection:
Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f};
Vec3f dir = diff.normalized(); // TODO: avoid normalization
Eigen::Matrix<float, 2, 3> tr2D;
tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()};
tr2D.row(1) = Vec3f{0.f, 0.f, 1.f};
// Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can
// omit 'a', pretend that its the origin and use BA as the vector b.
Vec2f b = tr2D * (B - A);
// Get the square sine of the ray emanating from 'a' towards 'b'. This ray might
// exceed the allowed angle but that is corrected subsequently.
// The sign of the original sine is also needed, hence b.y is multiplied by
// abs(b.y)
float b_sqn = b.squaredNorm();
float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f;
// sine2 from 'b' to 'a' is the opposite of sine2 from a to b
float sin2sig_b = -sin2sig_a;
// Derive the allowed angles from the given critical angle.
// critical_angle is measured from the horizontal X axis.
// The rays need to go downwards which corresponds to negative angles
float sincrit = std::sin(critical_angle); // sine of the critical angle
float sin2crit = -sincrit * sincrit; // signed sine squared
sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays
sin2sig_b = std::min(sin2sig_b, sin2crit); //
float sin2_a = std::abs(sin2sig_a); // Get cosine squared values
float sin2_b = std::abs(sin2sig_b);
float cos2_a = 1.f - sin2_a;
float cos2_b = 1.f - sin2_b;
// Derive the new direction vectors. This is by square rooting the sin2
// and cos2 values and restoring the original signs
Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)};
Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)};
// Determine where two rays ([0, 0], Da), (b, Db) intersect.
// Based on
// https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect
// One ray is emanating from (0, 0) so the formula is simplified
double t1 = (Db.y() * b.x() - b.y() * Db.x()) /
(Da.x() * Db.y() - Da.y() * Db.x());
Vec2f mp = t1 * Da;
Vec3f Mp = A + tr2D.transpose() * mp;
return t1 >= 0.f ? Mp : Vec3f{};
return sla::find_merge_pt(A, B, max_slope);
}
void to_eigen_mesh(const indexed_triangle_set &its,
@ -141,8 +74,6 @@ std::vector<Node> sample_mesh(const indexed_triangle_set &its, double radius)
std::vector<Node> sample_bed(const ExPolygons &bed, float z, double radius)
{
std::vector<Vec3f> ret;
auto triangles = triangulate_expolygons_3d(bed, z);
indexed_triangle_set its;
its.vertices.reserve(triangles.size());
@ -198,7 +129,10 @@ PointCloud::PointCloud(std::vector<Node> meshpts,
for (size_t i = 0; i < m_leafs.size(); ++i) {
Node &n = m_leafs[i];
n.id = int(LEAFS_BEGIN + i);
n.id = int(LEAFS_BEGIN + i);
n.left = Node::ID_NONE;
n.right = Node::ID_NONE;
m_ktree.insert({n.pos, n.id});
}
}

View File

@ -5,7 +5,7 @@
#include "BranchingTree.hpp"
#include "libslic3r/Execution/Execution.hpp"
//#include "libslic3r/Execution/Execution.hpp"
#include "libslic3r/MutablePriorityQueue.hpp"
#include "libslic3r/BoostAdapter.hpp"
@ -78,14 +78,6 @@ private:
rtree<PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>
m_ktree;
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const
{
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq < D.squaredNorm() * cos2bridge_slope;
}
template<class PC>
static auto *get_node(PC &&pc, size_t id)
{
@ -104,6 +96,14 @@ private:
public:
bool is_outside_support_cone(const Vec3f &supp, const Vec3f &pt) const
{
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq < D.squaredNorm() * cos2bridge_slope;
}
static constexpr auto Unqueued = size_t(-1);
struct ZCompareFn
@ -255,18 +255,42 @@ public:
}
};
template<class Fn> constexpr bool IsTraverseFn = std::is_invocable_v<Fn, Node&>;
struct TraverseReturnT
{
bool to_left : 1; // if true, continue traversing to the left
bool to_right : 1; // if true, continue traversing to the right
};
template<class PC, class Fn> void traverse(PC &&pc, size_t root, Fn &&fn)
{
if (auto nodeptr = pc.find(root); nodeptr != nullptr) {
auto &nroot = *nodeptr;
fn(nroot);
if (nroot.left >= 0) traverse(pc, nroot.left, fn);
if (nroot.right >= 0) traverse(pc, nroot.right, fn);
TraverseReturnT ret{true, true};
if constexpr (std::is_same_v<std::invoke_result_t<Fn, decltype(nroot)>, void>) {
// Our fn has no return value
fn(nroot);
} else {
// Fn returns instructions about how to continue traversing
ret = fn(nroot);
}
if (ret.to_left && nroot.left >= 0)
traverse(pc, nroot.left, fn);
if (ret.to_right && nroot.right >= 0)
traverse(pc, nroot.right, fn);
}
}
void build_tree(PointCloud &pcloud, Builder &builder);
inline void build_tree(PointCloud &&pc, Builder &builder)
{
build_tree(pc, builder);
}
}} // namespace Slic3r::branchingtree
#endif // POINTCLOUD_HPP

View File

@ -82,12 +82,11 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
if (floating_polylines.empty()) {
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
//use 3mm resolution (should be quite fast, and rough estimation should not cause any problems here)
auto [pc1, pc2] = compute_principal_components(overhang_area, 3.0);
if (pc2 == Vec2d::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
auto [pc1, pc2] = compute_principal_components(overhang_area);
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
return {Vec2d{1.0,0.0}, 0.0};
} else {
return {pc2.normalized(), 0.0};
return {pc2.normalized().cast<double>(), 0.0};
}
}

View File

@ -11,7 +11,7 @@ endif ()
set(OpenVDBUtils_SOURCES "")
if (TARGET OpenVDB::openvdb)
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp OpenVDBUtilsLegacy.hpp)
endif()
set(SLIC3R_SOURCES
@ -22,6 +22,7 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
AnyPtr.hpp
BoundingBox.cpp
BoundingBox.hpp
BridgeDetector.cpp
@ -39,6 +40,13 @@ set(SLIC3R_SOURCES
Color.hpp
Config.cpp
Config.hpp
CSGMesh/CSGMesh.hpp
CSGMesh/SliceCSGMesh.hpp
CSGMesh/ModelToCSGMesh.hpp
CSGMesh/PerformCSGMeshBooleans.hpp
CSGMesh/VoxelizeCSGMesh.hpp
CSGMesh/TriangleMeshAdapter.hpp
CSGMesh/CSGMeshCopy.hpp
EdgeGrid.cpp
EdgeGrid.hpp
ElephantFootCompensation.cpp
@ -323,6 +331,7 @@ set(SLIC3R_SOURCES
SLA/SupportTreeMesher.hpp
SLA/SupportTreeMesher.cpp
SLA/SupportTreeUtils.hpp
SLA/SupportTreeUtilsLegacy.hpp
SLA/SupportTreeBuilder.cpp
SLA/SupportTree.hpp
SLA/SupportTree.cpp

View File

@ -0,0 +1,86 @@
#ifndef CSGMESH_HPP
#define CSGMESH_HPP
#include <libslic3r/AnyPtr.hpp>
#include <admesh/stl.h>
namespace Slic3r { namespace csg {
// A CSGPartT should be an object that can provide at least a mesh + trafo and an
// associated csg operation. A collection of CSGPartT objects can then
// be interpreted as one model and used in various contexts. It can be assembled
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to
// deal with various parts of it according to the supported CSG types...
//
// A few simple templated interface functions are provided here and a default
// CSGPart class that implements the necessary means to be usable as a
// CSGPartT object.
// Supported CSG operation types
enum class CSGType { Union, Difference, Intersection };
// A CSG part can instruct the processing to push the sub-result until a new
// csg part with a pop instruction appears. This can be used to implement
// parentheses in a CSG expression represented by the collection of csg parts.
// A CSG part can not contain another CSG collection, only a mesh, this is why
// its easier to do this stacking instead of recursion in the data definition.
// CSGStackOp::Continue means no stack operation required.
// When a CSG part contains a Push instruction, it is expected that the CSG
// operation it contains refers to the whole collection spanning to the nearest
// part with a Pop instruction.
// e.g.:
// {
// CUBE1: { mesh: cube, op: Union, stack op: Continue },
// CUBE2: { mesh: cube, op: Difference, stack op: Push},
// CUBE3: { mesh: cube, op: Union, stack op: Pop}
// }
// is a collection of csg parts representing the expression CUBE1 - (CUBE2 + CUBE3)
enum class CSGStackOp { Push, Continue, Pop };
// Get the CSG operation of the part. Can be overriden for any type
template<class CSGPartT> CSGType get_operation(const CSGPartT &part)
{
return part.operation;
}
// Get the stack operation required by the CSG part.
template<class CSGPartT> CSGStackOp get_stack_operation(const CSGPartT &part)
{
return part.stack_operation;
}
// Get the mesh for the part. Can be overriden for any type
template<class CSGPartT>
const indexed_triangle_set *get_mesh(const CSGPartT &part)
{
return part.its_ptr.get();
}
// Get the transformation associated with the mesh inside a CSGPartT object.
// Can be overriden for any type.
template<class CSGPartT>
Transform3f get_transform(const CSGPartT &part)
{
return part.trafo;
}
// Default implementation
struct CSGPart {
AnyPtr<const indexed_triangle_set> its_ptr;
Transform3f trafo;
CSGType operation;
CSGStackOp stack_operation;
CSGPart(AnyPtr<const indexed_triangle_set> ptr = {},
CSGType op = CSGType::Union,
const Transform3f &tr = Transform3f::Identity())
: its_ptr{std::move(ptr)}
, operation{op}
, stack_operation{CSGStackOp::Continue}
, trafo{tr}
{}
};
}} // namespace Slic3r::csg
#endif // CSGMESH_HPP

View File

@ -0,0 +1,80 @@
#ifndef CSGMESHCOPY_HPP
#define CSGMESHCOPY_HPP
#include "CSGMesh.hpp"
namespace Slic3r { namespace csg {
// Copy a csg range but for the meshes, only copy the pointers. If the copy
// is made from a CSGPart compatible object, and the pointer is a shared one,
// it will be copied with reference counting.
template<class It, class OutIt>
void copy_csgrange_shallow(const Range<It> &csgrange, OutIt out)
{
for (const auto &part : csgrange) {
CSGPart cpy{{},
get_operation(part),
get_transform(part)};
cpy.stack_operation = get_stack_operation(part);
if constexpr (std::is_convertible_v<decltype(part), const CSGPart&>) {
if (auto shptr = part.its_ptr.get_shared_cpy()) {
cpy.its_ptr = shptr;
}
}
if (!cpy.its_ptr)
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
*out = std::move(cpy);
++out;
}
}
// Copy the csg range, allocating new meshes
template<class It, class OutIt>
void copy_csgrange_deep(const Range<It> &csgrange, OutIt out)
{
for (const auto &part : csgrange) {
CSGPart cpy{{}, get_operation(part), get_transform(part)};
if (auto meshptr = get_mesh(part)) {
cpy.its_ptr = std::make_unique<const indexed_triangle_set>(*meshptr);
}
cpy.stack_operation = get_stack_operation(part);
*out = std::move(cpy);
++out;
}
}
template<class ItA, class ItB>
bool is_same(const Range<ItA> &A, const Range<ItB> &B)
{
bool ret = true;
size_t s = A.size();
if (B.size() != s)
ret = false;
size_t i = 0;
auto itA = A.begin();
auto itB = B.begin();
for (; ret && i < s; ++itA, ++itB, ++i) {
ret = ret &&
get_mesh(*itA) == get_mesh(*itB) &&
get_operation(*itA) == get_operation(*itB) &&
get_stack_operation(*itA) == get_stack_operation(*itB) &&
get_transform(*itA).isApprox(get_transform(*itB));
}
return ret;
}
}} // namespace Slic3r::csg
#endif // CSGCOPY_HPP

View File

@ -0,0 +1,88 @@
#ifndef MODELTOCSGMESH_HPP
#define MODELTOCSGMESH_HPP
#include "CSGMesh.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/MeshSplitImpl.hpp"
namespace Slic3r { namespace csg {
// Flags to select which parts to export from Model into a csg part collection.
// These flags can be chained with the | operator
enum ModelParts {
mpartsPositive = 1, // Include positive parts
mpartsNegative = 2, // Include negative parts
mpartsDrillHoles = 4, // Include drill holes
mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts
};
template<class OutIt>
void model_to_csgmesh(const ModelObject &mo,
const Transform3d &trafo, // Applies to all exported parts
OutIt out, // Output iterator
// values of ModelParts OR-ed
int parts_to_include = mpartsPositive
)
{
bool do_positives = parts_to_include & mpartsPositive;
bool do_negatives = parts_to_include & mpartsNegative;
bool do_drillholes = parts_to_include & mpartsDrillHoles;
bool do_splits = parts_to_include & mpartsDoSplits;
for (const ModelVolume *vol : mo.volumes) {
if (vol && vol->mesh_ptr() &&
((do_positives && vol->is_model_part()) ||
(do_negatives && vol->is_negative_volume()))) {
if (do_splits && its_is_splittable(vol->mesh().its)) {
CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference};
part_begin.stack_operation = CSGStackOp::Push;
*out = std::move(part_begin);
++out;
its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) {
if (its.empty())
return;
CSGPart part{std::make_unique<indexed_triangle_set>(std::move(its)),
CSGType::Union,
(trafo * vol->get_matrix()).cast<float>()};
*out = std::move(part);
++out;
}});
CSGPart part_end{{}};
part_end.stack_operation = CSGStackOp::Pop;
*out = std::move(part_end);
++out;
} else {
CSGPart part{&(vol->mesh().its),
vol->is_model_part() ? CSGType::Union : CSGType::Difference,
(trafo * vol->get_matrix()).cast<float>()};
*out = std::move(part);
++out;
}
}
}
if (do_drillholes) {
sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo);
for (const sla::DrainHole &dhole : drainholes) {
CSGPart part{std::make_unique<const indexed_triangle_set>(
dhole.to_mesh()),
CSGType::Difference};
*out = std::move(part);
++out;
}
}
}
}} // namespace Slic3r::csg
#endif // MODELTOCSGMESH_HPP

View File

@ -0,0 +1,205 @@
#ifndef PERFORMCSGMESHBOOLEANS_HPP
#define PERFORMCSGMESHBOOLEANS_HPP
#include <stack>
#include <vector>
#include "CSGMesh.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
//#include "libslic3r/Execution/ExecutionSeq.hpp"
#include "libslic3r/MeshBoolean.hpp"
namespace Slic3r { namespace csg {
// This method can be overriden when a specific CSGPart type supports caching
// of the voxel grid
template<class CSGPartT>
MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
{
const indexed_triangle_set *its = csg::get_mesh(csgpart);
indexed_triangle_set dummy;
if (!its)
its = &dummy;
MeshBoolean::cgal::CGALMeshPtr ret;
indexed_triangle_set m = *its;
auto tr = get_transform(csgpart);
its_transform(m, get_transform(csgpart), true);
try {
ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
} catch (...) {
// errors are ignored, simply return null
ret = nullptr;
}
return ret;
}
namespace detail_cgal {
using MeshBoolean::cgal::CGALMeshPtr;
inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src)
{
if (!dst && op == CSGType::Union && src) {
dst = std::move(src);
return;
}
if (!dst || !src)
return;
switch (op) {
case CSGType::Union:
MeshBoolean::cgal::plus(*dst, *src);
break;
case CSGType::Difference:
MeshBoolean::cgal::minus(*dst, *src);
break;
case CSGType::Intersection:
MeshBoolean::cgal::intersect(*dst, *src);
break;
}
}
template<class Ex, class It>
std::vector<CGALMeshPtr> get_cgalptrs(Ex policy, const Range<It> &csgrange)
{
std::vector<CGALMeshPtr> ret(csgrange.size());
execution::for_each(policy, size_t(0), csgrange.size(),
[&csgrange, &ret](size_t i) {
auto it = csgrange.begin();
std::advance(it, i);
auto &csgpart = *it;
ret[i] = get_cgalmesh(csgpart);
});
return ret;
}
} // namespace detail
// Process the sequence of CSG parts with CGAL.
template<class It>
void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm,
const Range<It> &csgrange)
{
using MeshBoolean::cgal::CGALMesh;
using MeshBoolean::cgal::CGALMeshPtr;
using namespace detail_cgal;
struct Frame {
CSGType op; CGALMeshPtr cgalptr;
explicit Frame(CSGType csgop = CSGType::Union)
: op{csgop}
, cgalptr{MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{})}
{}
};
std::stack opstack{std::vector<Frame>{}};
opstack.push(Frame{});
std::vector<CGALMeshPtr> cgalmeshes = get_cgalptrs(ex_tbb, csgrange);
size_t csgidx = 0;
for (auto &csgpart : csgrange) {
auto op = get_operation(csgpart);
CGALMeshPtr &cgalptr = cgalmeshes[csgidx++];
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
opstack.push(Frame{op});
op = CSGType::Union;
}
Frame *top = &opstack.top();
perform_csg(get_operation(csgpart), top->cgalptr, cgalptr);
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
CGALMeshPtr src = std::move(top->cgalptr);
auto popop = opstack.top().op;
opstack.pop();
CGALMeshPtr &dst = opstack.top().cgalptr;
perform_csg(popop, dst, src);
}
}
cgalm = std::move(opstack.top().cgalptr);
}
template<class It, class Visitor>
It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
{
using namespace detail_cgal;
std::vector<CGALMeshPtr> cgalmeshes(csgrange.size());
auto check_part = [&csgrange, &cgalmeshes](size_t i)
{
auto it = csgrange.begin();
std::advance(it, i);
auto &csgpart = *it;
auto m = get_cgalmesh(csgpart);
// mesh can be nullptr if this is a stack push or pull
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
cgalmeshes[i] = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
return;
}
try {
if (!m || MeshBoolean::cgal::empty(*m))
return;
if (!MeshBoolean::cgal::does_bound_a_volume(*m))
return;
if (MeshBoolean::cgal::does_self_intersect(*m))
return;
}
catch (...) { return; }
cgalmeshes[i] = std::move(m);
};
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
It ret = csgrange.end();
for (size_t i = 0; i < csgrange.size(); ++i) {
if (!cgalmeshes[i]) {
auto it = csgrange.begin();
std::advance(it, i);
vfn(it);
if (ret == csgrange.end())
ret = it;
}
}
return ret;
}
template<class It>
It check_csgmesh_booleans(const Range<It> &csgrange)
{
return check_csgmesh_booleans(csgrange, [](auto &) {});
}
template<class It>
MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range<It> &csgparts)
{
auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
if (ret)
perform_csgmesh_booleans(ret, csgparts);
return ret;
}
} // namespace csg
} // namespace Slic3r
#endif // PERFORMCSGMESHBOOLEANS_HPP

View File

@ -0,0 +1,131 @@
#ifndef SLICECSGMESH_HPP
#define SLICECSGMESH_HPP
#include "CSGMesh.hpp"
#include <stack>
#include "libslic3r/TriangleMeshSlicer.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
namespace Slic3r { namespace csg {
namespace detail {
inline void merge_slices(csg::CSGType op, size_t i,
std::vector<ExPolygons> &target,
std::vector<ExPolygons> &source)
{
switch(op) {
case CSGType::Union:
for (ExPolygon &expoly : source[i])
target[i].emplace_back(std::move(expoly));
break;
case CSGType::Difference:
target[i] = diff_ex(target[i], source[i]);
break;
case CSGType::Intersection:
target[i] = intersection_ex(target[i], source[i]);
break;
}
}
inline void collect_nonempty_indices(csg::CSGType op,
const std::vector<float> &slicegrid,
const std::vector<ExPolygons> &slices,
std::vector<size_t> &indices)
{
indices.clear();
for (size_t i = 0; i < slicegrid.size(); ++i) {
if (op == CSGType::Intersection || !slices[i].empty())
indices.emplace_back(i);
}
}
} // namespace detail
template<class ItCSG>
std::vector<ExPolygons> slice_csgmesh_ex(
const Range<ItCSG> &csgrange,
const std::vector<float> &slicegrid,
const MeshSlicingParamsEx &params,
const std::function<void()> &throw_on_cancel = [] {})
{
using namespace detail;
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
std::stack opstack{std::vector<Frame>{}};
MeshSlicingParamsEx params_cpy = params;
auto trafo = params.trafo;
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
opstack.push({CSGType::Union, std::vector<ExPolygons>(slicegrid.size())});
for (const auto &csgpart : csgrange) {
const indexed_triangle_set *its = csg::get_mesh(csgpart);
auto op = get_operation(csgpart);
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
opstack.push({op, std::vector<ExPolygons>(slicegrid.size())});
op = CSGType::Union;
}
Frame *top = &opstack.top();
if (its) {
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
std::vector<ExPolygons> slices = slice_mesh_ex(*its,
slicegrid, params_cpy,
throw_on_cancel);
assert(slices.size() == slicegrid.size());
collect_nonempty_indices(op, slicegrid, slices, nonempty_indices);
execution::for_each(
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
[op, &slices, &top](size_t i) {
merge_slices(op, i, top->slices, slices);
}, execution::max_concurrency(ex_tbb));
}
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
std::vector<ExPolygons> popslices = std::move(top->slices);
auto popop = opstack.top().op;
opstack.pop();
std::vector<ExPolygons> &prev_slices = opstack.top().slices;
collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices);
execution::for_each(
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
[&popslices, &prev_slices, popop](size_t i) {
merge_slices(popop, i, prev_slices, popslices);
}, execution::max_concurrency(ex_tbb));
}
}
std::vector<ExPolygons> ret = std::move(opstack.top().slices);
// TODO: verify if this part can be omitted or not.
execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) {
auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){
return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON);
});
// Hopefully, ExPolygons are moved, not copied to new positions
// and that is cheap for expolygons
slice.erase(it, slice.end());
slice = union_ex(slice);
}, execution::max_concurrency(ex_tbb));
return ret;
}
}} // namespace Slic3r::csg
#endif // SLICECSGMESH_HPP

View File

@ -0,0 +1,95 @@
#ifndef TRIANGLEMESHADAPTER_HPP
#define TRIANGLEMESHADAPTER_HPP
#include "CSGMesh.hpp"
#include "libslic3r/TriangleMesh.hpp"
namespace Slic3r { namespace csg {
// Provide default overloads for indexed_triangle_set to be usable as a plain
// CSGPart with an implicit union operation
inline CSGType get_operation(const indexed_triangle_set &part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const indexed_triangle_set &part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part)
{
return &part;
}
inline Transform3f get_transform(const indexed_triangle_set &part)
{
return Transform3f::Identity();
}
inline CSGType get_operation(const indexed_triangle_set *const part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part)
{
return part;
}
inline Transform3f get_transform(const indexed_triangle_set *const part)
{
return Transform3f::Identity();
}
inline CSGType get_operation(const TriangleMesh &part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const TriangleMesh &part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const TriangleMesh &part)
{
return &part.its;
}
inline Transform3f get_transform(const TriangleMesh &part)
{
return Transform3f::Identity();
}
inline CSGType get_operation(const TriangleMesh * const part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const TriangleMesh * const part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part)
{
return &part->its;
}
inline Transform3f get_transform(const TriangleMesh * const part)
{
return Transform3f::Identity();
}
}} // namespace Slic3r::csg
#endif // TRIANGLEMESHADAPTER_HPP

View File

@ -0,0 +1,116 @@
#ifndef VOXELIZECSGMESH_HPP
#define VOXELIZECSGMESH_HPP
#include <functional>
#include <stack>
#include "CSGMesh.hpp"
#include "libslic3r/OpenVDBUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
namespace Slic3r { namespace csg {
using VoxelizeParams = MeshToGridParams;
// This method can be overriden when a specific CSGPart type supports caching
// of the voxel grid
template<class CSGPartT>
VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params)
{
const indexed_triangle_set *its = csg::get_mesh(csgpart);
VoxelGridPtr ret;
params.trafo(params.trafo() * csg::get_transform(csgpart));
if (its)
ret = mesh_to_grid(*its, params);
return ret;
}
namespace detail {
inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src)
{
if (!dst || !src)
return;
switch (op) {
case CSGType::Union:
if (is_grid_empty(*dst) && !is_grid_empty(*src))
dst = clone(*src);
else
grid_union(*dst, *src);
break;
case CSGType::Difference:
grid_difference(*dst, *src);
break;
case CSGType::Intersection:
grid_intersection(*dst, *src);
break;
}
}
} // namespace detail
template<class It>
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
const VoxelizeParams &params = {})
{
using namespace detail;
VoxelGridPtr ret;
std::vector<VoxelGridPtr> grids (csgrange.size());
execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) {
if (params.statusfn() && params.statusfn()(-1))
return;
auto it = csgrange.begin();
std::advance(it, csgidx);
auto &csgpart = *it;
grids[csgidx] = get_voxelgrid(csgpart, params);
}, execution::max_concurrency(ex_tbb));
size_t csgidx = 0;
struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; };
std::stack opstack{std::vector<Frame>{}};
opstack.push({CSGType::Union, mesh_to_grid({}, params)});
for (auto &csgpart : csgrange) {
if (params.statusfn() && params.statusfn()(-1))
break;
auto &partgrid = grids[csgidx++];
auto op = get_operation(csgpart);
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
opstack.push({op, mesh_to_grid({}, params)});
op = CSGType::Union;
}
Frame *top = &opstack.top();
perform_csg(get_operation(csgpart), top->grid, partgrid);
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
VoxelGridPtr popgrid = std::move(top->grid);
auto popop = opstack.top().op;
opstack.pop();
VoxelGridPtr &grid = opstack.top().grid;
perform_csg(popop, grid, popgrid);
}
}
ret = std::move(opstack.top().grid);
return ret;
}
}} // namespace Slic3r::csg
#endif // VOXELIZECSGMESH_HPP

View File

@ -776,7 +776,7 @@ public:
static int nil_value() { return std::numeric_limits<int>::max(); }
// A scalar is nil, or all values of a vector are nil.
bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; }
bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); }
bool is_nil(size_t idx) const override { return values[idx < this->values.size() ? idx : 0] == nil_value(); }
std::string serialize() const override
{

View File

@ -32,8 +32,8 @@ public:
ExPolygon& operator=(const ExPolygon &other) = default;
ExPolygon& operator=(ExPolygon &&other) = default;
Polygon contour;
Polygons holes;
Polygon contour; //CCW
Polygons holes; //CW
void clear() { contour.points.clear(); holes.clear(); }
void scale(double factor);

View File

@ -53,7 +53,7 @@ public:
I to,
const T &init,
MergeFn &&mergefn,
AccessFn &&access,
AccessFn &&accessfn,
size_t granularity = 1
)
{
@ -61,7 +61,7 @@ public:
tbb::blocked_range{from, to, granularity}, init,
[&](const auto &range, T subinit) {
T acc = subinit;
loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); });
loop_(range, [&](auto &i) { acc = mergefn(acc, accessfn(i)); });
return acc;
},
std::forward<MergeFn>(mergefn));

View File

@ -110,6 +110,11 @@ struct SurfaceFill {
SurfaceFillParams params;
};
static inline bool fill_type_monotonic(InfillPattern pattern)
{
return pattern == ipMonotonic || pattern == ipMonotonicLines;
}
std::vector<SurfaceFill> group_fills(const Layer &layer)
{
std::vector<SurfaceFill> surface_fills;
@ -138,7 +143,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
//FIXME for non-thick bridges, shall we allow a bottom surface pattern?
params.pattern = (surface.is_external() && ! is_bridge) ?
(surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) :
region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
fill_type_monotonic(region_config.top_fill_pattern) ? ipMonotonic : ipRectilinear;
} else if (params.density <= 0)
continue;
@ -279,7 +284,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
if (internal_solid_fill == nullptr) {
// Produce another solid fill.
params.extruder = layerm.region().extruder(frSolidInfill);
params.pattern = layerm.region().config().top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
params.pattern = fill_type_monotonic(layerm.region().config().top_fill_pattern) ? ipMonotonic : ipRectilinear;
params.density = 100.f;
params.extrusion_role = ExtrusionRole::InternalInfill;
params.angle = float(Geometry::deg2rad(layerm.region().config().fill_angle.value));

View File

@ -37,6 +37,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipRectilinear: return new FillRectilinear();
case ipAlignedRectilinear: return new FillAlignedRectilinear();
case ipMonotonic: return new FillMonotonic();
case ipMonotonicLines: return new FillMonotonicLines();
case ipLine: return new FillLine();
case ipGrid: return new FillGrid();
case ipTriangles: return new FillTriangles();

View File

@ -2970,7 +2970,18 @@ Polylines FillMonotonic::fill_surface(const Surface *surface, const FillParams &
params2.monotonic = true;
Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillMonotonous::fill_surface() failed to fill a region.";
BOOST_LOG_TRIVIAL(error) << "FillMonotonic::fill_surface() failed to fill a region.";
return polylines_out;
}
Polylines FillMonotonicLines::fill_surface(const Surface *surface, const FillParams &params)
{
FillParams params2 = params;
params2.monotonic = true;
params2.anchor_length_max = 0.0f;
Polylines polylines_out;
if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillMonotonicLines::fill_surface() failed to fill a region.";
return polylines_out;
}

View File

@ -49,6 +49,15 @@ public:
bool no_sort() const override { return true; }
};
class FillMonotonicLines : public FillRectilinear
{
public:
Fill* clone() const override { return new FillMonotonicLines(*this); }
~FillMonotonicLines() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
bool no_sort() const override { return true; }
};
class FillGrid : public FillRectilinear
{
public:

View File

@ -113,26 +113,19 @@ namespace Slic3r {
{
std::string gcode;
// move to the nearest standby point
if (!this->standby_points.empty()) {
// get current position in print coordinates
Vec3d writer_pos = gcodegen.writer().get_position();
Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
// find standby point
Point standby_point = nearest_point(this->standby_points, pos).first;
/* We don't call gcodegen.travel_to() because we don't need retraction (it was already
triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
of the destination point must not be transformed by origin nor current extruder offset. */
gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
"move to standby position");
}
if (gcodegen.config().standby_temperature_delta.value != 0) {
// we assume that heating is always slower than cooling, so no need to block
gcode += gcodegen.writer().set_temperature
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
unsigned int extruder_id = gcodegen.writer().extruder()->id();
const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature;
if (filament_idle_temp.is_nil(extruder_id)) {
// There is no idle temperature defined in filament settings.
// Use the delta value from print config.
if (gcodegen.config().standby_temperature_delta.value != 0) {
// we assume that heating is always slower than cooling, so no need to block
gcode += gcodegen.writer().set_temperature
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
}
} else {
// Use the value from filament settings. That one is absolute, not delta.
gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
}
return gcode;
@ -145,8 +138,7 @@ namespace Slic3r {
std::string();
}
int
OozePrevention::_get_temp(GCode& gcodegen)
int OozePrevention::_get_temp(const GCode& gcodegen) const
{
return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0)
? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
@ -238,8 +230,16 @@ namespace Slic3r {
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
if (! tcr.priming) {
// Move over the wipe tower.
double current_z = gcodegen.writer().get_position().z();
if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z;
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
const bool will_go_down = ! is_approx(z, current_z);
if (! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) {
// Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the
// toolchange will travel there anyway (if there is a toolchange).
gcode += gcodegen.retract();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to(
@ -248,82 +248,36 @@ namespace Slic3r {
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
}
double current_z = gcodegen.writer().get_position().z();
if (z == -1.) // in case no specific z was provided, print at current_z pos
z = current_z;
if (! is_approx(z, current_z)) {
if (will_go_down) {
gcode += gcodegen.writer().retract();
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
gcode += gcodegen.writer().unretract();
}
// Process the end filament gcode.
std::string end_filament_gcode_str;
if (gcodegen.writer().extruder() != nullptr) {
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) {
end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
check_add_eol(end_filament_gcode_str);
}
}
// Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
// Otherwise, leave control to the user completely.
std::string toolchange_gcode_str;
const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
if (! toolchange_gcode.empty()) {
DynamicConfig config;
int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
check_add_eol(toolchange_gcode_str);
std::string deretraction_str;
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
if (gcodegen.config().single_extruder_multi_material)
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
if (gcodegen.config().wipe_tower)
deretraction_str = gcodegen.unretract();
}
std::string toolchange_command;
if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
toolchange_gcode_str += toolchange_command;
else {
// We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
}
gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
// Process the start filament gcode.
std::string start_filament_gcode_str;
const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
if (!start_filament_gcode.empty()) {
// Process the start_filament_gcode for the active filament only.
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(gcodegen.writer().get_position()(2) - gcodegen.m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
check_add_eol(start_filament_gcode_str);
}
// Insert the end filament, toolchange, and start filament gcode into the generated gcode.
// Insert the toolchange and deretraction gcode into the generated gcode.
DynamicConfig config;
config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
gcode += tcr_gcode;
check_add_eol(toolchange_gcode_str);
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
@ -898,34 +852,7 @@ namespace DoExport {
static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
{
// Calculate wiping points if needed
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
Points skirt_points;
for (const ExtrusionEntity *ee : print.skirt().entities)
for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
append(skirt_points, path.polyline.points);
if (! skirt_points.empty()) {
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
Polygons skirts;
for (unsigned int extruder_id : print.extruders()) {
const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
Polygon s(outer_skirt);
s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
skirts.emplace_back(std::move(s));
}
ooze_prevention.enable = true;
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.)));
#if 0
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
#endif
}
}
ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
}
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
@ -1263,6 +1190,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
std::vector<unsigned char> is_extruder_used(print.config().nozzle_diameter.size(), 0);
for (unsigned int extruder_id : print.extruders())
is_extruder_used[extruder_id] = true;
m_placeholder_parser.set("is_extruder_used", new ConfigOptionBools(is_extruder_used));
}
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
@ -1282,8 +1214,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Set other general things.
file.write(this->preamble());
// Calculate wiping points if needed
// Enable ooze prevention if configured so.
DoExport::init_ooze_prevention(print, m_ooze_prevention);
print.throw_if_canceled();
// Collect custom seam data from all objects.
@ -1814,7 +1747,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr
m_writer.set_temperature(temp, wait, first_printing_extruder_id);
} else {
// Custom G-code does not set the extruder temperature. Do it now.
if (print.config().single_extruder_multi_material.value) {
if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
// Set temperature of the first printing extruder only.
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
if (temp > 0)
@ -2136,11 +2069,14 @@ LayerResult GCode::process_layer(
// Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
// first_layer_temperature vs. temperature settings.
for (const Extruder &extruder : m_writer.extruders()) {
if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id())
if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
// In single extruder multi material mode, set the temperature for the current extruder only.
continue;
// The same applies when ooze prevention is enabled.
if (extruder.id() != m_writer.extruder()->id())
continue;
}
int temperature = print.config().temperature.get_at(extruder.id());
if (temperature > 0 && temperature != print.config().first_layer_temperature.get_at(extruder.id()))
if (temperature > 0 && (temperature != print.config().first_layer_temperature.get_at(extruder.id())))
gcode += m_writer.set_temperature(temperature, false, extruder.id());
}
gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
@ -3186,6 +3122,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
if (! start_filament_gcode.empty()) {
// Process the start_filament_gcode for the filament.
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
check_add_eol(gcode);
@ -3201,8 +3140,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
m_wipe.reset_path();
if (m_writer.extruder() != nullptr) {
// Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
// so it should not be injected twice.
// Process the custom end_filament_gcode.
unsigned int old_extruder_id = m_writer.extruder()->id();
const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
if (! end_filament_gcode.empty()) {
@ -3212,8 +3150,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
}
// If ooze prevention is enabled, park current extruder in the nearest
// standby point and set it to the standby temperature.
// If ooze prevention is enabled, set current extruder to the standby temperature.
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
gcode += m_ooze_prevention.pre_toolchange(*this);
@ -3257,6 +3194,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
if (! start_filament_gcode.empty()) {
// Process the start_filament_gcode for the new filament.
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value));
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
check_add_eol(gcode);

View File

@ -39,14 +39,13 @@ struct PrintInstance;
class OozePrevention {
public:
bool enable;
Points standby_points;
OozePrevention() : enable(false) {}
std::string pre_toolchange(GCode &gcodegen);
std::string post_toolchange(GCode &gcodegen);
private:
int _get_temp(GCode &gcodegen);
int _get_temp(const GCode &gcodegen) const;
};
class Wipe {

View File

@ -122,39 +122,18 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
CurvatureEstimator cestim;
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
std::vector<P> extrusion_points;
{
if (max_line_length <= 0) {
extrusion_points = input_points;
} else {
extrusion_points.reserve(input_points.size() * 2);
for (size_t i = 0; i + 1 < input_points.size(); i++) {
const P &curr = input_points[i];
const P &next = input_points[i + 1];
extrusion_points.push_back(curr);
auto len = maybe_unscale(next - curr).squaredNorm();
double t = sqrt((max_line_length * max_line_length) / len);
size_t new_point_count = 1.0 / (t + EPSILON);
for (size_t j = 1; j < new_point_count + 1; j++) {
extrusion_points.push_back(curr * (1.0 - j * t) + next * (j * t));
}
}
extrusion_points.push_back(input_points.back());
}
}
std::vector<ExtendedPoint> points;
points.reserve(extrusion_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
{
ExtendedPoint start_point{maybe_unscale(extrusion_points.front())};
ExtendedPoint start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
start_point.distance = distance + boundary_offset;
start_point.nearest_prev_layer_line = nearest_line;
points.push_back(start_point);
}
for (size_t i = 1; i < extrusion_points.size(); i++) {
ExtendedPoint next_point{maybe_unscale(extrusion_points[i])};
for (size_t i = 1; i < input_points.size(); i++) {
ExtendedPoint next_point{maybe_unscale(input_points[i])};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
next_point.distance = distance + boundary_offset;
next_point.nearest_prev_layer_line = nearest_line;
@ -172,7 +151,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector<ExtendedPoint> new_points;
new_points.reserve(points.size() * 2);
new_points.reserve(points.size()*2);
new_points.push_back(points.front());
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
const ExtendedPoint &curr = points[point_idx];
@ -201,7 +180,30 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
}
new_points.push_back(next);
}
points = std::move(new_points);
points = new_points;
}
if (max_line_length > 0) {
std::vector<ExtendedPoint> new_points;
new_points.reserve(points.size()*2);
{
for (size_t i = 0; i + 1 < points.size(); i++) {
const ExtendedPoint &curr = points[i];
const ExtendedPoint &next = points[i + 1];
new_points.push_back(curr);
double len = (next.position - curr.position).squaredNorm();
double t = sqrt((max_line_length * max_line_length) / len);
size_t new_point_count = 1.0 / t;
for (size_t j = 1; j < new_point_count + 1; j++) {
Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t);
auto [p_dist, p_near_l,
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.cast<AABBScalar>());
new_points.push_back(ExtendedPoint{pos, float(p_dist + boundary_offset), p_near_l});
}
}
new_points.push_back(points.back());
}
points = new_points;
}
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {

View File

@ -71,6 +71,8 @@ public:
return *this;
}
WipeTowerWriter& set_position(const Vec2f &pos) { m_current_pos = pos; return *this; }
WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; }
WipeTowerWriter& set_z(float z)
@ -802,22 +804,26 @@ void WipeTower::toolchange_Unload(
{
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
writer.append("; CP TOOLCHANGE UNLOAD\n")
.change_analyzer_line_width(line_width);
unsigned i = 0; // iterates through ramming_speed
m_left_to_right = true; // current direction of ramming
float remaining = xr - xl ; // keeps track of distance to the next turnaround
float e_done = 0; // measures E move done from each segment
writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position
float e_done = 0; // measures E move done from each segment
if (m_semm)
writer.travel(ramming_start_pos); // move to starting position
else
writer.set_position(ramming_start_pos);
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
if (m_semm && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
// this is y of the center of previous sparse infill border
float sparse_beginning_y = 0.f;
@ -849,7 +855,7 @@ void WipeTower::toolchange_Unload(
writer.disable_linear_advance();
// now the ramming itself:
while (i < m_filpar[m_current_tool].ramming_speed.size())
while (m_semm && i < m_filpar[m_current_tool].ramming_speed.size())
{
const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move;
@ -898,7 +904,7 @@ void WipeTower::toolchange_Unload(
// Cooling:
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
if (number_of_moves > 0) {
if (m_semm && number_of_moves > 0) {
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
@ -916,14 +922,20 @@ void WipeTower::toolchange_Unload(
}
}
// let's wait is necessary:
writer.wait(m_filpar[m_current_tool].delay);
// we should be at the beginning of the cooling tube again - let's move to parking position:
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
if (m_semm) {
// let's wait is necessary:
writer.wait(m_filpar[m_current_tool].delay);
// we should be at the beginning of the cooling tube again - let's move to parking position:
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
}
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f);
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
if (m_semm)
writer.travel(pos, 2400.f);
else
writer.set_position(pos);
writer.resume_preview()
.flush_planner_queue();
@ -941,7 +953,7 @@ void WipeTower::toolchange_Change(
// This is where we want to place the custom gcodes. We will use placeholders for this.
// These will be substituted by the actual gcodes when the gcode is generated.
writer.append("[end_filament_gcode]\n");
//writer.append("[end_filament_gcode]\n");
writer.append("[toolchange_gcode]\n");
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
@ -952,11 +964,12 @@ void WipeTower::toolchange_Change(
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
+ never_skip_tag() + "\n");
writer.append("[deretraction_from_wipe_tower_generator]");
// The toolchange Tn command will be inserted later, only in case that the user does
// not provide a custom toolchange gcode.
writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed.
writer.append("[start_filament_gcode]\n");
//writer.append("[start_filament_gcode]\n");
writer.flush_planner_queue();
m_current_tool = new_tool;

View File

@ -290,7 +290,6 @@ private:
// Extruder specific parameters.
std::vector<FilamentParameters> m_filpar;
// State of the wipe tower generator.
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.

View File

@ -872,18 +872,15 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot
}
// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis.
double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_to)
{
const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
const Vec3d& axis = angle_axis.axis();
const double angle = angle_axis.angle();
#ifndef NDEBUG
if (std::abs(angle) > 1e-8) {
assert(std::abs(axis.x()) < 1e-8);
assert(std::abs(axis.y()) < 1e-8);
}
#endif /* NDEBUG */
return (axis.z() < 0) ? -angle : angle;
auto m = trafo_to.linear() * trafo_from.linear().inverse();
assert(std::abs(m.determinant() - 1) < EPSILON);
Vec3d vx = m * Vec3d(1., 0., 0);
// Verify that the linear part of rotation from trafo_from to trafo_to rotates around Z and is unity.
assert(std::abs(std::hypot(vx.x(), vx.y()) - 1.) < 1e-5);
assert(std::abs(vx.z()) < 1e-5);
return atan2(vx.y(), vx.x());
}
}} // namespace Slic3r::Geometry

View File

@ -470,8 +470,7 @@ public:
Transform3d get_mirror_matrix() const;
bool is_left_handed() const {
const Vec3d mirror = get_mirror();
return mirror.x() * mirror.y() * mirror.z() < 0.0;
return m_matrix.affine().determinant() < 0;
}
#else
bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; }
@ -547,7 +546,7 @@ extern Transform3d transform3d_from_string(const std::string& transform_str);
extern Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to);
// Rotation by Z to align rot_xyz_from to rot_xyz_to.
// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis.
extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to);
extern double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_to);
// Is the angle close to a multiple of 90 degrees?
inline bool is_rotation_ninety_degrees(double a)

View File

@ -577,12 +577,22 @@ void Layer::sort_perimeters_into_islands(
}
if (! sample_set) {
// If there is no infill, take a sample of some inner perimeter.
for (uint32_t iperimeter : extrusions.first)
if (const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter]; ! ee.role().is_external()) {
sample = ee.first_point();
for (uint32_t iperimeter : extrusions.first) {
const ExtrusionEntity &ee = *this_layer_region.perimeters().entities[iperimeter];
if (ee.is_collection()) {
for (const ExtrusionEntity *ee2 : dynamic_cast<const ExtrusionEntityCollection&>(ee).entities)
if (! ee2->role().is_external()) {
sample = ee2->first_point();
sample_set = true;
goto loop_end;
}
} else if (! ee.role().is_external()) {
sample = ee.first_point();
sample_set = true;
break;
}
}
loop_end:
if (! sample_set) {
if (! extrusions.second.empty()) {
// If there is no inner perimeter, take a sample of some gap fill extrusion.
@ -752,7 +762,9 @@ void Layer::sort_perimeters_into_islands(
const PrintRegionConfig &region_config = this_layer_region.region().config();
const auto bbox_eps = scaled<coord_t>(
EPSILON + print_config.gcode_resolution.value +
(region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value));
(region_config.fuzzy_skin.value == FuzzySkinType::None ? 0. : region_config.fuzzy_skin_thickness.value
//FIXME it looks as if Arachne could extend open lines by fuzzy_skin_point_dist, which does not seem right.
+ region_config.fuzzy_skin_point_dist.value));
auto point_inside_surface_dist2 =
[&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps]
(const size_t lslice_idx, const Point &point) {

View File

@ -137,7 +137,8 @@ inline Vec3f to_vec3f(const _EpecMesh::Point& v)
return { float(iv.x()), float(iv.y()), float(iv.z()) };
}
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
template<class _Mesh>
indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh)
{
indexed_triangle_set its;
its.vertices.reserve(cgalmesh.num_vertices());
@ -167,7 +168,7 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
its.indices.emplace_back(facet);
}
return TriangleMesh(std::move(its));
return its;
}
std::unique_ptr<CGALMesh, CGALMeshDeleter>
@ -181,7 +182,12 @@ triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh)
{
return cgal_to_triangle_mesh(cgalmesh.m);
return TriangleMesh{cgal_to_indexed_triangle_set(cgalmesh.m)};
}
indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh)
{
return cgal_to_indexed_triangle_set(cgalmesh.m);
}
// /////////////////////////////////////////////////////////////////////////////
@ -236,16 +242,28 @@ bool does_self_intersect(const CGALMesh &mesh) { return CGALProc::does_self_inte
// Now the public functions for TriangleMesh input:
// /////////////////////////////////////////////////////////////////////////////
template<class Op> void _mesh_boolean_do(Op &&op, indexed_triangle_set &A, const indexed_triangle_set &B)
{
CGALMesh meshA;
CGALMesh meshB;
triangle_mesh_to_cgal(A.vertices, A.indices, meshA.m);
triangle_mesh_to_cgal(B.vertices, B.indices, meshB.m);
_cgal_do(op, meshA, meshB);
A = cgal_to_indexed_triangle_set(meshA.m);
}
template<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B)
{
CGALMesh meshA;
CGALMesh meshB;
triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m);
triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m);
_cgal_do(op, meshA, meshB);
A = cgal_to_triangle_mesh(meshA.m);
A = cgal_to_triangle_mesh(meshA);
}
void minus(TriangleMesh &A, const TriangleMesh &B)
@ -263,6 +281,21 @@ void intersect(TriangleMesh &A, const TriangleMesh &B)
_mesh_boolean_do(_cgal_intersection, A, B);
}
void minus(indexed_triangle_set &A, const indexed_triangle_set &B)
{
_mesh_boolean_do(_cgal_diff, A, B);
}
void plus(indexed_triangle_set &A, const indexed_triangle_set &B)
{
_mesh_boolean_do(_cgal_union, A, B);
}
void intersect(indexed_triangle_set &A, const indexed_triangle_set &B)
{
_mesh_boolean_do(_cgal_intersection, A, B);
}
bool does_self_intersect(const TriangleMesh &mesh)
{
CGALMesh cgalm;
@ -282,6 +315,11 @@ bool empty(const CGALMesh &mesh)
return mesh.m.is_empty();
}
CGALMeshPtr clone(const CGALMesh &m)
{
return CGALMeshPtr{new CGALMesh{m}};
}
} // namespace cgal
} // namespace MeshBoolean

View File

@ -26,27 +26,35 @@ namespace cgal {
struct CGALMesh;
struct CGALMeshDeleter { void operator()(CGALMesh *ptr); };
using CGALMeshPtr = std::unique_ptr<CGALMesh, CGALMeshDeleter>;
std::unique_ptr<CGALMesh, CGALMeshDeleter>
triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
const std::vector<stl_triangle_vertex_indices> &F);
CGALMeshPtr clone(const CGALMesh &m);
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const indexed_triangle_set &M)
CGALMeshPtr triangle_mesh_to_cgal(
const std::vector<stl_vertex> &V,
const std::vector<stl_triangle_vertex_indices> &F);
inline CGALMeshPtr triangle_mesh_to_cgal(const indexed_triangle_set &M)
{
return triangle_mesh_to_cgal(M.vertices, M.indices);
}
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const TriangleMesh &M)
inline CGALMeshPtr triangle_mesh_to_cgal(const TriangleMesh &M)
{
return triangle_mesh_to_cgal(M.its);
}
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh);
indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh);
// Do boolean mesh difference with CGAL bypassing igl.
void minus(TriangleMesh &A, const TriangleMesh &B);
void plus(TriangleMesh &A, const TriangleMesh &B);
void intersect(TriangleMesh &A, const TriangleMesh &B);
void minus(indexed_triangle_set &A, const indexed_triangle_set &B);
void plus(indexed_triangle_set &A, const indexed_triangle_set &B);
void intersect(indexed_triangle_set &A, const indexed_triangle_set &B);
void minus(CGALMesh &A, CGALMesh &B);
void plus(CGALMesh &A, CGALMesh &B);
void intersect(CGALMesh &A, CGALMesh &B);

View File

@ -108,6 +108,21 @@ template<class IndexT> struct ItsNeighborsWrapper
const auto& get_index() const noexcept { return index_ref; }
};
// Can be used as the second argument to its_split to apply a functor on each
// part, instead of collecting them into a container.
template<class Fn>
struct SplitOutputFn {
Fn fn;
SplitOutputFn(Fn f): fn{std::move(f)} {}
SplitOutputFn &operator *() { return *this; }
void operator=(indexed_triangle_set &&its) { fn(std::move(its)); }
void operator=(indexed_triangle_set &its) { fn(its); }
SplitOutputFn& operator++() { return *this; };
};
// Splits a mesh into multiple meshes when possible.
template<class Its, class OutputIt>
void its_split(const Its &m, OutputIt out_it)
@ -155,7 +170,8 @@ void its_split(const Its &m, OutputIt out_it)
mesh.indices.emplace_back(new_face);
}
out_it = std::move(mesh);
*out_it = std::move(mesh);
++out_it;
}
}

View File

@ -794,6 +794,11 @@ bool ModelObject::is_mm_painted() const
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
}
bool ModelObject::is_text() const
{
return this->volumes.size() == 1 && this->volumes[0]->is_text();
}
void ModelObject::sort_volumes(bool full_sort)
{
// sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. "
@ -1775,7 +1780,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
// Adjust the instances.
for (size_t i = 0; i < this->instances.size(); ++ i) {
ModelInstance &model_instance = *this->instances[i];
model_instance.set_rotation(Vec3d(0., 0., Geometry::rotation_diff_z(reference_trafo.get_rotation(), model_instance.get_rotation())));
model_instance.set_rotation(Vec3d(0., 0., Geometry::rotation_diff_z(reference_trafo.get_matrix(), model_instance.get_matrix())));
model_instance.set_scaling_factor(Vec3d(new_scaling_factor, new_scaling_factor, new_scaling_factor));
model_instance.set_mirror(Vec3d(1., 1., 1.));
}
@ -2626,6 +2631,15 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); });
}
bool model_has_parameter_modifiers_in_objects(const Model &model)
{
for (const auto& model_object : model.objects)
for (const auto& volume : model_object->volumes)
if (volume->is_modifier())
return true;
return false;
}
bool model_has_multi_part_objects(const Model &model)
{
for (const ModelObject *model_object : model.objects)

View File

@ -383,7 +383,9 @@ public:
bool is_seam_painted() const;
// Checks if any of object volume is painted using the multi-material painting gizmo.
bool is_mm_painted() const;
// Checks if object contains just one volume and it's a text
bool is_text() const;
ModelInstance* add_instance();
ModelInstance* add_instance(const ModelInstance &instance);
ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror);
@ -797,6 +799,7 @@ public:
bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_text() const { return text_configuration.has_value(); }
t_model_material_id material_id() const { return m_material_id; }
void reset_extra_facets();
void apply_tolerance();
@ -1387,6 +1390,9 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
// The function assumes that volumes list is synchronized.
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has object(s) which contains a modofoer, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_parameter_modifiers_in_objects(const Model& model);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_multi_part_objects(const Model &model);

View File

@ -6,6 +6,7 @@
#pragma warning(push)
#pragma warning(disable : 4146)
#endif // _MSC_VER
#include <openvdb/openvdb.h>
#include <openvdb/tools/MeshToVolume.h>
#ifdef _MSC_VER
#pragma warning(pop)
@ -16,14 +17,44 @@
#include <openvdb/tools/LevelSetRebuild.h>
#include <openvdb/tools/FastSweeping.h>
//#include "MTUtils.hpp"
namespace Slic3r {
struct VoxelGrid
{
openvdb::FloatGrid grid;
mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor;
template<class...Args>
VoxelGrid(Args &&...args): grid{std::forward<Args>(args)...} {}
};
void VoxelGridDeleter::operator()(VoxelGrid *ptr) { delete ptr; }
// Similarly to std::make_unique()
template<class...Args>
VoxelGridPtr make_voxelgrid(Args &&...args)
{
VoxelGrid *ptr = nullptr;
try {
ptr = new VoxelGrid(std::forward<Args>(args)...);
} catch(...) {
delete ptr;
}
return VoxelGridPtr{ptr};
}
template VoxelGridPtr make_voxelgrid<>();
inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; }
inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>(); }
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[2]), int(v[1]), int(v[0])}; }
class TriangleMeshDataAdapter {
public:
const indexed_triangle_set &its;
float voxel_scale;
Transform3d trafo;
size_t polygonCount() const { return its.indices.size(); }
size_t pointCount() const { return its.vertices.size(); }
@ -35,19 +66,26 @@ public:
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const
{
auto vidx = size_t(its.indices[n](Eigen::Index(v)));
Slic3r::Vec3d p = its.vertices[vidx].cast<double>() * voxel_scale;
Slic3r::Vec3d p = trafo * its.vertices[vidx].cast<double>();
pos = {p.x(), p.y(), p.z()};
}
TriangleMeshDataAdapter(const indexed_triangle_set &m, float voxel_sc = 1.f)
: its{m}, voxel_scale{voxel_sc} {};
TriangleMeshDataAdapter(const indexed_triangle_set &m, const Transform3d tr = Transform3d::Identity())
: its{m}, trafo{tr} {}
};
openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
const openvdb::math::Transform &tr,
float voxel_scale,
float exteriorBandWidth,
float interiorBandWidth)
struct Interrupter
{
std::function<bool(int)> statusfn;
void start(const char* name = nullptr) { (void)name; }
void end() {}
inline bool wasInterrupted(int percent = -1) { return statusfn && statusfn(percent); }
};
VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh,
const MeshToGridParams &params)
{
// Might not be needed but this is now proven to be working
openvdb::initialize();
@ -55,51 +93,63 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
std::vector<indexed_triangle_set> meshparts = its_split(mesh);
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](auto &m) { return its_volume(m) < EPSILON; });
[](auto &m) {
return its_volume(m) < EPSILON;
});
meshparts.erase(it, meshparts.end());
Transform3d trafo = params.trafo().cast<double>();
trafo.prescale(params.voxel_scale());
Interrupter interrupter{params.statusfn()};
openvdb::FloatGrid::Ptr grid;
for (auto &m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{m, voxel_scale}, tr, 1.f, 1.f);
interrupter,
TriangleMeshDataAdapter{m, trafo},
openvdb::math::Transform{},
params.exterior_bandwidth(),
params.interior_bandwidth());
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
else if (subgrid) grid = std::move(subgrid);
if (interrupter.wasInterrupted())
break;
if (grid && subgrid)
openvdb::tools::csgUnion(*grid, *subgrid);
else if (subgrid)
grid = std::move(subgrid);
}
if (meshparts.size() > 1) {
// This is needed to avoid various artefacts on multipart meshes.
// TODO: replace with something faster
grid = openvdb::tools::levelSetRebuild(*grid, 0., 1.f, 1.f);
}
if(meshparts.empty()) {
if (interrupter.wasInterrupted())
return {};
if (meshparts.empty()) {
// Splitting failed, fall back to hollow the original mesh
grid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{mesh}, tr, 1.f, 1.f);
interrupter,
TriangleMeshDataAdapter{mesh, trafo},
openvdb::math::Transform{},
params.exterior_bandwidth(),
params.interior_bandwidth());
}
constexpr int DilateIterations = 1;
if (interrupter.wasInterrupted())
return {};
grid = openvdb::tools::dilateSdf(
*grid, interiorBandWidth, openvdb::tools::NN_FACE_EDGE,
DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE);
grid->transform().preScale(1./params.voxel_scale());
grid->insertMeta("voxel_scale", openvdb::FloatMetadata(params.voxel_scale()));
grid = openvdb::tools::dilateSdf(
*grid, exteriorBandWidth, openvdb::tools::NN_FACE_EDGE,
DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE);
VoxelGridPtr ret = make_voxelgrid(std::move(*grid));
grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale));
return grid;
return ret;
}
indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid,
double isovalue,
double adaptivity,
bool relaxDisorientedTriangles)
indexed_triangle_set grid_to_mesh(const VoxelGrid &vgrid,
double isovalue,
double adaptivity,
bool relaxDisorientedTriangles)
{
openvdb::initialize();
@ -107,51 +157,152 @@ indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid,
std::vector<openvdb::Vec3I> triangles;
std::vector<openvdb::Vec4I> quads;
auto &grid = vgrid.grid;
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
adaptivity, relaxDisorientedTriangles);
float scale = 1.;
try {
scale = grid.template metaValue<float>("voxel_scale");
} catch (...) { }
indexed_triangle_set ret;
ret.vertices.reserve(points.size());
ret.indices.reserve(triangles.size() + quads.size() * 2);
for (auto &v : points) ret.vertices.emplace_back(to_vec3f(v) / scale);
for (auto &v : points) ret.vertices.emplace_back(to_vec3f(v) /*/ scale*/);
for (auto &v : triangles) ret.indices.emplace_back(to_vec3i(v));
for (auto &quad : quads) {
ret.indices.emplace_back(quad(0), quad(1), quad(2));
ret.indices.emplace_back(quad(2), quad(3), quad(0));
ret.indices.emplace_back(quad(2), quad(1), quad(0));
ret.indices.emplace_back(quad(3), quad(2), quad(0));
}
return ret;
}
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso,
double er,
double ir)
VoxelGridPtr dilate_grid(const VoxelGrid &vgrid,
float exteriorBandWidth,
float interiorBandWidth)
{
auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso),
float(er), float(ir));
constexpr int DilateIterations = 1;
openvdb::FloatGrid::Ptr new_grid;
float scale = get_voxel_scale(vgrid);
if (interiorBandWidth > 0.f)
new_grid = openvdb::tools::dilateSdf(
vgrid.grid, scale * interiorBandWidth, openvdb::tools::NN_FACE_EDGE,
DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE);
auto &arg = new_grid? *new_grid : vgrid.grid;
if (exteriorBandWidth > 0.f)
new_grid = openvdb::tools::dilateSdf(
arg, scale * exteriorBandWidth, openvdb::tools::NN_FACE_EDGE,
DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE);
VoxelGridPtr ret;
if (new_grid)
ret = make_voxelgrid(std::move(*new_grid));
else
ret = make_voxelgrid(vgrid.grid);
// Copies voxel_scale metadata, if it exists.
new_grid->insertMeta(*grid.deepCopyMeta());
ret->grid.insertMeta(*vgrid.grid.deepCopyMeta());
return new_grid;
return ret;
}
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso)
VoxelGridPtr redistance_grid(const VoxelGrid &vgrid,
float iso,
float er,
float ir)
{
auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso));
auto new_grid = openvdb::tools::levelSetRebuild(vgrid.grid, iso, er, ir);
// Copies voxel_scale metadata, if it exists.
new_grid->insertMeta(*grid.deepCopyMeta());
auto ret = make_voxelgrid(std::move(*new_grid));
return new_grid;
// Copies voxel_scale metadata, if it exists.
ret->grid.insertMeta(*vgrid.grid.deepCopyMeta());
return ret;
}
VoxelGridPtr redistance_grid(const VoxelGrid &vgrid, float iso)
{
auto new_grid = openvdb::tools::levelSetRebuild(vgrid.grid, iso);
auto ret = make_voxelgrid(std::move(*new_grid));
// Copies voxel_scale metadata, if it exists.
ret->grid.insertMeta(*vgrid.grid.deepCopyMeta());
return ret;
}
void grid_union(VoxelGrid &grid, VoxelGrid &arg)
{
openvdb::tools::csgUnion(grid.grid, arg.grid);
}
void grid_difference(VoxelGrid &grid, VoxelGrid &arg)
{
openvdb::tools::csgDifference(grid.grid, arg.grid);
}
void grid_intersection(VoxelGrid &grid, VoxelGrid &arg)
{
openvdb::tools::csgIntersection(grid.grid, arg.grid);
}
void reset_accessor(const VoxelGrid &vgrid)
{
vgrid.accessor = vgrid.grid.getConstAccessor();
}
double get_distance_raw(const Vec3f &p, const VoxelGrid &vgrid)
{
if (!vgrid.accessor)
reset_accessor(vgrid);
auto v = (p).cast<double>();
auto grididx = vgrid.grid.transform().worldToIndexCellCentered(
{v.x(), v.y(), v.z()});
return vgrid.accessor->getValue(grididx) ;
}
float get_voxel_scale(const VoxelGrid &vgrid)
{
float scale = 1.;
try {
scale = vgrid.grid.template metaValue<float>("voxel_scale");
} catch (...) { }
return scale;
}
VoxelGridPtr clone(const VoxelGrid &grid)
{
return make_voxelgrid(grid);
}
void rescale_grid(VoxelGrid &grid, float scale)
{/*
float old_scale = get_voxel_scale(grid);
float nscale = scale / old_scale;*/
// auto tr = openvdb::math::Transform::createLinearTransform(scale);
grid.grid.transform().preScale(scale);
// grid.grid.insertMeta("voxel_scale", openvdb::FloatMetadata(nscale));
// grid.grid.setTransform(tr);
}
bool is_grid_empty(const VoxelGrid &grid)
{
return grid.grid.empty();
}
} // namespace Slic3r

View File

@ -3,21 +3,47 @@
#include <libslic3r/TriangleMesh.hpp>
#ifdef _MSC_VER
// Suppress warning C4146 in include/gmp.h(2177,31): unary minus operator applied to unsigned type, result still unsigned
#pragma warning(push)
#pragma warning(disable : 4146)
#endif // _MSC_VER
#include <openvdb/openvdb.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
namespace Slic3r {
inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; }
inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>(); }
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
struct VoxelGrid;
struct VoxelGridDeleter { void operator()(VoxelGrid *ptr); };
using VoxelGridPtr = std::unique_ptr<VoxelGrid, VoxelGridDeleter>;
// This is like std::make_unique for a voxelgrid
template<class... Args> VoxelGridPtr make_voxelgrid(Args &&...args);
// Default constructed voxelgrid can be obtained this way.
extern template VoxelGridPtr make_voxelgrid<>();
void reset_accessor(const VoxelGrid &vgrid);
double get_distance_raw(const Vec3f &p, const VoxelGrid &interior);
float get_voxel_scale(const VoxelGrid &grid);
VoxelGridPtr clone(const VoxelGrid &grid);
class MeshToGridParams {
Transform3f m_tr = Transform3f::Identity();
float m_voxel_scale = 1.f;
float m_exteriorBandWidth = 3.0f;
float m_interiorBandWidth = 3.0f;
std::function<bool(int)> m_statusfn;
public:
MeshToGridParams & trafo(const Transform3f &v) { m_tr = v; return *this; }
MeshToGridParams & voxel_scale(float v) { m_voxel_scale = v; return *this; }
MeshToGridParams & exterior_bandwidth(float v) { m_exteriorBandWidth = v; return *this; }
MeshToGridParams & interior_bandwidth(float v) { m_interiorBandWidth = v; return *this; }
MeshToGridParams & statusfn(std::function<bool(int)> fn) { m_statusfn = fn; return *this; }
const Transform3f& trafo() const noexcept { return m_tr; }
float voxel_scale() const noexcept { return m_voxel_scale; }
float exterior_bandwidth() const noexcept { return m_exteriorBandWidth; }
float interior_bandwidth() const noexcept { return m_interiorBandWidth; }
const std::function<bool(int)>& statusfn() const noexcept { return m_statusfn; }
};
// Here voxel_scale defines the scaling of voxels which affects the voxel count.
// 1.0 value means a voxel for every unit cube. 2 means the model is scaled to
@ -26,24 +52,32 @@ inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1
// achievable through the Transform parameter. (TODO: or is it?)
// The resulting grid will contain the voxel_scale in its metadata under the
// "voxel_scale" key to be used in grid_to_mesh function.
openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
const openvdb::math::Transform &tr = {},
float voxel_scale = 1.f,
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f);
VoxelGridPtr mesh_to_grid(const indexed_triangle_set &mesh,
const MeshToGridParams &params = {});
indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid,
double isovalue = 0.0,
double adaptivity = 0.0,
indexed_triangle_set grid_to_mesh(const VoxelGrid &grid,
double isovalue = 0.0,
double adaptivity = 0.0,
bool relaxDisorientedTriangles = true);
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso);
VoxelGridPtr dilate_grid(const VoxelGrid &grid,
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f);
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso,
double ext_range,
double int_range);
VoxelGridPtr redistance_grid(const VoxelGrid &grid, float iso);
VoxelGridPtr redistance_grid(const VoxelGrid &grid,
float iso,
float ext_range,
float int_range);
void rescale_grid(VoxelGrid &grid, float scale);
void grid_union(VoxelGrid &grid, VoxelGrid &arg);
void grid_difference(VoxelGrid &grid, VoxelGrid &arg);
void grid_intersection(VoxelGrid &grid, VoxelGrid &arg);
bool is_grid_empty(const VoxelGrid &grid);
} // namespace Slic3r

View File

@ -0,0 +1,100 @@
#ifndef OPENVDBUTILSLEGACY_HPP
#define OPENVDBUTILSLEGACY_HPP
#include "libslic3r/TriangleMesh.hpp"
#ifdef _MSC_VER
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
#pragma warning(push)
#pragma warning(disable : 4146)
#endif // _MSC_VER
#include <openvdb/openvdb.h>
#include <openvdb/tools/MeshToVolume.h>
#include <openvdb/tools/FastSweeping.h>
#include <openvdb/tools/Composite.h>
#include <openvdb/tools/LevelSetRebuild.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
namespace Slic3r {
openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
const openvdb::math::Transform &tr,
float voxel_scale,
float exteriorBandWidth,
float interiorBandWidth)
{
class TriangleMeshDataAdapter {
public:
const indexed_triangle_set &its;
float voxel_scale;
size_t polygonCount() const { return its.indices.size(); }
size_t pointCount() const { return its.vertices.size(); }
size_t vertexCount(size_t) const { return 3; }
// Return position pos in local grid index space for polygon n and vertex v
// The actual mesh will appear to openvdb as scaled uniformly by voxel_size
// And the voxel count per unit volume can be affected this way.
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const
{
auto vidx = size_t(its.indices[n](Eigen::Index(v)));
Slic3r::Vec3d p = its.vertices[vidx].cast<double>() * voxel_scale;
pos = {p.x(), p.y(), p.z()};
}
TriangleMeshDataAdapter(const indexed_triangle_set &m, float voxel_sc = 1.f)
: its{m}, voxel_scale{voxel_sc} {};
};
// Might not be needed but this is now proven to be working
openvdb::initialize();
std::vector<indexed_triangle_set> meshparts = its_split(mesh);
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](auto &m) { return its_volume(m) < EPSILON; });
meshparts.erase(it, meshparts.end());
openvdb::FloatGrid::Ptr grid;
for (auto &m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{m, voxel_scale}, tr, 1.f, 1.f);
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
else if (subgrid) grid = std::move(subgrid);
}
if (meshparts.size() > 1) {
// This is needed to avoid various artefacts on multipart meshes.
// TODO: replace with something faster
grid = openvdb::tools::levelSetRebuild(*grid, 0., 1.f, 1.f);
}
if(meshparts.empty()) {
// Splitting failed, fall back to hollow the original mesh
grid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{mesh}, tr, 1.f, 1.f);
}
constexpr int DilateIterations = 1;
grid = openvdb::tools::dilateSdf(
*grid, interiorBandWidth, openvdb::tools::NN_FACE_EDGE,
DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_LESS_THAN_ISOVALUE);
grid = openvdb::tools::dilateSdf(
*grid, exteriorBandWidth, openvdb::tools::NN_FACE_EDGE,
DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE);
grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale));
return grid;
}
} // namespace Slic3r
#endif // OPENVDBUTILSLEGACY_HPP

View File

@ -13,7 +13,9 @@ template<size_t N>
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
{
long ret = 0;
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
for (size_t i = 0; i < N; ++i)
ret += idx[i] * std::pow(gridsz, i);
return ret;
}

View File

@ -13,7 +13,7 @@
#include <utility>
#include <libslic3r/Optimize/Optimizer.hpp>
#include "Optimizer.hpp"
namespace Slic3r { namespace opt {
@ -40,69 +40,163 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
static const constexpr bool value = true;
};
// NLopt can wrap any of its algorithms to use the augmented lagrangian method
// for deriving an object function from all equality and inequality constraints
// This way one can use algorithms that do not support these constraints natively
template<class Alg> struct NLoptAUGLAG {};
template<nlopt_algorithm a1, nlopt_algorithm a2>
struct IsNLoptAlg<NLoptAUGLAG<NLoptAlgComb<a1, a2>>> {
static const constexpr bool value = true;
};
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAUGLAG<NLoptAlg<a>>> {
static const constexpr bool value = true;
};
template<class M, class T = void>
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
template<class M> struct GetNLoptAlg_ {
static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS;
static constexpr nlopt_algorithm Global = NLOPT_NUM_ALGORITHMS;
static constexpr bool IsAUGLAG = false;
};
template<nlopt_algorithm a> struct GetNLoptAlg_<NLoptAlg<a>> {
static constexpr nlopt_algorithm Local = NLOPT_NUM_ALGORITHMS;
static constexpr nlopt_algorithm Global = a;
static constexpr bool IsAUGLAG = false;
};
template<nlopt_algorithm g, nlopt_algorithm l>
struct GetNLoptAlg_<NLoptAlgComb<g, l>> {
static constexpr nlopt_algorithm Local = l;
static constexpr nlopt_algorithm Global = g;
static constexpr bool IsAUGLAG = false;
};
template<class M> constexpr nlopt_algorithm GetNLoptAlg_Global = GetNLoptAlg_<remove_cvref_t<M>>::Global;
template<class M> constexpr nlopt_algorithm GetNLoptAlg_Local = GetNLoptAlg_<remove_cvref_t<M>>::Local;
template<class M> constexpr bool IsAUGLAG = GetNLoptAlg_<remove_cvref_t<M>>::IsAUGLAG;
template<class M> struct GetNLoptAlg_<NLoptAUGLAG<M>> {
static constexpr nlopt_algorithm Local = GetNLoptAlg_Local<M>;
static constexpr nlopt_algorithm Global = GetNLoptAlg_Global<M>;
static constexpr bool IsAUGLAG = true;
};
enum class OptDir { MIN, MAX }; // Where to optimize
struct NLopt { // Helper RAII class for nlopt_opt
struct NLoptRAII { // Helper RAII class for nlopt_opt
nlopt_opt ptr = nullptr;
template<class...A> explicit NLopt(A&&...a)
template<class...A> explicit NLoptRAII(A&&...a)
{
ptr = nlopt_create(std::forward<A>(a)...);
}
NLopt(const NLopt&) = delete;
NLopt(NLopt&&) = delete;
NLopt& operator=(const NLopt&) = delete;
NLopt& operator=(NLopt&&) = delete;
NLoptRAII(const NLoptRAII&) = delete;
NLoptRAII(NLoptRAII&&) = delete;
NLoptRAII& operator=(const NLoptRAII&) = delete;
NLoptRAII& operator=(NLoptRAII&&) = delete;
~NLopt() { nlopt_destroy(ptr); }
~NLoptRAII() { nlopt_destroy(ptr); }
};
template<class Method> class NLoptOpt {};
// Map a generic function to each argument following the mapping function
template<class Fn, class...Args>
Fn for_each_argument(Fn &&fn, Args&&...args)
{
// see https://www.fluentcpp.com/2019/03/05/for_each_arg-applying-a-function-to-each-argument-of-a-function-in-cpp/
(fn(std::forward<Args>(args)),...);
// Optimizers based on NLopt.
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
protected:
return fn;
}
// Call fn on each element of the input tuple tup.
template<class Fn, class Tup>
Fn for_each_in_tuple(Fn fn, Tup &&tup)
{
auto mpfn = [&fn](auto&...pack) {
for_each_argument(fn, pack...);
};
std::apply(mpfn, tup);
return fn;
}
// Wrap each element of the tuple tup into a wrapper class W and return
// a new tuple with each element being of type W<T_i> where T_i is the type of
// i-th element of tup.
template<template<class> class W, class...Args>
auto wrap_tup(const std::tuple<Args...> &tup)
{
return std::tuple<W<Args>...>(tup);
}
template<class M, class = NLoptOnly<M>>
class NLoptOpt {
StopCriteria m_stopcr;
OptDir m_dir;
StopCriteria m_loc_stopcr;
OptDir m_dir = OptDir::MIN;
template<class Fn> using TOptData =
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
static constexpr double ConstraintEps = 1e-6;
template<class Fn> struct OptData {
Fn fn;
NLoptOpt *self = nullptr;
nlopt_opt opt_raw = nullptr;
OptData(const Fn &f): fn{f} {}
OptData(const Fn &f, NLoptOpt *s, nlopt_opt nlopt_raw)
: fn{f}, self{s}, opt_raw{nlopt_raw} {}
};
template<class Fn, size_t N>
static double optfunc(unsigned n, const double *params,
double *gradient,
void *data)
double *gradient, void *data)
{
assert(n >= N);
assert(n == N);
auto tdata = static_cast<TOptData<Fn>*>(data);
auto tdata = static_cast<OptData<Fn>*>(data);
if (std::get<1>(*tdata)->m_stopcr.stop_condition())
nlopt_force_stop(std::get<2>(*tdata));
if (tdata->self->m_stopcr.stop_condition())
nlopt_force_stop(tdata->opt_raw);
auto fnptr = std::get<0>(*tdata);
auto funval = to_arr<N>(params);
double scoreval = 0.;
using RetT = decltype((*fnptr)(funval));
using RetT = decltype(tdata->fn(funval));
if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
ScoreGradient<N> score = (*fnptr)(funval);
ScoreGradient<N> score = tdata->fn(funval);
for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
scoreval = score.score;
} else {
scoreval = (*fnptr)(funval);
scoreval = tdata->fn(funval);
}
return scoreval;
}
template<class Fn, size_t N>
static double constrain_func(unsigned n, const double *params,
double *gradient, void *data)
{
assert(n == N);
auto tdata = static_cast<OptData<Fn>*>(data);
auto funval = to_arr<N>(params);
return tdata->fn(funval);
}
template<size_t N>
void set_up(NLopt &nl, const Bounds<N>& bounds)
static void set_up(NLoptRAII &nl,
const Bounds<N> &bounds,
const StopCriteria &stopcr)
{
std::array<double, N> lb, ub;
@ -114,23 +208,45 @@ protected:
nlopt_set_lower_bounds(nl.ptr, lb.data());
nlopt_set_upper_bounds(nl.ptr, ub.data());
double abs_diff = m_stopcr.abs_score_diff();
double rel_diff = m_stopcr.rel_score_diff();
double stopval = m_stopcr.stop_score();
double abs_diff = stopcr.abs_score_diff();
double rel_diff = stopcr.rel_score_diff();
double stopval = stopcr.stop_score();
if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval);
if(m_stopcr.max_iterations() > 0)
nlopt_set_maxeval(nl.ptr, m_stopcr.max_iterations());
if(stopcr.max_iterations() > 0)
nlopt_set_maxeval(nl.ptr, stopcr.max_iterations());
}
template<class Fn, size_t N>
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
template<class Fn, size_t N, class...EqFns, class...IneqFns>
Result<N> optimize(NLoptRAII &nl, Fn &&fn, const Input<N> &initvals,
const std::tuple<EqFns...> &equalities,
const std::tuple<IneqFns...> &inequalities)
{
Result<N> r;
TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
OptData<Fn> data {fn, this, nl.ptr};
auto do_for_each_eq = [this, &nl](auto &arg) {
arg.self = this;
arg.opt_raw = nl.ptr;
using F = decltype(arg.fn);
nlopt_add_equality_constraint (nl.ptr, constrain_func<F, N>, &arg, ConstraintEps);
};
auto do_for_each_ineq = [this, &nl](auto &arg) {
arg.self = this;
arg.opt_raw = nl.ptr;
using F = decltype(arg.fn);
nlopt_add_inequality_constraint (nl.ptr, constrain_func<F, N>, &arg, ConstraintEps);
};
auto eq_data = wrap_tup<OptData>(equalities);
for_each_in_tuple(do_for_each_eq, eq_data);
auto ineq_data = wrap_tup<OptData>(inequalities);
for_each_in_tuple(do_for_each_ineq, ineq_data);
switch(m_dir) {
case OptDir::MIN:
@ -147,51 +263,81 @@ protected:
public:
template<class Func, size_t N>
Result<N> optimize(Func&& func,
template<class Fn, size_t N, class...EqFns, class...IneqFns>
Result<N> optimize(Fn&& f,
const Input<N> &initvals,
const Bounds<N>& bounds)
const Bounds<N>& bounds,
const std::tuple<EqFns...> &equalities,
const std::tuple<IneqFns...> &inequalities)
{
NLopt nl{alg, N};
set_up(nl, bounds);
if constexpr (IsAUGLAG<M>) {
NLoptRAII nl_wrap{NLOPT_AUGLAG, N};
set_up(nl_wrap, bounds, get_criteria());
return optimize(nl, std::forward<Func>(func), initvals);
NLoptRAII nl_glob{GetNLoptAlg_Global<M>, N};
set_up(nl_glob, bounds, get_criteria());
nlopt_set_local_optimizer(nl_wrap.ptr, nl_glob.ptr);
if constexpr (GetNLoptAlg_Local<M> < NLOPT_NUM_ALGORITHMS) {
NLoptRAII nl_loc{GetNLoptAlg_Local<M>, N};
set_up(nl_loc, bounds, m_loc_stopcr);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return optimize(nl_wrap, std::forward<Fn>(f), initvals,
equalities, inequalities);
} else {
return optimize(nl_wrap, std::forward<Fn>(f), initvals,
equalities, inequalities);
}
} else {
NLoptRAII nl_glob{GetNLoptAlg_Global<M>, N};
set_up(nl_glob, bounds, get_criteria());
if constexpr (GetNLoptAlg_Local<M> < NLOPT_NUM_ALGORITHMS) {
NLoptRAII nl_loc{GetNLoptAlg_Local<M>, N};
set_up(nl_loc, bounds, m_loc_stopcr);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return optimize(nl_glob, std::forward<Fn>(f), initvals,
equalities, inequalities);
} else {
return optimize(nl_glob, std::forward<Fn>(f), initvals,
equalities, inequalities);
}
}
assert(false);
return {};
}
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
explicit NLoptOpt(const StopCriteria &stopcr_glob = {})
: m_stopcr(stopcr_glob)
{}
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
void set_dir(OptDir dir) noexcept { m_dir = dir; }
void set_loc_criteria(const StopCriteria &cr) { m_loc_stopcr = cr; }
const StopCriteria &get_loc_criteria() const noexcept { return m_loc_stopcr; }
void set_dir(OptDir dir) noexcept { m_dir = dir; }
void seed(long s) { nlopt_srand(s); }
};
template<nlopt_algorithm glob, nlopt_algorithm loc>
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
{
using Base = NLoptOpt<NLoptAlg<glob>>;
public:
template<class Fn, size_t N>
Result<N> optimize(Fn&& f,
const Input<N> &initvals,
const Bounds<N>& bounds)
{
NLopt nl_glob{glob, N}, nl_loc{loc, N};
Base::set_up(nl_glob, bounds);
Base::set_up(nl_loc, bounds);
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
}
explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
template<class Alg> struct AlgFeatures_ {
static constexpr bool SupportsInequalities = false;
static constexpr bool SupportsEqualities = false;
};
} // namespace detail;
template<class Alg> constexpr bool SupportsEqualities =
detail::AlgFeatures_<remove_cvref_t<Alg>>::SupportsEqualities;
template<class Alg> constexpr bool SupportsInequalities =
detail::AlgFeatures_<remove_cvref_t<Alg>>::SupportsInequalities;
// Optimizers based on NLopt.
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
detail::NLoptOpt<M> m_opt;
@ -201,12 +347,24 @@ public:
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
template<class Func, size_t N>
template<class Func, size_t N, class...EqFns, class...IneqFns>
Result<N> optimize(Func&& func,
const Input<N> &initvals,
const Bounds<N>& bounds)
const Bounds<N>& bounds,
const std::tuple<EqFns...> &eq_constraints = {},
const std::tuple<IneqFns...> &ineq_constraint = {})
{
return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
static_assert(std::tuple_size_v<std::tuple<EqFns...>> == 0
|| SupportsEqualities<M>,
"Equality constraints are not supported.");
static_assert(std::tuple_size_v<std::tuple<IneqFns...>> == 0
|| SupportsInequalities<M>,
"Inequality constraints are not supported.");
return m_opt.optimize(std::forward<Func>(func), initvals, bounds,
eq_constraints,
ineq_constraint);
}
explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
@ -219,14 +377,56 @@ public:
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
void seed(long s) { m_opt.seed(s); }
void set_loc_criteria(const StopCriteria &cr) { m_opt.set_loc_criteria(cr); }
const StopCriteria &get_loc_criteria() const noexcept { return m_opt.get_loc_criteria(); }
};
// Predefinded NLopt algorithms
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
using AlgNLoptCobyla = detail::NLoptAlg<NLOPT_LN_COBYLA>;
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
using AlgNLoptORIG_DIRECT = detail::NLoptAlg<NLOPT_GN_ORIG_DIRECT>;
using AlgNLoptISRES = detail::NLoptAlg<NLOPT_GN_ISRES>;
using AlgNLoptAGS = detail::NLoptAlg<NLOPT_GN_AGS>;
using AlgNLoptMLSL_Subplx = detail::NLoptAlgComb<NLOPT_GN_MLSL_LDS, NLOPT_LN_SBPLX>;
using AlgNLoptMLSL_Cobyla = detail::NLoptAlgComb<NLOPT_GN_MLSL, NLOPT_LN_COBYLA>;
using AlgNLoptGenetic_Subplx = detail::NLoptAlgComb<NLOPT_GN_ESCH, NLOPT_LN_SBPLX>;
// To craft auglag algorithms (constraint support through object function transformation)
using detail::NLoptAUGLAG;
namespace detail {
template<> struct AlgFeatures_<AlgNLoptCobyla> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = true;
};
template<> struct AlgFeatures_<AlgNLoptISRES> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = false;
};
template<> struct AlgFeatures_<AlgNLoptORIG_DIRECT> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = false;
};
template<> struct AlgFeatures_<AlgNLoptAGS> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = true;
};
template<class M> struct AlgFeatures_<NLoptAUGLAG<M>> {
static constexpr bool SupportsInequalities = true;
static constexpr bool SupportsEqualities = true;
};
} // namespace detail
}} // namespace Slic3r::opt

View File

@ -12,6 +12,15 @@
namespace Slic3r { namespace opt {
template<class T, class O = T>
using FloatingOnly = std::enable_if_t<std::is_floating_point<T>::value, O>;
template<class T, class = FloatingOnly<T>>
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
constexpr float NaNf = NaN<float>;
constexpr double NaNd = NaN<double>;
// A type to hold the complete result of the optimization.
template<size_t N> struct Result {
int resultcode; // Method dependent
@ -79,7 +88,7 @@ public:
double stop_score() const { return m_stop_score; }
StopCriteria & max_iterations(double val)
StopCriteria & max_iterations(unsigned val)
{
m_max_iterations = val; return *this;
}
@ -137,16 +146,25 @@ public:
// For each dimension an interval (Bound) has to be given marking the bounds
// for that dimension.
//
// Optionally, some constraints can be given in the form of double(Input<N>)
// functors. The parameters eq_constraints and ineq_constraints can be used
// to add equality and inequality (<= 0) constraints to the optimization.
// Note that it is up the the particular method if it accepts these
// constraints.
//
// initvals have to be within the specified bounds, otherwise its undefined
// behavior.
//
// Func can return a score of type double or optionally a ScoreGradient
// class to indicate the function gradient for a optimization methods that
// make use of the gradient.
template<class Func, size_t N>
template<class Func, size_t N, class...EqConstraints, class...IneqConstraints>
Result<N> optimize(Func&& /*func*/,
const Input<N> &/*initvals*/,
const Bounds<N>& /*bounds*/) { return {}; }
const Bounds<N>& /*bounds*/,
const std::tuple<EqConstraints...> &eq_constraints = {},
const std::tuple<IneqConstraints...> &ineq_constraint = {}
) { return {}; }
// optional for randomized methods:
void seed(long /*s*/) {}

View File

@ -146,6 +146,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
res.changelog_url = changelog_url->second.data();
}
const auto templates_profile = vendor_section.find("templates_profile");
if (templates_profile != vendor_section.not_found()) {
res.templates_profile = templates_profile->second.data() == "1";
}
if (! load_all) {
return res;
}
@ -200,6 +205,10 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
}
model.bed_model = section.second.get<std::string>("bed_model", "");
model.bed_texture = section.second.get<std::string>("bed_texture", "");
model.thumbnail = section.second.get<std::string>("thumbnail", "");
if (model.thumbnail.empty())
model.thumbnail = model.id + "_thumbnail.png";
if (! model.id.empty() && ! model.variants.empty())
res.models.push_back(std::move(model));
}
@ -336,7 +345,8 @@ std::string Preset::label() const
bool is_compatible_with_print(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer)
{
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor)
// templates_profile vendor profiles should be decided as same vendor profiles
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile)
// The current profile has a vendor assigned and it is different from the active print's vendor.
return false;
auto &condition = preset.preset.compatible_prints_condition();
@ -358,7 +368,8 @@ bool is_compatible_with_print(const PresetWithVendorProfile &preset, const Prese
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config)
{
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor)
// templates_profile vendor profiles should be decided as same vendor profiles
if (preset.vendor != nullptr && preset.vendor != active_printer.vendor && !preset.vendor->templates_profile)
// The current profile has a vendor assigned and it is different from the active print's vendor.
return false;
auto &condition = preset.preset.compatible_printers_condition();
@ -439,7 +450,9 @@ static std::vector<std::string> s_Preset_print_options {
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
"support_material_contact_distance", "support_material_bottom_contact_distance",
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"support_material_buildplate_only",
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_tip_diameter",
"dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
@ -457,7 +470,7 @@ static std::vector<std::string> s_Preset_filament_options {
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode",
// Retract overrides
@ -495,14 +508,17 @@ static std::vector<std::string> s_Preset_sla_print_options {
"faded_layers",
"supports_enable",
"support_tree_type",
"support_head_front_diameter",
"support_head_penetration",
"support_head_width",
"support_pillar_diameter",
"support_small_pillar_diameter_percent",
"support_max_bridges_on_pillar",
"support_max_weight_on_model",
"support_pillar_connection_mode",
"support_buildplate_only",
"support_enforcers_only",
"support_pillar_widening_factor",
"support_base_diameter",
"support_base_height",
@ -511,6 +527,25 @@ static std::vector<std::string> s_Preset_sla_print_options {
"support_max_bridge_length",
"support_max_pillar_link_distance",
"support_object_elevation",
"branchingsupport_head_front_diameter",
"branchingsupport_head_penetration",
"branchingsupport_head_width",
"branchingsupport_pillar_diameter",
"branchingsupport_small_pillar_diameter_percent",
"branchingsupport_max_bridges_on_pillar",
"branchingsupport_max_weight_on_model",
"branchingsupport_pillar_connection_mode",
"branchingsupport_buildplate_only",
"branchingsupport_pillar_widening_factor",
"branchingsupport_base_diameter",
"branchingsupport_base_height",
"branchingsupport_base_safety_distance",
"branchingsupport_critical_angle",
"branchingsupport_max_bridge_length",
"branchingsupport_max_pillar_link_distance",
"branchingsupport_object_elevation",
"support_points_density_relative",
"support_points_minimal_distance",
"slice_closing_radius",
@ -1164,6 +1199,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
if (opt)
config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast<const ConfigOptionFloats*>(opt)->values.size()));
bool some_compatible = false;
std::vector<size_t> indices_of_template_presets;
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) {
bool selected = idx_preset == m_idx_selected;
Preset &preset_selected = m_presets[idx_preset];
@ -1180,7 +1216,29 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
m_idx_selected = size_t(-1);
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
if (preset_edited.vendor && preset_edited.vendor->templates_profile) {
indices_of_template_presets.push_back(idx_preset);
}
}
// filter out template profiles where profile with same alias and compability exists
if (!indices_of_template_presets.empty()) {
for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++idx_preset) {
if (m_presets[idx_preset].vendor && !m_presets[idx_preset].vendor->templates_profile && m_presets[idx_preset].is_compatible) {
std::string preset_alias = m_presets[idx_preset].alias;
for (size_t idx_of_template_in_presets : indices_of_template_presets) {
if (m_presets[idx_of_template_in_presets].alias == preset_alias) {
// unselect selected template filament if there is non-template alias compatible
if (idx_of_template_in_presets == m_idx_selected && (unselect_if_incompatible == PresetSelectCompatibleType::Always || unselect_if_incompatible == PresetSelectCompatibleType::OnlyIfWasCompatible)) {
m_idx_selected = size_t(-1);
}
m_presets[idx_of_template_in_presets].is_compatible = false;
break;
}
}
}
}
}
// Update visibility of the default profiles here if the defaults are suppressed, the current profile is not compatible and we don't want to select another compatible profile.
if (m_idx_selected >= m_num_default_presets && m_default_suppressed)
for (size_t i = 0; i < m_num_default_presets; ++ i)
@ -2078,6 +2136,25 @@ namespace PresetUtils {
}
return out;
}
bool vendor_profile_has_all_resources(const VendorProfile& vp)
{
namespace fs = boost::filesystem;
std::string vendor_folder = Slic3r::data_dir() + "/vendor/" + vp.id + "/";
std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/";
std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/";
for (const VendorProfile::PrinterModel& model : vp.models) {
for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail } ) {
if (! res.empty()
&& !fs::exists(fs::path(vendor_folder + res))
&& !fs::exists(fs::path(rsrc_folder + res))
&& !fs::exists(fs::path(cache_folder + res)))
return false;
}
}
return true;
}
} // namespace PresetUtils
} // namespace Slic3r

View File

@ -34,6 +34,7 @@ public:
Semver config_version;
std::string config_update_url;
std::string changelog_url;
bool templates_profile { false };
struct PrinterVariant {
PrinterVariant() {}
@ -52,6 +53,7 @@ public:
// Vendor & Printer Model specific print bed model & texture.
std::string bed_model;
std::string bed_texture;
std::string thumbnail;
PrinterVariant* variant(const std::string &name) {
for (auto &v : this->variants)
@ -619,6 +621,7 @@ namespace PresetUtils {
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
std::string system_printer_bed_model(const Preset& preset);
std::string system_printer_bed_texture(const Preset& preset);
bool vendor_profile_has_all_resources(const VendorProfile& vp);
} // namespace PresetUtils

View File

@ -159,6 +159,7 @@ void PresetBundle::setup_directories()
data_dir,
data_dir / "vendor",
data_dir / "cache",
data_dir / "cache" / "vendor",
data_dir / "shapes",
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
// Store the print/filament/printer presets into a "presets" directory.
@ -1313,11 +1314,11 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
const VendorProfile *vendor_profile = nullptr;
if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
auto vp = VendorProfile::from_ini(tree, path);
if (vp.models.size() == 0) {
VendorProfile vp = VendorProfile::from_ini(tree, path);
if (vp.models.size() == 0 && !vp.templates_profile) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
return std::make_pair(PresetsConfigSubstitutions{}, 0);
} else if (vp.num_variants() == 0) {
} else if (vp.num_variants() == 0 && !vp.templates_profile) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
return std::make_pair(PresetsConfigSubstitutions{}, 0);
}
@ -1359,6 +1360,9 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
} else if (boost::starts_with(section.first, "filament:")) {
presets = &this->filaments;
preset_name = section.first.substr(9);
if (vendor_profile && vendor_profile->templates_profile) {
preset_name += " @Template";
}
} else if (boost::starts_with(section.first, "sla_print:")) {
presets = &this->sla_prints;
preset_name = section.first.substr(10);

View File

@ -3,53 +3,97 @@
namespace Slic3r {
// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first
std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, const double unscaled_resolution)
// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance
// none of the values is divided/normalized by area.
// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous)
// f(x,y) = x^2 for second moment of area
// and f(x,y) = x*y for second moment of area covariance
std::tuple<float, Vec2f, Vec2f, float> compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c)
{
// USING UNSCALED VALUES
const Vec2d pixel_size = Vec2d(unscaled_resolution, unscaled_resolution);
const auto bb = get_extents(polys);
const Vec2i pixel_count = unscaled(bb.size()).cwiseQuotient(pixel_size).cast<int>() + Vec2i::Ones();
// based on the following guide:
// Denote the vertices of S by a, b, c. Then the map
// g:(u,v)↦a+u(ba)+v(ca) ,
// which in coordinates appears as
// g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1a1)+v(c1a1)=a2+u(b2a2)+v(c2a2) ,(1)
// obviously maps S bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain
// ∫Sf(x,y)d(x,y)=∫Sf(x(u,v),y(u,v))Jg(u,v) d(u,v) .
// In the case at hand the Jacobian determinant is a constant: From (1) we obtain
// Jg(u,v)=det[xuyuxvyv]=(b1a1)(c2a2)(c1a1)(b2a2) .
// Therefore we can write
// ∫Sf(x,y)d(x,y)=Jg∫10∫1u0f~(u,v) dv du ,
// where f~ denotes the pullback of f to S:
// f~(u,v):=f(x(u,v),y(u,v)) .
// Don't forget taking the absolute value of Jg!
std::vector<Linef> lines{};
for (Line l : to_lines(polys)) { lines.emplace_back(unscaled(l.a), unscaled(l.b)); }
AABBTreeIndirect::Tree<2, double> tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
auto is_inside = [&](const Vec2d &point) {
size_t nearest_line_index_out = 0;
Vec2d nearest_point_out = Vec2d::Zero();
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, point, nearest_line_index_out, nearest_point_out);
if (distance < 0) return false;
const Linef &line = lines[nearest_line_index_out];
Vec2d v1 = line.b - line.a;
Vec2d v2 = point - line.a;
if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { return true; }
return false;
};
float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y()));
double pixel_area = pixel_size.x() * pixel_size.y();
Vec2d centroid_accumulator = Vec2d::Zero();
Vec2d second_moment_of_area_accumulator = Vec2d::Zero();
double second_moment_of_area_covariance_accumulator = 0.0;
double area = 0.0;
// coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x)
// coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y)
// second moment of area for x: f(x, y) = x^2;
// f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form)
for (int x = 0; x < pixel_count.x(); x++) {
for (int y = 0; y < pixel_count.y(); y++) {
Vec2d position = unscaled(bb.min) + pixel_size.cwiseProduct(Vec2d{x, y});
if (is_inside(position)) {
area += pixel_area;
centroid_accumulator += pixel_area * position;
second_moment_of_area_accumulator += pixel_area * position.cwiseProduct(position);
second_moment_of_area_covariance_accumulator += pixel_area * position.x() * position.y();
}
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
// integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2)
Vec2f second_moment_of_area_xy = jacobian_determinant_abs *
(a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) +
a.cwiseProduct(b + c)) /
12.0f;
// second moment of area covariance : f(x, y) = x*y;
// f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form)
//(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2))
// == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2))
// intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv =
// 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2
// b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u -
// 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du =
// 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2)
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) *
(a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() +
b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y());
float area = jacobian_determinant_abs * 0.5f;
Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f;
return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance};
};
// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first
std::tuple<Vec2f, Vec2f> compute_principal_components(const Polygons &polys)
{
Vec2f centroid_accumulator = Vec2f::Zero();
Vec2f second_moment_of_area_accumulator = Vec2f::Zero();
float second_moment_of_area_covariance_accumulator = 0.0f;
float area = 0.0f;
for (const Polygon &poly : polys) {
Vec2f p0 = unscaled(poly.first_point()).cast<float>();
for (size_t i = 2; i < poly.points.size(); i++) {
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(poly.points[i]).cast<float>();
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
auto [triangle_area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
area += sign * triangle_area;
centroid_accumulator += sign * first_moment_of_area;
second_moment_of_area_accumulator += sign * second_moment_area;
second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
}
}
if (area <= 0.0) {
return {Vec2d::Zero(), Vec2d::Zero()};
return {Vec2f::Zero(), Vec2f::Zero()};
}
Vec2d centroid = centroid_accumulator / area;
Vec2d variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid);
Vec2f centroid = centroid_accumulator / area;
Vec2f variance = second_moment_of_area_accumulator / area - centroid.cwiseProduct(centroid);
double covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y();
#if 0
std::cout << "area : " << area << std::endl;
@ -58,7 +102,7 @@ std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, con
std::cout << "covariance : " << covariance << std::endl;
#endif
if (abs(covariance) < EPSILON) {
std::tuple<Vec2d, Vec2d> result{Vec2d{variance.x(), 0.0}, Vec2d{0.0, variance.y()}};
std::tuple<Vec2f, Vec2f> result{Vec2f{variance.x(), 0.0}, Vec2f{0.0, variance.y()}};
if (variance.y() > variance.x()) {
return {std::get<1>(result), std::get<0>(result)};
} else
@ -72,12 +116,12 @@ std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, con
// Eigenvalues are solutions to det(C - lI) = 0, where l is the eigenvalue and I unit matrix
// Eigenvector for eigenvalue l is any vector v such that Cv = lv
double eigenvalue_a = 0.5 * (variance.x() + variance.y() +
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance));
double eigenvalue_b = 0.5 * (variance.x() + variance.y() -
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4 * covariance * covariance));
Vec2d eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0};
Vec2d eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0};
float eigenvalue_a = 0.5f * (variance.x() + variance.y() +
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance));
float eigenvalue_b = 0.5f * (variance.x() + variance.y() -
sqrt((variance.x() - variance.y()) * (variance.x() - variance.y()) + 4.0f * covariance * covariance));
Vec2f eigenvector_a{(eigenvalue_a - variance.y()) / covariance, 1.0f};
Vec2f eigenvector_b{(eigenvalue_b - variance.y()) / covariance, 1.0f};
#if 0
std::cout << "eigenvalue_a: " << eigenvalue_a << std::endl;

View File

@ -9,8 +9,15 @@
namespace Slic3r {
// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance
// none of the values is divided/normalized by area.
// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous)
// f(x,y) = x^2 for second moment of area
// and f(x,y) = x*y for second moment of area covariance
std::tuple<float, Vec2f, Vec2f, float> compute_moments_of_area_of_triangle(const Vec2f &a, const Vec2f &b, const Vec2f &c);
// returns two eigenvectors of the area covered by given polygons. The vectors are sorted by their corresponding eigenvalue, largest first
std::tuple<Vec2d, Vec2d> compute_principal_components(const Polygons &polys, const double unscaled_resolution);
std::tuple<Vec2f, Vec2f> compute_principal_components(const Polygons &polys);
}

View File

@ -191,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "infill_first"
|| opt_key == "single_extruder_multi_material"
|| opt_key == "temperature"
|| opt_key == "idle_temperature"
|| opt_key == "wipe_tower"
|| opt_key == "wipe_tower_width"
|| opt_key == "wipe_tower_brim_width"
@ -347,7 +348,7 @@ std::vector<ObjectID> Print::print_object_ids() const
bool Print::has_infinite_skirt() const
{
return (m_config.draft_shield == dsEnabled && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
return (m_config.draft_shield == dsEnabled && m_config.skirts > 0)/* || (m_config.ooze_prevention && this->extruders().size() > 1)*/;
}
bool Print::has_skirt() const
@ -404,7 +405,7 @@ bool Print::sequential_print_horizontal_clearance_valid(const Print& print, Poly
}
// Make a copy, so it may be rotated for instances.
Polygon convex_hull0 = it_convex_hull->second;
const double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation());
const double z_diff = Geometry::rotation_diff_z(model_instance0->get_matrix(), print_object->instances().front().model_instance->get_matrix());
if (std::abs(z_diff) > EPSILON)
convex_hull0.rotate(z_diff);
// Now we check that no instance of convex_hull intersects any of the previously checked object instances.
@ -505,8 +506,8 @@ std::string Print::validate(std::string* warning) const
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.");
if (! m_config.use_relative_e_distances)
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
if (m_config.ooze_prevention)
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
return L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.");
if (m_config.use_volumetric_e)
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
if (m_config.complete_objects && extruders.size() > 1)

View File

@ -740,6 +740,21 @@ public:
PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); }
PrintStateBase::StateWithWarnings step_state_with_warnings(PrintObjectStepEnum step) const { return m_state.state_with_warnings(step, PrintObjectBase::state_mutex(m_print)); }
auto last_completed_step() const
{
static_assert(COUNT > 0, "Step count should be > 0");
auto s = int(COUNT) - 1;
std::lock_guard lk(state_mutex(m_print));
while (s >= 0 && ! is_step_done_unguarded(PrintObjectStepEnum(s)))
--s;
if (s < 0)
s = COUNT;
return PrintObjectStepEnum(s);
}
protected:
PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {}

View File

@ -96,6 +96,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType)
static const t_config_enum_values s_keys_map_InfillPattern {
{ "rectilinear", ipRectilinear },
{ "monotonic", ipMonotonic },
{ "monotoniclines", ipMonotonicLines },
{ "alignedrectilinear", ipAlignedRectilinear },
{ "grid", ipGrid },
{ "triangles", ipTriangles },
@ -181,7 +182,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLAMaterialSpeed);
static inline const t_config_enum_values s_keys_map_SLASupportTreeType = {
{"default", int(sla::SupportTreeType::Default)},
{"branching", int(sla::SupportTreeType::Branching)}
{"branching", int(sla::SupportTreeType::Branching)},
//TODO: {"organic", int(sla::SupportTreeType::Organic)}
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SLASupportTreeType);
@ -227,6 +229,11 @@ static void assign_printer_technology_to_unknown(t_optiondef_map &options, Print
kvp.second.printer_technology = printer_technology;
}
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
namespace {
const int max_temp = 1500;
};
PrintConfigDef::PrintConfigDef()
{
this->init_common_params();
@ -768,20 +775,17 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells.");
def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern";
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
def->enum_values.push_back("rectilinear");
def->enum_values.push_back("monotonic");
def->enum_values.push_back("alignedrectilinear");
def->enum_values.push_back("concentric");
def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords");
def->enum_values.push_back("octagramspiral");
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Monotonic"));
def->enum_labels.push_back(L("Aligned Rectilinear"));
def->enum_labels.push_back(L("Concentric"));
def->enum_labels.push_back(L("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
def->set_enum_values({
{ "rectilinear", L("Rectilinear") },
{ "monotonic", L("Monotonic") },
{ "monotoniclines", L("Monotonic Lines") },
{ "alignedrectilinear", L("Aligned Rectilinear") },
{ "concentric", L("Concentric") },
{ "hilbertcurve", L("Hilbert Curve") },
{ "archimedeanchords", L("Archimedean Chords") },
{ "octagramspiral", L("Octagram Spiral") }
});
// solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern.
def->aliases = { "solid_fill_pattern", "external_fill_pattern" };
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipMonotonic));
@ -1973,9 +1977,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("ooze_prevention", coBool);
def->label = L("Enable");
def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. "
"It will enable a tall skirt automatically and move extruders outside such "
"skirt when changing temperatures.");
def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. ");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
@ -2476,7 +2478,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("standby_temperature_delta", coInt);
def->label = L("Temperature variation");
def->tooltip = L("Temperature difference to be applied when an extruder is not active. "
"Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped.");
"The value is not used when 'idle_temperature' in filament settings "
"is defined.");
def->sidetext = "∆°C";
def->min = -max_temp;
def->max = max_temp;
@ -2875,6 +2878,71 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("support_tree_angle", coFloat);
def->label = L("Tree Support Maximum Branch Angle");
def->category = L("Support material");
def->tooltip = L("The maximum angle of the branches, when the branches have to avoid the model. "
"Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach.");
def->sidetext = L("°");
def->min = 0;
def->max = 85;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(40));
def = this->add("support_tree_angle_slow", coFloat);
def->label = L("Tree Support Preferred Branch Angle");
def->category = L("Support material");
def->tooltip = L("The preferred angle of the branches, when they do not have to avoid the model. "
"Use a lower angle to make them more vertical and more stable. Use a higher angle for branches to merge faster.");
def->sidetext = L("°");
def->min = 10;
def->max = 85;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(25));
def = this->add("support_tree_tip_diameter", coFloat);
def->label = L("Tree Support Tip Diameter");
def->category = L("Support material");
def->tooltip = L("The diameter of the top of the tip of the branches of tree support.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.8));
def = this->add("support_tree_branch_diameter", coFloat);
def->label = L("Tree Support Branch Diameter");
def->category = L("Support material");
def->tooltip = L("The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. "
"Branches towards the base will be thicker than this.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(2));
def = this->add("support_tree_branch_diameter_angle", coFloat);
def->label = L("Tree Support Branch Diameter Angle");
def->category = L("Support material");
def->tooltip = L("The angle of the branches' diameter as they gradually become thicker towards the bottom. "
"An angle of 0 will cause the branches to have uniform thickness over their length. "
"A bit of an angle can increase stability of the tree support.");
def->sidetext = L("°");
def->min = 0;
def->max = 15;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5));
def = this->add("support_tree_top_rate", coPercent);
def->label = L("Tree Support Branch Density");
def->category = L("Support material");
def->tooltip = L("Adjusts the density of the support structure used to generate the tips of the branches. "
"A higher value results in better overhangs, but the supports are harder to remove. "
"Use Support Roof for very high values or ensure support density is similarly high at the top.");
def->sidetext = L("%");
def->min = 5;
def->max_literal = 35;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercent(30));
def = this->add("temperature", coInts);
def->label = L("Other layers");
def->tooltip = L("Nozzle temperature for layers after the first one. Set this to zero to disable "
@ -3272,6 +3340,219 @@ void PrintConfigDef::init_extruder_option_keys()
assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end()));
}
void PrintConfigDef::init_sla_support_params(const std::string &prefix)
{
ConfigOptionDef* def;
constexpr const char * pretext_unavailable = L("Unavailable for this method.\n");
std::string pretext;
def = this->add(prefix + "support_head_front_diameter", coFloat);
def->label = L("Pinhead front diameter");
def->category = L("Supports");
def->tooltip = L("Diameter of the pointing side of the head");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.4));
def = this->add(prefix + "support_head_penetration", coFloat);
def->label = L("Head penetration");
def->category = L("Supports");
def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm");
def->mode = comAdvanced;
def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add(prefix + "support_head_width", coFloat);
def->label = L("Pinhead width");
def->category = L("Supports");
def->tooltip = L("Width from the back sphere center to the front sphere center");
def->sidetext = L("mm");
def->min = 0;
def->max = 20;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add(prefix + "support_pillar_diameter", coFloat);
def->label = L("Pillar diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the support pillars");
def->sidetext = L("mm");
def->min = 0;
def->max = 15;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add(prefix + "support_small_pillar_diameter_percent", coPercent);
def->label = L("Small pillar diameter percent");
def->category = L("Supports");
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
"which are used in problematic areas where a normal pilla cannot fit.");
def->sidetext = L("%");
def->min = 1;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercent(50));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar");
def->tooltip = pretext + L(
"Maximum number of bridges that can be placed on a pillar. Bridges "
"hold support point pinheads and connect to pillars as small branches.");
def->min = 0;
def->max = 50;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInt(prefix == "branching" ? 2 : 3));
pretext = "";
if (prefix.empty())
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_weight_on_model", coFloat);
def->label = L("Max weight on model");
def->category = L("Supports");
def->tooltip = pretext + L(
"Maximum weight of sub-trees that terminate on the model instead of the print bed. The weight is the sum of the lenghts of all "
"branches emanating from the endpoint.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(10.));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_pillar_connection_mode", coEnum);
def->label = L("Pillar connection mode");
def->tooltip = pretext + L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending"
" on the distance of the two pillars.");
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_values = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels[0] = L("Zig-Zag");
def->enum_labels[1] = L("Cross");
def->enum_labels[2] = L("Dynamic");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic));
def = this->add(prefix + "support_buildplate_only", coBool);
def->label = L("Support on build plate only");
def->category = L("Supports");
def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add(prefix + "support_pillar_widening_factor", coFloat);
def->label = L("Pillar widening factor");
def->category = L("Supports");
pretext = "";
if (prefix.empty())
pretext = pretext_unavailable;
def->tooltip = pretext +
L("Merging bridges or pillars into another pillars can "
"increase the radius. Zero means no increase, one means "
"full increase. The exact amount of increase is unspecified and can "
"change in the future.");
def->min = 0;
def->max = 1;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.5));
def = this->add(prefix + "support_base_diameter", coFloat);
def->label = L("Support base diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the pillar base");
def->sidetext = L("mm");
def->min = 0;
def->max = 30;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(4.0));
def = this->add(prefix + "support_base_height", coFloat);
def->label = L("Support base height");
def->category = L("Supports");
def->tooltip = L("The height of the pillar base cone");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add(prefix + "support_base_safety_distance", coFloat);
def->label = L("Support base safety distance");
def->category = L("Supports");
def->tooltip = L(
"The minimum distance of the pillar base from the model in mm. "
"Makes sense in zero elevation mode where a gap according "
"to this parameter is inserted between the model and the pad.");
def->sidetext = L("mm");
def->min = 0;
def->max = 10;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(1));
def = this->add(prefix + "support_critical_angle", coFloat);
def->label = L("Critical angle");
def->category = L("Supports");
def->tooltip = L("The default angle for connecting support sticks and junctions.");
def->sidetext = L("°");
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(45));
def = this->add(prefix + "support_max_bridge_length", coFloat);
def->label = L("Max bridge length");
def->category = L("Supports");
def->tooltip = L("The max length of a bridge");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
double default_val = 15.0;
if (prefix == "branching")
default_val = 5.0;
def->set_default_value(new ConfigOptionFloat(default_val));
pretext = "";
if (prefix == "branching")
pretext = pretext_unavailable;
def = this->add(prefix + "support_max_pillar_link_distance", coFloat);
def->label = L("Max pillar linking distance");
def->category = L("Supports");
def->tooltip = pretext + L("The max distance of two pillars to get linked with each other."
" A zero value will prohibit pillar cascading.");
def->sidetext = L("mm");
def->min = 0; // 0 means no linking
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(10.0));
def = this->add(prefix + "support_object_elevation", coFloat);
def->label = L("Object elevation");
def->category = L("Supports");
def->tooltip = L("How much the supports should lift up the supported object. "
"If \"Pad around object\" is enabled, this value is ignored.");
def->sidetext = L("mm");
def->min = 0;
def->max = 150; // This is the max height of print on SL1
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5.0));
}
void PrintConfigDef::init_sla_params()
{
ConfigOptionDef* def;
@ -3454,6 +3735,15 @@ void PrintConfigDef::init_sla_params()
def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.3));
def = this->add_nullable("idle_temperature", coInts);
def->label = L("Idle temperature");
def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups."
"This is only used when 'Ooze prevention is active in Print Settings.'");
def->sidetext = L("°C");
def->min = 0;
def->max = max_temp;
def->set_default_value(new ConfigOptionIntsNullable { ConfigOptionIntsNullable::nil_value() });
def = this->add("bottle_volume", coFloat);
def->label = L("Bottle volume");
def->tooltip = L("Bottle volume");
@ -3613,179 +3903,21 @@ void PrintConfigDef::init_sla_params()
def->enum_values = ConfigOptionEnum<sla::SupportTreeType>::get_enum_names();
def->enum_labels = ConfigOptionEnum<sla::SupportTreeType>::get_enum_names();
def->enum_labels[0] = L("Default");
def->enum_labels[1] = L("Branching");
def->mode = comAdvanced;
def->enum_labels[1] = L("Branching (experimental)");
// TODO: def->enum_labels[2] = L("Organic");
def->mode = comSimple;
def->set_default_value(new ConfigOptionEnum(sla::SupportTreeType::Default));
def = this->add("support_head_front_diameter", coFloat);
def->label = L("Pinhead front diameter");
def->category = L("Supports");
def->tooltip = L("Diameter of the pointing side of the head");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.4));
init_sla_support_params("");
init_sla_support_params("branching");
def = this->add("support_head_penetration", coFloat);
def->label = L("Head penetration");
def = this->add("support_enforcers_only", coBool);
def->label = L("Support only in enforced regions");
def->category = L("Supports");
def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm");
def->mode = comAdvanced;
def->min = 0;
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add("support_head_width", coFloat);
def->label = L("Pinhead width");
def->category = L("Supports");
def->tooltip = L("Width from the back sphere center to the front sphere center");
def->sidetext = L("mm");
def->min = 0;
def->max = 20;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_pillar_diameter", coFloat);
def->label = L("Pillar diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the support pillars");
def->sidetext = L("mm");
def->min = 0;
def->max = 15;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_small_pillar_diameter_percent", coPercent);
def->label = L("Small pillar diameter percent");
def->category = L("Supports");
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
"which are used in problematic areas where a normal pilla cannot fit.");
def->sidetext = L("%");
def->min = 1;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionPercent(50));
def = this->add("support_max_bridges_on_pillar", coInt);
def->label = L("Max bridges on a pillar");
def->tooltip = L(
"Maximum number of bridges that can be placed on a pillar. Bridges "
"hold support point pinheads and connect to pillars as small branches.");
def->min = 0;
def->max = 50;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInt(3));
def = this->add("support_pillar_connection_mode", coEnum);
def->label = L("Pillar connection mode");
def->tooltip = L("Controls the bridge type between two neighboring pillars."
" Can be zig-zag, cross (double zig-zag) or dynamic which"
" will automatically switch between the first two depending"
" on the distance of the two pillars.");
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_keys_map = &ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_values();
def->enum_values = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels = ConfigOptionEnum<SLAPillarConnectionMode>::get_enum_names();
def->enum_labels[0] = L("Zig-Zag");
def->enum_labels[1] = L("Cross");
def->enum_labels[2] = L("Dynamic");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum(SLAPillarConnectionMode::dynamic));
def = this->add("support_buildplate_only", coBool);
def->label = L("Support on build plate only");
def->category = L("Supports");
def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
def->tooltip = L("Only create support if it lies in a support enforcer.");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("support_pillar_widening_factor", coFloat);
def->label = L("Pillar widening factor");
def->category = L("Supports");
def->tooltip = L(
"Merging bridges or pillars into another pillars can "
"increase the radius. Zero means no increase, one means "
"full increase. The exact amount of increase is unspecified and can "
"change in the future. What is garanteed is that thickness will not "
"exceed \"support_base_diameter\"");
def->min = 0;
def->max = 1;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.15));
def = this->add("support_base_diameter", coFloat);
def->label = L("Support base diameter");
def->category = L("Supports");
def->tooltip = L("Diameter in mm of the pillar base");
def->sidetext = L("mm");
def->min = 0;
def->max = 30;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(4.0));
def = this->add("support_base_height", coFloat);
def->label = L("Support base height");
def->category = L("Supports");
def->tooltip = L("The height of the pillar base cone");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(1.0));
def = this->add("support_base_safety_distance", coFloat);
def->label = L("Support base safety distance");
def->category = L("Supports");
def->tooltip = L(
"The minimum distance of the pillar base from the model in mm. "
"Makes sense in zero elevation mode where a gap according "
"to this parameter is inserted between the model and the pad.");
def->sidetext = L("mm");
def->min = 0;
def->max = 10;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(1));
def = this->add("support_critical_angle", coFloat);
def->label = L("Critical angle");
def->category = L("Supports");
def->tooltip = L("The default angle for connecting support sticks and junctions.");
def->sidetext = L("°");
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(45));
def = this->add("support_max_bridge_length", coFloat);
def->label = L("Max bridge length");
def->category = L("Supports");
def->tooltip = L("The max length of a bridge");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(15.0));
def = this->add("support_max_pillar_link_distance", coFloat);
def->label = L("Max pillar linking distance");
def->category = L("Supports");
def->tooltip = L("The max distance of two pillars to get linked with each other."
" A zero value will prohibit pillar cascading.");
def->sidetext = L("mm");
def->min = 0; // 0 means no linking
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(10.0));
def = this->add("support_object_elevation", coFloat);
def->label = L("Object elevation");
def->category = L("Supports");
def->tooltip = L("How much the supports should lift up the supported object. "
"If \"Pad around object\" is enabled, this value is ignored.");
def->sidetext = L("mm");
def->min = 0;
def->max = 150; // This is the max height of print on SL1
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(5.0));
def = this->add("support_points_density_relative", coInt);
def->label = L("Support points density");
def->category = L("Supports");
@ -4392,6 +4524,12 @@ std::string validate(const FullPrintConfig &cfg)
assert(opt != nullptr);
const ConfigOptionDef *optdef = print_config_def.get(opt_key);
assert(optdef != nullptr);
if (opt->nullable() && opt->is_nil()) {
// Do not check nil values
continue;
}
bool out_of_range = false;
switch (opt->type()) {
case coFloat:
@ -4753,6 +4891,23 @@ Points get_bed_shape(const PrintConfig &cfg)
Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); }
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config)
{
const auto *suptreetype = config.option<ConfigOptionEnum<sla::SupportTreeType>>("support_tree_type");
std::string slatree = "";
if (suptreetype) {
auto ttype = static_cast<sla::SupportTreeType>(suptreetype->getInt());
switch (ttype) {
case sla::SupportTreeType::Branching: slatree = "branching"; break;
case sla::SupportTreeType::Organic: slatree = "organic"; break;
default:
;
}
}
return slatree;
}
} // namespace Slic3r
#include <cereal/types/polymorphic.hpp>

View File

@ -57,7 +57,7 @@ enum class FuzzySkinType {
};
enum InfillPattern : int {
ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
ipLightning,
ipCount,
@ -185,6 +185,7 @@ private:
void init_fff_params();
void init_extruder_option_keys();
void init_sla_params();
void init_sla_support_params(const std::string &method_prefix);
std::vector<std::string> m_extruder_option_keys;
std::vector<std::string> m_extruder_retract_keys;
@ -546,6 +547,14 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInt, support_material_threshold))
((ConfigOptionBool, support_material_with_sheath))
((ConfigOptionFloatOrPercent, support_material_xy_spacing))
// Tree supports
((ConfigOptionFloat, support_tree_angle))
((ConfigOptionFloat, support_tree_angle_slow))
((ConfigOptionFloat, support_tree_branch_diameter))
((ConfigOptionFloat, support_tree_branch_diameter_angle))
((ConfigOptionPercent, support_tree_top_rate))
((ConfigOptionFloat, support_tree_tip_diameter))
// The rest
((ConfigOptionBool, thick_bridges))
((ConfigOptionFloat, xy_size_compensation))
((ConfigOptionBool, wipe_into_objects))
@ -760,6 +769,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloatOrPercent, first_layer_height))
((ConfigOptionFloatOrPercent, first_layer_speed))
((ConfigOptionInts, first_layer_temperature))
((ConfigOptionIntsNullable, idle_temperature))
((ConfigOptionInts, full_fan_speed_layer))
((ConfigOptionFloat, infill_acceleration))
((ConfigOptionBool, infill_first))
@ -861,6 +871,11 @@ PRINT_CONFIG_CLASS_DEFINE(
// Generate only ground facing supports
((ConfigOptionBool, support_buildplate_only))
((ConfigOptionFloat, support_max_weight_on_model))
// Generate only ground facing supports
((ConfigOptionBool, support_enforcers_only))
// TODO: unimplemented at the moment. This coefficient will have an impact
// when bridges and pillars are merged. The resulting pillar should be a bit
// thicker than the ones merging into it. How much thicker? I don't know
@ -889,6 +904,62 @@ PRINT_CONFIG_CLASS_DEFINE(
// and the model object's bounding box bottom. Units in mm.
((ConfigOptionFloat, support_object_elevation))/*= 5.0*/
// Branching tree
// Diameter in mm of the pointing side of the head.
((ConfigOptionFloat, branchingsupport_head_front_diameter))/*= 0.2*/
// How much the pinhead has to penetrate the model surface
((ConfigOptionFloat, branchingsupport_head_penetration))/*= 0.2*/
// Width in mm from the back sphere center to the front sphere center.
((ConfigOptionFloat, branchingsupport_head_width))/*= 1.0*/
// Radius in mm of the support pillars.
((ConfigOptionFloat, branchingsupport_pillar_diameter))/*= 0.8*/
// The percentage of smaller pillars compared to the normal pillar diameter
// which are used in problematic areas where a normal pilla cannot fit.
((ConfigOptionPercent, branchingsupport_small_pillar_diameter_percent))
// How much bridge (supporting another pinhead) can be placed on a pillar.
((ConfigOptionInt, branchingsupport_max_bridges_on_pillar))
// How the pillars are bridged together
((ConfigOptionEnum<SLAPillarConnectionMode>, branchingsupport_pillar_connection_mode))
// Generate only ground facing supports
((ConfigOptionBool, branchingsupport_buildplate_only))
((ConfigOptionFloat, branchingsupport_max_weight_on_model))
((ConfigOptionFloat, branchingsupport_pillar_widening_factor))
// Radius in mm of the pillar base.
((ConfigOptionFloat, branchingsupport_base_diameter))/*= 2.0*/
// The height of the pillar base cone in mm.
((ConfigOptionFloat, branchingsupport_base_height))/*= 1.0*/
// The minimum distance of the pillar base from the model in mm.
((ConfigOptionFloat, branchingsupport_base_safety_distance)) /*= 1.0*/
// The default angle for connecting support sticks and junctions.
((ConfigOptionFloat, branchingsupport_critical_angle))/*= 45*/
// The max length of a bridge in mm
((ConfigOptionFloat, branchingsupport_max_bridge_length))/*= 15.0*/
// The max distance of two pillars to get cross linked.
((ConfigOptionFloat, branchingsupport_max_pillar_link_distance))
// The elevation in Z direction upwards. This is the space between the pad
// and the model object's bounding box bottom. Units in mm.
((ConfigOptionFloat, branchingsupport_object_elevation))/*= 5.0*/
/////// Following options influence automatic support points placement:
((ConfigOptionInt, support_points_density_relative))
((ConfigOptionFloat, support_points_minimal_distance))
@ -1108,6 +1179,8 @@ Points get_bed_shape(const DynamicPrintConfig &cfg);
Points get_bed_shape(const PrintConfig &cfg);
Points get_bed_shape(const SLAPrinterConfig &cfg);
std::string get_sla_suptree_prefix(const DynamicPrintConfig &config);
// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp.
// Each change of ModelConfig is tracked by assigning a new timestamp from a global counter.
// The counter is used for faster synchronization of the background slicing thread

View File

@ -1,4 +1,6 @@
#include "Exception.hpp"
#include "KDTreeIndirect.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@ -21,14 +23,19 @@
#include "SupportSpotsGenerator.hpp"
#include "TriangleSelectorWrapper.hpp"
#include "format.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <cmath>
#include <float.h>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <vector>
using namespace std::literals;
@ -69,8 +76,8 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Transfor
BoundingBoxf3 bbox = model_object->raw_bounding_box();
Vec3d bbox_center = bbox.center();
// We may need to rotate the bbox / bbox_center from the original instance to the current instance.
double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_rotation(), instances.front().model_instance->get_rotation());
if (std::abs(z_diff) > EPSILON) {
double z_diff = Geometry::rotation_diff_z(model_object->instances.front()->get_matrix(), instances.front().model_instance->get_matrix());
if (std::abs(z_diff) > EPSILON) {
auto z_rot = Eigen::AngleAxisd(z_diff, Vec3d::UnitZ());
bbox = bbox.transformed(Transform3d(z_rot));
bbox_center = (z_rot * bbox_center).eval();
@ -406,32 +413,58 @@ void PrintObject::ironing()
}
}
/*
std::vector<size_t> problematic_layers = SupportSpotsGenerator::quick_search(this);
if (!problematic_layers.empty()) {
std::cout << "Object needs supports" << std::endl;
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
L("Supportable issues found. Consider enabling supports for this object"));
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
L("Supportable issues found. Consider enabling supports for this object"));
for (size_t index = 0; index < std::min(problematic_layers.size(), size_t(4)); ++index) {
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
format(L("Layer with issues: %1%"), problematic_layers[index] + 1));
}
}
*/
void PrintObject::generate_support_spots()
{
if (this->set_started(posSupportSpotsSearch)) {
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start";
m_print->set_status(75, L("Searching support spots"));
if (!this->shared_regions()->generated_support_points.has_value()) {
PrintTryCancel cancel_func = m_print->make_try_cancel();
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values};
SupportSpotsGenerator::SupportPoints supp_points = SupportSpotsGenerator::full_search(this, cancel_func, params);
PrintTryCancel cancel_func = m_print->make_try_cancel();
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values,
float(this->print()->m_config.perimeter_acceleration.getFloat()),
this->config().raft_layers.getInt(), this->config().brim_type.value,
float(this->config().brim_width.getFloat())};
auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params);
this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points};
m_print->throw_if_canceled();
auto alert_fn = [&](PrintStateBase::WarningLevel level, SupportSpotsGenerator::SupportPointCause cause) {
switch (cause) {
case SupportSpotsGenerator::SupportPointCause::LongBridge:
this->active_step_add_warning(level, L("There are bridges longer than recommended length. Consider adding supports. ") +
(L("Object name")) + ": " + this->model_object()->name);
break;
case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor:
this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed. ") + (L("Object name")) +
": " + this->model_object()->name);
break;
case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion:
if (level == PrintStateBase::WarningLevel::CRITICAL) {
this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed. ") +
(L("Object name")) + ": " + this->model_object()->name);
} else {
this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ") +
(L("Object name")) + ": " + this->model_object()->name);
}
break;
case SupportSpotsGenerator::SupportPointCause::SeparationFromBed:
this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports. ") +
(L("Object name")) + ": " + this->model_object()->name);
break;
case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart:
this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed. ") + (L("Object name")) +
": " + this->model_object()->name);
break;
case SupportSpotsGenerator::SupportPointCause::WeakObjectPart:
this->active_step_add_warning(level, L("Thin parts of the object may break. Consider adding supports. ") +
(L("Object name")) + ": " + this->model_object()->name);
break;
}
};
if (!this->has_support()) {
SupportSpotsGenerator::raise_alerts_for_issues(supp_points, partial_objects, alert_fn);
}
}
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end";
this->set_done(posSupportSpotsSearch);
@ -468,7 +501,10 @@ void PrintObject::estimate_curled_extrusions()
// Estimate curling of support material and add it to the malformaition lines of each layer
float support_flow_width = support_material_flow(this, this->config().layer_height).width();
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values};
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values,
float(this->print()->m_config.perimeter_acceleration.getFloat()),
this->config().raft_layers.getInt(), this->config().brim_type.value,
float(this->config().brim_width.getFloat())};
SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params);
SupportSpotsGenerator::estimate_malformations(this->layers(), params);
m_print->throw_if_canceled();
@ -580,6 +616,7 @@ bool PrintObject::invalidate_state_by_config_options(
if ( opt_key == "brim_width"
|| opt_key == "brim_separation"
|| opt_key == "brim_type") {
steps.emplace_back(posSupportSpotsSearch);
// Brim is printed below supports, support invalidates brim and skirt.
steps.emplace_back(posSupportMaterial);
} else if (
@ -657,6 +694,12 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "support_material_synchronize_layers"
|| opt_key == "support_material_threshold"
|| opt_key == "support_material_with_sheath"
|| opt_key == "support_tree_angle"
|| opt_key == "support_tree_angle_slow"
|| opt_key == "support_tree_branch_diameter"
|| opt_key == "support_tree_branch_diameter_angle"
|| opt_key == "support_tree_top_rate"
|| opt_key == "support_tree_tip_diameter"
|| opt_key == "raft_expansion"
|| opt_key == "raft_first_layer_density"
|| opt_key == "raft_first_layer_expansion"

View File

@ -4,6 +4,8 @@
// paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf
// sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/
// inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
#ifndef PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP
#define PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP
#include <cstdint>
#include <functional>
@ -30,3 +32,5 @@ void its_quadric_edge_collapse(
} // namespace Slic3r
#endif // slic3r_quadric_edge_collapse_hpp_
#endif // PRUSASLICER_QUADRIC_EDGE_COLLAPSE_HPP

View File

@ -13,20 +13,28 @@
namespace Slic3r { namespace sla {
inline constexpr const auto &beam_ex_policy = ex_tbb;
class BranchingTreeBuilder: public branchingtree::Builder {
SupportTreeBuilder &m_builder;
const SupportableMesh &m_sm;
const branchingtree::PointCloud &m_cloud;
std::vector<branchingtree::Node> m_pillars; // to put an index over them
// cache succesfull ground connections
mutable std::map<int, GroundConnection> m_gnd_connections;
mutable execution::SpinningMutex<ExecutionTBB> m_gnd_connections_mtx;
// Scaling of the input value 'widening_factor:<0, 1>' to produce resonable
// widening behaviour
static constexpr double WIDENING_SCALE = 0.02;
static constexpr double WIDENING_SCALE = 0.05;
double get_radius(const branchingtree::Node &j)
double get_radius(const branchingtree::Node &j) const
{
double w = WIDENING_SCALE * m_sm.cfg.pillar_widening_factor * j.weight;
return std::min(m_sm.cfg.base_radius_mm, double(j.Rmin) + w);
return double(j.Rmin) + w;
}
std::vector<size_t> m_unroutable_pinheads;
@ -78,6 +86,44 @@ class BranchingTreeBuilder: public branchingtree::Builder {
});
}
void discard_subtree_rescure(size_t root)
{
// Discard all the support points connecting to this branch.
// As a last resort, try to route child nodes to ground and stop
// traversing if any child branch succeeds.
traverse(m_cloud, root, [this](const branchingtree::Node &node) {
branchingtree::TraverseReturnT ret{true, true};
int suppid_parent = m_cloud.get_leaf_id(node.id);
int suppid_left = branchingtree::Node::ID_NONE;
int suppid_right = branchingtree::Node::ID_NONE;
double glvl = ground_level(m_sm);
branchingtree::Node dst = node;
dst.pos.z() = glvl;
dst.weight += node.pos.z() - glvl;
if (node.left >= 0 && add_ground_bridge(m_cloud.get(node.left), dst))
ret.to_left = false;
else
suppid_left = m_cloud.get_leaf_id(node.left);
if (node.right >= 0 && add_ground_bridge(m_cloud.get(node.right), dst))
ret.to_right = false;
else
suppid_right = m_cloud.get_leaf_id(node.right);
if (suppid_parent >= 0)
m_unroutable_pinheads.emplace_back(suppid_parent);
if (suppid_left >= 0)
m_unroutable_pinheads.emplace_back(suppid_left);
if (suppid_right >= 0)
m_unroutable_pinheads.emplace_back(suppid_right);
return ret;
});
}
public:
BranchingTreeBuilder(SupportTreeBuilder &builder,
const SupportableMesh &sm,
@ -98,13 +144,24 @@ public:
bool add_mesh_bridge(const branchingtree::Node &from,
const branchingtree::Node &to) override;
std::optional<Vec3f> suggest_avoidance(const branchingtree::Node &from,
float max_bridge_len) const override;
void report_unroutable(const branchingtree::Node &j) override
{
BOOST_LOG_TRIVIAL(error) << "Cannot route junction at " << j.pos.x()
<< " " << j.pos.y() << " " << j.pos.z();
double glvl = ground_level(m_sm);
branchingtree::Node dst = j;
dst.pos.z() = glvl;
dst.weight += j.pos.z() - glvl;
if (add_ground_bridge(j, dst))
return;
BOOST_LOG_TRIVIAL(warning) << "Cannot route junction at " << j.pos.x()
<< " " << j.pos.y() << " " << j.pos.z();
// Discard all the support points connecting to this branch.
discard_subtree(j.id);
discard_subtree_rescure(j.id);
// discard_subtree(j.id);
}
const std::vector<size_t>& unroutable_pinheads() const
@ -113,15 +170,28 @@ public:
}
bool is_valid() const override { return !m_builder.ctl().stopcondition(); }
const std::vector<branchingtree::Node> & pillars() const { return m_pillars; }
const GroundConnection *ground_conn(size_t pillar) const
{
const GroundConnection *ret = nullptr;
auto it = m_gnd_connections.find(m_pillars[pillar].id);
if (it != m_gnd_connections.end())
ret = &it->second;
return ret;
}
};
bool BranchingTreeBuilder::add_bridge(const branchingtree::Node &from,
const branchingtree::Node &to)
const branchingtree::Node &to)
{
Vec3d fromd = from.pos.cast<double>(), tod = to.pos.cast<double>();
double fromR = get_radius(from), toR = get_radius(to);
Beam beam{Ball{fromd, fromR}, Ball{tod, toR}};
auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh, beam,
auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam,
m_sm.cfg.safety_distance_mm);
bool ret = hit.distance() > (tod - fromd).norm();
@ -144,8 +214,8 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node,
Beam beam2{Ball{from2d, closestR}, Ball{tod, mergeR}};
auto sd = m_sm.cfg.safety_distance_mm ;
auto hit1 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam1, sd);
auto hit2 = beam_mesh_hit(ex_tbb, m_sm.emesh, beam2, sd);
auto hit1 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam1, sd);
auto hit2 = beam_mesh_hit(beam_ex_policy , m_sm.emesh, beam2, sd);
bool ret = hit1.distance() > (tod - from1d).norm() &&
hit2.distance() > (tod - from2d).norm();
@ -156,12 +226,31 @@ bool BranchingTreeBuilder::add_merger(const branchingtree::Node &node,
bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
const branchingtree::Node &to)
{
bool ret = search_ground_route(ex_tbb, m_builder, m_sm,
sla::Junction{from.pos.cast<double>(),
get_radius(from)},
get_radius(to)).first;
bool ret = false;
if (ret) {
namespace bgi = boost::geometry::index;
auto it = m_gnd_connections.find(from.id);
const GroundConnection *connptr = nullptr;
if (it == m_gnd_connections.end()) {
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
Vec3d init_dir = (to.pos - from.pos).cast<double>().normalized();
auto conn = deepsearch_ground_connection(beam_ex_policy , m_sm, j,
get_radius(to), init_dir);
// Remember that this node was tested if can go to ground, don't
// test it with any other destination ground point because
// it is unlikely that search_ground_route would find a better solution
connptr = &(m_gnd_connections[from.id] = conn);
} else {
connptr = &(it->second);
}
if (connptr && *connptr) {
m_pillars.emplace_back(from);
ret = true;
build_subtree(from.id);
}
@ -171,17 +260,20 @@ bool BranchingTreeBuilder::add_ground_bridge(const branchingtree::Node &from,
bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
const branchingtree::Node &to)
{
if (from.weight > m_sm.cfg.max_weight_on_model_support)
return false;
sla::Junction fromj = {from.pos.cast<double>(), get_radius(from)};
auto anchor = m_sm.cfg.ground_facing_only ?
std::optional<Anchor>{} : // If no mesh connections are allowed
calculate_anchor_placement(ex_tbb, m_sm, fromj,
calculate_anchor_placement(beam_ex_policy , m_sm, fromj,
to.pos.cast<double>());
if (anchor) {
sla::Junction toj = {anchor->junction_point(), anchor->r_back_mm};
auto hit = beam_mesh_hit(ex_tbb, m_sm.emesh,
auto hit = beam_mesh_hit(beam_ex_policy , m_sm.emesh,
Beam{{fromj.pos, fromj.r}, {toj.pos, toj.r}}, 0.);
if (hit.distance() > distance(fromj.pos, toj.pos)) {
@ -197,6 +289,70 @@ bool BranchingTreeBuilder::add_mesh_bridge(const branchingtree::Node &from,
return bool(anchor);
}
static std::optional<Vec3f> get_avoidance(const GroundConnection &conn,
float maxdist)
{
std::optional<Vec3f> ret;
if (conn) {
if (conn.path.size() > 1) {
ret = conn.path[1].pos.cast<float>();
} else {
Vec3f pbeg = conn.path[0].pos.cast<float>();
Vec3f pend = conn.pillar_base->pos.cast<float>();
pbeg.z() = std::max(pbeg.z() - maxdist, pend.z());
ret = pbeg;
}
}
return ret;
}
std::optional<Vec3f> BranchingTreeBuilder::suggest_avoidance(
const branchingtree::Node &from, float max_bridge_len) const
{
std::optional<Vec3f> ret;
double glvl = ground_level(m_sm);
branchingtree::Node dst = from;
dst.pos.z() = glvl;
dst.weight += from.pos.z() - glvl;
sla::Junction j{from.pos.cast<double>(), get_radius(from)};
auto found_it = m_gnd_connections.end();
{
std::lock_guard lk{m_gnd_connections_mtx};
found_it = m_gnd_connections.find(from.id);
}
if (found_it != m_gnd_connections.end()) {
ret = get_avoidance(found_it->second, max_bridge_len);
} else {
auto conn = deepsearch_ground_connection(
beam_ex_policy , m_sm, j, get_radius(dst), sla::DOWN);
{
std::lock_guard lk{m_gnd_connections_mtx};
m_gnd_connections[from.id] = conn;
}
ret = get_avoidance(conn, max_bridge_len);
}
return ret;
}
inline void build_pillars(SupportTreeBuilder &builder,
BranchingTreeBuilder &vbuilder,
const SupportableMesh &sm)
{
for (size_t pill_id = 0; pill_id < vbuilder.pillars().size(); ++pill_id) {
auto * conn = vbuilder.ground_conn(pill_id);
if (conn)
build_ground_connection(builder, sm, *conn);
}
}
void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &sm)
{
auto coordfn = [&sm](size_t id, size_t dim) { return sm.pts[id].pos(dim); };
@ -221,7 +377,7 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
for (auto &h : heads)
if (h && h->is_valid()) {
leafs.emplace_back(h->junction_point().cast<float>(), h->r_back_mm);
h->id = leafs.size() - 1;
h->id = long(leafs.size() - 1);
builder.add_head(h->id, *h);
}
@ -240,15 +396,29 @@ void create_branching_tree(SupportTreeBuilder &builder, const SupportableMesh &s
props.sampling_radius());
auto bedpts = branchingtree::sample_bed(props.bed_shape(),
props.ground_level(),
float(props.ground_level()),
props.sampling_radius());
for (auto &bp : bedpts)
bp.Rmin = sm.cfg.head_back_radius_mm;
branchingtree::PointCloud nodes{std::move(meshpts), std::move(bedpts),
std::move(leafs), props};
BranchingTreeBuilder vbuilder{builder, sm, nodes};
execution::for_each(ex_tbb,
size_t(0),
nodes.get_leafs().size(),
[&nodes, &vbuilder](size_t leaf_idx) {
vbuilder.suggest_avoidance(nodes.get_leafs()[leaf_idx],
nodes.properties().max_branch_length());
});
branchingtree::build_tree(nodes, vbuilder);
build_pillars(builder, vbuilder, sm);
for (size_t id : vbuilder.unroutable_pinheads())
builder.head(id).invalidate();

View File

@ -340,14 +340,17 @@ bool DefaultSupportTree::connect_to_nearpillar(const Head &head,
return true;
}
bool DefaultSupportTree::create_ground_pillar(const Vec3d &hjp,
bool DefaultSupportTree::create_ground_pillar(const Junction &hjp,
const Vec3d &sourcedir,
double radius,
long head_id)
{
auto [ret, pillar_id] = sla::create_ground_pillar(suptree_ex_policy,
m_builder, m_sm, hjp,
sourcedir, radius, radius,
m_builder,
m_sm,
hjp.pos,
sourcedir,
hjp.r,
hjp.r,
head_id);
if (pillar_id >= 0) // Save the pillar endpoint in the spatial index
@ -362,20 +365,19 @@ void DefaultSupportTree::add_pinheads()
// The minimum distance for two support points to remain valid.
const double /*constexpr*/ D_SP = 0.1;
// Get the points that are too close to each other and keep only the
// first one
// Get the points that are too close to each other and keep only the
// first one
auto aliases = cluster(m_points, D_SP, 2);
PtIndices filtered_indices;
filtered_indices.reserve(aliases.size());
m_iheads.reserve(aliases.size());
m_iheadless.reserve(aliases.size());
for(auto& a : aliases) {
// Here we keep only the front point of the cluster.
filtered_indices.emplace_back(a.front());
}
// calculate the normals to the triangles for filtered points
// calculate the normals to the triangles for filtered points
auto nmls = normals(suptree_ex_policy, m_points, m_sm.emesh,
m_sm.cfg.head_front_radius_mm, m_thr,
filtered_indices);
@ -404,22 +406,22 @@ void DefaultSupportTree::add_pinheads()
Vec3d n = nmls.row(Eigen::Index(i));
// for all normals we generate the spherical coordinates and
// saturate the polar angle to 45 degrees from the bottom then
// convert back to standard coordinates to get the new normal.
// Then we just create a quaternion from the two normals
// (Quaternion::FromTwoVectors) and apply the rotation to the
// arrow head.
// for all normals we generate the spherical coordinates and
// saturate the polar angle to 45 degrees from the bottom then
// convert back to standard coordinates to get the new normal.
// Then we just create a quaternion from the two normals
// (Quaternion::FromTwoVectors) and apply the rotation to the
// arrow head.
auto [polar, azimuth] = dir_to_spheric(n);
// skip if the tilt is not sane
// skip if the tilt is not sane
if (polar < PI - m_sm.cfg.normal_cutoff_angle) return;
// We saturate the polar angle to 3pi/4
// We saturate the polar angle to 3pi/4
polar = std::max(polar, PI - m_sm.cfg.bridge_slope);
// save the head (pinpoint) position
// save the head (pinpoint) position
Vec3d hp = m_points.row(fidx);
double lmin = m_sm.cfg.head_width_mm, lmax = lmin;
@ -428,18 +430,17 @@ void DefaultSupportTree::add_pinheads()
lmin = 0., lmax = m_sm.cfg.head_penetration_mm;
}
// The distance needed for a pinhead to not collide with model.
// The distance needed for a pinhead to not collide with model.
double w = lmin + 2 * back_r + 2 * m_sm.cfg.head_front_radius_mm -
m_sm.cfg.head_penetration_mm;
double pin_r = double(m_sm.pts[fidx].head_front_radius);
// Reassemble the now corrected normal
// Reassemble the now corrected normal
auto nn = spheric_to_dir(polar, azimuth).normalized();
// check available distance
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r,
back_r, w);
// check available distance
AABBMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, back_r, w);
if (t.distance() < w) {
// Let's try to optimize this angle, there might be a
@ -509,10 +510,10 @@ void DefaultSupportTree::classify()
ground_head_indices.reserve(m_iheads.size());
m_iheads_onmodel.reserve(m_iheads.size());
// First we decide which heads reach the ground and can be full
// pillars and which shall be connected to the model surface (or
// search a suitable path around the surface that leads to the
// ground -- TODO)
// First we decide which heads reach the ground and can be full
// pillars and which shall be connected to the model surface (or
// search a suitable path around the surface that leads to the
// ground -- TODO)
for(unsigned i : m_iheads) {
m_thr();
@ -530,10 +531,10 @@ void DefaultSupportTree::classify()
m_head_to_ground_scans[i] = hit;
}
// We want to search for clusters of points that are far enough
// from each other in the XY plane to not cross their pillar bases
// These clusters of support points will join in one pillar,
// possibly in their centroid support point.
// We want to search for clusters of points that are far enough
// from each other in the XY plane to not cross their pillar bases
// These clusters of support points will join in one pillar,
// possibly in their centroid support point.
auto pointfn = [this](unsigned i) {
return m_builder.head(i).junction_point();
@ -559,13 +560,13 @@ void DefaultSupportTree::routing_to_ground()
for (auto &cl : m_pillar_clusters) {
m_thr();
// place all the centroid head positions into the index. We
// will query for alternative pillar positions. If a sidehead
// cannot connect to the cluster centroid, we have to search
// for another head with a full pillar. Also when there are two
// elements in the cluster, the centroid is arbitrary and the
// sidehead is allowed to connect to a nearby pillar to
// increase structural stability.
// place all the centroid head positions into the index. We
// will query for alternative pillar positions. If a sidehead
// cannot connect to the cluster centroid, we have to search
// for another head with a full pillar. Also when there are two
// elements in the cluster, the centroid is arbitrary and the
// sidehead is allowed to connect to a nearby pillar to
// increase structural stability.
if (cl.empty()) continue;
@ -587,7 +588,7 @@ void DefaultSupportTree::routing_to_ground()
Head &h = m_builder.head(hid);
if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
if (!create_ground_pillar(h.junction(), h.dir, h.id)) {
BOOST_LOG_TRIVIAL(warning)
<< "Pillar cannot be created for support point id: " << hid;
m_iheads_onmodel.emplace_back(h.id);
@ -595,11 +596,11 @@ void DefaultSupportTree::routing_to_ground()
}
}
// now we will go through the clusters ones again and connect the
// sidepoints with the cluster centroid (which is a ground pillar)
// or a nearby pillar if the centroid is unreachable.
// now we will go through the clusters ones again and connect the
// sidepoints with the cluster centroid (which is a ground pillar)
// or a nearby pillar if the centroid is unreachable.
size_t ci = 0;
for (auto cl : m_pillar_clusters) {
for (const std::vector<unsigned> &cl : m_pillar_clusters) {
m_thr();
auto cidx = cl_centroids[ci++];
@ -615,10 +616,9 @@ void DefaultSupportTree::routing_to_ground()
if (!connect_to_nearpillar(sidehead, centerpillarID) &&
!search_pillar_and_connect(sidehead)) {
Vec3d pstart = sidehead.junction_point();
// Vec3d pend = Vec3d{pstart.x(), pstart.y(), gndlvl};
// Could not find a pillar, create one
create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id);
create_ground_pillar(sidehead.junction(), sidehead.dir, sidehead.id);
}
}
}
@ -664,10 +664,10 @@ bool DefaultSupportTree::connect_to_model_body(Head &head)
zangle = std::max(zangle, PI/4);
double h = std::sin(zangle) * head.fullwidth();
// The width of the tail head that we would like to have...
// The width of the tail head that we would like to have...
h = std::min(hit.distance() - head.r_back_mm, h);
// If this is a mini pillar dont bother with the tail width, can be 0.
// If this is a mini pillar dont bother with the tail width, can be 0.
if (head.r_back_mm < m_sm.cfg.head_back_radius_mm) h = std::max(h, 0.);
else if (h <= 0.) return false;
@ -773,23 +773,23 @@ void DefaultSupportTree::interconnect_pillars()
// Ideally every pillar should be connected with at least one of its
// neighbors if that neighbor is within max_pillar_link_distance
// Pillars with height exceeding H1 will require at least one neighbor
// to connect with. Height exceeding H2 require two neighbors.
// Pillars with height exceeding H1 will require at least one neighbor
// to connect with. Height exceeding H2 require two neighbors.
double H1 = m_sm.cfg.max_solo_pillar_height_mm;
double H2 = m_sm.cfg.max_dual_pillar_height_mm;
double d = m_sm.cfg.max_pillar_link_distance_mm;
//A connection between two pillars only counts if the height ratio is
// bigger than 50%
// A connection between two pillars only counts if the height ratio is
// bigger than 50%
double min_height_ratio = 0.5;
std::set<unsigned long> pairs;
// A function to connect one pillar with its neighbors. THe number of
// neighbors is given in the configuration. This function if called
// for every pillar in the pillar index. A pair of pillar will not
// be connected multiple times this is ensured by the 'pairs' set which
// remembers the processed pillar pairs
// A function to connect one pillar with its neighbors. THe number of
// neighbors is given in the configuration. This function if called
// for every pillar in the pillar index. A pair of pillar will not
// be connected multiple times this is ensured by the 'pairs' set which
// remembers the processed pillar pairs
auto cascadefn =
[this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el)
{
@ -797,10 +797,10 @@ void DefaultSupportTree::interconnect_pillars()
const Pillar& pillar = m_builder.pillar(el.second); // actual pillar
// Get the max number of neighbors a pillar should connect to
// Get the max number of neighbors a pillar should connect to
unsigned neighbors = m_sm.cfg.pillar_cascade_neighbors;
// connections are already enough for the pillar
// connections are already enough for the pillar
if(pillar.links >= neighbors) return;
double max_d = d * pillar.r_start / m_sm.cfg.head_back_radius_mm;
@ -809,7 +809,7 @@ void DefaultSupportTree::interconnect_pillars()
return distance(e.first, qp) < max_d;
});
// sort the result by distance (have to check if this is needed)
// sort the result by distance (have to check if this is needed)
std::sort(qres.begin(), qres.end(),
[qp](const PointIndexEl& e1, const PointIndexEl& e2){
return distance(e1.first, qp) < distance(e2.first, qp);
@ -821,23 +821,23 @@ void DefaultSupportTree::interconnect_pillars()
auto a = el.second, b = re.second;
// Get unique hash for the given pair (order doesn't matter)
// Get unique hash for the given pair (order doesn't matter)
auto hashval = pairhash(a, b);
// Search for the pair amongst the remembered pairs
// Search for the pair amongst the remembered pairs
if(pairs.find(hashval) != pairs.end()) continue;
const Pillar& neighborpillar = m_builder.pillar(re.second);
// this neighbor is occupied, skip
// this neighbor is occupied, skip
if (neighborpillar.links >= neighbors) continue;
if (neighborpillar.r_start < pillar.r_start) continue;
if(interconnect(pillar, neighborpillar)) {
pairs.insert(hashval);
// If the interconnection length between the two pillars is
// less than 50% of the longer pillar's height, don't count
// If the interconnection length between the two pillars is
// less than 50% of the longer pillar's height, don't count
if(pillar.height < H1 ||
neighborpillar.height / pillar.height > min_height_ratio)
m_builder.increment_links(pillar);
@ -848,30 +848,30 @@ void DefaultSupportTree::interconnect_pillars()
}
// connections are enough for one pillar
// connections are enough for one pillar
if(pillar.links >= neighbors) break;
}
};
// Run the cascade for the pillars in the index
// Run the cascade for the pillars in the index
m_pillar_index.foreach(cascadefn);
// We would be done here if we could allow some pillars to not be
// connected with any neighbors. But this might leave the support tree
// unprintable.
//
// The current solution is to insert additional pillars next to these
// lonely pillars. One or even two additional pillar might get inserted
// depending on the length of the lonely pillar.
// We would be done here if we could allow some pillars to not be
// connected with any neighbors. But this might leave the support tree
// unprintable.
//
// The current solution is to insert additional pillars next to these
// lonely pillars. One or even two additional pillar might get inserted
// depending on the length of the lonely pillar.
size_t pillarcount = m_builder.pillarcount();
// Again, go through all pillars, this time in the whole support tree
// not just the index.
// Again, go through all pillars, this time in the whole support tree
// not just the index.
for(size_t pid = 0; pid < pillarcount; pid++) {
auto pillar = [this, pid]() { return m_builder.pillar(pid); };
// Decide how many additional pillars will be needed:
// Decide how many additional pillars will be needed:
unsigned needpillars = 0;
if (pillar().bridges > m_sm.cfg.max_bridges_on_pillar)
@ -887,17 +887,17 @@ void DefaultSupportTree::interconnect_pillars()
needpillars = std::max(pillar().links, needpillars) - pillar().links;
if (needpillars == 0) continue;
// Search for new pillar locations:
// Search for new pillar locations:
bool found = false;
double alpha = 0; // goes to 2Pi
double r = 2 * m_sm.cfg.base_radius_mm;
Vec3d pillarsp = pillar().startpoint();
// temp value for starting point detection
// temp value for starting point detection
Vec3d sp(pillarsp.x(), pillarsp.y(), pillarsp.z() - r);
// A vector of bool for placement feasbility
// A vector of bool for placement feasbility
std::vector<bool> canplace(needpillars, false);
std::vector<Vec3d> spts(needpillars); // vector of starting points
@ -916,12 +916,12 @@ void DefaultSupportTree::interconnect_pillars()
s.y() += std::sin(a) * r;
spts[n] = s;
// Check the path vertically down
// Check the path vertically down
Vec3d check_from = s + Vec3d{0., 0., pillar().r_start};
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r_start);
Vec3d gndsp{s.x(), s.y(), gnd};
// If the path is clear, check for pillar base collisions
// If the path is clear, check for pillar base collisions
canplace[n] = std::isinf(hr.distance()) &&
std::sqrt(m_sm.emesh.squared_distance(gndsp)) >
min_dist;
@ -930,7 +930,7 @@ void DefaultSupportTree::interconnect_pillars()
found = std::all_of(canplace.begin(), canplace.end(),
[](bool v) { return v; });
// 20 angles will be tried...
// 20 angles will be tried...
alpha += 0.1 * PI;
}

View File

@ -1,7 +1,7 @@
#ifndef LEGACYSUPPORTTREE_HPP
#define LEGACYSUPPORTTREE_HPP
#include "SupportTreeUtils.hpp"
#include "SupportTreeUtilsLegacy.hpp"
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/Execution/ExecutionTBB.hpp>
@ -62,7 +62,6 @@ class DefaultSupportTree {
PtIndices m_iheads; // support points with pinhead
PtIndices m_iheads_onmodel;
PtIndices m_iheadless; // headless support points
std::map<unsigned, AABBMesh::hit_result> m_head_to_ground_scans;
@ -176,9 +175,8 @@ class DefaultSupportTree {
// jp is the starting junction point which needs to be routed down.
// sourcedir is the allowed direction of an optional bridge between the
// jp junction and the final pillar.
bool create_ground_pillar(const Vec3d &jp,
bool create_ground_pillar(const Junction &jp,
const Vec3d &sourcedir,
double radius,
long head_id = SupportTreeNode::ID_UNSET);
void add_pillar_base(long pid)

View File

@ -1,20 +1,25 @@
#include <functional>
#include <optional>
#include <numeric>
#include <unordered_set>
#include <random>
#include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/TriangleMeshSlicer.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/AABBMesh.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/QuadricEdgeCollapse.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp>
#include <libslic3r/Execution/ExecutionSeq.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/MeshBoolean.hpp>
#include <boost/log/trivial.hpp>
#include <openvdb/tools/FastSweeping.h>
#include <libslic3r/MTUtils.hpp>
#include <libslic3r/I18N.hpp>
@ -27,19 +32,17 @@ namespace sla {
struct Interior {
indexed_triangle_set mesh;
openvdb::FloatGrid::Ptr gridptr;
mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor;
VoxelGridPtr gridptr;
double iso_surface = 0.;
double thickness = 0.;
double voxel_scale = 1.;
double full_narrowb = 2.;
void reset_accessor() const // This resets the accessor and its cache
// Not a thread safe call!
{
if (gridptr)
accessor = gridptr->getConstAccessor();
Slic3r::reset_accessor(*gridptr);
}
};
@ -58,46 +61,43 @@ const indexed_triangle_set &get_mesh(const Interior &interior)
return interior.mesh;
}
static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
const JobController &ctl,
double min_thickness,
double voxel_scale,
double closing_dist)
const VoxelGrid &get_grid(const Interior &interior)
{
double offset = voxel_scale * min_thickness;
double D = voxel_scale * closing_dist;
float in_range = 1.1f * float(offset + D);
auto narrowb = 1.;
float out_range = narrowb;
return *interior.gridptr;
}
VoxelGrid &get_grid(Interior &interior)
{
return *interior.gridptr;
}
InteriorPtr generate_interior(const VoxelGrid &vgrid,
const HollowingConfig &hc,
const JobController &ctl)
{
double voxsc = get_voxel_scale(vgrid);
double offset = hc.min_thickness; // world units
double D = hc.closing_distance; // world units
float in_range = 1.1f * float(offset + D); // world units
float out_range = 1.f / voxsc; // world units
auto narrowb = 1.f; // voxel units (voxel count)
if (ctl.stopcondition()) return {};
else ctl.statuscb(0, L("Hollowing"));
auto gridptr = mesh_to_grid(mesh.its, {}, voxel_scale, out_range, in_range);
assert(gridptr);
if (!gridptr) {
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
return {};
}
auto gridptr = dilate_grid(vgrid, out_range, in_range);
if (ctl.stopcondition()) return {};
else ctl.statuscb(30, L("Hollowing"));
double iso_surface = D;
if (D > EPSILON) {
in_range = narrowb;
gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, in_range);
gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb);
constexpr int DilateIterations = 1;
gridptr = openvdb::tools::dilateSdf(
*gridptr, std::ceil(iso_surface),
openvdb::tools::NN_FACE_EDGE_VERTEX, DilateIterations,
openvdb::tools::FastSweepingDomain::SWEEP_GREATER_THAN_ISOVALUE);
gridptr = dilate_grid(*gridptr, 1.1 * std::ceil(iso_surface), 0.f);
out_range = iso_surface;
in_range = narrowb / voxsc;
} else {
iso_surface = -offset;
}
@ -109,68 +109,14 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
InteriorPtr interior = InteriorPtr{new Interior{}};
interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
interior->gridptr = gridptr;
interior->gridptr = std::move(gridptr);
if (ctl.stopcondition()) return {};
else ctl.statuscb(100, L("Hollowing"));
interior->iso_surface = iso_surface;
interior->thickness = offset;
interior->voxel_scale = voxel_scale;
interior->full_narrowb = out_range + in_range;
return interior;
}
InteriorPtr generate_interior(const TriangleMesh & mesh,
const HollowingConfig &hc,
const JobController & ctl)
{
static constexpr double MIN_SAMPLES_IN_WALL = 3.5;
static constexpr double MAX_OVERSAMPL = 8.;
static constexpr double UNIT_VOLUME = 500000; // empiric
// I can't figure out how to increase the grid resolution through openvdb
// API so the model will be scaled up before conversion and the result
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
// scales the whole geometry down, and doesn't increase the number of
// voxels.
//
// First an allowed range for voxel scale is determined from an initial
// range of <MIN_SAMPLES_IN_WALL, MAX_OVERSAMPL>. The final voxel scale is
// then chosen from this range using the 'quality:<0, 1>' parameter.
// The minimum can be lowered if the wall thickness is great enough and
// the maximum is lowered if the model volume very big.
double mesh_vol = its_volume(mesh.its);
double sc_divider = std::max(1.0, (mesh_vol / UNIT_VOLUME));
double min_oversampl = std::max(MIN_SAMPLES_IN_WALL / hc.min_thickness, 1.);
double max_oversampl_scaled = std::max(min_oversampl, MAX_OVERSAMPL / sc_divider);
auto voxel_scale = min_oversampl + (max_oversampl_scaled - min_oversampl) * hc.quality;
BOOST_LOG_TRIVIAL(debug) << "Hollowing: max oversampl will be: " << max_oversampl_scaled;
BOOST_LOG_TRIVIAL(debug) << "Hollowing: voxel scale will be: " << voxel_scale;
BOOST_LOG_TRIVIAL(debug) << "Hollowing: mesh volume is: " << mesh_vol;
InteriorPtr interior = generate_interior_verbose(mesh, ctl,
hc.min_thickness,
voxel_scale,
hc.closing_distance);
if (interior && !interior->mesh.empty()) {
// flip normals back...
swap_normals(interior->mesh);
// simplify mesh lossless
float loss_less_max_error = 2*std::numeric_limits<float>::epsilon();
its_quadric_edge_collapse(interior->mesh, 0U, &loss_less_max_error);
its_compactify_vertices(interior->mesh);
its_merge_vertices(interior->mesh);
// flip normals back...
swap_normals(interior->mesh);
}
interior->full_narrowb = (out_range + in_range) / 2.;
return interior;
}
@ -179,9 +125,9 @@ indexed_triangle_set DrainHole::to_mesh() const
{
auto r = double(radius);
auto h = double(height);
indexed_triangle_set hole = sla::cylinder(r, h, steps);
indexed_triangle_set hole = its_make_cylinder(r, h); //sla::cylinder(r, h, steps);
Eigen::Quaternionf q;
q.setFromTwoVectors(Vec3f{0.f, 0.f, 1.f}, normal);
q.setFromTwoVectors(Vec3f::UnitZ(), normal);
for(auto& p : hole.vertices) p = q * p + pos;
return hole;
@ -208,7 +154,6 @@ bool DrainHole::is_inside(const Vec3f& pt) const
return false;
}
// Given a line s+dir*t, find parameter t of intersections with the hole
// and the normal (points inside the hole). Outputs through out reference,
// returns true if two intersections were found.
@ -331,7 +276,7 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags)
{
InteriorPtr interior = generate_interior(mesh, cfg, JobController{});
InteriorPtr interior = generate_interior(mesh.its, cfg, JobController{});
if (!interior) return;
hollow_mesh(mesh, *interior, flags);
@ -344,7 +289,24 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
if (flags & hfRemoveInsideTriangles && interior.gridptr)
remove_inside_triangles(mesh, interior);
mesh.merge(TriangleMesh{interior.mesh});
indexed_triangle_set interi = interior.mesh;
sla::swap_normals(interi);
TriangleMesh inter{std::move(interi)};
mesh.merge(inter);
}
void hollow_mesh(indexed_triangle_set &mesh, const Interior &interior, int flags)
{
if (mesh.empty() || interior.mesh.empty()) return;
if (flags & hfRemoveInsideTriangles && interior.gridptr)
remove_inside_triangles(mesh, interior);
indexed_triangle_set interi = interior.mesh;
sla::swap_normals(interi);
its_merge(mesh, interi);
}
// Get the distance of p to the interior's zero iso_surface. Interior should
@ -354,13 +316,7 @@ static double get_distance_raw(const Vec3f &p, const Interior &interior)
{
assert(interior.gridptr);
if (!interior.accessor) interior.reset_accessor();
auto v = (p * interior.voxel_scale).cast<double>();
auto grididx = interior.gridptr->transform().worldToIndexCellCentered(
{v.x(), v.y(), v.z()});
return interior.accessor->getValue(grididx) ;
return Slic3r::get_distance_raw(p, *interior.gridptr);
}
struct TriangleBubble { Vec3f center; double R; };
@ -369,7 +325,7 @@ struct TriangleBubble { Vec3f center; double R; };
// triangle is too big to be measured.
static double get_distance(const TriangleBubble &b, const Interior &interior)
{
double R = b.R * interior.voxel_scale;
double R = b.R;
double D = 2. * R;
double Dst = get_distance_raw(b.center, interior);
@ -379,10 +335,16 @@ static double get_distance(const TriangleBubble &b, const Interior &interior)
Dst - interior.iso_surface;
}
double get_distance(const Vec3f &p, const Interior &interior)
inline double get_distance(const Vec3f &p, const Interior &interior)
{
double d = get_distance_raw(p, interior) - interior.iso_surface;
return d / interior.voxel_scale;
return d;
}
template<class T>
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
{
return get_distance(Vec3f(p.template cast<float>()), interior);
}
// A face that can be divided. Stores the indices into the original mesh if its
@ -434,14 +396,14 @@ void divide_triangle(const DivFace &face, Fn &&visitor)
divide_triangle(child2, std::forward<Fn>(visitor));
}
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask)
{
enum TrPos { posInside, posTouch, posOutside };
auto &faces = mesh.its.indices;
auto &vertices = mesh.its.vertices;
auto bb = mesh.bounding_box();
auto &faces = mesh.indices;
auto &vertices = mesh.vertices;
auto bb = bounding_box(mesh); //mesh.bounding_box();
bool use_exclude_mask = faces.size() == exclude_mask.size();
auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) {
@ -477,8 +439,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
// or not.
std::vector<bool> to_remove;
MeshMods(const TriangleMesh &mesh):
to_remove(mesh.its.indices.size(), false) {}
MeshMods(const indexed_triangle_set &mesh):
to_remove(mesh.indices.size(), false) {}
// Number of triangles that need to be removed.
size_t to_remove_cnt() const
@ -500,7 +462,7 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
TriangleBubble bubble{facebb.center().cast<float>(), facebb.radius()};
double D = get_distance(bubble, interior);
double R = bubble.R * interior.voxel_scale;
double R = bubble.R;
if (std::isnan(D)) // The distance cannot be measured, triangle too big
return true;
@ -540,7 +502,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const Vec3i &face = faces[face_idx];
// If the triangle is excluded, we need to keep it.
if (is_excluded(face_idx)) return;
if (is_excluded(face_idx))
return;
std::array<Vec3f, 3> pts = {vertices[face(0)], vertices[face(1)],
vertices[face(2)]};
@ -548,7 +511,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
BoundingBoxf3 facebb{pts.begin(), pts.end()};
// Face is certainly outside the cavity
if (!facebb.intersects(bb)) return;
if (!facebb.intersects(bb))
return;
DivFace df{face, pts, long(face_idx)};
@ -581,8 +545,356 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
faces.swap(new_faces);
new_faces = {};
mesh = TriangleMesh{mesh.its};
// mesh = TriangleMesh{mesh.its};
//FIXME do we want to repair the mesh? Are there duplicate vertices or flipped triangles?
}
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask)
{
remove_inside_triangles(mesh.its, interior, exclude_mask);
}
struct FaceHash {
// A 64 bit number's max hex digits
static constexpr size_t MAX_NUM_CHARS = 16;
// A hash is created for each triangle to be identifiable. The hash uses
// only the triangle's geometric traits, not the index in a particular mesh.
std::unordered_set<std::string> facehash;
// Returns the string in reverse, but that is ok for hashing
static std::array<char, MAX_NUM_CHARS + 1> to_chars(int64_t val)
{
std::array<char, MAX_NUM_CHARS + 1> ret;
static const constexpr char * Conv = "0123456789abcdef";
auto ptr = ret.begin();
auto uval = static_cast<uint64_t>(std::abs(val));
while (uval) {
*ptr = Conv[uval & 0xf];
++ptr;
uval = uval >> 4;
}
if (val < 0) { *ptr = '-'; ++ptr; }
*ptr = '\0'; // C style string ending
return ret;
}
static std::string hash(const Vec<3, int64_t> &v)
{
std::string ret;
ret.reserve(3 * MAX_NUM_CHARS);
for (auto val : v)
ret += to_chars(val).data();
return ret;
}
static std::string facekey(const Vec3i &face, const std::vector<Vec3f> &vertices)
{
// Scale to integer to avoid floating points
std::array<Vec<3, int64_t>, 3> pts = {
scaled<int64_t>(vertices[face(0)]),
scaled<int64_t>(vertices[face(1)]),
scaled<int64_t>(vertices[face(2)])
};
// Get the first two sides of the triangle, do a cross product and move
// that vector to the center of the triangle. This encodes all
// information to identify an identical triangle at the same position.
Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2];
Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
// Return a concatenated string representation of the coordinates
return hash(c);
}
FaceHash (const indexed_triangle_set &its): facehash(its.indices.size())
{
for (Vec3i face : its.indices) {
std::swap(face(0), face(2));
facehash.insert(facekey(face, its.vertices));
}
}
bool find(const std::string &key)
{
auto it = facehash.find(key);
return it != facehash.end();
}
};
static void exclude_neighbors(const Vec3i &face,
std::vector<bool> &mask,
const indexed_triangle_set &its,
const VertexFaceIndex &index,
size_t recursions)
{
for (int i = 0; i < 3; ++i) {
const auto &neighbors_range = index[face(i)];
for (size_t fi_n : neighbors_range) {
mask[fi_n] = true;
if (recursions > 0)
exclude_neighbors(its.indices[fi_n], mask, its, index, recursions - 1);
}
}
}
std::vector<bool> create_exclude_mask(const indexed_triangle_set &its,
const Interior &interior,
const std::vector<DrainHole> &holes)
{
FaceHash interior_hash{sla::get_mesh(interior)};
std::vector<bool> exclude_mask(its.indices.size(), false);
VertexFaceIndex neighbor_index{its};
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
auto &face = its.indices[fi];
if (interior_hash.find(FaceHash::facekey(face, its.vertices))) {
exclude_mask[fi] = true;
continue;
}
if (exclude_mask[fi]) {
exclude_neighbors(face, exclude_mask, its, neighbor_index, 1);
continue;
}
// Lets deal with the holes. All the triangles of a hole and all the
// neighbors of these triangles need to be kept. The neigbors were
// created by CGAL mesh boolean operation that modified the original
// interior inside the input mesh to contain the holes.
Vec3d tr_center = (
its.vertices[face(0)] +
its.vertices[face(1)] +
its.vertices[face(2)]
).cast<double>() / 3.;
// If the center is more than half a mm inside the interior,
// it cannot possibly be part of a hole wall.
if (sla::get_distance(tr_center, interior) < -0.5)
continue;
Vec3f U = its.vertices[face(1)] - its.vertices[face(0)];
Vec3f V = its.vertices[face(2)] - its.vertices[face(0)];
Vec3f C = U.cross(V);
Vec3f face_normal = C.normalized();
for (const sla::DrainHole &dh : holes) {
if (dh.failed) continue;
Vec3d dhpos = dh.pos.cast<double>();
Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height;
Linef3 holeaxis{dhpos, dhend};
double D_hole_center = line_alg::distance_to(holeaxis, tr_center);
double D_hole = std::abs(D_hole_center - dh.radius);
float dot = dh.normal.dot(face_normal);
// Empiric tolerances for center distance and normals angle.
// For triangles that are part of a hole wall the angle of
// triangle normal and the hole axis is around 90 degrees,
// so the dot product is around zero.
double D_tol = dh.radius / sla::DrainHole::steps;
float normal_angle_tol = 1.f / sla::DrainHole::steps;
if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) {
exclude_mask[fi] = true;
exclude_neighbors(face, exclude_mask, its, neighbor_index, 1);
}
}
}
return exclude_mask;
}
DrainHoles transformed_drainhole_points(const ModelObject &mo,
const Transform3d &trafo)
{
auto pts = mo.sla_drain_holes;
// const Transform3d& vol_trafo = mo.volumes.front()->get_transformation().get_matrix();
const Geometry::Transformation trans(trafo /** vol_trafo*/);
const Transform3d& tr = trans.get_matrix();
for (sla::DrainHole &hl : pts) {
Vec3d pos = hl.pos.cast<double>();
Vec3d nrm = hl.normal.cast<double>();
pos = tr * pos;
nrm = tr * nrm - tr.translation();
// Now shift the hole a bit above the object and make it deeper to
// compensate for it. This is to avoid problems when the hole is placed
// on (nearly) flat surface.
pos -= nrm.normalized() * sla::HoleStickOutLength;
hl.pos = pos.cast<float>();
hl.normal = nrm.cast<float>();
hl.height += sla::HoleStickOutLength;
}
return pts;
}
double get_voxel_scale(double mesh_volume, const HollowingConfig &hc)
{
static constexpr double MIN_SAMPLES_IN_WALL = 3.5;
static constexpr double MAX_OVERSAMPL = 8.;
static constexpr double UNIT_VOLUME = 500000; // empiric
// I can't figure out how to increase the grid resolution through openvdb
// API so the model will be scaled up before conversion and the result
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
// scales the whole geometry down, and doesn't increase the number of
// voxels.
//
// First an allowed range for voxel scale is determined from an initial
// range of <MIN_SAMPLES_IN_WALL, MAX_OVERSAMPL>. The final voxel scale is
// then chosen from this range using the 'quality:<0, 1>' parameter.
// The minimum can be lowered if the wall thickness is great enough and
// the maximum is lowered if the model volume very big.
double sc_divider = std::max(1.0, (mesh_volume / UNIT_VOLUME));
double min_oversampl = std::max(MIN_SAMPLES_IN_WALL / hc.min_thickness, 1.);
double max_oversampl_scaled = std::max(min_oversampl, MAX_OVERSAMPL / sc_divider);
auto voxel_scale = min_oversampl + (max_oversampl_scaled - min_oversampl) * hc.quality;
BOOST_LOG_TRIVIAL(debug) << "Hollowing: max oversampl will be: " << max_oversampl_scaled;
BOOST_LOG_TRIVIAL(debug) << "Hollowing: voxel scale will be: " << voxel_scale;
BOOST_LOG_TRIVIAL(debug) << "Hollowing: mesh volume is: " << mesh_volume;
return voxel_scale;
}
// The same as its_compactify_vertices, but returns a new mesh, doesn't touch
// the original
static indexed_triangle_set
remove_unconnected_vertices(const indexed_triangle_set &its)
{
if (its.indices.empty()) {};
indexed_triangle_set M;
std::vector<int> vtransl(its.vertices.size(), -1);
int vcnt = 0;
for (auto &f : its.indices) {
for (int i = 0; i < 3; ++i)
if (vtransl[size_t(f(i))] < 0) {
M.vertices.emplace_back(its.vertices[size_t(f(i))]);
vtransl[size_t(f(i))] = vcnt++;
}
std::array<int, 3> new_f = {
vtransl[size_t(f(0))],
vtransl[size_t(f(1))],
vtransl[size_t(f(2))]
};
M.indices.emplace_back(new_f[0], new_f[1], new_f[2]);
}
return M;
}
int hollow_mesh_and_drill(indexed_triangle_set &hollowed_mesh,
const Interior &interior,
const DrainHoles &drainholes,
std::function<void(size_t)> on_hole_fail)
{
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
hollowed_mesh.vertices,
hollowed_mesh.indices
);
std::uniform_real_distribution<float> dist(0., float(EPSILON));
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {});
indexed_triangle_set part_to_drill = hollowed_mesh;
std::mt19937 m_rng{std::random_device{}()};
for (size_t i = 0; i < drainholes.size(); ++i) {
sla::DrainHole holept = drainholes[i];
holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
holept.normal.normalize();
holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
indexed_triangle_set m = holept.to_mesh();
part_to_drill.indices.clear();
auto bb = bounding_box(m);
Eigen::AlignedBox<float, 3> ebb{bb.min.cast<float>(),
bb.max.cast<float>()};
AABBTreeIndirect::traverse(
tree,
AABBTreeIndirect::intersecting(ebb),
[&part_to_drill, &hollowed_mesh](const auto& node)
{
part_to_drill.indices.emplace_back(hollowed_mesh.indices[node.idx]);
// continue traversal
return true;
});
auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal(
remove_unconnected_vertices(part_to_drill));
if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) {
on_hole_fail(i);
continue;
}
auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole);
}
auto ret = static_cast<int>(HollowMeshResult::Ok);
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) {
ret |= static_cast<int>(HollowMeshResult::DrillingFailed);
}
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
if (!MeshBoolean::cgal::does_bound_a_volume(*hollowed_mesh_cgal)) {
ret |= static_cast<int>(HollowMeshResult::FaultyMesh);
}
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)
&& !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) {
ret |= static_cast<int>(HollowMeshResult::FaultyHoles);
}
// Don't even bother
if (ret & static_cast<int>(HollowMeshResult::DrillingFailed))
return ret;
try {
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal))
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh =
MeshBoolean::cgal::cgal_to_indexed_triangle_set(*hollowed_mesh_cgal);
std::vector<bool> exclude_mask =
create_exclude_mask(hollowed_mesh, interior, drainholes);
sla::remove_inside_triangles(hollowed_mesh, interior, exclude_mask);
} catch (const Slic3r::RuntimeError &) {
ret |= static_cast<int>(HollowMeshResult::DrillingFailed);
}
return ret;
}
}} // namespace Slic3r::sla

View File

@ -3,10 +3,14 @@
#include <memory>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/SLA/JobController.hpp>
#include <libslic3r/CSGMesh/VoxelizeCSGMesh.hpp>
namespace Slic3r {
class ModelObject;
namespace sla {
struct HollowingConfig
@ -28,6 +32,9 @@ using InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>;
indexed_triangle_set & get_mesh(Interior &interior);
const indexed_triangle_set &get_mesh(const Interior &interior);
const VoxelGrid & get_grid(const Interior &interior);
VoxelGrid &get_grid(Interior &interior);
struct DrainHole
{
Vec3f pos;
@ -46,18 +53,18 @@ struct DrainHole
DrainHole(const DrainHole& rhs) :
DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {}
bool operator==(const DrainHole &sp) const;
bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); }
bool is_inside(const Vec3f& pt) const;
bool get_intersections(const Vec3f& s, const Vec3f& dir,
std::array<std::pair<float, Vec3d>, 2>& out) const;
indexed_triangle_set to_mesh() const;
template<class Archive> inline void serialize(Archive &ar)
{
ar(pos, normal, radius, height, failed);
@ -70,26 +77,116 @@ using DrainHoles = std::vector<DrainHole>;
constexpr float HoleStickOutLength = 1.f;
InteriorPtr generate_interior(const TriangleMesh &mesh,
double get_voxel_scale(double mesh_volume, const HollowingConfig &hc);
InteriorPtr generate_interior(const VoxelGrid &mesh,
const HollowingConfig & = {},
const JobController &ctl = {});
inline InteriorPtr generate_interior(const indexed_triangle_set &mesh,
const HollowingConfig &hc = {},
const JobController &ctl = {})
{
auto voxel_scale = get_voxel_scale(its_volume(mesh), hc);
auto statusfn = [&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); };
auto grid = mesh_to_grid(mesh, MeshToGridParams{}
.voxel_scale(voxel_scale)
.exterior_bandwidth(3.f)
.interior_bandwidth(3.f)
.statusfn(statusfn));
if (!grid || (ctl.stopcondition && ctl.stopcondition()))
return {};
// if (its_is_splittable(mesh))
grid = redistance_grid(*grid, 0.0f, 3.f, 3.f);
return grid ? generate_interior(*grid, hc, ctl) : InteriorPtr{};
}
template<class Cont> double csgmesh_positive_maxvolume(const Cont &csg)
{
double mesh_vol = 0;
bool skip = false;
for (const auto &m : csg) {
auto op = csg::get_operation(m);
auto stackop = csg::get_stack_operation(m);
if (stackop == csg::CSGStackOp::Push && op != csg::CSGType::Union)
skip = true;
if (!skip && csg::get_mesh(m) && op == csg::CSGType::Union)
mesh_vol = std::max(mesh_vol,
double(its_volume(*(csg::get_mesh(m)))));
if (stackop == csg::CSGStackOp::Pop)
skip = false;
}
return mesh_vol;
}
template<class It>
InteriorPtr generate_interior(const Range<It> &csgparts,
const HollowingConfig &hc = {},
const JobController &ctl = {})
{
double mesh_vol = csgmesh_positive_maxvolume(csgparts);
double voxsc = get_voxel_scale(mesh_vol, hc);
auto params = csg::VoxelizeParams{}
.voxel_scale(voxsc)
.exterior_bandwidth(3.f)
.interior_bandwidth(3.f)
.statusfn([&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); });
auto ptr = csg::voxelize_csgmesh(csgparts, params);
if (!ptr || (ctl.stopcondition && ctl.stopcondition()))
return {};
// TODO: figure out issues without the redistance
// if (csgparts.size() > 1 || its_is_splittable(*csg::get_mesh(*csgparts.begin())))
ptr = redistance_grid(*ptr, 0.0f, 3.f, 3.f);
return ptr ? generate_interior(*ptr, hc, ctl) : InteriorPtr{};
}
// Will do the hollowing
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0);
// Hollowing prepared in "interior", merge with original mesh
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0);
// Will do the hollowing
void hollow_mesh(indexed_triangle_set &mesh, const HollowingConfig &cfg, int flags = 0);
// Hollowing prepared in "interior", merge with original mesh
void hollow_mesh(indexed_triangle_set &mesh, const Interior &interior, int flags = 0);
enum class HollowMeshResult {
Ok = 0,
FaultyMesh = 1,
FaultyHoles = 2,
DrillingFailed = 4
};
// Return HollowMeshResult codes OR-ed.
int hollow_mesh_and_drill(
indexed_triangle_set &mesh,
const Interior& interior,
const DrainHoles &holes,
std::function<void(size_t)> on_hole_fail = [](size_t){});
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask = {});
double get_distance(const Vec3f &p, const Interior &interior);
void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask = {});
template<class T>
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
{
return get_distance(Vec3f(p.template cast<float>()), interior);
}
sla::DrainHoles transformed_drainhole_points(const ModelObject &mo,
const Transform3d &trafo);
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
const std::vector<float> &slicegrid,
@ -103,6 +200,16 @@ inline void swap_normals(indexed_triangle_set &its)
std::swap(face(0), face(2));
}
// Create exclude mask for triangle removal inside hollowed interiors.
// This is necessary when the interior is already part of the mesh which was
// drilled using CGAL mesh boolean operation. Excluded will be the triangles
// originally part of the interior mesh and triangles that make up the drilled
// hole walls.
std::vector<bool> create_exclude_mask(
const indexed_triangle_set &its,
const sla::Interior &interior,
const std::vector<sla::DrainHole> &holes);
} // namespace sla
} // namespace Slic3r

View File

@ -3,7 +3,11 @@
#include <libslic3r/Point.hpp>
namespace Slic3r { namespace sla {
namespace Slic3r {
class ModelObject;
namespace sla {
// An enum to keep track of where the current points on the ModelObject came from.
enum class PointsStatus {
@ -62,6 +66,9 @@ struct SupportPoint
using SupportPoints = std::vector<SupportPoint>;
SupportPoints transformed_support_points(const ModelObject &mo,
const Transform3d &trafo);
}} // namespace Slic3r::sla
#endif // SUPPORTPOINT_HPP

View File

@ -661,5 +661,17 @@ void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const s
}
#endif
SupportPoints transformed_support_points(const ModelObject &mo,
const Transform3d &trafo)
{
auto spts = mo.sla_support_points;
Transform3f tr = trafo.cast<float>();
for (sla::SupportPoint& suppt : spts) {
suppt.pos = tr * suppt.pos;
}
return spts;
}
} // namespace sla
} // namespace Slic3r

View File

@ -18,6 +18,8 @@
#include <boost/log/trivial.hpp>
#include <libslic3r/I18N.hpp>
#include <libnest2d/tools/benchmark.h>
//! macro used to mark string used at localization,
//! return same string
#define L(s) Slic3r::I18N::translate(s)
@ -30,6 +32,9 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
auto builder = make_unique<SupportTreeBuilder>(ctl);
if (sm.cfg.enabled) {
Benchmark bench;
bench.start();
switch (sm.cfg.tree_type) {
case SupportTreeType::Default: {
create_default_tree(*builder, sm);
@ -39,9 +44,18 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm,
create_branching_tree(*builder, sm);
break;
}
case SupportTreeType::Organic: {
// TODO
}
default:;
}
bench.stop();
BOOST_LOG_TRIVIAL(info) << "Support tree creation took: "
<< bench.getElapsedSec()
<< " seconds";
builder->merge_and_cleanup(); // clean metadata, leave only the meshes.
}

View File

@ -50,7 +50,7 @@ struct SupportTreeConfig
// when bridges and pillars are merged. The resulting pillar should be a bit
// thicker than the ones merging into it. How much thicker? I don't know
// but it will be derived from this value.
double pillar_widening_factor = .05;
double pillar_widening_factor = .5;
// Radius in mm of the pillar base.
double base_radius_mm = 2.0;
@ -76,12 +76,20 @@ struct SupportTreeConfig
double pillar_base_safety_distance_mm = 0.5;
unsigned max_bridges_on_pillar = 3;
double max_weight_on_model_support = 10.f;
double head_fullwidth() const {
return 2 * head_front_radius_mm + head_width_mm +
2 * head_back_radius_mm - head_penetration_mm;
}
double safety_distance() const { return safety_distance_mm; }
double safety_distance(double r) const
{
return std::min(safety_distance_mm, r * safety_distance_mm / head_back_radius_mm);
}
// /////////////////////////////////////////////////////////////////////////
// Compile time configuration values (candidates for runtime)
// /////////////////////////////////////////////////////////////////////////
@ -89,13 +97,15 @@ struct SupportTreeConfig
// The max Z angle for a normal at which it will get completely ignored.
static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
// The shortest distance of any support structure from the model surface
// The safety gap between a support structure and model body. For support
// struts smaller than head_back_radius, the safety distance is scaled
// down accordingly. see method safety_distance()
static const double constexpr safety_distance_mm = 0.5;
static const double constexpr max_solo_pillar_height_mm = 15.0;
static const double constexpr max_dual_pillar_height_mm = 35.0;
static const double constexpr optimizer_rel_score_diff = 1e-6;
static const unsigned constexpr optimizer_max_iterations = 1000;
static const double constexpr optimizer_rel_score_diff = 1e-10;
static const unsigned constexpr optimizer_max_iterations = 2000;
static const unsigned constexpr pillar_cascade_neighbors = 3;
};
@ -108,6 +118,7 @@ struct SupportableMesh
SupportPoints pts;
SupportTreeConfig cfg;
PadConfig pad_cfg;
double zoffset = 0.;
explicit SupportableMesh(const indexed_triangle_set &trmsh,
const SupportPoints &sp,
@ -115,16 +126,16 @@ struct SupportableMesh
: emesh{trmsh}, pts{sp}, cfg{c}
{}
explicit SupportableMesh(const AABBMesh &em,
const SupportPoints &sp,
const SupportTreeConfig &c)
: emesh{em}, pts{sp}, cfg{c}
{}
// explicit SupportableMesh(const AABBMesh &em,
// const SupportPoints &sp,
// const SupportTreeConfig &c)
// : emesh{em}, pts{sp}, cfg{c}
// {}
};
inline double ground_level(const SupportableMesh &sm)
{
double lvl = sm.emesh.ground_level() -
double lvl = sm.zoffset -
!bool(sm.pad_cfg.embed_object) * sm.cfg.enabled * sm.cfg.object_elevation_mm +
bool(sm.pad_cfg.embed_object) * sm.pad_cfg.wall_thickness_mm;

View File

@ -67,25 +67,32 @@ struct SupportTreeNode
long id = ID_UNSET; // For identification withing a tree.
};
// A junction connecting bridges and pillars
struct Junction: public SupportTreeNode {
double r = 1;
Vec3d pos;
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
};
// A pinhead originating from a support point
struct Head: public SupportTreeNode {
Vec3d dir = DOWN;
Vec3d pos = {0, 0, 0};
double r_back_mm = 1;
double r_pin_mm = 0.5;
double width_mm = 2;
double penetration_mm = 0.5;
// If there is a pillar connecting to this head, then the id will be set.
long pillar_id = ID_UNSET;
long bridge_id = ID_UNSET;
inline void invalidate() { id = ID_UNSET; }
inline bool is_valid() const { return id >= 0; }
Head(double r_big_mm,
double r_small_mm,
double length_mm,
@ -103,21 +110,21 @@ struct Head: public SupportTreeNode {
{
return real_width() - penetration_mm;
}
inline Junction junction() const
{
Junction j{pos + (fullwidth() - r_back_mm) * dir, r_back_mm};
j.id = -this->id; // Remember that this junction is from a head
return j;
}
inline Vec3d junction_point() const
{
return pos + (fullwidth() - r_back_mm) * dir;
return junction().pos;
}
};
// A junction connecting bridges and pillars
struct Junction: public SupportTreeNode {
double r = 1;
Vec3d pos;
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
};
// A straight pillar. Only has an endpoint and a height. No explicit starting
// point is given, as it would allow the pillar to be angled.
// Some connection info with other primitives can also be tracked.
@ -189,6 +196,10 @@ struct DiffBridge: public Bridge {
DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
: Bridge{p_s, p_e, r_s}, end_r{r_e}
{}
DiffBridge(const Junction &j_s, const Junction &j_e)
: Bridge{j_s.pos, j_e.pos, j_s.r}, end_r{j_e.r}
{}
};
// This class will hold the support tree parts (not meshes, but logical parts)

View File

@ -165,7 +165,8 @@ indexed_triangle_set halfcone(double baseheight,
{
assert(steps > 0);
if (baseheight <= 0 || steps <= 0) return {};
if (baseheight <= 0 || steps <= 0 || (r_bottom <= 0. && r_top <= 0.))
return {};
indexed_triangle_set base;

View File

@ -5,7 +5,7 @@
namespace Slic3r { namespace sla {
enum class SupportTreeType { Default, Branching };
enum class SupportTreeType { Default, Branching, Organic };
enum class PillarConnectionMode { zigzag, cross, dynamic };
}} // namespace Slic3r::sla

View File

@ -6,10 +6,13 @@
#include <libslic3r/Execution/Execution.hpp>
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
#include <libslic3r/MeshNormals.hpp>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <boost/variant.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace sla {
@ -23,36 +26,12 @@ using Slic3r::opt::AlgNLoptGenetic;
using Slic3r::Geometry::dir_to_spheric;
using Slic3r::Geometry::spheric_to_dir;
// Helper function for pillar interconnection where pairs of already connected
// pillars should be checked for not to be processed again. This can be done
// in constant time with a set of hash values uniquely representing a pair of
// integers. The order of numbers within the pair should not matter, it has
// the same unique hash. The hash value has to have twice as many bits as the
// arguments need. If the same integral type is used for args and return val,
// make sure the arguments use only the half of the type's bit depth.
template<class I, class DoubleI = IntegerOnly<I>>
IntegerOnly<DoubleI> pairhash(I a, I b)
{
using std::ceil; using std::log2; using std::max; using std::min;
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
I g = min(a, b), l = max(a, b);
// Assume the hash will fit into the output variable
assert((g ? (ceil(log2(g))) : 0) <= shift);
assert((l ? (ceil(log2(l))) : 0) <= shift);
return (DoubleI(g) << shift) + l;
}
// Give points on a 3D ring with given center, radius and orientation
// method based on:
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
template<size_t N>
class PointRing {
std::array<double, N> m_phis;
std::array<double, N - 1> m_phis;
// Two vectors that will be perpendicular to each other and to the
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
@ -72,7 +51,7 @@ class PointRing {
public:
PointRing(const Vec3d &n) : m_phis{linspace_array<N>(0., 2 * PI)}
PointRing(const Vec3d &n) : m_phis{linspace_array<N - 1>(0., 2 * PI)}
{
// We have to address the case when the direction vector v (same as
// dir) is coincident with one of the world axes. In this case two of
@ -91,7 +70,10 @@ public:
Vec3d get(size_t idx, const Vec3d &src, double r) const
{
double phi = m_phis[idx];
if (idx == 0)
return src;
double phi = m_phis[idx - 1];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
@ -131,31 +113,40 @@ inline StopCriteria get_criteria(const SupportTreeConfig &cfg)
// A simple sphere with a center and a radius
struct Ball { Vec3d p; double R; };
struct Beam { // Defines a set of rays displaced along a cone's surface
static constexpr size_t SAMPLES = 8;
template<size_t Samples = 8>
struct Beam_ { // Defines a set of rays displaced along a cone's surface
static constexpr size_t SAMPLES = Samples;
Vec3d src;
Vec3d dir;
Vec3d src;
Vec3d dir;
double r1;
double r2; // radius of the beam 1 unit further from src in dir direction
Beam(const Vec3d &s, const Vec3d &d, double R1, double R2):
src{s}, dir{d}, r1{R1}, r2{R2} {};
Beam_(const Vec3d &s, const Vec3d &d, double R1, double R2)
: src{s}, dir{d}, r1{R1}, r2{R2} {};
Beam(const Ball &src_ball, const Ball &dst_ball):
src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R}
Beam_(const Ball &src_ball, const Ball &dst_ball)
: src{src_ball.p}, dir{dirv(src_ball.p, dst_ball.p)}, r1{src_ball.R}
{
r2 = src_ball.R +
(dst_ball.R - src_ball.R) / distance(src_ball.p, dst_ball.p);
r2 = src_ball.R;
auto d = distance(src_ball.p, dst_ball.p);
if (d > EPSILON)
r2 += (dst_ball.R - src_ball.R) / d;
}
Beam(const Vec3d &s, const Vec3d &d, double R)
Beam_(const Vec3d &s, const Vec3d &d, double R)
: src{s}, dir{d}, r1{R}, r2{R}
{}
};
template<class Ex>
Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
using Beam = Beam_<>;
template<class Ex, size_t RayCount = Beam::SAMPLES>
Hit beam_mesh_hit(Ex policy,
const AABBMesh &mesh,
const Beam_<RayCount> &beam,
double sd)
{
Vec3d src = beam.src;
Vec3d dst = src + beam.dir;
@ -164,19 +155,19 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
Vec3d D = (dst - src);
Vec3d dir = D.normalized();
PointRing<Beam::SAMPLES> ring{dir};
PointRing<RayCount> ring{dir};
using Hit = AABBMesh::hit_result;
// Hit results
std::array<Hit, Beam::SAMPLES> hits;
std::array<Hit, RayCount> hits;
execution::for_each(
ex, size_t(0), hits.size(),
policy, size_t(0), hits.size(),
[&mesh, r_src, r_dst, src, dst, &ring, dir, sd, &hits](size_t i) {
Hit &hit = hits[i];
// Point on the circle on the pin sphere
// Point on the circle on the pin sphere
Vec3d p_src = ring.get(i, src, r_src + sd);
Vec3d p_dst = ring.get(i, dst, r_dst + sd);
Vec3d raydir = (p_dst - p_src).normalized();
@ -193,7 +184,7 @@ Hit beam_mesh_hit(Ex ex, const AABBMesh &mesh, const Beam &beam, double sd)
}
} else
hit = hr;
}, std::min(execution::max_concurrency(ex), Beam::SAMPLES));
}, std::min(execution::max_concurrency(policy), RayCount));
return min_hit(hits.begin(), hits.end());
}
@ -208,7 +199,10 @@ Hit pinhead_mesh_hit(Ex ex,
double width,
double sd)
{
static const size_t SAMPLES = 8;
// Support tree generation speed depends heavily on this value. 8 is almost
// ok, but to prevent rare cases of collision, 16 is necessary, which makes
// the algorithm run about 60% longer.
static const size_t SAMPLES = 16;
// Move away slightly from the touching point to avoid raycasting on the
// inner surface of the mesh.
@ -283,199 +277,12 @@ Hit pinhead_mesh_hit(Ex ex,
template<class Ex>
Hit pinhead_mesh_hit(Ex ex,
const AABBMesh &mesh,
const Head &head,
double safety_d)
const AABBMesh &mesh,
const Head &head,
double safety_d)
{
return pinhead_mesh_hit(ex, mesh, head.pos, head.dir, head.r_pin_mm,
head.r_back_mm, head.width_mm, safety_d);
}
template<class Ex>
std::optional<DiffBridge> search_widening_path(Ex policy,
const SupportableMesh &sm,
const Vec3d &jp,
const Vec3d &dir,
double radius,
double new_radius)
{
double w = radius + 2 * sm.cfg.head_back_radius_mm;
double stopval = w + jp.z() - ground_level(sm);
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
auto [polar, azimuth] = dir_to_spheric(dir);
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
auto &[plr, azm, t] = input;
auto d = spheric_to_dir(plr, azm).normalized();
auto sd = new_radius * sm.cfg.safety_distance_mm /
sm.cfg.head_back_radius_mm;
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
new_radius, t, sd)
.distance();
Beam beam{jp + t * d, d, new_radius};
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
if (ret > t && std::isinf(down))
ret += jp.z() - ground_level(sm);
return ret;
},
initvals({polar, azimuth, w}), // start with what we have
bounds({
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
{-PI, PI}, // azimuth can be a full search
{radius + sm.cfg.head_back_radius_mm,
fallback_ratio * sm.cfg.max_bridge_length_mm}
}));
if (oresult.score >= stopval) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
double t = std::get<2>(oresult.optimum);
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
}
return {};
}
// This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode.
// 'pinhead_junctionpt' is the starting junction point which needs to be
// routed down. sourcedir is the allowed direction of an optional bridge
// between the jp junction and the final pillar.
template<class Ex>
std::pair<bool, long> create_ground_pillar(
Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Vec3d &pinhead_junctionpt,
const Vec3d &sourcedir,
double radius,
double end_radius,
long head_id = SupportTreeNode::ID_UNSET)
{
Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir;
long pillar_id = SupportTreeNode::ID_UNSET;
bool can_add_base = false, non_head = false;
double gndlvl = 0.; // The Z level where pedestals should be
double jp_gnd = 0.; // The lowest Z where a junction center can be
double gap_dist = 0.; // The gap distance between the model and the pad
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
(bool base_en = true)
{
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
gndlvl = ground_level(sm);
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
};
eval_limits();
// We are dealing with a mini pillar that's potentially too long
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
{
std::optional<DiffBridge> diffbr =
search_widening_path(policy, sm, jp, dir, radius,
sm.cfg.head_back_radius_mm);
if (diffbr && diffbr->endp.z() > jp_gnd) {
auto &br = builder.add_diffbridge(*diffbr);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
endp = diffbr->endp;
radius = diffbr->end_r;
builder.add_junction(endp, radius);
non_head = true;
dir = diffbr->get_dir();
eval_limits();
} else return {false, pillar_id};
}
if (sm.cfg.object_elevation_mm < EPSILON)
{
// get a suitable direction for the corrector bridge. It is the
// original sourcedir's azimuth but the polar angle is saturated to the
// configured bridge slope.
auto [polar, azimuth] = dir_to_spheric(dir);
polar = PI - sm.cfg.bridge_slope;
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
t = 0.;
double zd = endp.z() - jp_gnd;
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
Vec3d nexp = endp;
double dlast = 0.;
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
t < tmax)
{
t += radius;
nexp = endp + t * d;
}
if (dlast < gap_dist && can_add_base) {
nexp = endp;
t = 0.;
can_add_base = false;
eval_limits(can_add_base);
zd = endp.z() - jp_gnd;
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
t += radius;
nexp = endp + t * d;
}
}
// Could not find a path to avoid the pad gap
if (dlast < gap_dist) return {false, pillar_id};
if (t > 0.) { // Need to make additional bridge
const Bridge& br = builder.add_bridge(endp, nexp, radius);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
builder.add_junction(nexp, radius);
endp = nexp;
non_head = true;
}
}
Vec3d gp = to_floor(endp);
double h = endp.z() - gp.z();
pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) :
builder.add_pillar(gp, h, radius, end_radius);
if (can_add_base)
builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm,
sm.cfg.base_radius_mm);
return {true, pillar_id};
head.r_back_mm, head.width_mm, safety_d);
}
inline double distance(const SupportPoint &a, const SupportPoint &b)
@ -530,13 +337,13 @@ bool optimize_pinhead_placement(Ex policy,
double back_r = head.r_back_mm;
// skip if the tilt is not sane
// skip if the tilt is not sane
if (polar < PI - m.cfg.normal_cutoff_angle) return false;
// We saturate the polar angle to 3pi/4
// We saturate the polar angle to 3pi/4
polar = std::max(polar, PI - m.cfg.bridge_slope);
// save the head (pinpoint) position
// save the head (pinpoint) position
Vec3d hp = head.pos;
double lmin = m.cfg.head_width_mm, lmax = lmin;
@ -545,28 +352,26 @@ bool optimize_pinhead_placement(Ex policy,
lmin = 0., lmax = m.cfg.head_penetration_mm;
}
// The distance needed for a pinhead to not collide with model.
// The distance needed for a pinhead to not collide with model.
double w = lmin + 2 * back_r + 2 * m.cfg.head_front_radius_mm -
m.cfg.head_penetration_mm;
double pin_r = head.r_pin_mm;
// Reassemble the now corrected normal
// Reassemble the now corrected normal
auto nn = spheric_to_dir(polar, azimuth).normalized();
double sd = back_r * m.cfg.safety_distance_mm /
m.cfg.head_back_radius_mm;
double sd = m.cfg.safety_distance(back_r);
// check available distance
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w,
sd);
// check available distance
Hit t = pinhead_mesh_hit(policy, m.emesh, hp, nn, pin_r, back_r, w, sd);
if (t.distance() < w) {
// Let's try to optimize this angle, there might be a
// viable normal that doesn't collide with the model
// geometry and its very close to the default.
Optimizer<AlgNLoptGenetic> solver(get_criteria(m.cfg).stop_score(w).max_iterations(100));
Optimizer<opt::AlgNLoptMLSL_Subplx> solver(get_criteria(m.cfg).stop_score(w).max_iterations(100));
solver.seed(0); // we want deterministic behavior
auto oresult = solver.to_max().optimize(
@ -627,7 +432,7 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
};
if (optimize_pinhead_placement(policy, sm, head)) {
head.id = suppt_idx;
head.id = long(suppt_idx);
return head;
}
@ -635,81 +440,335 @@ std::optional<Head> calculate_pinhead_placement(Ex policy,
return {};
}
template<class Ex>
std::pair<bool, long> connect_to_ground(Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Junction &j,
const Vec3d &dir,
double end_r)
struct GroundConnection {
// Currently, a ground connection will contain at most 2 additional junctions
// which will not require any allocations. If I come up with an algo that
// can produce a route to ground with more junctions, this design will be
// able to handle that.
static constexpr size_t MaxExpectedJunctions = 3;
boost::container::small_vector<Junction, MaxExpectedJunctions> path;
std::optional<Pedestal> pillar_base;
operator bool() const { return pillar_base.has_value() && !path.empty(); }
};
inline long build_ground_connection(SupportTreeBuilder &builder,
const SupportableMesh &sm,
const GroundConnection &conn)
{
auto hjp = j.pos;
double r = j.r;
auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
long ret = SupportTreeNode::ID_UNSET;
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
double d = 0, tdown = 0;
t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
if (!conn)
return ret;
while (d < t &&
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
Beam{hjp + d * dir, DOWN, r, r2}, sd)
.distance())) {
d += r;
auto it = conn.path.begin();
auto itnx = std::next(it);
while (itnx != conn.path.end()) {
builder.add_diffbridge(*it, *itnx);
builder.add_junction(*itnx);
++it; ++itnx;
}
if(!std::isinf(tdown))
return {false, SupportTreeNode::ID_UNSET};
auto gp = conn.path.back().pos;
gp.z() = ground_level(sm);
double h = conn.path.back().pos.z() - gp.z();
Vec3d endp = hjp + d * dir;
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r);
if (conn.pillar_base->r_top < sm.cfg.head_back_radius_mm) {
h += sm.pad_cfg.wall_thickness_mm;
gp.z() -= sm.pad_cfg.wall_thickness_mm;
}
if (ret.second >= 0) {
builder.add_bridge(hjp, endp, r);
builder.add_junction(endp, r);
// TODO: does not work yet
// if (conn.path.back().id < 0) {
// // this is a head
// long head_id = std::abs(conn.path.back().id);
// ret = builder.add_pillar(head_id, h);
// } else
ret = builder.add_pillar(gp, h, conn.path.back().r, conn.pillar_base->r_top);
if (conn.pillar_base->r_top >= sm.cfg.head_back_radius_mm)
builder.add_pillar_base(ret, conn.pillar_base->height, conn.pillar_base->r_bottom);
return ret;
}
template<class Fn>
constexpr bool IsWideningFn = std::is_invocable_r_v</*retval*/ double,
Fn,
Ball /*source*/,
Vec3d /*dir*/,
double /*length*/>;
// A widening function can determine how many ray samples should a beam contain
// (see in beam_mesh_hit)
template<class WFn> struct BeamSamples { static constexpr size_t Value = 8; };
template<class WFn> constexpr size_t BeamSamplesV = BeamSamples<remove_cvref_t<WFn>>::Value;
// To use with check_ground_route, full will check the bridge and the pillar,
// PillarOnly checks only the pillar for collisions.
enum class GroundRouteCheck { Full, PillarOnly };
// Returns the collision point with mesh if there is a collision or a ground point,
// given a source point with a direction of a potential avoidance bridge and
// a bridge length.
template<class Ex, class WideningFn,
class = std::enable_if_t<IsWideningFn<WideningFn>> >
Vec3d check_ground_route(
Ex policy,
const SupportableMesh &sm,
const Junction &source, // source location
const Vec3d &dir, // direction of the bridge from the source
double bridge_len, // lenght of the avoidance bridge
WideningFn &&wideningfn, // Widening strategy
GroundRouteCheck type = GroundRouteCheck::Full
)
{
static const constexpr auto Samples = BeamSamplesV<WideningFn>;
Vec3d ret;
const auto sd = sm.cfg.safety_distance(source.r);
const auto gndlvl = ground_level(sm);
// Intersection of the suggested bridge with ground plane. If the bridge
// spans below ground, stop it at ground level.
double t = (gndlvl - source.pos.z()) / dir.z();
bridge_len = std::min(t, bridge_len);
Vec3d bridge_end = source.pos + bridge_len * dir;
double down_l = bridge_end.z() - gndlvl;
double bridge_r = wideningfn(Ball{source.pos, source.r}, dir, bridge_len);
double brhit_dist = 0.;
if (bridge_len > EPSILON && type == GroundRouteCheck::Full) {
// beam_mesh_hit with a zero lenght bridge is invalid
Beam_<Samples> bridgebeam{Ball{source.pos, source.r},
Ball{bridge_end, bridge_r}};
auto brhit = beam_mesh_hit(policy, sm.emesh, bridgebeam, sd);
brhit_dist = brhit.distance();
} else {
brhit_dist = bridge_len;
}
if (brhit_dist < bridge_len) {
ret = (source.pos + brhit_dist * dir);
} else if (down_l > 0.) {
// check if pillar can be placed below
auto gp = Vec3d{bridge_end.x(), bridge_end.y(), gndlvl};
double end_radius = wideningfn(
Ball{bridge_end, bridge_r}, DOWN, bridge_end.z() - gndlvl);
Beam_<Samples> gndbeam {{bridge_end, bridge_r}, {gp, end_radius}};
auto gndhit = beam_mesh_hit(policy, sm.emesh, gndbeam, sd);
double gnd_hit_d = std::min(gndhit.distance(), down_l + EPSILON);
if (source.r >= sm.cfg.head_back_radius_mm && gndhit.distance() > down_l && sm.cfg.object_elevation_mm < EPSILON) {
// Dealing with zero elevation mode, to not route pillars
// into the gap between the optional pad and the model
double gap = std::sqrt(sm.emesh.squared_distance(gp));
double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
double min_gap = sm.cfg.pillar_base_safety_distance_mm + base_r;
if (gap < min_gap) {
gnd_hit_d = down_l - min_gap + gap;
}
}
ret = Vec3d{bridge_end.x(), bridge_end.y(), bridge_end.z() - gnd_hit_d};
} else {
ret = bridge_end;
}
return ret;
}
template<class Ex>
std::pair<bool, long> search_ground_route(Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Junction &j,
double end_radius,
const Vec3d &init_dir = DOWN)
// Searching a ground connection from an arbitrary source point.
// Currently, the result will contain one avoidance bridge (at most) and a
// pillar to the ground, if it's feasible
template<class Ex, class WideningFn,
class = std::enable_if_t<IsWideningFn<WideningFn>> >
GroundConnection deepsearch_ground_connection(
Ex policy,
const SupportableMesh &sm,
const Junction &source,
WideningFn &&wideningfn,
const Vec3d &init_dir = DOWN)
{
double downdst = j.pos.z() - ground_level(sm);
constexpr unsigned MaxIterationsGlobal = 5000;
constexpr unsigned MaxIterationsLocal = 100;
constexpr double RelScoreDiff = 0.05;
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius);
if (res.first)
return res;
const auto gndlvl = ground_level(sm);
// Optimize bridge direction:
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
auto [polar, azimuth] = dir_to_spheric(init_dir);
// The used solver (AlgNLoptMLSL_Subplx search method) is composed of a global (MLSL)
// and a local (Subplex) search method. Criteria can be set in a way that
// local searches are quick and less accurate. The global method will only
// consider the max iteration number and the stop score (Z level <= ground)
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
solver.seed(0); // we want deterministic behavior
auto criteria = get_criteria(sm.cfg); // get defaults from cfg
criteria.max_iterations(MaxIterationsGlobal);
criteria.abs_score_diff(NaNd);
criteria.rel_score_diff(NaNd);
criteria.stop_score(gndlvl);
auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
auto &[plr, azm] = input;
Vec3d n = spheric_to_dir(plr, azm).normalized();
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
},
initvals({polar, azimuth}), // let's start with what we have
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
);
auto criteria_loc = criteria;
criteria_loc.max_iterations(MaxIterationsLocal);
criteria_loc.abs_score_diff(EPSILON);
criteria_loc.rel_score_diff(RelScoreDiff);
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
Optimizer<opt::AlgNLoptMLSL_Subplx> solver(criteria);
solver.set_loc_criteria(criteria_loc);
solver.seed(0); // require repeatability
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius);
// functor returns the z height of collision point, given a polar and
// azimuth angles as bridge direction and bridge length. The route is
// traced from source, through this bridge and an attached pillar. If there
// is a collision with the mesh, the Z height is returned. Otherwise the
// z level of ground is returned.
auto z_fn = [&](const opt::Input<3> &input) {
// solver suggests polar, azimuth and bridge length values:
auto &[plr, azm, bridge_len] = input;
Vec3d n = spheric_to_dir(plr, azm);
Vec3d hitpt = check_ground_route(policy, sm, source, n, bridge_len, wideningfn);
return hitpt.z();
};
// Calculate the initial direction of the search by
// saturating the polar angle to max tilt defined in config
auto [plr_init, azm_init] = dir_to_spheric(init_dir);
plr_init = std::max(plr_init, PI - sm.cfg.bridge_slope);
auto bound_constraints =
bounds({
{PI - sm.cfg.bridge_slope, PI}, // bounds for polar angle
{-PI, PI}, // bounds for azimuth
{0., sm.cfg.max_bridge_length_mm} // bounds bridge length
});
// The optimizer can navigate fairly well on the mesh surface, finding
// lower and lower Z coordinates as collision points. MLSL is not a local
// search method, so it should not be trapped in a local minima. Eventually,
// this search should arrive at a ground location.
auto oresult = solver.to_min().optimize(
z_fn,
initvals({plr_init, azm_init, 0.}),
bound_constraints
);
GroundConnection conn;
// Extract and apply the result
auto [plr, azm, bridge_l] = oresult.optimum;
Vec3d n = spheric_to_dir(plr, azm);
assert(std::abs(n.norm() - 1.) < EPSILON);
double t = (gndlvl - source.pos.z()) / n.z();
bridge_l = std::min(t, bridge_l);
// Now the optimizer gave a possible route to ground with a bridge direction
// and length. This length can be shortened further by brute-force queries
// of free route straigt down for a possible pillar.
// NOTE: This requirement could be incorporated into the optimization as a
// constraint, but it would not find quickly enough an accurate solution,
// and it would be very hard to define a stop score which is very useful in
// terminating the search as soon as the ground is found.
double l = 0., l_max = bridge_l;
double zlvl = std::numeric_limits<double>::infinity();
while(zlvl > gndlvl && l <= l_max) {
zlvl = check_ground_route(policy, sm, source, n, l, wideningfn,
GroundRouteCheck::PillarOnly).z();
if (zlvl <= gndlvl)
bridge_l = l;
l += source.r;
}
Vec3d bridge_end = source.pos + bridge_l * n;
Vec3d gp{bridge_end.x(), bridge_end.y(), gndlvl};
double bridge_r = wideningfn(Ball{source.pos, source.r}, n, bridge_l);
double down_l = bridge_end.z() - gndlvl;
double end_radius = wideningfn(Ball{bridge_end, bridge_r}, DOWN, down_l);
double base_r = std::max(sm.cfg.base_radius_mm, end_radius);
// Even if the search was not succesful, the result is populated by the
// source and the last best result of the optimization.
conn.path.emplace_back(source);
if (bridge_l > EPSILON)
conn.path.emplace_back(Junction{bridge_end, bridge_r});
// The resulting ground connection is only valid if the pillar base is set.
// At this point it will only be set if the search was succesful.
if (z_fn(opt::Input<3>({plr, azm, bridge_l})) <= gndlvl)
conn.pillar_base =
Pedestal{gp, sm.cfg.base_height_mm, base_r, end_radius};
return conn;
}
// Ground route search with a predefined end radius
template<class Ex>
GroundConnection deepsearch_ground_connection(Ex policy,
const SupportableMesh &sm,
const Junction &source,
double end_radius,
const Vec3d &init_dir = DOWN)
{
double gndlvl = ground_level(sm);
auto wfn = [end_radius, gndlvl](const Ball &src, const Vec3d &dir, double len) {
if (len < EPSILON)
return src.R;
Vec3d dst = src.p + len * dir;
double widening = end_radius - src.R;
double zlen = dst.z() - gndlvl;
double full_len = len + zlen;
double r = src.R + widening * len / full_len;
return r;
};
static_assert(IsWideningFn<decltype(wfn)>, "Not a widening function");
return deepsearch_ground_connection(policy, sm, source, wfn, init_dir);
}
struct DefaultWideningModel {
static constexpr double WIDENING_SCALE = 0.02;
const SupportableMesh &sm;
double operator()(const Ball &src, const Vec3d & /*dir*/, double len) {
static_assert(IsWideningFn<DefaultWideningModel>,
"DefaultWideningModel is not a widening function");
double w = WIDENING_SCALE * sm.cfg.pillar_widening_factor * len;
return std::max(src.R, sm.cfg.head_back_radius_mm) + w;
};
};
template<> struct BeamSamples<DefaultWideningModel> {
static constexpr size_t Value = 16;
};
template<class Ex>
GroundConnection deepsearch_ground_connection(Ex policy,
const SupportableMesh &sm,
const Junction &source,
const Vec3d &init_dir = DOWN)
{
return deepsearch_ground_connection(policy, sm, source,
DefaultWideningModel{sm}, init_dir);
}
template<class Ex>
@ -729,8 +788,7 @@ bool optimize_anchor_placement(Ex policy,
double lmax = std::min(sm.cfg.head_width_mm,
distance(from.pos, anchor.pos) - 2 * from.r);
double sd = anchor.r_back_mm * sm.cfg.safety_distance_mm /
sm.cfg.head_back_radius_mm;
double sd = sm.cfg.safety_distance(anchor.r_back_mm);
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg)
.stop_score(anchor.fullwidth())
@ -796,6 +854,92 @@ std::optional<Anchor> calculate_anchor_placement(Ex policy,
return anchor;
}
inline bool is_outside_support_cone(const Vec3f &supp,
const Vec3f &pt,
float angle)
{
using namespace Slic3r;
Vec3d D = (pt - supp).cast<double>();
double dot_sq = -D.z() * std::abs(-D.z());
return dot_sq <
D.squaredNorm() * std::cos(angle) * std::abs(std::cos(angle));
}
inline // TODO: should be in a cpp
std::optional<Vec3f> find_merge_pt(const Vec3f &A,
const Vec3f &B,
float critical_angle)
{
// The idea is that A and B both have their support cones. But searching
// for the intersection of these support cones is difficult and its enough
// to reduce this problem to 2D and search for the intersection of two
// rays that merge somewhere between A and B. The 2D plane is a vertical
// slice of the 3D scene where the 2D Y axis is equal to the 3D Z axis and
// the 2D X axis is determined by the XY direction of the AB vector.
//
// Z^
// | A *
// | . . B *
// | . . . .
// | . . . .
// | . x .
// -------------------> XY
// Determine the transformation matrix for the 2D projection:
Vec3f diff = {B.x() - A.x(), B.y() - A.y(), 0.f};
Vec3f dir = diff.normalized(); // TODO: avoid normalization
Eigen::Matrix<float, 2, 3> tr2D;
tr2D.row(0) = Vec3f{dir.x(), dir.y(), dir.z()};
tr2D.row(1) = Vec3f{0.f, 0.f, 1.f};
// Transform the 2 vectors A and B into 2D vector 'a' and 'b'. Here we can
// omit 'a', pretend that its the origin and use BA as the vector b.
Vec2f b = tr2D * (B - A);
// Get the square sine of the ray emanating from 'a' towards 'b'. This ray might
// exceed the allowed angle but that is corrected subsequently.
// The sign of the original sine is also needed, hence b.y is multiplied by
// abs(b.y)
float b_sqn = b.squaredNorm();
float sin2sig_a = b_sqn > EPSILON ? (b.y() * std::abs(b.y())) / b_sqn : 0.f;
// sine2 from 'b' to 'a' is the opposite of sine2 from a to b
float sin2sig_b = -sin2sig_a;
// Derive the allowed angles from the given critical angle.
// critical_angle is measured from the horizontal X axis.
// The rays need to go downwards which corresponds to negative angles
float sincrit = std::sin(critical_angle); // sine of the critical angle
float sin2crit = -sincrit * sincrit; // signed sine squared
sin2sig_a = std::min(sin2sig_a, sin2crit); // Do the angle saturation of both rays
sin2sig_b = std::min(sin2sig_b, sin2crit); //
float sin2_a = std::abs(sin2sig_a); // Get cosine squared values
float sin2_b = std::abs(sin2sig_b);
float cos2_a = 1.f - sin2_a;
float cos2_b = 1.f - sin2_b;
// Derive the new direction vectors. This is by square rooting the sin2
// and cos2 values and restoring the original signs
Vec2f Da = {std::copysign(std::sqrt(cos2_a), b.x()), std::copysign(std::sqrt(sin2_a), sin2sig_a)};
Vec2f Db = {-std::copysign(std::sqrt(cos2_b), b.x()), std::copysign(std::sqrt(sin2_b), sin2sig_b)};
// Determine where two rays ([0, 0], Da), (b, Db) intersect.
// Based on
// https://stackoverflow.com/questions/27459080/given-two-points-and-two-direction-vectors-find-the-point-where-they-intersect
// One ray is emanating from (0, 0) so the formula is simplified
double t1 = (Db.y() * b.x() - b.y() * Db.x()) /
(Da.x() * Db.y() - Da.y() * Db.x());
Vec2f mp = t1 * Da;
Vec3f Mp = A + tr2D.transpose() * mp;
return t1 >= 0.f ? Mp : Vec3f{};
}
}} // namespace Slic3r::sla
#endif // SLASUPPORTTREEUTILS_H

View File

@ -0,0 +1,300 @@
#ifndef SUPPORTTREEUTILSLEGACY_HPP
#define SUPPORTTREEUTILSLEGACY_HPP
#include "SupportTreeUtils.hpp"
// Old functions are gathered here that are used in DefaultSupportTree
// to maintain functionality that was well tested.
namespace Slic3r { namespace sla {
// Helper function for pillar interconnection where pairs of already connected
// pillars should be checked for not to be processed again. This can be done
// in constant time with a set of hash values uniquely representing a pair of
// integers. The order of numbers within the pair should not matter, it has
// the same unique hash. The hash value has to have twice as many bits as the
// arguments need. If the same integral type is used for args and return val,
// make sure the arguments use only the half of the type's bit depth.
template<class I, class DoubleI = IntegerOnly<I>>
IntegerOnly<DoubleI> pairhash(I a, I b)
{
using std::ceil; using std::log2; using std::max; using std::min;
static const auto constexpr Ibits = int(sizeof(I) * CHAR_BIT);
static const auto constexpr DoubleIbits = int(sizeof(DoubleI) * CHAR_BIT);
static const auto constexpr shift = DoubleIbits / 2 < Ibits ? Ibits / 2 : Ibits;
I g = min(a, b), l = max(a, b);
// Assume the hash will fit into the output variable
assert((g ? (ceil(log2(g))) : 0) <= shift);
assert((l ? (ceil(log2(l))) : 0) <= shift);
return (DoubleI(g) << shift) + l;
}
template<class Ex>
std::optional<DiffBridge> search_widening_path(Ex policy,
const SupportableMesh &sm,
const Vec3d &jp,
const Vec3d &dir,
double radius,
double new_radius)
{
double w = radius + 2 * sm.cfg.head_back_radius_mm;
double stopval = w + jp.z() - ground_level(sm);
Optimizer<AlgNLoptSubplex> solver(get_criteria(sm.cfg).stop_score(stopval));
auto [polar, azimuth] = dir_to_spheric(dir);
double fallback_ratio = radius / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&policy, &sm, jp, radius, new_radius](const opt::Input<3> &input) {
auto &[plr, azm, t] = input;
auto d = spheric_to_dir(plr, azm).normalized();
auto sd = sm.cfg.safety_distance(new_radius);
double ret = pinhead_mesh_hit(policy, sm.emesh, jp, d, radius,
new_radius, t, sd)
.distance();
Beam beam{jp + t * d, d, new_radius};
double down = beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
if (ret > t && std::isinf(down))
ret += jp.z() - ground_level(sm);
return ret;
},
initvals({polar, azimuth, w}), // start with what we have
bounds({
{PI - sm.cfg.bridge_slope, PI}, // Must not exceed the slope limit
{-PI, PI}, // azimuth can be a full search
{radius + sm.cfg.head_back_radius_mm,
fallback_ratio * sm.cfg.max_bridge_length_mm}
}));
if (oresult.score >= stopval) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
double t = std::get<2>(oresult.optimum);
Vec3d endp = jp + t * spheric_to_dir(polar, azimuth);
return DiffBridge(jp, endp, radius, sm.cfg.head_back_radius_mm);
}
return {};
}
// This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode.
// 'pinhead_junctionpt' is the starting junction point which needs to be
// routed down. sourcedir is the allowed direction of an optional bridge
// between the jp junction and the final pillar.
template<class Ex>
std::pair<bool, long> create_ground_pillar(
Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Vec3d &pinhead_junctionpt,
const Vec3d &sourcedir,
double radius,
double end_radius,
long head_id = SupportTreeNode::ID_UNSET)
{
Vec3d jp = pinhead_junctionpt, endp = jp, dir = sourcedir;
long pillar_id = SupportTreeNode::ID_UNSET;
bool can_add_base = false, non_head = false;
double gndlvl = 0.; // The Z level where pedestals should be
double jp_gnd = 0.; // The lowest Z where a junction center can be
double gap_dist = 0.; // The gap distance between the model and the pad
double r2 = radius + (end_radius - radius) / (jp.z() - ground_level(sm));
auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
auto eval_limits = [&sm, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
(bool base_en = true)
{
can_add_base = base_en && radius >= sm.cfg.head_back_radius_mm;
double base_r = can_add_base ? sm.cfg.base_radius_mm : 0.;
gndlvl = ground_level(sm);
if (!can_add_base) gndlvl -= sm.pad_cfg.wall_thickness_mm;
jp_gnd = gndlvl + (can_add_base ? 0. : sm.cfg.head_back_radius_mm);
gap_dist = sm.cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
};
eval_limits();
// We are dealing with a mini pillar that's potentially too long
if (radius < sm.cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
{
std::optional<DiffBridge> diffbr =
search_widening_path(policy, sm, jp, dir, radius,
sm.cfg.head_back_radius_mm);
if (diffbr && diffbr->endp.z() > jp_gnd) {
auto &br = builder.add_diffbridge(*diffbr);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
endp = diffbr->endp;
radius = diffbr->end_r;
builder.add_junction(endp, radius);
non_head = true;
dir = diffbr->get_dir();
eval_limits();
} else return {false, pillar_id};
}
if (sm.cfg.object_elevation_mm < EPSILON)
{
// get a suitable direction for the corrector bridge. It is the
// original sourcedir's azimuth but the polar angle is saturated to the
// configured bridge slope.
auto [polar, azimuth] = dir_to_spheric(dir);
polar = PI - sm.cfg.bridge_slope;
Vec3d d = spheric_to_dir(polar, azimuth).normalized();
auto sd = radius * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double t = beam_mesh_hit(policy, sm.emesh, Beam{endp, d, radius, r2}, sd).distance();
double tmax = std::min(sm.cfg.max_bridge_length_mm, t);
t = 0.;
double zd = endp.z() - jp_gnd;
double tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
Vec3d nexp = endp;
double dlast = 0.;
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius, r2}, sd).distance())) &&
t < tmax)
{
t += radius;
nexp = endp + t * d;
}
if (dlast < gap_dist && can_add_base) {
nexp = endp;
t = 0.;
can_add_base = false;
eval_limits(can_add_base);
zd = endp.z() - jp_gnd;
tmax2 = zd / std::sqrt(1 - sm.cfg.bridge_slope * sm.cfg.bridge_slope);
tmax = std::min(tmax, tmax2);
while (((dlast = std::sqrt(sm.emesh.squared_distance(to_floor(nexp)))) < gap_dist ||
!std::isinf(beam_mesh_hit(policy, sm.emesh, Beam{nexp, DOWN, radius}, sd).distance())) && t < tmax) {
t += radius;
nexp = endp + t * d;
}
}
// Could not find a path to avoid the pad gap
if (dlast < gap_dist) return {false, pillar_id};
if (t > 0.) { // Need to make additional bridge
const Bridge& br = builder.add_bridge(endp, nexp, radius);
if (head_id >= 0) builder.head(head_id).bridge_id = br.id;
builder.add_junction(nexp, radius);
endp = nexp;
non_head = true;
}
}
Vec3d gp = to_floor(endp);
double h = endp.z() - gp.z();
pillar_id = head_id >= 0 && !non_head ? builder.add_pillar(head_id, h) :
builder.add_pillar(gp, h, radius, end_radius);
if (can_add_base)
builder.add_pillar_base(pillar_id, sm.cfg.base_height_mm,
sm.cfg.base_radius_mm);
return {true, pillar_id};
}
template<class Ex>
std::pair<bool, long> connect_to_ground(Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Junction &j,
const Vec3d &dir,
double end_r)
{
auto hjp = j.pos;
double r = j.r;
auto sd = r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
double r2 = j.r + (end_r - j.r) / (j.pos.z() - ground_level(sm));
double t = beam_mesh_hit(policy, sm.emesh, Beam{hjp, dir, r, r2}, sd).distance();
double d = 0, tdown = 0;
t = std::min(t, sm.cfg.max_bridge_length_mm * r / sm.cfg.head_back_radius_mm);
while (d < t &&
!std::isinf(tdown = beam_mesh_hit(policy, sm.emesh,
Beam{hjp + d * dir, DOWN, r, r2}, sd)
.distance())) {
d += r;
}
if(!std::isinf(tdown))
return {false, SupportTreeNode::ID_UNSET};
Vec3d endp = hjp + d * dir;
auto ret = create_ground_pillar(policy, builder, sm, endp, dir, r, end_r);
if (ret.second >= 0) {
builder.add_bridge(hjp, endp, r);
builder.add_junction(endp, r);
}
return ret;
}
template<class Ex>
std::pair<bool, long> search_ground_route(Ex policy,
SupportTreeBuilder &builder,
const SupportableMesh &sm,
const Junction &j,
double end_radius,
const Vec3d &init_dir = DOWN)
{
double downdst = j.pos.z() - ground_level(sm);
auto res = connect_to_ground(policy, builder, sm, j, init_dir, end_radius);
if (res.first)
return res;
// Optimize bridge direction:
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
auto [polar, azimuth] = dir_to_spheric(init_dir);
Optimizer<AlgNLoptGenetic> solver(get_criteria(sm.cfg).stop_score(1e6));
solver.seed(0); // we want deterministic behavior
auto sd = j.r * sm.cfg.safety_distance_mm / sm.cfg.head_back_radius_mm;
auto oresult = solver.to_max().optimize(
[&j, sd, &policy, &sm, &downdst, &end_radius](const opt::Input<2> &input) {
auto &[plr, azm] = input;
Vec3d n = spheric_to_dir(plr, azm).normalized();
Beam beam{Ball{j.pos, j.r}, Ball{j.pos + downdst * n, end_radius}};
return beam_mesh_hit(policy, sm.emesh, beam, sd).distance();
},
initvals({polar, azimuth}), // let's start with what we have
bounds({ {PI - sm.cfg.bridge_slope, PI}, {-PI, PI} })
);
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
return connect_to_ground(policy, builder, sm, j, bridgedir, end_radius);
}
}} // namespace Slic3r::sla
#endif // SUPPORTTREEUTILSLEGACY_HPP

View File

@ -1,13 +1,9 @@
#include "SLAPrint.hpp"
#include "SLAPrintSteps.hpp"
#include "CSGMesh/CSGMeshCopy.hpp"
#include "CSGMesh/PerformCSGMeshBooleans.hpp"
#include "Format/SL1.hpp"
#include "Format/SL1_SVG.hpp"
#include "Format/pwmx.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
#include "Thread.hpp"
#include <unordered_set>
@ -41,31 +37,66 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c)
sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
{
sla::SupportTreeConfig scfg;
scfg.enabled = c.supports_enable.getBool();
scfg.tree_type = c.support_tree_type.value;
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
scfg.head_back_radius_mm = pillar_r;
scfg.head_fallback_radius_mm =
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ?
0. : c.support_object_elevation.getFloat();
scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
scfg.pillar_connection_mode = c.support_pillar_connection_mode.value;
scfg.ground_facing_only = c.support_buildplate_only.getBool();
scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
scfg.base_height_mm = c.support_base_height.getFloat();
scfg.pillar_base_safety_distance_mm =
c.support_base_safety_distance.getFloat() < EPSILON ?
scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt());
switch(scfg.tree_type) {
case sla::SupportTreeType::Default: {
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
scfg.head_back_radius_mm = pillar_r;
scfg.head_fallback_radius_mm =
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ?
0. : c.support_object_elevation.getFloat();
scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
scfg.pillar_connection_mode = c.support_pillar_connection_mode.value;
scfg.ground_facing_only = c.support_buildplate_only.getBool();
scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
scfg.base_height_mm = c.support_base_height.getFloat();
scfg.pillar_base_safety_distance_mm =
c.support_base_safety_distance.getFloat() < EPSILON ?
scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
scfg.max_bridges_on_pillar = unsigned(c.support_max_bridges_on_pillar.getInt());
scfg.max_weight_on_model_support = c.support_max_weight_on_model.getFloat();
break;
}
case sla::SupportTreeType::Branching:
[[fallthrough]];
case sla::SupportTreeType::Organic:{
scfg.head_front_radius_mm = 0.5*c.branchingsupport_head_front_diameter.getFloat();
double pillar_r = 0.5 * c.branchingsupport_pillar_diameter.getFloat();
scfg.head_back_radius_mm = pillar_r;
scfg.head_fallback_radius_mm =
0.01 * c.branchingsupport_small_pillar_diameter_percent.getFloat() * pillar_r;
scfg.head_penetration_mm = c.branchingsupport_head_penetration.getFloat();
scfg.head_width_mm = c.branchingsupport_head_width.getFloat();
scfg.object_elevation_mm = is_zero_elevation(c) ?
0. : c.branchingsupport_object_elevation.getFloat();
scfg.bridge_slope = c.branchingsupport_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.branchingsupport_max_bridge_length.getFloat();
scfg.max_pillar_link_distance_mm = c.branchingsupport_max_pillar_link_distance.getFloat();
scfg.pillar_connection_mode = c.branchingsupport_pillar_connection_mode.value;
scfg.ground_facing_only = c.branchingsupport_buildplate_only.getBool();
scfg.pillar_widening_factor = c.branchingsupport_pillar_widening_factor.getFloat();
scfg.base_radius_mm = 0.5*c.branchingsupport_base_diameter.getFloat();
scfg.base_height_mm = c.branchingsupport_base_height.getFloat();
scfg.pillar_base_safety_distance_mm =
c.branchingsupport_base_safety_distance.getFloat() < EPSILON ?
scfg.safety_distance_mm : c.branchingsupport_base_safety_distance.getFloat();
scfg.max_bridges_on_pillar = unsigned(c.branchingsupport_max_bridges_on_pillar.getInt());
scfg.max_weight_on_model_support = c.branchingsupport_max_weight_on_model.getFloat();
break;
}
}
return scfg;
}
@ -159,14 +190,13 @@ static std::vector<SLAPrintObject::Instance> sla_instances(const ModelObject &mo
std::vector<SLAPrintObject::Instance> instances;
assert(! model_object.instances.empty());
if (! model_object.instances.empty()) {
Vec3d rotation0 = model_object.instances.front()->get_rotation();
rotation0(2) = 0.;
const Transform3d& trafo0 = model_object.instances.front()->get_matrix();
for (ModelInstance *model_instance : model_object.instances)
if (model_instance->is_printable()) {
instances.emplace_back(
model_instance->id(),
Point::new_scale(model_instance->get_offset(X), model_instance->get_offset(Y)),
float(Geometry::rotation_diff_z(rotation0, model_instance->get_rotation())));
float(Geometry::rotation_diff_z(trafo0, model_instance->get_matrix())));
}
}
return instances;
@ -388,7 +418,12 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con
if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id())
it_print_object_status = print_object_status.end();
// Check whether a model part volume was added or removed, their transformations or order changed.
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
bool model_parts_differ =
model_volume_list_changed(model_object, model_object_new,
{ModelVolumeType::MODEL_PART,
ModelVolumeType::NEGATIVE_VOLUME,
ModelVolumeType::SUPPORT_ENFORCER,
ModelVolumeType::SUPPORT_BLOCKER});
bool sla_trafo_differs =
model_object.instances.empty() != model_object_new.instances.empty() ||
(! model_object.instances.empty() &&
@ -597,7 +632,7 @@ void SLAPrint::process()
// We want to first process all objects...
std::vector<SLAPrintObjectStep> level1_obj_steps = {
slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
slaposAssembly, slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
};
// and then slice all supports to allow preview to be displayed ASAP
@ -793,12 +828,6 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const
SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object)
: Inherited(print, model_object)
, m_transformed_rmesh([this](TriangleMesh &obj) {
obj = m_model_object->raw_mesh();
if (!obj.empty()) {
obj.transform(m_trafo);
}
})
{}
SLAPrintObject::~SLAPrintObject() {}
@ -827,6 +856,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "supports_enable"
|| opt_key == "support_tree_type"
|| opt_key == "support_object_elevation"
|| opt_key == "branchingsupport_object_elevation"
|| opt_key == "pad_around_object"
|| opt_key == "pad_around_object_everywhere"
|| opt_key == "slice_closing_radius"
@ -834,6 +864,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
steps.emplace_back(slaposObjectSlice);
} else if (
opt_key == "support_points_density_relative"
|| opt_key == "support_enforcers_only"
|| opt_key == "support_points_minimal_distance") {
steps.emplace_back(slaposSupportPoints);
} else if (
@ -843,6 +874,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "support_pillar_diameter"
|| opt_key == "support_pillar_widening_factor"
|| opt_key == "support_small_pillar_diameter_percent"
|| opt_key == "support_max_weight_on_model"
|| opt_key == "support_max_bridges_on_pillar"
|| opt_key == "support_pillar_connection_mode"
|| opt_key == "support_buildplate_only"
@ -852,6 +884,24 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "support_max_bridge_length"
|| opt_key == "support_max_pillar_link_distance"
|| opt_key == "support_base_safety_distance"
|| opt_key == "branchingsupport_head_front_diameter"
|| opt_key == "branchingsupport_head_penetration"
|| opt_key == "branchingsupport_head_width"
|| opt_key == "branchingsupport_pillar_diameter"
|| opt_key == "branchingsupport_pillar_widening_factor"
|| opt_key == "branchingsupport_small_pillar_diameter_percent"
|| opt_key == "branchingsupport_max_weight_on_model"
|| opt_key == "branchingsupport_max_bridges_on_pillar"
|| opt_key == "branchingsupport_pillar_connection_mode"
|| opt_key == "branchingsupport_buildplate_only"
|| opt_key == "branchingsupport_base_diameter"
|| opt_key == "branchingsupport_base_height"
|| opt_key == "branchingsupport_critical_angle"
|| opt_key == "branchingsupport_max_bridge_length"
|| opt_key == "branchingsupport_max_pillar_link_distance"
|| opt_key == "branchingsupport_base_safety_distance"
|| opt_key == "pad_object_gap"
) {
steps.emplace_back(slaposSupportTree);
@ -882,8 +932,10 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
{
bool invalidated = Inherited::invalidate_step(step);
// propagate to dependent steps
if (step == slaposHollowing) {
if (step == slaposAssembly) {
invalidated |= this->invalidate_all_steps();
} else if (step == slaposHollowing) {
invalidated |= invalidated |= this->invalidate_steps({ slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
} else if (step == slaposDrillHoles) {
invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
@ -907,7 +959,7 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
bool SLAPrintObject::invalidate_all_steps()
{
return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps();
return Inherited::invalidate_all_steps() || m_print->invalidate_all_steps();
}
double SLAPrintObject::get_elevation() const {
@ -998,113 +1050,71 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const
return idx >= v.size() ? EMPTY_SLICE : v[idx];
}
bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
{
switch (step) {
case slaposDrillHoles:
return m_hollowing_data && !m_hollowing_data->hollow_mesh_with_holes.empty();
case slaposSupportTree:
return ! this->support_mesh().empty();
case slaposPad:
return ! this->pad_mesh().empty();
default:
return false;
}
}
TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
{
switch (step) {
case slaposSupportTree:
return this->support_mesh();
case slaposPad:
return this->pad_mesh();
case slaposDrillHoles:
if (m_hollowing_data)
return get_mesh_to_print();
[[fallthrough]];
default:
return TriangleMesh();
}
}
const TriangleMesh& SLAPrintObject::support_mesh() const
{
if(m_config.supports_enable.getBool() && m_supportdata)
if (m_config.supports_enable.getBool() &&
is_step_done(slaposSupportTree) &&
m_supportdata)
return m_supportdata->tree_mesh;
return EMPTY_MESH;
}
const TriangleMesh& SLAPrintObject::pad_mesh() const
{
if(m_config.pad_enable.getBool() && m_supportdata)
if(m_config.pad_enable.getBool() && is_step_done(slaposPad) && m_supportdata)
return m_supportdata->pad_mesh;
return EMPTY_MESH;
}
const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const
const std::shared_ptr<const indexed_triangle_set> &
SLAPrintObject::get_mesh_to_print() const
{
if (m_hollowing_data && m_hollowing_data->interior &&
m_config.hollowing_enable.getBool())
return sla::get_mesh(*m_hollowing_data->interior);
return EMPTY_TRIANGLE_SET;
int s = last_completed_step();
while (s > 0 && ! m_preview_meshes[s])
--s;
return m_preview_meshes[s];
}
const TriangleMesh &SLAPrintObject::transformed_mesh() const {
// we need to transform the raw mesh...
// currently all the instances share the same x and y rotation and scaling
// so we have to extract those from e.g. the first instance and apply to the
// raw mesh. This is also true for the support points.
// BUT: when the support structure is spawned for each instance than it has
// to omit the X, Y rotation and scaling as those have been already applied
// or apply an inverse transformation on the support structure after it
// has been created.
std::vector<csg::CSGPart> SLAPrintObject::get_parts_to_slice() const
{
return get_parts_to_slice(slaposCount);
}
return m_transformed_rmesh.get();
std::vector<csg::CSGPart>
SLAPrintObject::get_parts_to_slice(SLAPrintObjectStep untilstep) const
{
auto laststep = last_completed_step();
SLAPrintObjectStep s = std::min(untilstep, laststep);
if (s == slaposCount)
return {};
std::vector<csg::CSGPart> ret;
for (int step = 0; step < s; ++step) {
auto r = m_mesh_to_slice.equal_range(SLAPrintObjectStep(step));
csg::copy_csgrange_shallow(Range{r.first, r.second}, std::back_inserter(ret));
}
return ret;
}
sla::SupportPoints SLAPrintObject::transformed_support_points() const
{
assert(m_model_object != nullptr);
auto spts = m_model_object->sla_support_points;
const Transform3d& vol_trafo = m_model_object->volumes.front()->get_transformation().get_matrix();
const Transform3f& tr = (trafo() * vol_trafo).cast<float>();
for (sla::SupportPoint& suppt : spts) {
suppt.pos = tr * suppt.pos;
}
return spts;
assert(model_object());
return sla::transformed_support_points(*model_object(), trafo());
}
sla::DrainHoles SLAPrintObject::transformed_drainhole_points() const
{
assert(m_model_object != nullptr);
auto pts = m_model_object->sla_drain_holes;
const Transform3d& vol_trafo = m_model_object->volumes.front()->get_transformation().get_matrix();
const Geometry::Transformation trans(trafo() * vol_trafo);
const Transform3f& tr = trans.get_matrix().cast<float>();
const Vec3f sc = trans.get_scaling_factor().cast<float>();
for (sla::DrainHole &hl : pts) {
hl.pos = tr * hl.pos;
hl.normal = tr * hl.normal - tr.translation();
assert(model_object());
// The normal scales as a covector (and we must also
// undo the damage already done).
hl.normal = Vec3f(hl.normal(0)/(sc(0)*sc(0)),
hl.normal(1)/(sc(1)*sc(1)),
hl.normal(2)/(sc(2)*sc(2)));
// Now shift the hole a bit above the object and make it deeper to
// compensate for it. This is to avoid problems when the hole is placed
// on (nearly) flat surface.
hl.pos -= hl.normal.normalized() * sla::HoleStickOutLength;
hl.height += sla::HoleStickOutLength;
}
return pts;
return sla::transformed_drainhole_points(*model_object(), trafo());
}
DynamicConfig SLAPrintStatistics::config() const
@ -1161,4 +1171,17 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p,
p.set_status(int(std::round(st)), msg, flags);
}
namespace csg {
MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartForStep &part)
{
if (!part.cgalcache && csg::get_mesh(part)) {
part.cgalcache = csg::get_cgalmesh(static_cast<const csg::CSGPart&>(part));
}
return part.cgalcache? clone(*part.cgalcache) : nullptr;
}
} // namespace csg
} // namespace Slic3r

View File

@ -3,16 +3,18 @@
#include <cstdint>
#include <mutex>
#include <set>
#include "PrintBase.hpp"
#include "SLA/RasterBase.hpp"
#include "SLA/SupportTree.hpp"
#include "Point.hpp"
#include "MTUtils.hpp"
#include "Zipper.hpp"
#include "Format/SLAArchiveWriter.hpp"
#include "GCode/ThumbnailData.hpp"
#include "libslic3r/CSGMesh/CSGMesh.hpp"
#include "libslic3r/MeshBoolean.hpp"
#include "libslic3r/OpenVDBUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include <boost/functional/hash.hpp>
namespace Slic3r {
@ -23,6 +25,7 @@ enum SLAPrintStep : unsigned int {
};
enum SLAPrintObjectStep : unsigned int {
slaposAssembly,
slaposHollowing,
slaposDrillHoles,
slaposObjectSlice,
@ -45,10 +48,47 @@ using _SLAPrintObjectBase =
enum SliceOrigin { soSupport, soModel };
} // namespace Slic3r
namespace Slic3r {
// Each sla object step can hold a collection of csg operations on the
// sla model to be sliced. Currently, Assembly step adds negative and positive
// volumes, hollowing adds the negative interior, drilling adds the hole cylinders.
// They need to be processed in this specific order. If CSGPartForStep instances
// are put into a multiset container the key being the sla step,
// iterating over the container will maintain the correct order of csg operations.
struct CSGPartForStep : public csg::CSGPart
{
SLAPrintObjectStep key;
mutable MeshBoolean::cgal::CGALMeshPtr cgalcache;
CSGPartForStep(SLAPrintObjectStep k, CSGPart &&p = {})
: key{k}, CSGPart{std::move(p)}
{}
CSGPartForStep &operator=(CSGPart &&part)
{
this->its_ptr = std::move(part.its_ptr);
this->operation = part.operation;
return *this;
}
bool operator<(const CSGPartForStep &other) const { return key < other.key; }
};
namespace csg {
MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartForStep &part);
} // namespace csg
class SLAPrintObject : public _SLAPrintObjectBase
{
private: // Prevents erroneous use by other classes.
using Inherited = _SLAPrintObjectBase;
using CSGContainer = std::multiset<CSGPartForStep>;
public:
@ -72,31 +112,20 @@ public:
};
const std::vector<Instance>& instances() const { return m_instances; }
bool has_mesh(SLAPrintObjectStep step) const;
TriangleMesh get_mesh(SLAPrintObjectStep step) const;
// Get a support mesh centered around origin in XY, and with zero rotation around Z applied.
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
const TriangleMesh& support_mesh() const;
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied.
// Support mesh is only valid if this->is_step_done(slaposPad) is true.
const TriangleMesh& pad_mesh() const;
// Ready after this->is_step_done(slaposDrillHoles) is true
const indexed_triangle_set &hollowed_interior_mesh() const;
// Get the mesh that is going to be printed with all the modifications
// like hollowing and drilled holes.
const TriangleMesh & get_mesh_to_print() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh();
}
const std::shared_ptr<const indexed_triangle_set>& get_mesh_to_print() const;
const TriangleMesh & get_mesh_to_slice() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh();
}
std::vector<csg::CSGPart> get_parts_to_slice() const;
// This will return the transformed mesh which is cached
const TriangleMesh& transformed_mesh() const;
std::vector<csg::CSGPart> get_parts_to_slice(SLAPrintObjectStep step) const;
sla::SupportPoints transformed_support_points() const;
sla::DrainHoles transformed_drainhole_points() const;
@ -275,7 +304,8 @@ protected:
{ m_config.apply_only(other, keys, ignore_nonexistent); }
void set_trafo(const Transform3d& trafo, bool left_handed) {
m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; });
m_trafo = trafo;
m_left_handed = left_handed;
}
template<class InstVec> inline void set_instances(InstVec&& instances) { m_instances = std::forward<InstVec>(instances); }
@ -306,9 +336,6 @@ private:
std::vector<float> m_model_height_levels;
// Caching the transformed (m_trafo) raw mesh of the object
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
struct SupportData
{
sla::SupportableMesh input; // the input
@ -318,6 +345,10 @@ private:
inline SupportData(const TriangleMesh &t)
: input{t.its, {}, {}}
{}
inline SupportData(const indexed_triangle_set &t)
: input{t, {}, {}}
{}
void create_support_tree(const sla::JobController &ctl)
{
@ -329,16 +360,32 @@ private:
pad_mesh = TriangleMesh{sla::create_pad(input, tree_mesh.its, ctl)};
}
};
std::unique_ptr<SupportData> m_supportdata;
std::unique_ptr<SupportData> m_supportdata;
// Holds CSG operations for the printed object, prioritized by print steps.
CSGContainer m_mesh_to_slice;
auto mesh_to_slice(SLAPrintObjectStep s) const
{
auto r = m_mesh_to_slice.equal_range(s);
return Range{r.first, r.second};
}
auto mesh_to_slice() const { return range(m_mesh_to_slice); }
// Holds the preview of the object to be printed (as it will look like with
// all its holes and cavities, negatives and positive volumes unified.
// Essentially this should be a m_mesh_to_slice after the CSG operations
// or an approximation of that.
std::array<std::shared_ptr<const indexed_triangle_set>, SLAPrintObjectStep::slaposCount + 1> m_preview_meshes;
class HollowingData
{
public:
sla::InteriorPtr interior;
mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
mutable TriangleMesh hollow_mesh_with_holes_trimmed;
};
std::unique_ptr<HollowingData> m_hollowing_data;
@ -421,6 +468,11 @@ public:
const PrintObjects& objects() const { return m_objects; }
// PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects
// in the notification center.
const SLAPrintObject* get_print_object_by_model_object_id(ObjectID object_id) const {
auto it = std::find_if(m_objects.begin(), m_objects.end(),
[object_id](const SLAPrintObject* obj) { return obj->model_object()->id() == object_id; });
return (it == m_objects.end()) ? nullptr : *it;
}
const SLAPrintObject* get_object(ObjectID object_id) const {
auto it = std::find_if(m_objects.begin(), m_objects.end(),
[object_id](const SLAPrintObject *obj) { return obj->id() == object_id; });

View File

@ -14,8 +14,17 @@
#include <libslic3r/ElephantFootCompensation.hpp>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/MeshSplitImpl.hpp>
#include <libslic3r/SlicesToTriangleMesh.hpp>
#include <libslic3r/CSGMesh/ModelToCSGMesh.hpp>
#include <libslic3r/CSGMesh/SliceCSGMesh.hpp>
#include <libslic3r/CSGMesh/VoxelizeCSGMesh.hpp>
#include <libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp>
#include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/QuadricEdgeCollapse.hpp>
#include <libslic3r/ClipperUtils.hpp>
//#include <libslic3r/ShortEdgeCollapse.hpp>
#include <boost/log/trivial.hpp>
@ -32,18 +41,20 @@ namespace Slic3r {
namespace {
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
10, // slaposHollowing,
10, // slaposDrillHoles
10, // slaposObjectSlice,
20, // slaposSupportPoints,
10, // slaposSupportTree,
10, // slaposPad,
30, // slaposSliceSupports,
13, // slaposAssembly
13, // slaposHollowing,
13, // slaposDrillHoles
13, // slaposObjectSlice,
13, // slaposSupportPoints,
13, // slaposSupportTree,
11, // slaposPad,
11, // slaposSliceSupports,
};
std::string OBJ_STEP_LABELS(size_t idx)
{
switch (idx) {
case slaposAssembly: return L("Assembling model from parts");
case slaposHollowing: return L("Hollowing model");
case slaposDrillHoles: return L("Drilling holes into model.");
case slaposObjectSlice: return L("Slicing model");
@ -76,7 +87,6 @@ std::string PRINT_STEP_LABELS(size_t idx)
SLAPrint::Steps::Steps(SLAPrint *print)
: m_print{print}
, m_rng{std::random_device{}()}
, objcount{m_print->m_objects.size()}
, ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
, ilh{float(ilhd)}
@ -119,9 +129,228 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin
}
}
template<class Cont> bool is_all_positive(const Cont &csgmesh)
{
bool is_all_pos =
std::all_of(csgmesh.begin(),
csgmesh.end(),
[](auto &part) {
return csg::get_operation(part) == csg::CSGType::Union;
});
return is_all_pos;
}
template<class Cont>
static indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh)
{
indexed_triangle_set m;
for (auto &csgpart : csgmesh) {
auto op = csg::get_operation(csgpart);
const indexed_triangle_set * pmesh = csg::get_mesh(csgpart);
if (pmesh && op == csg::CSGType::Union) {
indexed_triangle_set mcpy = *pmesh;
its_transform(mcpy, csg::get_transform(csgpart), true);
its_merge(m, mcpy);
}
}
return m;
}
indexed_triangle_set SLAPrint::Steps::generate_preview_vdb(
SLAPrintObject &po, SLAPrintObjectStep step)
{
// Empirical upper limit to not get excessive performance hit
constexpr double MaxPreviewVoxelScale = 12.;
// update preview mesh
double vscale = std::min(MaxPreviewVoxelScale,
1. / po.m_config.layer_height.getFloat());
auto voxparams = csg::VoxelizeParams{}
.voxel_scale(vscale)
.exterior_bandwidth(1.f)
.interior_bandwidth(1.f);
voxparams.statusfn([&po](int){
return po.m_print->cancel_status() != CancelStatus::NOT_CANCELED;
});
auto r = range(po.m_mesh_to_slice);
auto grid = csg::voxelize_csgmesh(r, voxparams);
auto m = grid ? grid_to_mesh(*grid, 0., 0.01) : indexed_triangle_set{};
float loss_less_max_error = 1e-6;
its_quadric_edge_collapse(m, 0U, &loss_less_max_error);
return m;
}
void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step)
{
Benchmark bench;
bench.start();
auto r = range(po.m_mesh_to_slice);
auto m = indexed_triangle_set{};
bool handled = false;
if (is_all_positive(r)) {
m = csgmesh_merge_positive_parts(r);
handled = true;
} else if (csg::check_csgmesh_booleans(r) == r.end()) {
auto cgalmeshptr = csg::perform_csgmesh_booleans(r);
if (cgalmeshptr) {
m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr);
handled = true;
} else {
BOOST_LOG_TRIVIAL(warning) << "CSG mesh is not egligible for proper CGAL booleans!";
}
} else {
// Normal cgal processing failed. If there are no negative volumes,
// the hollowing can be tried with the old algorithm which didn't handled volumes.
// If that fails for any of the drillholes, the voxelization fallback is
// used.
bool is_pure_model = is_all_positive(po.mesh_to_slice(slaposAssembly));
bool can_hollow = po.m_hollowing_data && po.m_hollowing_data->interior &&
!sla::get_mesh(*po.m_hollowing_data->interior).empty();
bool hole_fail = false;
if (step == slaposHollowing && is_pure_model) {
if (can_hollow) {
m = csgmesh_merge_positive_parts(r);
sla::hollow_mesh(m, *po.m_hollowing_data->interior,
sla::hfRemoveInsideTriangles);
}
handled = true;
} else if (step == slaposDrillHoles && is_pure_model) {
if (po.m_model_object->sla_drain_holes.empty()) {
// Get the last printable preview
auto &meshp = po.get_mesh_to_print();
if (meshp)
m = *(meshp);
handled = true;
} else if (can_hollow) {
m = csgmesh_merge_positive_parts(r);
sla::hollow_mesh(m, *po.m_hollowing_data->interior);
sla::DrainHoles drainholes = po.transformed_drainhole_points();
auto ret = sla::hollow_mesh_and_drill(
m, *po.m_hollowing_data->interior, drainholes,
[/*&po, &drainholes, */&hole_fail](size_t i)
{
hole_fail = /*drainholes[i].failed =
po.model_object()->sla_drain_holes[i].failed =*/ true;
});
if (ret & static_cast<int>(sla::HollowMeshResult::FaultyMesh)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL,
L("Mesh to be hollowed is not suitable for hollowing (does not "
"bound a volume)."));
}
if (ret & static_cast<int>(sla::HollowMeshResult::FaultyHoles)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL,
L("Unable to drill the current configuration of holes into the "
"model."));
}
handled = true;
if (ret & static_cast<int>(sla::HollowMeshResult::DrillingFailed)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL, L(
"Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
handled = false;
}
if (hole_fail) {
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
L("Failed to drill some holes into the model"));
handled = false;
}
}
}
}
if (!handled) { // Last resort to voxelization.
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
L("Can't perform full mesh booleans! "
"Some parts of the print will be previewed with approximated meshes. "
"This does not affect the quality of slices or the physical print in any way."));
m = generate_preview_vdb(po, step);
}
po.m_preview_meshes[step] =
std::make_shared<const indexed_triangle_set>(std::move(m));
for (size_t i = size_t(step) + 1; i < slaposCount; ++i)
{
po.m_preview_meshes[i] = {};
}
bench.stop();
if (!m.empty())
BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec();
else
BOOST_LOG_TRIVIAL(error) << "Preview failed!";
using namespace std::string_literals;
report_status(-2, "Reload preview from step "s + std::to_string(int(step)), SlicingStatus::RELOAD_SLA_PREVIEW);
}
static inline
void clear_csg(std::multiset<CSGPartForStep> &s, SLAPrintObjectStep step)
{
auto r = s.equal_range(step);
s.erase(r.first, r.second);
}
struct csg_inserter {
std::multiset<CSGPartForStep> &m;
SLAPrintObjectStep key;
csg_inserter &operator*() { return *this; }
void operator=(csg::CSGPart &&part)
{
part.its_ptr.convert_unique_to_shared();
m.emplace(key, std::move(part));
}
csg_inserter& operator++() { return *this; }
};
void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po)
{
po.m_mesh_to_slice.clear();
po.m_supportdata.reset();
po.m_hollowing_data.reset();
csg::model_to_csgmesh(*po.model_object(), po.trafo(),
csg_inserter{po.m_mesh_to_slice, slaposAssembly},
csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits);
generate_preview(po, slaposAssembly);
}
void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
{
po.m_hollowing_data.reset();
po.m_supportdata.reset();
clear_csg(po.m_mesh_to_slice, slaposDrillHoles);
clear_csg(po.m_mesh_to_slice, slaposHollowing);
if (! po.m_config.hollowing_enable.getBool()) {
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
@ -134,348 +363,104 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
double quality = po.m_config.hollowing_quality.getFloat();
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
sla::JobController ctl;
ctl.stopcondition = [this]() { return canceled(); };
ctl.cancelfn = [this]() { throw_if_canceled(); };
sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg);
sla::InteriorPtr interior =
generate_interior(po.mesh_to_slice(), hlwcfg, ctl);
if (!interior || sla::get_mesh(*interior).empty())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->interior = std::move(interior);
}
}
struct FaceHash {
indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior);
// A 64 bit number's max hex digits
static constexpr size_t MAX_NUM_CHARS = 16;
if (!m.empty()) {
// simplify mesh lossless
float loss_less_max_error = 2*std::numeric_limits<float>::epsilon();
its_quadric_edge_collapse(m, 0U, &loss_less_max_error);
// A hash is created for each triangle to be identifiable. The hash uses
// only the triangle's geometric traits, not the index in a particular mesh.
std::unordered_set<std::string> facehash;
// Returns the string in reverse, but that is ok for hashing
static std::array<char, MAX_NUM_CHARS + 1> to_chars(int64_t val)
{
std::array<char, MAX_NUM_CHARS + 1> ret;
static const constexpr char * Conv = "0123456789abcdef";
auto ptr = ret.begin();
auto uval = static_cast<uint64_t>(std::abs(val));
while (uval) {
*ptr = Conv[uval & 0xf];
++ptr;
uval = uval >> 4;
}
if (val < 0) { *ptr = '-'; ++ptr; }
*ptr = '\0'; // C style string ending
return ret;
}
static std::string hash(const Vec<3, int64_t> &v)
{
std::string ret;
ret.reserve(3 * MAX_NUM_CHARS);
for (auto val : v)
ret += to_chars(val).data();
return ret;
}
static std::string facekey(const Vec3i &face, const std::vector<Vec3f> &vertices)
{
// Scale to integer to avoid floating points
std::array<Vec<3, int64_t>, 3> pts = {
scaled<int64_t>(vertices[face(0)]),
scaled<int64_t>(vertices[face(1)]),
scaled<int64_t>(vertices[face(2)])
};
// Get the first two sides of the triangle, do a cross product and move
// that vector to the center of the triangle. This encodes all
// information to identify an identical triangle at the same position.
Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2];
Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
// Return a concatenated string representation of the coordinates
return hash(c);
}
FaceHash (const indexed_triangle_set &its): facehash(its.indices.size())
{
for (const Vec3i &face : its.indices)
facehash.insert(facekey(face, its.vertices));
}
bool find(const std::string &key)
{
auto it = facehash.find(key);
return it != facehash.end();
}
};
static void exclude_neighbors(const Vec3i &face,
std::vector<bool> &mask,
const indexed_triangle_set &its,
const VertexFaceIndex &index,
size_t recursions)
{
for (int i = 0; i < 3; ++i) {
const auto &neighbors_range = index[face(i)];
for (size_t fi_n : neighbors_range) {
mask[fi_n] = true;
if (recursions > 0)
exclude_neighbors(its.indices[fi_n], mask, its, index, recursions - 1);
}
}
}
// Create exclude mask for triangle removal inside hollowed interiors.
// This is necessary when the interior is already part of the mesh which was
// drilled using CGAL mesh boolean operation. Excluded will be the triangles
// originally part of the interior mesh and triangles that make up the drilled
// hole walls.
static std::vector<bool> create_exclude_mask(
const indexed_triangle_set &its,
const sla::Interior &interior,
const std::vector<sla::DrainHole> &holes)
{
FaceHash interior_hash{sla::get_mesh(interior)};
std::vector<bool> exclude_mask(its.indices.size(), false);
VertexFaceIndex neighbor_index{its};
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
auto &face = its.indices[fi];
if (interior_hash.find(FaceHash::facekey(face, its.vertices))) {
exclude_mask[fi] = true;
continue;
its_compactify_vertices(m);
its_merge_vertices(m);
}
if (exclude_mask[fi]) {
exclude_neighbors(face, exclude_mask, its, neighbor_index, 1);
continue;
}
// Put the interior into the target mesh as a negative
po.m_mesh_to_slice
.emplace(slaposHollowing,
csg::CSGPart{std::make_shared<indexed_triangle_set>(m),
csg::CSGType::Difference});
// Lets deal with the holes. All the triangles of a hole and all the
// neighbors of these triangles need to be kept. The neigbors were
// created by CGAL mesh boolean operation that modified the original
// interior inside the input mesh to contain the holes.
Vec3d tr_center = (
its.vertices[face(0)] +
its.vertices[face(1)] +
its.vertices[face(2)]
).cast<double>() / 3.;
// If the center is more than half a mm inside the interior,
// it cannot possibly be part of a hole wall.
if (sla::get_distance(tr_center, interior) < -0.5)
continue;
Vec3f U = its.vertices[face(1)] - its.vertices[face(0)];
Vec3f V = its.vertices[face(2)] - its.vertices[face(0)];
Vec3f C = U.cross(V);
Vec3f face_normal = C.normalized();
for (const sla::DrainHole &dh : holes) {
if (dh.failed) continue;
Vec3d dhpos = dh.pos.cast<double>();
Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height;
Linef3 holeaxis{dhpos, dhend};
double D_hole_center = line_alg::distance_to(holeaxis, tr_center);
double D_hole = std::abs(D_hole_center - dh.radius);
float dot = dh.normal.dot(face_normal);
// Empiric tolerances for center distance and normals angle.
// For triangles that are part of a hole wall the angle of
// triangle normal and the hole axis is around 90 degrees,
// so the dot product is around zero.
double D_tol = dh.radius / sla::DrainHole::steps;
float normal_angle_tol = 1.f / sla::DrainHole::steps;
if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) {
exclude_mask[fi] = true;
exclude_neighbors(face, exclude_mask, its, neighbor_index, 1);
}
}
generate_preview(po, slaposHollowing);
}
return exclude_mask;
}
static indexed_triangle_set
remove_unconnected_vertices(const indexed_triangle_set &its)
{
if (its.indices.empty()) {};
indexed_triangle_set M;
std::vector<int> vtransl(its.vertices.size(), -1);
int vcnt = 0;
for (auto &f : its.indices) {
for (int i = 0; i < 3; ++i)
if (vtransl[size_t(f(i))] < 0) {
M.vertices.emplace_back(its.vertices[size_t(f(i))]);
vtransl[size_t(f(i))] = vcnt++;
}
std::array<int, 3> new_f = {
vtransl[size_t(f(0))],
vtransl[size_t(f(1))],
vtransl[size_t(f(2))]
};
M.indices.emplace_back(new_f[0], new_f[1], new_f[2]);
}
return M;
}
// Drill holes into the hollowed/original mesh.
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
{
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
bool is_hollowed =
(po.m_hollowing_data && po.m_hollowing_data->interior &&
!sla::get_mesh(*po.m_hollowing_data->interior).empty());
po.m_supportdata.reset();
clear_csg(po.m_mesh_to_slice, slaposDrillHoles);
if (! is_hollowed && ! needs_drilling) {
// In this case we can dump any data that might have been
// generated on previous runs.
po.m_hollowing_data.reset();
return;
}
csg::model_to_csgmesh(*po.model_object(), po.trafo(),
csg_inserter{po.m_mesh_to_slice, slaposDrillHoles},
csg::mpartsDrillHoles);
if (! po.m_hollowing_data)
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
generate_preview(po, slaposDrillHoles);
// Hollowing and/or drilling is active, m_hollowing_data is valid.
// Release the data, won't be needed anymore, takes huge amount of ram
if (po.m_hollowing_data && po.m_hollowing_data->interior)
po.m_hollowing_data->interior.reset();
}
// Regenerate hollowed mesh, even if it was there already. It may contain
// holes that are no longer on the frontend.
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.transformed_mesh();
if (is_hollowed)
sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior);
TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed;
if (! needs_drilling) {
mesh_view = po.transformed_mesh();
if (is_hollowed)
sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior,
sla::hfRemoveInsideTriangles);
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
return;
}
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
sla::DrainHoles drainholes = po.transformed_drainhole_points();
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
hollowed_mesh.its.vertices,
hollowed_mesh.its.indices
);
std::uniform_real_distribution<float> dist(0., float(EPSILON));
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {});
indexed_triangle_set part_to_drill = hollowed_mesh.its;
bool hole_fail = false;
for (size_t i = 0; i < drainholes.size(); ++i) {
sla::DrainHole holept = drainholes[i];
holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
holept.normal.normalize();
holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
indexed_triangle_set m = holept.to_mesh();
part_to_drill.indices.clear();
auto bb = bounding_box(m);
Eigen::AlignedBox<float, 3> ebb{bb.min.cast<float>(),
bb.max.cast<float>()};
AABBTreeIndirect::traverse(
tree,
AABBTreeIndirect::intersecting(ebb),
[&part_to_drill, &hollowed_mesh](const auto& node)
{
part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[node.idx]);
// continue traversal
return true;
});
auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal(
remove_unconnected_vertices(part_to_drill));
if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) {
BOOST_LOG_TRIVIAL(error) << "Failed to drill hole";
hole_fail = drainholes[i].failed =
po.model_object()->sla_drain_holes[i].failed = true;
continue;
template<class Pred>
static std::vector<ExPolygons> slice_volumes(
const ModelVolumePtrs &volumes,
const std::vector<float> &slice_grid,
const Transform3d &trafo,
const MeshSlicingParamsEx &slice_params,
Pred &&predicate)
{
indexed_triangle_set mesh;
for (const ModelVolume *vol : volumes) {
if (predicate(vol)) {
indexed_triangle_set vol_mesh = vol->mesh().its;
its_transform(vol_mesh, trafo * vol->get_matrix());
its_merge(mesh, vol_mesh);
}
auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole);
}
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
throw Slic3r::SlicingError(L("Too many overlapping holes."));
std::vector<ExPolygons> out;
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
if (!MeshBoolean::cgal::does_bound_a_volume(*hollowed_mesh_cgal)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL,
L("Mesh to be hollowed is not suitable for hollowing (does not "
"bound a volume)."));
if (!mesh.empty()) {
out = slice_mesh_ex(mesh, slice_grid, slice_params);
}
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)
&& !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL,
L("Unable to drill the current configuration of holes into the "
"model."));
return out;
}
template<class Cont> BoundingBoxf3 csgmesh_positive_bb(const Cont &csg)
{
// Calculate the biggest possible bounding box of the mesh to be sliced
// from all the positive parts that it contains.
BoundingBoxf3 bb3d;
bool skip = false;
for (const auto &m : csg) {
auto op = csg::get_operation(m);
auto stackop = csg::get_stack_operation(m);
if (stackop == csg::CSGStackOp::Push && op != csg::CSGType::Union)
skip = true;
if (!skip && csg::get_mesh(m) && op == csg::CSGType::Union)
bb3d.merge(bounding_box(*csg::get_mesh(m), csg::get_transform(m)));
if (stackop == csg::CSGStackOp::Pop)
skip = false;
}
try {
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal))
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
mesh_view = hollowed_mesh;
if (is_hollowed) {
auto &interior = *po.m_hollowing_data->interior;
std::vector<bool> exclude_mask =
create_exclude_mask(mesh_view.its, interior, drainholes);
sla::remove_inside_triangles(mesh_view, interior, exclude_mask);
}
} catch (const Slic3r::RuntimeError &) {
throw Slic3r::SlicingError(L(
"Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
}
if (hole_fail)
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
L("Failed to drill some holes into the model"));
return bb3d;
}
// The slicing will be performed on an imaginary 1D grid which starts from
@ -488,14 +473,17 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
{
const TriangleMesh &mesh = po.get_mesh_to_slice();
// The first mesh in the csg sequence is assumed to be a positive part
assert(po.m_mesh_to_slice.empty() ||
csg::get_operation(*po.m_mesh_to_slice.begin()) == csg::CSGType::Union);
auto bb3d = csgmesh_positive_bb(po.m_mesh_to_slice);
// We need to prepare the slice index...
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
float lh = float(lhd);
coord_t lhs = scaled(lhd);
auto && bb3d = mesh.bounding_box();
double minZ = bb3d.min(Z) - po.get_elevation();
double maxZ = bb3d.max(Z);
auto minZf = float(minZ);
@ -537,27 +525,8 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
}
auto thr = [this]() { m_print->throw_if_canceled(); };
auto &slice_grid = po.m_model_height_levels;
po.m_model_slices = slice_mesh_ex(mesh.its, slice_grid, params, thr);
sla::Interior *interior = po.m_hollowing_data ?
po.m_hollowing_data->interior.get() :
nullptr;
if (interior && ! sla::get_mesh(*interior).empty()) {
indexed_triangle_set interiormesh = sla::get_mesh(*interior);
sla::swap_normals(interiormesh);
params.mode = MeshSlicingParams::SlicingMode::Regular;
std::vector<ExPolygons> interior_slices = slice_mesh_ex(interiormesh, slice_grid, params, thr);
execution::for_each(
ex_tbb, size_t(0), interior_slices.size(),
[&po, &interior_slices](size_t i) {
const ExPolygons &slice = interior_slices[i];
po.m_model_slices[i] = diff_ex(po.m_model_slices[i], slice);
},
execution::max_concurrency(ex_tbb));
}
po.m_model_slices = slice_csgmesh_ex(po.mesh_to_slice(), slice_grid, params, thr);
auto mit = slindex_it;
for (size_t id = 0;
@ -569,10 +538,67 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
// We apply the printer correction offset here.
apply_printer_corrections(po, soModel);
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
{
po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print()));
// po.m_preview_meshes[slaposObjectSlice] = po.get_mesh_to_print();
// report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
}
struct SuppPtMask {
const std::vector<ExPolygons> &blockers;
const std::vector<ExPolygons> &enforcers;
bool enforcers_only = false;
};
static void filter_support_points_by_modifiers(sla::SupportPoints &pts,
const SuppPtMask &mask,
const std::vector<float> &slice_grid)
{
assert((mask.blockers.empty() || mask.blockers.size() == slice_grid.size()) &&
(mask.enforcers.empty() || mask.enforcers.size() == slice_grid.size()));
auto new_pts = reserve_vector<sla::SupportPoint>(pts.size());
for (size_t i = 0; i < pts.size(); ++i) {
const sla::SupportPoint &sp = pts[i];
Point sp2d = scaled(to_2d(sp.pos));
auto it = std::lower_bound(slice_grid.begin(), slice_grid.end(), sp.pos.z());
if (it != slice_grid.end()) {
size_t idx = std::distance(slice_grid.begin(), it);
bool is_enforced = false;
if (idx < mask.enforcers.size()) {
for (size_t enf_idx = 0;
!is_enforced && enf_idx < mask.enforcers[idx].size();
++enf_idx)
{
if (mask.enforcers[idx][enf_idx].contains(sp2d))
is_enforced = true;
}
}
bool is_blocked = false;
if (!is_enforced) {
if (!mask.enforcers_only) {
if (idx < mask.blockers.size()) {
for (size_t blk_idx = 0;
!is_blocked && blk_idx < mask.blockers[idx].size();
++blk_idx)
{
if (mask.blockers[idx][blk_idx].contains(sp2d))
is_blocked = true;
}
}
} else {
is_blocked = true;
}
}
if (!is_blocked)
new_pts.emplace_back(sp);
}
}
pts.swap(new_pts);
}
// In this step we check the slices, identify island and cover them with
@ -582,8 +608,15 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
// If supports are disabled, we can skip the model scan.
if(!po.m_config.supports_enable.getBool()) return;
if (!po.m_supportdata)
po.m_supportdata.reset(new SLAPrintObject::SupportData(po.get_mesh_to_print()));
if (!po.m_supportdata) {
auto &meshp = po.get_mesh_to_print();
assert(meshp);
po.m_supportdata =
std::make_unique<SLAPrintObject::SupportData>(*meshp);
}
po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice)
.min.z();
const ModelObject& mo = *po.m_model_object;
@ -598,11 +631,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
// calculate heights of slices (slices are calculated already)
const std::vector<float>& heights = po.m_model_height_levels;
// Tell the mesh where drain holes are. Although the points are
// calculated on slices, the algorithm then raycasts the points
// so they actually lie on the mesh.
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
throw_if_canceled();
sla::SupportPointGenerator::Config config;
const SLAPrintObjectConfig& cfg = po.config();
@ -630,8 +658,28 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
heights, config, [this]() { throw_if_canceled(); }, statuscb);
// Now let's extract the result.
const std::vector<sla::SupportPoint>& points = auto_supports.output();
std::vector<sla::SupportPoint>& points = auto_supports.output();
throw_if_canceled();
MeshSlicingParamsEx params;
params.closing_radius = float(po.config().slice_closing_radius.value);
std::vector<ExPolygons> blockers =
slice_volumes(po.model_object()->volumes,
po.m_model_height_levels, po.trafo(), params,
[](const ModelVolume *vol) {
return vol->is_support_blocker();
});
std::vector<ExPolygons> enforcers =
slice_volumes(po.model_object()->volumes,
po.m_model_height_levels, po.trafo(), params,
[](const ModelVolume *vol) {
return vol->is_support_enforcer();
});
SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()};
filter_support_points_by_modifiers(points, mask, po.m_model_height_levels);
po.m_supportdata->input.pts = points;
BOOST_LOG_TRIVIAL(debug)
@ -653,23 +701,17 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
{
if(!po.m_supportdata) return;
// sla::PadConfig pcfg = make_pad_cfg(po.m_config);
// if (pcfg.embed_object)
// po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
// If the zero elevation mode is engaged, we have to filter out all the
// points that are on the bottom of the object
if (is_zero_elevation(po.config())) {
remove_bottom_points(po.m_supportdata->input.pts,
float(
po.m_supportdata->input.emesh.ground_level() +
po.m_supportdata->input.zoffset +
EPSILON));
}
po.m_supportdata->input.cfg = make_support_cfg(po.m_config);
po.m_supportdata->input.pad_cfg = make_pad_cfg(po.m_config);
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
@ -713,6 +755,13 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
// repeated)
if(po.m_config.pad_enable.getBool()) {
if (!po.m_supportdata) {
auto &meshp = po.get_mesh_to_print();
assert(meshp);
po.m_supportdata =
std::make_unique<SLAPrintObject::SupportData>(*meshp);
}
// Get the distilled pad configuration from the config
// (Again, despite it was retrieved in the previous step. Note that
// on a param change event, the previous step might not be executed
@ -720,16 +769,6 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
po.m_supportdata->input.pad_cfg = pcfg;
// if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
// // No support (thus no elevation) or zero elevation mode
// // we sometimes call it "builtin pad" is enabled so we will
// // get a sample from the bottom of the mesh and use it for pad
// // creation.
// sla::pad_blueprint(trmesh.its, bp, float(pad_h),
// float(po.m_config.layer_height.getFloat()),
// [this](){ throw_if_canceled(); });
// }
sla::JobController ctl;
ctl.stopcondition = [this]() { return canceled(); };
ctl.cancelfn = [this]() { throw_if_canceled(); };
@ -1162,6 +1201,7 @@ double SLAPrint::Steps::progressrange(SLAPrintStep step) const
void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
{
switch(step) {
case slaposAssembly: mesh_assembly(obj); break;
case slaposHollowing: hollow_model(obj); break;
case slaposDrillHoles: drill_holes(obj); break;
case slaposObjectSlice: slice_model(obj); break;

View File

@ -14,40 +14,43 @@ class SLAPrint::Steps
{
private:
SLAPrint *m_print = nullptr;
std::mt19937 m_rng;
public:
public:
// where the per object operations start and end
static const constexpr unsigned min_objstatus = 0;
static const constexpr unsigned max_objstatus = 50;
static const constexpr unsigned min_objstatus = 0;
static const constexpr unsigned max_objstatus = 70;
private:
const size_t objcount;
// shortcut to initial layer height
const double ilhd;
const float ilh;
const coord_t ilhs;
// the coefficient that multiplies the per object status values which
// are set up for <0, 100>. They need to be scaled into the whole process
const double objectstep_scale;
template<class...Args> void report_status(Args&&...args)
{
m_print->m_report_status(*m_print, std::forward<Args>(args)...);
}
double current_status() const { return m_print->m_report_status.status(); }
void throw_if_canceled() const { m_print->throw_if_canceled(); }
bool canceled() const { return m_print->canceled(); }
void initialize_printer_input();
void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o);
void generate_preview(SLAPrintObject &po, SLAPrintObjectStep step);
indexed_triangle_set generate_preview_vdb(SLAPrintObject &po, SLAPrintObjectStep step);
public:
explicit Steps(SLAPrint *print);
void mesh_assembly(SLAPrintObject &po);
void hollow_model(SLAPrintObject &po);
void drill_holes (SLAPrintObject &po);
void slice_model(SLAPrintObject& po);
@ -55,20 +58,20 @@ public:
void support_tree(SLAPrintObject& po);
void generate_pad(SLAPrintObject& po);
void slice_supports(SLAPrintObject& po);
void merge_slices_and_eval_stats();
void rasterize();
void execute(SLAPrintObjectStep step, SLAPrintObject &obj);
void execute(SLAPrintStep step);
static std::string label(SLAPrintObjectStep step);
static std::string label(SLAPrintStep step);
double progressrange(SLAPrintObjectStep step) const;
double progressrange(SLAPrintStep step) const;
};
}
} // namespace Slic3r
#endif // SLAPRINTSTEPS_HPP

View File

@ -9,6 +9,8 @@
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <boost/log/trivial.hpp>
namespace Slic3r {
// Same as walls() but with identical higher and lower polygons.
@ -52,7 +54,8 @@ indexed_triangle_set slices_to_mesh(
Layers layers(slices.size());
size_t len = slices.size() - 1;
tbb::parallel_for(size_t(0), len, [&slices, &layers, &grid](size_t i) {
auto threads_cnt = execution::max_concurrency(ex_tbb);
execution::for_each(ex_tbb, size_t(0), len, [&slices, &layers, &grid](size_t i) {
const ExPolygons &upper = slices[i + 1];
const ExPolygons &lower = slices[i];
@ -64,14 +67,15 @@ indexed_triangle_set slices_to_mesh(
its_merge(layers[i], triangulate_expolygons_3d(free_top, grid[i], NORMALS_UP));
its_merge(layers[i], triangulate_expolygons_3d(overhang, grid[i], NORMALS_DOWN));
its_merge(layers[i], straight_walls(upper, grid[i], grid[i + 1]));
});
}, threads_cnt);
auto merge_fn = []( const indexed_triangle_set &a, const indexed_triangle_set &b ) {
indexed_triangle_set res{a}; its_merge(res, b); return res;
};
auto ret = execution::reduce(ex_tbb, layers.begin(), layers.end(),
indexed_triangle_set{}, merge_fn);
indexed_triangle_set{}, merge_fn,
threads_cnt);
its_merge(ret, triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN));
its_merge(ret, straight_walls(slices.front(), zmin, grid.front()));
@ -80,9 +84,14 @@ indexed_triangle_set slices_to_mesh(
// FIXME: these repairs do not fix the mesh entirely. There will be cracks
// in the output. It is very hard to do the meshing in a way that does not
// leave errors.
its_merge_vertices(ret);
its_remove_degenerate_faces(ret);
its_compactify_vertices(ret);
int num_mergedv = its_merge_vertices(ret);
BOOST_LOG_TRIVIAL(debug) << "Merged vertices count: " << num_mergedv;
int remcnt = its_remove_degenerate_faces(ret);
BOOST_LOG_TRIVIAL(debug) << "Removed degenerate faces count: " << remcnt;
int num_erasedv = its_compactify_vertices(ret);
BOOST_LOG_TRIVIAL(debug) << "Erased vertices count: " << num_erasedv;
return ret;
}

View File

@ -1,5 +1,6 @@
#include "SupportSpotsGenerator.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
@ -7,8 +8,10 @@
#include "Line.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "PrincipalComponents2D.hpp"
#include "Print.hpp"
#include "PrintBase.hpp"
#include "PrintConfig.hpp"
#include "Tesselate.hpp"
#include "libslic3r.h"
#include "tbb/parallel_for.h"
@ -21,6 +24,7 @@
#include <cstddef>
#include <cstdio>
#include <functional>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <stack>
@ -66,7 +70,7 @@ public:
float len;
const ExtrusionEntity *origin_entity;
bool support_point_generated = false;
std::optional<SupportSpotsGenerator::SupportPointCause> support_point_generated = {};
float form_quality = 1.0f;
float curled_up_height = 0.0f;
@ -79,10 +83,6 @@ auto get_b(ExtrusionLine &&l) { return l.b; }
namespace SupportSpotsGenerator {
SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction)
: position(position), force(force), spot_radius(spot_radius), direction(direction)
{}
using LD = AABBTreeLines::LinesDistancer<ExtrusionLine>;
struct SupportGridFilter
@ -117,13 +117,14 @@ public:
size_t to_cell_index(const Vec3i &cell_coords) const
{
#ifdef DETAILED_DEBUG_LOGS
assert(cell_coords.x() >= 0);
assert(cell_coords.x() < cell_count.x());
assert(cell_coords.y() >= 0);
assert(cell_coords.y() < cell_count.y());
assert(cell_coords.z() >= 0);
assert(cell_coords.z() < cell_count.z());
#endif
return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x();
}
@ -237,13 +238,67 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end());
}
return checked_lines_out;
} else if (entity->role().is_bridge() && !entity->role().is_perimeter()) {
// pure bridges are handled separately, beacuse we need to align the forward and backward direction support points
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
return {};
}
const float flow_width = get_flow_width(layer_region, entity->role());
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, true, true>(entity->as_polyline().points,
prev_layer_boundary, flow_width,
params.bridge_distance);
std::vector<ExtrusionLine> lines_out;
lines_out.reserve(annotated_points.size());
float bridged_distance = 0.0f;
std::optional<Vec2d> bridging_dir{};
for (size_t i = 0; i < annotated_points.size(); ++i) {
ExtendedPoint &curr_point = annotated_points[i];
ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i - 1];
SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor :
SupportPointCause::LongBridge;
float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f;
Vec2d line_dir = (curr_point.position - prev_point.position).normalized();
ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast<float>() : curr_point.position.cast<float>(),
curr_point.position.cast<float>(), line_len, entity};
float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f,
params.bridge_distance /
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) *
(1.0f + std::abs(curr_point.curvature))));
if (!bridging_dir.has_value() && curr_point.distance > flow_width && line_len > params.bridge_distance * 0.6) {
bridging_dir = (prev_point.position - curr_point.position).normalized();
}
if (curr_point.distance > flow_width && potential_cause == SupportPointCause::LongBridge && bridging_dir.has_value() &&
bridging_dir->dot(line_dir) < 0.8) { // skip backward direction of bridge - supported by forward points enough
bridged_distance += line_len;
} else if (curr_point.distance > flow_width) {
bridged_distance += line_len;
if (bridged_distance > max_bridge_len) {
bridged_distance = 0.0f;
line_out.support_point_generated = potential_cause;
}
} else {
bridged_distance = 0.0f;
}
lines_out.push_back(line_out);
}
return lines_out;
} else { // single extrusion path, with possible varying parameters
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
return {};
}
const float flow_width = get_flow_width(layer_region, entity->role());
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
prev_layer_lines, flow_width,
params.bridge_distance);
@ -259,27 +314,51 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
curr_point.position.cast<float>(), line_len, entity};
const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ?
prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) :
ExtrusionLine{};
prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) :
ExtrusionLine{};
// correctify the distance sign using slice polygons
float sign = (prev_layer_boundary.distance_from_lines<true>(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
curr_point.distance *= sign;
float max_bridge_len = params.bridge_distance /
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)));
SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion;
if (bridged_distance + line_len > params.bridge_distance * 0.8 && std::abs(curr_point.curvature) < 0.1) {
potential_cause = SupportPointCause::FloatingExtrusion;
}
float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f,
params.bridge_distance /
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) *
(1.0f + std::abs(curr_point.curvature))));
if (curr_point.distance > 2.0f * flow_width) {
line_out.form_quality = 0.8f;
bridged_distance += line_len;
if (bridged_distance > max_bridge_len) {
line_out.support_point_generated = true;
std::cout << "Problem found A: " << std::endl;
std::cout << "bridged_distance: " << bridged_distance << std::endl;
std::cout << "max_bridge_len: " << max_bridge_len << std::endl;
std::cout << "line_out.form_quality: " << line_out.form_quality << std::endl;
std::cout << "curr_point.distance: " << curr_point.distance << std::endl;
std::cout << "curr_point.curvature: " << curr_point.curvature << std::endl;
std::cout << "flow_width: " << flow_width << std::endl;
line_out.support_point_generated = potential_cause;
bridged_distance = 0.0f;
}
} else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) {
bridged_distance += line_len;
line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f;
if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) {
line_out.support_point_generated = true;
std::cout << "Problem found B: " << std::endl;
std::cout << "bridged_distance: " << bridged_distance << std::endl;
std::cout << "max_bridge_len: " << max_bridge_len << std::endl;
std::cout << "line_out.form_quality: " << line_out.form_quality << std::endl;
std::cout << "curr_point.distance: " << curr_point.distance << std::endl;
std::cout << "curr_point.curvature: " << curr_point.curvature << std::endl;
std::cout << "flow_width: " << flow_width << std::endl;
line_out.support_point_generated = potential_cause;
line_out.form_quality = 0.5f;
bridged_distance = 0.0f;
}
@ -297,84 +376,39 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
}
}
// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance
// none of the values is divided/normalized by area.
// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous)
// f(x,y) = x^2 for second moment of area
// and f(x,y) = x*y for second moment of area covariance
std::tuple<float, Vec2f, Vec2f, float> compute_triangle_moments_of_area(const Vec2f &a, const Vec2f &b, const Vec2f &c)
{
// based on the following guide:
// Denote the vertices of S by a, b, c. Then the map
// g:(u,v)↦a+u(ba)+v(ca) ,
// which in coordinates appears as
// g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1a1)+v(c1a1)=a2+u(b2a2)+v(c2a2) ,(1)
// obviously maps S bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain
// ∫Sf(x,y)d(x,y)=∫Sf(x(u,v),y(u,v))Jg(u,v) d(u,v) .
// In the case at hand the Jacobian determinant is a constant: From (1) we obtain
// Jg(u,v)=det[xuyuxvyv]=(b1a1)(c2a2)(c1a1)(b2a2) .
// Therefore we can write
// ∫Sf(x,y)d(x,y)=Jg∫10∫1u0f~(u,v) dv du ,
// where f~ denotes the pullback of f to S:
// f~(u,v):=f(x(u,v),y(u,v)) .
// Don't forget taking the absolute value of Jg!
float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y()));
// coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x)
// coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y)
// second moment of area for x: f(x, y) = x^2;
// f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form)
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
// integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2)
Vec2f second_moment_of_area_xy = jacobian_determinant_abs *
(a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) +
a.cwiseProduct(b + c)) /
12.0f;
// second moment of area covariance : f(x, y) = x*y;
// f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form)
//(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2))
// == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2))
// intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv =
// 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2
// b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u -
// 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du =
// 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2)
// result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du
float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) *
(a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() +
b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y());
float area = jacobian_determinant_abs * 0.5f;
Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f;
return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance};
};
SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer)
{
SliceConnection connection;
const LayerSlice &slice = layer->lslices_ex[slice_idx];
ExPolygon slice_poly = layer->lslices[slice_idx];
Polygons slice_polys = to_polygons(layer->lslices[slice_idx]);
BoundingBox slice_bb = get_extents(slice_polys);
const Layer *lower_layer = layer->lower_layer;
ExPolygons below_polys{};
for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); }
ExPolygons overlap = intersection_ex({slice_poly}, below_polys);
ExPolygons below{};
for (const auto &link : slice.overlaps_below) { below.push_back(lower_layer->lslices[link.slice_idx]); }
Polygons below_polys = to_polygons(below);
std::vector<Vec2f> triangles = triangulate_expolygons_2f(overlap);
for (size_t idx = 0; idx < triangles.size(); idx += 3) {
auto [area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]);
connection.area += area;
connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area);
connection.second_moment_of_area_accumulator += second_moment_area;
connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance;
BoundingBox below_bb = get_extents(below_polys);
Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb),
ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb));
for (const Polygon &poly : overlap) {
Vec2f p0 = unscaled(poly.first_point()).cast<float>();
for (size_t i = 2; i < poly.points.size(); i++) {
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(poly.points[i]).cast<float>();
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
auto [area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
connection.area += sign * area;
connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area);
connection.second_moment_of_area_accumulator += sign * second_moment_area;
connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
}
}
return connection;
@ -389,11 +423,13 @@ public:
Vec3f sticking_centroid_accumulator = Vec3f::Zero();
Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero();
float sticking_second_moment_of_area_covariance_accumulator{};
bool connected_to_bed = false;
ObjectPart() = default;
void add(const ObjectPart &other)
{
this->connected_to_bed = this->connected_to_bed || other.connected_to_bed;
this->volume_centroid_accumulator += other.volume_centroid_accumulator;
this->volume += other.volume;
this->sticking_area += other.sticking_area;
@ -456,7 +492,7 @@ public:
return elastic_section_modulus;
}
float is_stable_while_extruding(const SliceConnection &connection,
std::tuple<float, SupportPointCause> is_stable_while_extruding(const SliceConnection &connection,
const ExtrusionLine &extruded_line,
const Vec3f &extreme_point,
float layer_z,
@ -474,7 +510,7 @@ public:
// section for bed calculations
{
if (this->sticking_area < EPSILON) return 1.0f;
if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart};
Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area;
float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator,
@ -503,7 +539,7 @@ public:
#ifdef DETAILED_DEBUG_LOGS
BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z();
BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque;
BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm;
BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm_len;
BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque;
BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm;
BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque;
@ -515,16 +551,19 @@ public:
BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z;
#endif
if (bed_total_torque > 0) return bed_total_torque / bed_conflict_torque_arm;
if (bed_total_torque > 0) {
return {bed_total_torque / bed_conflict_torque_arm,
(this->connected_to_bed ? SupportPointCause::SeparationFromBed : SupportPointCause::UnstableFloatingPart)};
}
}
// section for weak connection calculations
{
if (connection.area < EPSILON) return 1.0f;
if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart};
Vec3f conn_centroid = connection.centroid_accumulator / connection.area;
if (layer_z - conn_centroid.z() < 3.0f) { return -1.0f; }
if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; }
float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator,
connection.second_moment_of_area_accumulator,
connection.second_moment_of_area_covariance_accumulator,
@ -532,7 +571,7 @@ public:
params.material_yield_strength;
float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm();
float conn_weight_torque = conn_weight_arm * weight * (conn_centroid.z() / layer_z);
float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z);
float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z());
float conn_movement_torque = movement_force * conn_movement_arm;
@ -554,18 +593,20 @@ public:
BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z;
#endif
return conn_total_torque / conn_conflict_torque_arm;
return {conn_total_torque / conn_conflict_torque_arm, SupportPointCause::WeakObjectPart};
}
}
};
// return new object part and actual area covered by extrusions
std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &slice, const Layer *layer)
std::tuple<ObjectPart, float> build_object_part_from_slice(const size_t &slice_idx, const Layer *layer, const Params& params)
{
ObjectPart new_object_part;
float area_covered_by_extrusions = 0;
const LayerSlice& slice = layer->lslices_ex.at(slice_idx);
auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions](const ExtrusionEntity *e, const LayerRegion *region) {
auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions, &params](const ExtrusionEntity *e,
const LayerRegion *region) {
float flow_width = get_flow_width(region, e->role());
const Layer *l = region->layer();
float slice_z = l->slice_z;
@ -577,8 +618,9 @@ std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &sli
new_object_part.volume += volume;
new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume;
if (l->bottom_z() < EPSILON) { // layer attached on bed
float sticking_area = line.len * flow_width;
if (l->id() == params.raft_layers_count) { // layer attached on bed/raft
new_object_part.connected_to_bed = true;
float sticking_area = line.len * flow_width;
new_object_part.sticking_area += sticking_area;
Vec2f middle = Vec2f((line.a + line.b) / 2.0f);
new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z);
@ -619,6 +661,49 @@ std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &sli
}
}
// BRIM HANDLING
if (layer->id() == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim) {
// TODO: The algorithm here should take into account that multiple slices may have coliding Brim areas and the final brim area is
// smaller,
// thus has lower adhesion. For now this effect will be neglected.
ExPolygon slice_poly = layer->lslices[slice_idx];
ExPolygons brim;
if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btOuterOnly) {
Polygon brim_hole = slice_poly.contour;
brim_hole.reverse();
brim.push_back(ExPolygon{expand(slice_poly.contour, scale_(params.brim_width)).front(), brim_hole});
}
if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btInnerOnly) {
Polygons brim_contours = slice_poly.holes;
polygons_reverse(brim_contours);
for (const Polygon &brim_contour : brim_contours) {
Polygons brim_holes = shrink({brim_contour}, scale_(params.brim_width));
polygons_reverse(brim_holes);
ExPolygon inner_brim{brim_contour};
inner_brim.holes = brim_holes;
brim.push_back(inner_brim);
}
}
for (const Polygon &poly : to_polygons(brim)) {
Vec2f p0 = unscaled(poly.first_point()).cast<float>();
for (size_t i = 2; i < poly.points.size(); i++) {
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(poly.points[i]).cast<float>();
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
auto [area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
new_object_part.sticking_area += sign * area;
new_object_part.sticking_centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(),
layer->print_z * area);
new_object_part.sticking_second_moment_of_area_accumulator += sign * second_moment_area;
new_object_part.sticking_second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
}
}
}
return {new_object_part, area_covered_by_extrusions};
}
@ -661,11 +746,12 @@ public:
}
};
SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params)
std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po, const PrintTryCancel &cancel_func, const Params &params)
{
SupportPoints supp_points{};
SupportPoints supp_points{};
SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points);
ActiveObjectParts active_object_parts{};
PartialObjects partial_objects{};
LD prev_layer_ext_perim_lines;
std::unordered_map<size_t, size_t> prev_slice_idx_to_object_part_mapping;
@ -673,6 +759,14 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
std::unordered_map<size_t, SliceConnection> prev_slice_idx_to_weakest_connection;
std::unordered_map<size_t, SliceConnection> next_slice_idx_to_weakest_connection;
auto remember_partial_object = [&active_object_parts, &partial_objects](size_t object_part_id) {
auto object_part = active_object_parts.access(object_part_id);
if (object_part.volume > EPSILON) {
partial_objects.emplace_back(object_part.volume_centroid_accumulator / object_part.volume, object_part.volume,
object_part.connected_to_bed);
}
};
for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) {
cancel_func();
const Layer *layer = po->get_layer(layer_idx);
@ -681,7 +775,7 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) {
const LayerSlice &slice = layer->lslices_ex.at(slice_idx);
auto [new_part, covered_area] = build_object_part_from_slice(slice, layer);
auto [new_part, covered_area] = build_object_part_from_slice(slice_idx, layer, params);
SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer);
#ifdef DETAILED_DEBUG_LOGS
@ -710,7 +804,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
final_part_id = *parts_ids.begin();
for (size_t part_id : parts_ids) {
if (final_part_id != part_id) { active_object_parts.merge(part_id, final_part_id); }
if (final_part_id != part_id) {
remember_partial_object(part_id);
active_object_parts.merge(part_id, final_part_id);
}
}
}
auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) {
@ -771,18 +868,26 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
// Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info,
// and the support presence grid and add the point to the issues.
auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, &params,
&layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) {
&layer_idx](SupportPointCause cause, const Vec3f &support_point, float force,
const Vec2f &dir) {
// if position is taken and point is for global stability (force > 0) or we are too close to the bed, do not add
// This allows local support points (e.g. bridging) to be generated densely
if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) {
return;
}
float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI);
part.add_support_point(support_point, area);
// add the stability effect of the point only if the spot is not taken, so that the densely created local support points do
// not add unrealistic amount of stability to the object (due to overlaping of local support points)
if (!(supports_presence_grid.position_taken(support_point))) {
part.add_support_point(support_point, area);
}
float radius = params.support_points_interface_radius;
supp_points.emplace_back(support_point, force, radius, dir);
if (force > 0) {
supports_presence_grid.take_position(support_point);
}
supp_points.emplace_back(cause, support_point, force, radius, dir);
supports_presence_grid.take_position(support_point);
// The support point also increases the stability of the weakest connection of the object, which should be reflected
if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist
weakest_conn.area += area;
weakest_conn.centroid_accumulator += support_point * area;
@ -801,9 +906,11 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx];
if (entity->role() == ExtrusionRole::BridgeInfill) {
for (const ExtrusionLine &bridge :
check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines,prev_layer_boundary, params)) {
if (bridge.support_point_generated) {
reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero());
check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, prev_layer_boundary,
params)) {
if (bridge.support_point_generated.has_value()) {
reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b),
-EPSILON, Vec2f::Zero());
}
}
}
@ -816,10 +923,13 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
std::vector<ExtrusionLine> perims = check_extrusion_entity_stability(entity, perimeter_region,
prev_layer_ext_perim_lines,prev_layer_boundary, params);
for (const ExtrusionLine &perim : perims) {
if (perim.support_point_generated) {
reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero());
if (perim.support_point_generated.has_value()) {
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON,
Vec2f::Zero());
}
if (perim.is_external_perimeter()) {
current_slice_ext_perims_lines.push_back(perim);
}
if (perim.is_external_perimeter()) { current_slice_ext_perims_lines.push_back(perim); }
}
}
}
@ -828,7 +938,8 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
float unchecked_dist = params.min_distance_between_support_points + 1.0f;
for (const ExtrusionLine &line : current_slice_ext_perims_lines) {
if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) || line.len < EPSILON) {
if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) ||
line.len < EPSILON) {
unchecked_dist += line.len;
} else {
unchecked_dist = line.len;
@ -836,8 +947,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
auto [dist, nidx,
nearest_point] = current_slice_lines_distancer.distance_from_lines_extra<false>(pivot_site_search_point);
Vec3f support_point = create_support_point_position(nearest_point);
auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params);
if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); }
auto [force, cause] = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params);
if (force > 0) {
reckon_new_support_point(cause, support_point, force, (line.b - line.a).normalized());
}
}
}
current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(),
@ -845,11 +958,16 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
} // slice iterations
prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines);
} // layer iterations
return supp_points;
for (const auto& active_obj_pair : prev_slice_idx_to_object_part_mapping) {
remember_partial_object(active_obj_pair.second);
}
return {supp_points, partial_objects};
}
#ifdef DEBUG_FILES
void debug_export(SupportPoints support_points, std::string file_name)
void debug_export(const SupportPoints& support_points,const PartialObjects& objects, std::string file_name)
{
Slic3r::CNumericLocalesSetter locales_setter;
{
@ -860,13 +978,27 @@ void debug_export(SupportPoints support_points, std::string file_name)
}
for (size_t i = 0; i < support_points.size(); ++i) {
if (support_points[i].force <= 0) {
fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1),
support_points[i].position(2), 0.0, 1.0, 0.0);
} else {
fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1),
support_points[i].position(2), 1.0, 0.0, 0.0);
Vec3f color{1.0f, 1.0f, 1.0f};
switch (support_points[i].cause) {
case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED
case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW
case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE
case SupportPointCause::SeparationFromBed: color = {0.0f, 1.0f, 0.0}; break; // GREEN
case SupportPointCause::UnstableFloatingPart: color = {0.105469f, 0.699219f, 0.84375f}; break; // BLUE
case SupportPointCause::WeakObjectPart: color = {0.609375f, 0.210938f, 0.621094f}; break; // PURPLE
}
fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1),
support_points[i].position(2), color[0], color[1], color[2]);
}
for (size_t i = 0; i < objects.size(); ++i) {
Vec3f color{1.0f, 0.0f, 1.0f};
if (objects[i].connected_to_bed) {
color = {1.0f, 0.0f, 0.0f};
}
fprintf(fp, "v %f %f %f %f %f %f\n", objects[i].centroid(0), objects[i].centroid(1), objects[i].centroid(2), color[0],
color[1], color[2]);
}
fclose(fp);
@ -874,17 +1006,15 @@ void debug_export(SupportPoints support_points, std::string file_name)
}
#endif
// std::vector<size_t> quick_search(const PrintObject *po, const Params &params) {
// return {};
// }
SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params)
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params)
{
SupportPoints supp_points = check_stability(po, cancel_func, params);
auto results = check_stability(po, cancel_func, params);
#ifdef DEBUG_FILES
debug_export(supp_points, "issues");
auto [supp_points, objects] = results;
debug_export(supp_points, objects, "issues");
#endif
return supp_points;
return results;
}
void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, const Params &params)
@ -973,6 +1103,9 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
std::vector<ExtrusionLine> current_layer_lines;
for (const LayerRegion *layer_region : l->regions()) {
for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) {
if (!extrusion->role().is_external_perimeter()) continue;
Points extrusion_pts;
extrusion->collect_points(extrusion_pts);
float flow_width = get_flow_width(layer_region, extrusion->role());
@ -1028,5 +1161,84 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
#endif
}
void raise_alerts_for_issues(const SupportPoints &support_points,
PartialObjects &partial_objects,
std::function<void(PrintStateBase::WarningLevel, SupportPointCause)> alert_fn)
{
for (const SupportPoint &sp : support_points) {
if (sp.cause == SupportPointCause::SeparationFromBed) {
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::SeparationFromBed);
break;
}
}
std::reverse(partial_objects.begin(), partial_objects.end());
std::sort(partial_objects.begin(), partial_objects.end(),
[](const PartialObject &left, const PartialObject &right) { return left.volume > right.volume; });
float max_volume_part = partial_objects.front().volume;
for (const PartialObject &p : partial_objects) {
if (p.volume > max_volume_part / 500.0f && !p.connected_to_bed) {
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart);
return;
}
}
for (const SupportPoint &sp : support_points) {
if (sp.cause == SupportPointCause::UnstableFloatingPart) {
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart);
return;
}
}
std::vector<SupportPoint> ext_supp_points{};
ext_supp_points.reserve(support_points.size());
for (const SupportPoint &sp : support_points) {
switch (sp.cause) {
case SupportPointCause::FloatingBridgeAnchor:
case SupportPointCause::FloatingExtrusion: ext_supp_points.push_back(sp); break;
default: break;
}
}
auto coord_fn = [&ext_supp_points](size_t idx, size_t dim) { return ext_supp_points[idx].position[dim]; };
KDTreeIndirect<3, float, decltype(coord_fn)> ext_points_tree{coord_fn, ext_supp_points.size()};
for (const SupportPoint &sp : ext_supp_points) {
auto cluster = find_nearby_points(ext_points_tree, sp.position, 3.0);
int score = 0;
bool floating_bridge = false;
for (size_t idx : cluster) {
score += ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor ? 3 : 1;
floating_bridge = floating_bridge || ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor;
}
if (score > 5) {
if (floating_bridge) {
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingBridgeAnchor);
} else {
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingExtrusion);
}
return;
}
}
if (ext_supp_points.size() > 5) {
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::FloatingExtrusion);
}
for (const SupportPoint &sp : support_points) {
if (sp.cause == SupportPointCause::LongBridge) {
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::LongBridge);
break;
}
}
for (const SupportPoint &sp : support_points) {
if (sp.cause == SupportPointCause::WeakObjectPart) {
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::WeakObjectPart);
break;
}
}
}
} // namespace SupportSpotsGenerator
} // namespace Slic3r

View File

@ -4,43 +4,52 @@
#include "Layer.hpp"
#include "Line.hpp"
#include "PrintBase.hpp"
#include "PrintConfig.hpp"
#include <boost/log/trivial.hpp>
#include <cstddef>
#include <vector>
namespace Slic3r {
namespace SupportSpotsGenerator {
struct Params {
Params(const std::vector<std::string> &filament_types) {
struct Params
{
Params(
const std::vector<std::string> &filament_types, float max_acceleration, int raft_layers_count, BrimType brim_type, float brim_width)
: max_acceleration(max_acceleration), raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width)
{
if (filament_types.size() > 1) {
BOOST_LOG_TRIVIAL(warning)
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
}
if (filament_types.empty() || filament_types[0].empty()) {
BOOST_LOG_TRIVIAL(error)
<< "SupportSpotsGenerator error: empty filament_type";
BOOST_LOG_TRIVIAL(error) << "SupportSpotsGenerator error: empty filament_type";
filament_type = std::string("PLA");
} else {
filament_type = filament_types[0];
BOOST_LOG_TRIVIAL(debug)
<< "SupportSpotsGenerator: applying filament type: " << filament_type;
BOOST_LOG_TRIVIAL(debug) << "SupportSpotsGenerator: applying filament type: " << filament_type;
}
}
// the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2]
const float bridge_distance = 12.0f; //mm
const float bridge_distance = 12.0f; // mm
const float max_acceleration; // mm/s^2 ; max acceleration of object in XY -- should be applicable only to printers with bed slinger,
// however we do not have such info yet. The force is usually small anyway, so not such a big deal to include it everytime
const int raft_layers_count;
std::string filament_type;
BrimType brim_type;
const float brim_width;
const std::pair<float,float> malformation_distance_factors = std::pair<float, float> { 0.4, 1.2 };
const float max_curled_height_factor = 10.0f;
const float min_distance_between_support_points = 3.0f; //mm
const float support_points_interface_radius = 1.5f; // mm
const float connections_min_considerable_area = 1.5f; //mm^2
const float min_distance_to_allow_local_supports = 1.0f; //mm
std::string filament_type;
const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position.
const float max_acceleration = 9 * 1000.0f; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the jerk phase, so the usual machine limits are too low)
const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important
const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials.
const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... );
@ -48,10 +57,16 @@ struct Params {
// MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface
double get_bed_adhesion_yield_strength() const {
if (raft_layers_count > 0) {
return get_support_spots_adhesion_strength() * 2.0;
}
if (filament_type == "PLA") {
return 0.018 * 1e6;
} else if (filament_type == "PET" || filament_type == "PETG") {
return 0.3 * 1e6;
} else if (filament_type == "ABS" || filament_type == "ASA") {
return 0.1 * 1e6; //TODO do measurements
} else { //PLA default value - defensive approach, PLA has quite low adhesion
return 0.018 * 1e6;
}
@ -63,11 +78,51 @@ struct Params {
}
};
struct SupportPoint {
SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction);
enum class SupportPointCause {
LongBridge, // point generated on bridge and straight perimeter extrusion longer than the allowed length
FloatingBridgeAnchor, // point generated on unsupported bridge endpoint
FloatingExtrusion, // point generated on extrusion that does not hold on its own
SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help)
UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here)
WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass)
};
// The support points can be sorted into two groups
// 1. Local extrusion support for extrusions that are printed in the air and would not
// withstand on their own (too long bridges, sharp turns in large overhang, concave bridge holes, etc.)
// These points have negative force (-EPSILON) and Vec2f::Zero() direction
// The algorithm still expects that these points will be supported and accounts for them in the global stability check.
// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line
// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts.
// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference
// between forces that destabilize the object (extruder conflicts with curled filament, weight if instable center of mass, bed movements etc)
// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass).
// Note that the force is only the difference - the amount needed to stabilize the object again.
struct SupportPoint
{
SupportPoint(SupportPointCause cause, const Vec3f &position, float force, float spot_radius, const Vec2f &direction)
: cause(cause), position(position), force(force), spot_radius(spot_radius), direction(direction)
{}
bool is_local_extrusion_support() const
{
return cause == SupportPointCause::LongBridge || cause == SupportPointCause::FloatingExtrusion;
}
bool is_global_object_support() const { return !is_local_extrusion_support(); }
SupportPointCause cause; // reason why this support point was generated. Used for the user alerts
// position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes
Vec3f position;
// force that destabilizes the object to the point of falling/breaking. g*mm/s^2 units
// It is valid only for global_object_support. For local extrusion support points, the force is -EPSILON
// values gathered from large XL model: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103
// For reference 18713800 is weight of 1.8 Kg object, 329103 is weight of 0.03 Kg
// The final sliced object weight was approx 0.5 Kg
float force;
// Expected spot size. The support point strength is calculated from the area defined by this value.
// Currently equal to the support_points_interface_radius parameter above
float spot_radius;
// direction of the fall of the object (z part is neglected)
Vec2f direction;
};
@ -77,13 +132,28 @@ struct Malformations {
std::vector<Lines> layers; //for each layer
};
// std::vector<size_t> quick_search(const PrintObject *po, const Params &params);
SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params);
struct PartialObject
{
PartialObject(Vec3f centroid, float volume, bool connected_to_bed)
: centroid(centroid), volume(volume), connected_to_bed(connected_to_bed)
{}
void estimate_supports_malformations(std::vector<SupportLayer*> &layers, float supports_flow_width, const Params &params);
void estimate_malformations(std::vector<Layer*> &layers, const Params &params);
Vec3f centroid;
float volume;
bool connected_to_bed;
};
} // namespace SupportSpotsGenerator
}
using PartialObjects = std::vector<PartialObject>;
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params);
void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params &params);
void estimate_malformations(std::vector<Layer *> &layers, const Params &params);
void raise_alerts_for_issues(const SupportPoints &support_points,
PartialObjects &partial_objects,
std::function<void(PrintStateBase::WarningLevel, SupportPointCause)> alert_fn);
}} // namespace Slic3r::SupportSpotsGenerator
#endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */

View File

@ -18,6 +18,8 @@
#include "PrintConfig.hpp"
#include "Utils.hpp"
#include <string_view>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
@ -26,6 +28,8 @@
namespace Slic3r::FFFTreeSupport
{
using namespace std::literals;
// or warning
// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL()
#define error_level_not_in_cache error
@ -50,6 +54,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
this->layer_height = scaled<coord_t>(config.layer_height.value);
this->resolution = scaled<coord_t>(print_config.gcode_resolution.value);
// Arache feature
this->min_feature_size = scaled<coord_t>(config.min_feature_size.value);
// +1 makes the threshold inclusive
this->support_angle = 0.5 * M_PI - std::clamp<double>((config.support_material_threshold + 1) * M_PI / 180., 0., 0.5 * M_PI);
@ -84,6 +89,14 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
// this->minimum_support_area =
// this->minimum_bottom_area =
// this->support_offset =
// this->support_tree_branch_distance = 2.5 * line_width ??
this->support_tree_angle = std::clamp<double>(config.support_tree_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON);
this->support_tree_angle_slow = std::clamp<double>(config.support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON);
this->support_tree_branch_diameter = scaled<coord_t>(config.support_tree_branch_diameter.value);
this->support_tree_branch_diameter_angle = std::clamp<double>(config.support_tree_branch_diameter_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON);
this->support_tree_top_rate = config.support_tree_top_rate.value; // percent
// this->support_tree_tip_diameter = this->support_line_width;
this->support_tree_tip_diameter = std::clamp(scaled<coord_t>(config.support_tree_tip_diameter.value), 0, this->support_tree_branch_diameter);
}
//FIXME Machine border is currently ignored.
@ -336,12 +349,20 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Collision requested.", false);
tree_supports_show_error("Not precalculated Collision requested."sv, false);
}
const_cast<TreeModelVolumes*>(this)->calculateCollision(radius, layer_idx);
return getCollision(orig_radius, layer_idx, min_xy_dist);
}
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
// It is expected that the collision area is precalculated for a given layer at least for the radius zero.
// Used for pushing tree supports away from object during the final Organic optimization step.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> TreeModelVolumes::get_collision_lower_bound_area(LayerIndex layer_id, coord_t max_radius) const
{
return m_collision_cache.get_lower_bound_area({ max_radius, layer_id });
}
// Private. Only called internally by calculateAvoidance() and calculateAvoidanceToModel(), radius is already snapped to grid.
const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const
{
@ -351,7 +372,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Holefree Collision requested.", false);
tree_supports_show_error("Not precalculated Holefree Collision requested."sv, false);
}
const_cast<TreeModelVolumes*>(this)->calculateCollisionHolefree({ radius, layer_idx });
return getCollisionHolefree(radius, layer_idx);
@ -375,10 +396,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI
if (m_precalculated) {
if (to_model) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false);
tree_supports_show_error("Not precalculated Avoidance(to model) requested."sv, false);
} else {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested.", false);
tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested."sv, false);
}
}
const_cast<TreeModelVolumes*>(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model);
@ -396,7 +417,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L
return (*result).get();
if (m_precalculated) {
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
tree_supports_show_error("Not precalculated Placeable areas requested.", false);
tree_supports_show_error("Not precalculated Placeable areas requested."sv, false);
}
const_cast<TreeModelVolumes*>(this)->calculatePlaceables(radius, layer_idx);
return getPlaceableAreas(orig_radius, layer_idx);
@ -422,7 +443,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius,
tree_supports_show_error(
min_xy_dist ?
"Not precalculated Wall restriction of minimum xy distance requested )." :
"Not precalculated Wall restriction requested )."
"Not precalculated Wall restriction requested )."sv
, false);
}
const_cast<TreeModelVolumes*>(this)->calculateWallRestrictions({ radius, layer_idx });
@ -561,7 +582,8 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerP
tbb::parallel_for(tbb::blocked_range<LayerIndex>(0, max_layer + 1, keys.size()),
[&](const tbb::blocked_range<LayerIndex> &range) {
RadiusLayerPolygonCacheData data;
std::vector<std::pair<RadiusLayerPair, Polygons>> data;
data.reserve(range.size() * keys.size());
for (LayerIndex layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (RadiusLayerPair key : keys)
if (layer_idx <= key.second) {
@ -572,10 +594,10 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerP
coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - radius;
assert(increase_radius_ceil > 0);
// this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area.
data[RadiusLayerPair(radius, layer_idx)] = polygons_simplify(
offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)),
data.emplace_back(RadiusLayerPair(radius, layer_idx), polygons_simplify(
offset(union_ex(this->getCollision(m_increase_until_radius, layer_idx, false)),
5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution),
m_min_resolution);
m_min_resolution));
}
}
m_collision_cache_holefree.insert(std::move(data));
@ -819,13 +841,25 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const
return out;
}
void TreeModelVolumes::RadiusLayerPolygonCache::allocate_layers(size_t num_layers)
{
if (num_layers > m_data.size()) {
if (num_layers > m_data.capacity())
reserve_power_of_2(m_data, num_layers);
m_data.resize(num_layers, {});
}
}
// For debugging purposes, sorted by layer index, then by radius.
std::vector<std::pair<TreeModelVolumes::RadiusLayerPair, std::reference_wrapper<const Polygons>>> TreeModelVolumes::RadiusLayerPolygonCache::sorted() const
{
std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> out;
for (auto it = this->data.begin(); it != this->data.end(); ++ it)
out.emplace_back(it->first, it->second);
std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; });
for (auto &layer : m_data) {
auto layer_idx = LayerIndex(&layer - m_data.data());
for (auto &radius_polygons : layer)
out.emplace_back(std::make_pair(radius_polygons.first, layer_idx), radius_polygons.second);
}
assert(std::is_sorted(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; }));
return out;
}

View File

@ -11,7 +11,6 @@
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <boost/functional/hash.hpp>
@ -43,7 +42,7 @@ struct TreeSupportMeshGroupSettings {
// the print will be less accurate, but the g-code will be smaller. Maximum Deviation is a limit for Maximum Resolution,
// so if the two conflict the Maximum Deviation will always be held true.
coord_t resolution { scaled<coord_t>(0.025) };
// Minimum Feature Size (aka minimum line width)
// Minimum Feature Size (aka minimum line width) - Arachne specific
// Minimum thickness of thin features. Model features that are thinner than this value will not be printed, while features thicker
// than the Minimum Feature Size will be widened to the Minimum Wall Line Width.
coord_t min_feature_size { scaled<coord_t>(0.1) };
@ -183,7 +182,7 @@ struct TreeSupportMeshGroupSettings {
// 5%-35%
double support_tree_top_rate { 15. };
// Tree Support Tip Diameter
// The diameter of the top of the tip of the branches of tree support."
// The diameter of the top of the tip of the branches of tree support.
// minimum: min_wall_line_width, minimum warning: min_wall_line_width+0.05, maximum_value: support_tree_branch_diameter, value: support_line_width
coord_t support_tree_tip_diameter { scaled<coord_t>(0.4) };
@ -238,6 +237,11 @@ public:
*/
const Polygons& getCollision(const coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const;
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
// It is expected that the collision area is precalculated for a given layer at least for the radius zero.
// Used for pushing tree supports away from object during the final Organic optimization step.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> get_collision_lower_bound_area(LayerIndex layer_id, coord_t max_radius) const;
/*!
* \brief Provides the areas that have to be avoided by the tree's branches
* in order to reach the build plate.
@ -306,36 +310,36 @@ private:
* \brief Convenience typedef for the keys to the caches
*/
using RadiusLayerPair = std::pair<coord_t, LayerIndex>;
using RadiusLayerPolygonCacheData = std::unordered_map<RadiusLayerPair, Polygons, boost::hash<RadiusLayerPair>>;
class RadiusLayerPolygonCache {
// Map from radius to Polygons. Cache of one layer collision regions.
using LayerData = std::map<coord_t, Polygons>;
// Vector of layers, at each layer map of radius to Polygons.
// Reference to Polygons returned shall be stable to insertion.
using Layers = std::vector<LayerData>;
public:
RadiusLayerPolygonCache() = default;
RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : data(std::move(rhs.data)) {}
RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { data = std::move(rhs.data); return *this; }
RadiusLayerPolygonCache(RadiusLayerPolygonCache &&rhs) : m_data(std::move(rhs.m_data)) {}
RadiusLayerPolygonCache& operator=(RadiusLayerPolygonCache &&rhs) { m_data = std::move(rhs.m_data); return *this; }
RadiusLayerPolygonCache(const RadiusLayerPolygonCache&) = delete;
RadiusLayerPolygonCache& operator=(const RadiusLayerPolygonCache&) = delete;
void insert(RadiusLayerPolygonCacheData &&in) {
std::lock_guard<std::mutex> guard(this->mutex);
for (auto& d : in)
this->data.emplace(d.first, std::move(d.second));
}
void insert(std::vector<std::pair<RadiusLayerPair, Polygons>> &&in) {
std::lock_guard<std::mutex> guard(this->mutex);
for (auto& d : in)
this->data.emplace(d.first, std::move(d.second));
std::lock_guard<std::mutex> guard(m_mutex);
for (auto &d : in)
this->get_allocate_layer_data(d.first.second).emplace(d.first.first, std::move(d.second));
}
// by layer
void insert(std::vector<std::pair<coord_t, Polygons>> &&in, coord_t radius) {
std::lock_guard<std::mutex> guard(this->mutex);
std::lock_guard<std::mutex> guard(m_mutex);
for (auto &d : in)
this->data.emplace(RadiusLayerPair{ radius, d.first }, std::move(d.second));
this->get_allocate_layer_data(d.first).emplace(radius, std::move(d.second));
}
void insert(std::vector<Polygons> &&in, coord_t first_layer_idx, coord_t radius) {
std::lock_guard<std::mutex> guard(this->mutex);
std::lock_guard<std::mutex> guard(m_mutex);
allocate_layers(first_layer_idx + in.size());
for (auto &d : in)
this->data.emplace(RadiusLayerPair{ radius, first_layer_idx ++ }, std::move(d));
m_data[first_layer_idx ++].emplace(radius, std::move(d));
}
/*!
* \brief Checks a cache for a given RadiusLayerPair and returns it if it is found
@ -343,11 +347,30 @@ private:
* \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found)
*/
std::optional<std::reference_wrapper<const Polygons>> getArea(const TreeModelVolumes::RadiusLayerPair &key) const {
std::lock_guard<std::mutex> guard(this->mutex);
const auto it = this->data.find(key);
return it == this->data.end() ?
std::lock_guard<std::mutex> guard(m_mutex);
if (key.second >= m_data.size())
return std::optional<std::reference_wrapper<const Polygons>>{};
const auto &layer = m_data[key.second];
auto it = layer.find(key.first);
return it == layer.end() ?
std::optional<std::reference_wrapper<const Polygons>>{} : std::optional<std::reference_wrapper<const Polygons>>{ it->second };
}
// Get a collision area at a given layer for a radius that is a lower or equial to the key radius.
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> get_lower_bound_area(const TreeModelVolumes::RadiusLayerPair &key) const {
std::lock_guard<std::mutex> guard(m_mutex);
if (key.second >= m_data.size())
return {};
const auto &layer = m_data[key.second];
if (layer.empty())
return {};
auto it = layer.lower_bound(key.first);
if (it == layer.end() || it->first != key.first) {
if (it == layer.begin())
return {};
-- it;
}
return std::make_pair(it->first, std::reference_wrapper<const Polygons>(it->second));
}
/*!
* \brief Get the highest already calculated layer in the cache.
* \param radius The radius for which the highest already calculated layer has to be found.
@ -356,22 +379,27 @@ private:
* \return A wrapped optional reference of the requested area (if it was found, an empty optional if nothing was found)
*/
LayerIndex getMaxCalculatedLayer(coord_t radius) const {
std::lock_guard<std::mutex> guard(this->mutex);
int max_layer = -1;
// the placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist.
if (this->data.find({ radius, 1 }) != this->data.end())
max_layer = 1;
while (this->data.count(TreeModelVolumes::RadiusLayerPair(radius, max_layer + 1)) > 0)
++ max_layer;
return max_layer;
std::lock_guard<std::mutex> guard(m_mutex);
auto layer_idx = LayerIndex(m_data.size()) - 1;
for (; layer_idx > 0; -- layer_idx)
if (const auto &layer = m_data[layer_idx]; layer.find(radius) != layer.end())
break;
// The placeable on model areas do not exist on layer 0, as there can not be model below it. As such it may be possible that layer 1 is available, but layer 0 does not exist.
return layer_idx == 0 ? -1 : layer_idx;
}
// For debugging purposes, sorted by layer index, then by radius.
[[nodiscard]] std::vector<std::pair<RadiusLayerPair, std::reference_wrapper<const Polygons>>> sorted() const;
private:
RadiusLayerPolygonCacheData data;
mutable std::mutex mutex;
LayerData& get_allocate_layer_data(LayerIndex layer_idx) {
allocate_layers(layer_idx + 1);
return m_data[layer_idx];
}
void allocate_layers(size_t num_layers);
Layers m_data;
mutable std::mutex m_mutex;
};

View File

@ -8,6 +8,7 @@
#include "TreeSupport.hpp"
#include "AABBTreeIndirect.hpp"
#include "AABBTreeLines.hpp"
#include "BuildVolume.hpp"
#include "ClipperUtils.hpp"
#include "EdgeGrid.hpp"
@ -20,7 +21,7 @@
#include "MutablePolygon.hpp"
#include "SupportMaterial.hpp"
#include "TriangleMeshSlicer.hpp"
#include "OpenVDBUtils.hpp"
#include "OpenVDBUtilsLegacy.hpp"
#include <openvdb/tools/VolumeToSpheres.h>
#include <cassert>
@ -40,6 +41,10 @@
#include <tbb/parallel_for.h>
#include <tbb/spin_mutex.h>
#if defined(TREE_SUPPORT_SHOW_ERRORS) && defined(_WIN32)
#define TREE_SUPPORT_SHOW_ERRORS_WIN32
#endif
namespace Slic3r
{
@ -58,6 +63,7 @@ enum class LineStatus
using LineInformation = std::vector<std::pair<Point, LineStatus>>;
using LineInformations = std::vector<LineInformation>;
using namespace std::literals;
static inline void validate_range(const Point &pt)
{
@ -103,16 +109,16 @@ static inline void validate_range(const LineInformations &lines)
static inline void check_self_intersections(const Polygons &polygons, const std::string_view message)
{
#ifdef _WIN32
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
if (!intersecting_edges(polygons).empty())
::MessageBoxA(nullptr, (std::string("TreeSupport infill self intersections: ") + std::string(message)).c_str(), "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // _WIN32
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
}
static inline void check_self_intersections(const ExPolygon &expoly, const std::string_view message)
{
#ifdef _WIN32
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
check_self_intersections(to_polygons(expoly), message);
#endif // _WIN32
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
}
static constexpr const auto tiny_area_threshold = sqr(scaled<double>(0.001));
@ -191,22 +197,21 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
}
#endif
//todo Remove! Only relevant for public BETA!
static bool inline g_showed_critical_error = false;
static bool inline g_showed_performance_warning = false;
void tree_supports_show_error(std::string message, bool critical)
void tree_supports_show_error(std::string_view message, bool critical)
{ // todo Remove! ONLY FOR PUBLIC BETA!!
#if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS)
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
static bool showed_critical = false;
static bool showed_performance = false;
auto bugtype = std::string(critical ? " This is a critical bug. It may cause missing or malformed branches.\n" : "This bug should only decrease performance.\n");
bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning);
(critical ? g_showed_critical_error : g_showed_performance_warning) = true;
if (show)
MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(),
MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + std::string(message) + "\n" + bugtype).c_str(),
"Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // WIN32
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
}
[[nodiscard]] static const std::vector<Polygons> generate_overhangs(const PrintObject &print_object)
@ -572,7 +577,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
// In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here...
BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance <<
") is smaller than 100";
tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing.", true);
tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing."sv, true);
if (next_distance > 2 * current_distance)
// This case should never happen, but better safe than sorry.
break;
@ -644,14 +649,6 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
append(lines, to_polylines(polygons));
return lines;
#else
#ifdef _WIN32
// Max dimensions for MK3
// if (! BoundingBox(Point::new_scale(-170., -170.), Point::new_scale(170., 170.)).contains(get_extents(polygon)))
// Max dimensions for XL
if (! BoundingBox(Point::new_scale(-250., -250.), Point::new_scale(250., 250.)).contains(get_extents(polygon)))
::MessageBoxA(nullptr, "TreeSupport infill kravsky", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // _WIN32
const Flow &flow = roof ? support_params.support_material_interface_flow : support_params.support_material_flow;
std::unique_ptr<Fill> filler = std::unique_ptr<Fill>(Fill::new_from_type(roof ? support_params.interface_fill_pattern : support_params.base_fill_pattern));
FillParams fill_params;
@ -670,20 +667,20 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
for (ExPolygon &expoly : union_ex(polygon)) {
// The surface type does not matter.
assert(area(expoly) > 0.);
#ifdef _WIN32
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
if (area(expoly) <= 0.)
::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // _WIN32
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
assert(intersecting_edges(to_polygons(expoly)).empty());
check_self_intersections(expoly, "generate_support_infill_lines");
Surface surface(stInternal, std::move(expoly));
try {
Polylines pl = filler->fill_surface(&surface, fill_params);
assert(pl.empty() || get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl)));
#ifdef _WIN32
#ifdef TREE_SUPPORT_SHOW_ERRORS_WIN32
if (! pl.empty() && ! get_extents(surface.expolygon).inflated(SCALED_EPSILON).contains(get_extents(pl)))
::MessageBoxA(nullptr, "TreeSupport infill failure", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // _WIN32
#endif // TREE_SUPPORT_SHOW_ERRORS_WIN32
append(out, std::move(pl));
} catch (InfillFailedException &) {
}
@ -759,7 +756,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret);
if (safe_step_size < 0 || last_step_offset_without_check < 0) {
BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!";
tree_supports_show_error("Negative offset distance... How did you manage this ?", true);
tree_supports_show_error("Negative offset distance... How did you manage this ?"sv, true);
return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret);
}
@ -805,6 +802,14 @@ static double layer_z(const SlicingParameters &slicing_params, const size_t laye
{
return slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height;
}
static LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const double z)
{
return LayerIndex(ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
}
static LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const double z)
{
return LayerIndex(floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
}
static inline SupportGeneratorLayer& layer_initialize(
SupportGeneratorLayer &layer_new,
@ -951,7 +956,7 @@ static void generate_initial_areas(
bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE;
if (!mesh_config.support_rests_on_model && !to_bp) {
BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point";
tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly.", true);
tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly."sv, true);
return;
}
Polygons circle{ base_circle };
@ -1517,7 +1522,7 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
if (area(check_layer_data) < tiny_area_threshold) {
BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " <<
volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance);
tree_supports_show_error("Area lost catching up radius. May not cause visible malformation.", true);
tree_supports_show_error("Area lost catching up radius. May not cause visible malformation."sv, true);
}
}
}
@ -1783,7 +1788,7 @@ static void increase_areas_one_layer(
"Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx <<
" Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist <<
" to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until;
tree_supports_show_error("Potentially lost branch!", true);
tree_supports_show_error("Potentially lost branch!"sv, true);
} else
result = increase_single_area(volumes, config, settings, layer_idx, parent,
settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer);
@ -2270,7 +2275,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp
if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) {
if (area(elem.areas.influence_areas) < tiny_area_threshold) {
BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1;
tree_supports_show_error("Insert error of area after bypassing merge.\n", true);
tree_supports_show_error("Insert error of area after bypassing merge.\n"sv, true);
}
// Move the area to output.
this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas));
@ -2303,7 +2308,7 @@ static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupp
Polygons new_area = safe_union(elem.areas.influence_areas);
if (area(new_area) < tiny_area_threshold) {
BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate;
tree_supports_show_error("Insert error of area after merge.\n", true);
tree_supports_show_error("Insert error of area after merge.\n"sv, true);
}
this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area));
}
@ -2331,7 +2336,7 @@ static void set_points_on_areas(const SupportElement &elem, SupportElements *lay
// Based on the branch center point of the current layer, the point on the next (further up) layer is calculated.
if (! elem.state.result_on_layer_is_set()) {
BOOST_LOG_TRIVIAL(error) << "Uninitialized support element";
tree_supports_show_error("Uninitialized support element. A branch may be missing.\n", true);
tree_supports_show_error("Uninitialized support element. A branch may be missing.\n"sv, true);
return;
}
@ -2394,7 +2399,7 @@ static void set_to_model_contact_to_model_gracious(
// Could not find valid placement, even though it should exist => error handling
if (last_successfull_layer == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx;
tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true);
tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches."sv, true);
first_elem.state.to_model_gracious = false;
set_to_model_contact_simple(first_elem);
} else {
@ -2494,7 +2499,7 @@ static void create_nodes_from_area(
if (elem.state.to_buildplate) {
BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") "
"at target_height: " << elem.state.target_height << " layer: " << layer_idx;
tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially.", true);
tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially."sv, true);
}
// we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set
elem.state.deleted = true;
@ -2537,6 +2542,7 @@ static void create_nodes_from_area(
double radius_increase = config.getRadius(elem.state) - config.getRadius(parent.state);
assert(radius_increase >= 0);
double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast<double>().norm();
//FIXME this assert fails a lot. Is it correct?
assert(shift < radius_increase + 2. * config.maximum_move_distance_slow);
}
}
@ -2561,6 +2567,7 @@ static void create_nodes_from_area(
double radius_increase = config.getRadius(elem.state) - config.getRadius(parent.state);
assert(radius_increase >= 0);
double shift = (elem.state.result_on_layer - parent.state.result_on_layer).cast<double>().norm();
//FIXME this assert fails a lot. Is it correct?
assert(shift < radius_increase + 2. * config.maximum_move_distance_slow);
}
}
@ -3382,6 +3389,306 @@ static void extrude_branch(
}
#endif
// #define TREE_SUPPORT_ORGANIC_NUDGE_NEW 1
#ifdef TREE_SUPPORT_ORGANIC_NUDGE_NEW
// New version using per layer AABB trees of lines for nudging spheres away from an object.
static void organic_smooth_branches_avoid_collisions(
const PrintObject &print_object,
const TreeModelVolumes &volumes,
const TreeSupportSettings &config,
std::vector<SupportElements> &move_bounds,
const std::vector<std::pair<SupportElement*, int>> &elements_with_link_down,
const std::vector<size_t> &linear_data_layers)
{
struct LayerCollisionCache {
coord_t min_element_radius{ std::numeric_limits<coord_t>::max() };
bool min_element_radius_known() const { return this->min_element_radius != std::numeric_limits<coord_t>::max(); }
coord_t collision_radius{ 0 };
std::vector<Linef> lines;
AABBTreeIndirect::Tree<2, double> aabbtree_lines;
bool empty() const { return this->lines.empty(); }
};
std::vector<LayerCollisionCache> layer_collision_cache;
layer_collision_cache.reserve(1024);
const SlicingParameters &slicing_params = print_object.slicing_parameters();
for (const std::pair<SupportElement*, int>& element : elements_with_link_down) {
LayerIndex layer_idx = element.first->state.layer_idx;
if (size_t num_layers = layer_idx + 1; num_layers > layer_collision_cache.size()) {
if (num_layers > layer_collision_cache.capacity())
reserve_power_of_2(layer_collision_cache, num_layers);
layer_collision_cache.resize(num_layers, {});
}
auto& l = layer_collision_cache[layer_idx];
l.min_element_radius = std::min(l.min_element_radius, config.getRadius(element.first->state));
}
for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_collision_cache.size()); ++layer_idx)
if (LayerCollisionCache& l = layer_collision_cache[layer_idx]; !l.min_element_radius_known())
l.min_element_radius = 0;
else {
//FIXME
l.min_element_radius = 0;
std::optional<std::pair<coord_t, std::reference_wrapper<const Polygons>>> res = volumes.get_collision_lower_bound_area(layer_idx, l.min_element_radius);
assert(res.has_value());
l.collision_radius = res->first;
Lines alines = to_lines(res->second.get());
l.lines.reserve(alines.size());
for (const Line &line : alines)
l.lines.push_back({ unscaled<double>(line.a), unscaled<double>(line.b) });
l.aabbtree_lines = AABBTreeLines::build_aabb_tree_over_indexed_lines(l.lines);
}
struct CollisionSphere {
const SupportElement& element;
int element_below_id;
const bool locked;
float radius;
// Current position, when nudged away from the collision.
Vec3f position;
// Previous position, for Laplacian smoothing.
Vec3f prev_position;
//
Vec3f last_collision;
double last_collision_depth;
// Minimum Z for which the sphere collision will be evaluated.
// Limited by the minimum sloping angle and by the bottom of the tree.
float min_z{ -std::numeric_limits<float>::max() };
// Maximum Z for which the sphere collision will be evaluated.
// Limited by the minimum sloping angle and by the tip of the current branch.
float max_z{ std::numeric_limits<float>::max() };
uint32_t layer_begin;
uint32_t layer_end;
};
std::vector<CollisionSphere> collision_spheres;
collision_spheres.reserve(elements_with_link_down.size());
for (const std::pair<SupportElement*, int> &element_with_link : elements_with_link_down) {
const SupportElement &element = *element_with_link.first;
const int link_down = element_with_link.second;
collision_spheres.push_back({
element,
link_down,
// locked
element.parents.empty() || (link_down == -1 && element.state.layer_idx > 0),
unscaled<float>(config.getRadius(element.state)),
// 3D position
to_3d(unscaled<float>(element.state.result_on_layer), float(layer_z(slicing_params, element.state.layer_idx)))
});
// Update min_z coordinate to min_z of the tree below.
CollisionSphere &collision_sphere = collision_spheres.back();
if (link_down != -1) {
const size_t offset_below = linear_data_layers[element.state.layer_idx - 1];
collision_sphere.min_z = collision_spheres[offset_below + link_down].min_z;
} else
collision_sphere.min_z = collision_sphere.position.z();
}
// Update max_z by propagating max_z from the tips of the branches.
for (int collision_sphere_id = int(collision_spheres.size()) - 1; collision_sphere_id >= 0; -- collision_sphere_id) {
CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id];
if (collision_sphere.element.parents.empty())
// Tip
collision_sphere.max_z = collision_sphere.position.z();
else {
// Below tip
const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1];
for (auto iparent : collision_sphere.element.parents) {
float parent_z = collision_spheres[offset_above + iparent].max_z;
// collision_sphere.max_z = collision_sphere.max_z == std::numeric_limits<float>::max() ? parent_z : std::max(collision_sphere.max_z, parent_z);
collision_sphere.max_z = std::min(collision_sphere.max_z, parent_z);
}
}
}
// Update min_z / max_z to limit the search Z span of a given sphere for collision detection.
for (CollisionSphere &collision_sphere : collision_spheres) {
//FIXME limit the collision span by the tree slope.
collision_sphere.min_z = std::max(collision_sphere.min_z, collision_sphere.position.z() - collision_sphere.radius);
collision_sphere.max_z = std::min(collision_sphere.max_z, collision_sphere.position.z() + collision_sphere.radius);
collision_sphere.layer_begin = std::min(collision_sphere.element.state.layer_idx, layer_idx_ceil(slicing_params, collision_sphere.min_z));
collision_sphere.layer_end = std::max(collision_sphere.element.state.layer_idx, layer_idx_floor(slicing_params, collision_sphere.max_z)) + 1;
}
static constexpr const double collision_extra_gap = 0.1;
static constexpr const double max_nudge_collision_avoidance = 0.2;
static constexpr const double max_nudge_smoothing = 0.2;
static constexpr const size_t num_iter = 100; // 1000;
for (size_t iter = 0; iter < num_iter; ++ iter) {
// Back up prev position before Laplacian smoothing.
for (CollisionSphere &collision_sphere : collision_spheres)
collision_sphere.prev_position = collision_sphere.position;
std::atomic<size_t> num_moved{ 0 };
tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()),
[&collision_spheres, &layer_collision_cache, &slicing_params, &move_bounds, &linear_data_layers, &num_moved](const tbb::blocked_range<size_t> range) {
for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id)
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
// Calculate collision of multiple 2D layers against a collision sphere.
collision_sphere.last_collision_depth = - std::numeric_limits<double>::max();
for (uint32_t layer_id = collision_sphere.layer_begin; layer_id != collision_sphere.layer_end; ++ layer_id) {
double dz = (layer_id - collision_sphere.element.state.layer_idx) * slicing_params.layer_height;
if (double r2 = sqr(collision_sphere.radius) - sqr(dz); r2 > 0) {
if (const LayerCollisionCache &layer_collision_cache_item = layer_collision_cache[layer_id]; ! layer_collision_cache.empty()) {
size_t hit_idx_out;
Vec2d hit_point_out;
double dist = sqrt(AABBTreeLines::squared_distance_to_indexed_lines(
layer_collision_cache_item.lines, layer_collision_cache_item.aabbtree_lines, Vec2d(to_2d(collision_sphere.position).cast<double>()),
hit_idx_out, hit_point_out, r2));
double collision_depth = sqrt(r2) - dist;
if (collision_depth > collision_sphere.last_collision_depth) {
collision_sphere.last_collision_depth = collision_depth;
collision_sphere.last_collision = to_3d(hit_point_out.cast<float>(), float(layer_z(slicing_params, layer_id)));
}
}
}
}
if (collision_sphere.last_collision_depth > 0) {
// Collision detected to be removed.
// Nudge the circle center away from the collision.
if (collision_sphere.last_collision_depth > EPSILON)
// a little bit of hysteresis to detect end of
++ num_moved;
// Shift by maximum 2mm.
double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance);
Vec2d nudge_vector = (to_2d(collision_sphere.position) - to_2d(collision_sphere.last_collision)).cast<double>().normalized() * nudge_dist;
collision_sphere.position.head<2>() += (nudge_vector * nudge_dist).cast<float>();
}
// Laplacian smoothing
Vec2d avg{ 0, 0 };
const SupportElements &above = move_bounds[collision_sphere.element.state.layer_idx + 1];
const size_t offset_above = linear_data_layers[collision_sphere.element.state.layer_idx + 1];
double weight = 0.;
for (auto iparent : collision_sphere.element.parents) {
double w = collision_sphere.radius;
avg += w * to_2d(collision_spheres[offset_above + iparent].prev_position.cast<double>());
weight += w;
}
if (collision_sphere.element_below_id != -1) {
const size_t offset_below = linear_data_layers[collision_sphere.element.state.layer_idx - 1];
const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state);
avg += w * to_2d(collision_spheres[offset_below + collision_sphere.element_below_id].prev_position.cast<double>());
weight += w;
}
avg /= weight;
static constexpr const double smoothing_factor = 0.5;
Vec2d old_pos = to_2d(collision_sphere.position).cast<double>();
Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg;
Vec2d shift = new_pos - old_pos;
double nudge_dist_max = shift.norm();
// Shift by maximum 1mm, less than the collision avoidance factor.
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing);
collision_sphere.position.head<2>() += (shift.normalized() * nudge_dist).cast<float>();
}
});
// printf("iteration: %d, moved: %d\n", int(iter), int(num_moved));
if (num_moved == 0)
break;
}
for (size_t i = 0; i < collision_spheres.size(); ++ i)
elements_with_link_down[i].first->state.result_on_layer = scaled<coord_t>(to_2d(collision_spheres[i].position));
}
#else // TREE_SUPPORT_ORGANIC_NUDGE_NEW
// Old version using OpenVDB, works but it is extremely slow for complex meshes.
static void organic_smooth_branches_avoid_collisions(
const PrintObject &print_object,
const TreeModelVolumes &volumes,
const TreeSupportSettings &config,
std::vector<SupportElements> &move_bounds,
const std::vector<std::pair<SupportElement*, int>> &elements_with_link_down,
const std::vector<size_t> &linear_data_layers)
{
TriangleMesh mesh = print_object.model_object()->raw_mesh();
mesh.transform(print_object.trafo_centered());
double scale = 10.;
openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, openvdb::math::Transform{}, scale, 0., 0.);
std::unique_ptr<openvdb::tools::ClosestSurfacePoint<openvdb::FloatGrid>> closest_surface_point = openvdb::tools::ClosestSurfacePoint<openvdb::FloatGrid>::create(*grid);
std::vector<openvdb::Vec3R> pts, prev, projections;
std::vector<float> distances;
for (const std::pair<SupportElement*, int>& element : elements_with_link_down) {
Vec3d pt = to_3d(unscaled<double>(element.first->state.result_on_layer), layer_z(print_object.slicing_parameters(), element.first->state.layer_idx)) * scale;
pts.push_back({ pt.x(), pt.y(), pt.z() });
}
const double collision_extra_gap = 1. * scale;
const double max_nudge_collision_avoidance = 2. * scale;
const double max_nudge_smoothing = 1. * scale;
static constexpr const size_t num_iter = 100; // 1000;
for (size_t iter = 0; iter < num_iter; ++ iter) {
prev = pts;
projections = pts;
distances.assign(pts.size(), std::numeric_limits<float>::max());
closest_surface_point->searchAndReplace(projections, distances);
size_t num_moved = 0;
for (size_t i = 0; i < projections.size(); ++ i) {
const SupportElement &element = *elements_with_link_down[i].first;
const int below = elements_with_link_down[i].second;
const bool locked = below == -1 && element.state.layer_idx > 0;
if (! locked && pts[i] != projections[i]) {
// Nudge the circle center away from the collision.
Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() };
double depth = v.norm();
assert(std::abs(distances[i] - depth) < EPSILON);
double radius = unscaled<double>(config.getRadius(element.state)) * scale;
if (depth < radius) {
// Collision detected to be removed.
++ num_moved;
double dxy = sqrt(sqr(radius) - sqr(v.z()));
double nudge_dist_max = dxy - std::hypot(v.x(), v.y())
//FIXME 1mm gap
+ collision_extra_gap;
// Shift by maximum 2mm.
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_collision_avoidance);
Vec2d nudge_v = to_2d(v).normalized() * (- nudge_dist);
pts[i].x() += nudge_v.x();
pts[i].y() += nudge_v.y();
}
}
// Laplacian smoothing
if (! locked && ! element.parents.empty()) {
Vec2d avg{ 0, 0 };
const SupportElements &above = move_bounds[element.state.layer_idx + 1];
const size_t offset_above = linear_data_layers[element.state.layer_idx + 1];
double weight = 0.;
for (auto iparent : element.parents) {
double w = config.getRadius(above[iparent].state);
avg.x() += w * prev[offset_above + iparent].x();
avg.y() += w * prev[offset_above + iparent].y();
weight += w;
}
size_t cnt = element.parents.size();
if (below != -1) {
const size_t offset_below = linear_data_layers[element.state.layer_idx - 1];
const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state);
avg.x() += w * prev[offset_below + below].x();
avg.y() += w * prev[offset_below + below].y();
++ cnt;
weight += w;
}
//avg /= double(cnt);
avg /= weight;
static constexpr const double smoothing_factor = 0.5;
Vec2d old_pos{ pts[i].x(), pts[i].y() };
Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg;
Vec2d shift = new_pos - old_pos;
double nudge_dist_max = shift.norm();
// Shift by maximum 1mm, less than the collision avoidance factor.
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing);
Vec2d nudge_v = shift.normalized() * nudge_dist;
pts[i].x() += nudge_v.x();
pts[i].y() += nudge_v.y();
}
}
// printf("iteration: %d, moved: %d\n", int(iter), int(num_moved));
if (num_moved == 0)
break;
}
for (size_t i = 0; i < projections.size(); ++ i) {
elements_with_link_down[i].first->state.result_on_layer.x() = scaled<coord_t>(pts[i].x()) / scale;
elements_with_link_down[i].first->state.result_on_layer.y() = scaled<coord_t>(pts[i].y()) / scale;
}
}
#endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW
static void draw_branches(
PrintObject &print_object,
const TreeModelVolumes &volumes,
@ -3396,8 +3703,6 @@ static void draw_branches(
{
static int irun = 0;
const SlicingParameters& slicing_params = print_object.slicing_parameters();
// All SupportElements are put into a layer independent storage to improve parallelization.
std::vector<std::pair<SupportElement*, int>> elements_with_link_down;
std::vector<size_t> linear_data_layers;
@ -3436,102 +3741,7 @@ static void draw_branches(
}
}
std::unique_ptr<openvdb::tools::ClosestSurfacePoint<openvdb::FloatGrid>> closest_surface_point;
{
TriangleMesh mesh = print_object.model_object()->raw_mesh();
mesh.transform(print_object.trafo_centered());
double scale = 10.;
openvdb::FloatGrid::Ptr grid = mesh_to_grid(mesh.its, {}, scale, 0., 0.);
closest_surface_point = openvdb::tools::ClosestSurfacePoint<openvdb::FloatGrid>::create(*grid);
std::vector<openvdb::Vec3R> pts, prev, projections;
std::vector<float> distances;
for (const std::pair<SupportElement*, int> &element : elements_with_link_down) {
Vec3d pt = to_3d(unscaled<double>(element.first->state.result_on_layer), layer_z(slicing_params, element.first->state.layer_idx)) * scale;
pts.push_back({ pt.x(), pt.y(), pt.z() });
}
const double collision_extra_gap = 1. * scale;
const double max_nudge_collision_avoidance = 2. * scale;
const double max_nudge_smoothing = 1. * scale;
static constexpr const size_t num_iter = 100; // 1000;
for (size_t iter = 0; iter < num_iter; ++ iter) {
prev = pts;
projections = pts;
distances.assign(pts.size(), std::numeric_limits<float>::max());
closest_surface_point->searchAndReplace(projections, distances);
size_t num_moved = 0;
for (size_t i = 0; i < projections.size(); ++ i) {
const SupportElement &element = *elements_with_link_down[i].first;
const int below = elements_with_link_down[i].second;
const bool locked = below == -1 && element.state.layer_idx > 0;
if (! locked && pts[i] != projections[i]) {
// Nudge the circle center away from the collision.
Vec3d v{ projections[i].x() - pts[i].x(), projections[i].y() - pts[i].y(), projections[i].z() - pts[i].z() };
double depth = v.norm();
assert(std::abs(distances[i] - depth) < EPSILON);
double radius = unscaled<double>(config.getRadius(element.state)) * scale;
if (depth < radius) {
// Collision detected to be removed.
++ num_moved;
double dxy = sqrt(sqr(radius) - sqr(v.z()));
double nudge_dist_max = dxy - std::hypot(v.x(), v.y())
//FIXME 1mm gap
+ collision_extra_gap;
// Shift by maximum 2mm.
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_collision_avoidance);
Vec2d nudge_v = to_2d(v).normalized() * (- nudge_dist);
pts[i].x() += nudge_v.x();
pts[i].y() += nudge_v.y();
}
}
// Laplacian smoothing
if (! locked && ! element.parents.empty()) {
Vec2d avg{ 0, 0 };
const SupportElements &above = move_bounds[element.state.layer_idx + 1];
const size_t offset_above = linear_data_layers[element.state.layer_idx + 1];
double weight = 0.;
for (auto iparent : element.parents) {
double w = config.getRadius(above[iparent].state);
avg.x() += w * prev[offset_above + iparent].x();
avg.y() += w * prev[offset_above + iparent].y();
weight += w;
}
size_t cnt = element.parents.size();
if (below != -1) {
const size_t offset_below = linear_data_layers[element.state.layer_idx - 1];
const double w = weight; // config.getRadius(move_bounds[element.state.layer_idx - 1][below].state);
avg.x() += w * prev[offset_below + below].x();
avg.y() += w * prev[offset_below + below].y();
++ cnt;
weight += w;
}
//avg /= double(cnt);
avg /= weight;
static constexpr const double smoothing_factor = 0.5;
Vec2d old_pos{ pts[i].x(), pts[i].y() };
Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg;
Vec2d shift = new_pos - old_pos;
double nudge_dist_max = shift.norm();
// Shift by maximum 1mm, less than the collision avoidance factor.
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing);
Vec2d nudge_v = shift.normalized() * nudge_dist;
pts[i].x() += nudge_v.x();
pts[i].y() += nudge_v.y();
}
}
printf("iteration: %d, moved: %d\n", int(iter), int(num_moved));
if (num_moved == 0)
break;
}
#if 1
for (size_t i = 0; i < projections.size(); ++ i) {
elements_with_link_down[i].first->state.result_on_layer.x() = scaled<coord_t>(pts[i].x()) / scale;
elements_with_link_down[i].first->state.result_on_layer.y() = scaled<coord_t>(pts[i].y()) / scale;
}
#endif
}
organic_smooth_branches_avoid_collisions(print_object, volumes, config, move_bounds, elements_with_link_down, linear_data_layers);
std::vector<Polygons> support_layer_storage(move_bounds.size());
std::vector<Polygons> support_roof_storage(move_bounds.size());
@ -3543,6 +3753,7 @@ static void draw_branches(
// Traverse all nodes, generate tubes.
// Traversal stack with nodes and thier current parent
const SlicingParameters &slicing_params = print_object.slicing_parameters();
std::vector<SupportElement*> path;
indexed_triangle_set cummulative_mesh;
indexed_triangle_set partial_mesh;

View File

@ -17,7 +17,7 @@
#include "BoundingBox.hpp"
#include "Utils.hpp"
#define TREE_SUPPORT_SHOW_ERRORS
// #define TREE_SUPPORT_SHOW_ERRORS
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
// The various stages of the process can be weighted differently in the progress bar.
@ -576,8 +576,7 @@ public:
}
};
// todo Remove! ONLY FOR PUBLIC BETA!!
void tree_supports_show_error(std::string message, bool critical);
void tree_supports_show_error(std::string_view message, bool critical);
} // namespace FFFTreeSupport

View File

@ -269,12 +269,18 @@ inline int its_triangle_edge_index(const stl_triangle_vertex_indices &triangle_i
using its_triangle = std::array<stl_vertex, 3>;
inline its_triangle its_triangle_vertices(const indexed_triangle_set &its,
const Vec3i &face)
{
return {its.vertices[face(0)],
its.vertices[face(1)],
its.vertices[face(2)]};
}
inline its_triangle its_triangle_vertices(const indexed_triangle_set &its,
size_t face_id)
{
return {its.vertices[its.indices[face_id](0)],
its.vertices[its.indices[face_id](1)],
its.vertices[its.indices[face_id](2)]};
return its_triangle_vertices(its, its.indices[face_id]);
}
inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
@ -346,6 +352,22 @@ inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its)
return {bmin.cast<double>(), bmax.cast<double>()};
}
inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its, const Transform3f &tr)
{
if (its.vertices.empty())
return {};
Vec3f bmin = tr * its.vertices.front(), bmax = tr * its.vertices.front();
for (const Vec3f &p : its.vertices) {
Vec3f pp = tr * p;
bmin = pp.cwiseMin(bmin);
bmax = pp.cwiseMax(bmax);
}
return {bmin.cast<double>(), bmax.cast<double>()};
}
}
// Serialization through the Cereal library

View File

@ -176,6 +176,21 @@ template<class T> size_t next_highest_power_of_2(T v,
return next_highest_power_of_2(uint32_t(v));
}
template<class VectorType> void reserve_power_of_2(VectorType &vector, size_t n)
{
vector.reserve(next_highest_power_of_2(n));
}
template<class VectorType> void reserve_more(VectorType &vector, size_t n)
{
vector.reserve(vector.size() + n);
}
template<class VectorType> void reserve_more_power_of_2(VectorType &vector, size_t n)
{
vector.reserve(next_highest_power_of_2(vector.size() + n));
}
template<typename INDEX_TYPE>
inline INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
{

View File

@ -351,10 +351,15 @@ public:
Range(It b, It e) : from(std::move(b)), to(std::move(e)) {}
// Some useful container-like methods...
inline size_t size() const { return end() - begin(); }
inline bool empty() const { return size() == 0; }
inline size_t size() const { return std::distance(from, to); }
inline bool empty() const { return from == to; }
};
template<class Cont> auto range(Cont &&cont)
{
return Range{std::begin(cont), std::end(cont)};
}
template<class T, class = FloatingOnly<T>>
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
@ -380,6 +385,8 @@ inline IntegerOnly<I, I> fast_round_up(double a)
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
}
template<class T> using SamePair = std::pair<T, T>;
} // namespace Slic3r
#endif // _libslic3r_h_

View File

@ -39,6 +39,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmosCommon.hpp
GUI/Gizmos/GLGizmoBase.cpp
GUI/Gizmos/GLGizmoBase.hpp
GUI/Gizmos/GLGizmoSlaBase.cpp
GUI/Gizmos/GLGizmoSlaBase.hpp
GUI/Gizmos/GLGizmoEmboss.cpp
GUI/Gizmos/GLGizmoEmboss.hpp
GUI/Gizmos/GLGizmoMove.cpp

View File

@ -476,51 +476,6 @@ int GLVolumeCollection::load_object_volume(
return int(this->volumes.size() - 1);
}
// Load SLA auxiliary GLVolumes (for support trees or pad).
// This function produces volumes for multiple instances in a single shot,
// as some object specific mesh conversions may be expensive.
void GLVolumeCollection::load_object_auxiliary(
const SLAPrintObject* print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>>& instances,
SLAPrintObjectStep milestone,
// Timestamp of the last change of the milestone
size_t timestamp)
{
assert(print_object->is_step_done(milestone));
Transform3d mesh_trafo_inv = print_object->trafo().inverse();
// Get the support mesh.
TriangleMesh mesh = print_object->get_mesh(milestone);
mesh.transform(mesh_trafo_inv);
// Convex hull is required for out of print bed detection.
TriangleMesh convex_hull = mesh.convex_hull_3d();
for (const std::pair<size_t, size_t>& instance_idx : instances) {
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
GLVolume& v = *this->volumes.back();
#if ENABLE_SMOOTH_NORMALS
v.model.init_from(mesh, true);
#else
v.model.init_from(mesh);
v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR);
v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(mesh));
#endif // ENABLE_SMOOTH_NORMALS
v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
if (&instance_idx == &instances.back())
v.set_convex_hull(std::move(convex_hull));
else
v.set_convex_hull(convex_hull);
v.is_modifier = false;
v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree);
v.set_instance_transformation(model_instance.get_transformation());
// Leave the volume transformation at identity.
// v.set_volume_transformation(model_volume->get_transformation());
}
}
#if ENABLE_OPENGL_ES
int GLVolumeCollection::load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height,

View File

@ -181,7 +181,7 @@ public:
bool force_neutral_color : 1;
// Whether or not to force rendering of sinking contours
bool force_sinking_contours : 1;
};
}; // this gets instantiated automatically in the parent struct
// Is mouse or rectangle selection over this object to select/deselect it ?
EHoverState hover;
@ -416,16 +416,6 @@ public:
int volume_idx,
int instance_idx);
// Load SLA auxiliary GLVolumes (for support trees or pad).
void load_object_auxiliary(
const SLAPrintObject* print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>>& instances,
SLAPrintObjectStep milestone,
// Timestamp of the last change of the milestone
size_t timestamp);
#if ENABLE_OPENGL_ES
int load_wipe_tower_preview(
float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr);

View File

@ -374,22 +374,43 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
bool is_default_tree = treetype == sla::SupportTreeType::Default;
bool is_branching_tree = treetype == sla::SupportTreeType::Branching;
toggle_field("support_head_front_diameter", supports_en);
toggle_field("support_head_penetration", supports_en);
toggle_field("support_head_width", supports_en);
toggle_field("support_pillar_diameter", supports_en);
toggle_field("support_small_pillar_diameter_percent", supports_en);
toggle_field("support_tree_type", supports_en);
toggle_field("support_head_front_diameter", supports_en && is_default_tree);
toggle_field("support_head_penetration", supports_en && is_default_tree);
toggle_field("support_head_width", supports_en && is_default_tree);
toggle_field("support_pillar_diameter", supports_en && is_default_tree);
toggle_field("support_small_pillar_diameter_percent", supports_en && is_default_tree);
toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree);
toggle_field("support_pillar_connection_mode", supports_en && is_default_tree);
toggle_field("support_tree_type", supports_en);
toggle_field("support_buildplate_only", supports_en);
toggle_field("support_base_diameter", supports_en);
toggle_field("support_base_height", supports_en);
toggle_field("support_base_safety_distance", supports_en);
toggle_field("support_critical_angle", supports_en);
toggle_field("support_max_bridge_length", supports_en);
toggle_field("support_buildplate_only", supports_en && is_default_tree);
toggle_field("support_base_diameter", supports_en && is_default_tree);
toggle_field("support_base_height", supports_en && is_default_tree);
toggle_field("support_base_safety_distance", supports_en && is_default_tree);
toggle_field("support_critical_angle", supports_en && is_default_tree);
toggle_field("support_max_bridge_length", supports_en && is_default_tree);
toggle_field("support_enforcers_only", supports_en);
toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree);
toggle_field("support_pillar_widening_factor", supports_en && is_branching_tree);
toggle_field("support_pillar_widening_factor", false);
toggle_field("support_max_weight_on_model", false);
toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree);
toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree);
toggle_field("branchingsupport_head_width", supports_en && is_branching_tree);
toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree);
toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_bridges_on_pillar", false);
toggle_field("branchingsupport_pillar_connection_mode", false);
toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree);
toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree);
toggle_field("branchingsupport_base_height", supports_en && is_branching_tree);
toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree);
toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_pillar_link_distance", false);
toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree);
toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree);
toggle_field("support_points_density_relative", supports_en);
toggle_field("support_points_minimal_distance", supports_en);
@ -406,7 +427,8 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
toggle_field("support_object_elevation", supports_en && !zero_elev);
toggle_field("support_object_elevation", supports_en && is_default_tree && !zero_elev);
toggle_field("branchingsupport_object_elevation", supports_en && is_branching_tree && !zero_elev);
toggle_field("pad_object_gap", zero_elev);
toggle_field("pad_around_object_everywhere", zero_elev);
toggle_field("pad_object_connector_stride", zero_elev);

File diff suppressed because it is too large Load Diff

View File

@ -60,18 +60,25 @@ enum Technology {
T_ANY = ~0,
};
enum BundleLocation{
IN_VENDOR,
IN_ARCHIVE,
IN_RESOURCES
};
struct Bundle
{
std::unique_ptr<PresetBundle> preset_bundle;
VendorProfile* vendor_profile{ nullptr };
bool is_in_resources{ false };
//bool is_in_resources{ false };
BundleLocation location;
bool is_prusa_bundle{ false };
Bundle() = default;
Bundle(Bundle&& other);
// Returns false if not loaded. Reason for that is logged as boost::log error.
bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false);
bool load(fs::path source_path, BundleLocation location, bool is_prusa_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; }
};
@ -84,74 +91,8 @@ struct BundleMap : std::map<std::string /* = vendor ID */, Bundle>
const Bundle& prusa_bundle() const;
};
struct Materials
{
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// String is alias of material, size_t number of compatible counters
std::vector<std::pair<std::string, size_t>> compatibility_counter;
std::set<std::string> types;
std::set<const Preset*> printers;
struct Materials;
Materials(Technology technology) : technology(technology) {}
void push(const Preset *preset);
void add_printer(const Preset* preset);
void clear();
bool containts(const Preset *preset) const {
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
return std::find_if(presets.begin(), presets.end(),
[preset](const Preset* element) { return element == preset; }) != presets.end();
}
bool get_omnipresent(const Preset* preset) {
return get_printer_counter(preset) == printers.size();
}
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
std::vector<const Preset*> ret_vec;
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it)->alias == name)
ret_vec.push_back((*it));
}
return ret_vec;
}
size_t get_printer_counter(const Preset* preset) {
for (auto it : compatibility_counter) {
if (it.first == preset->alias)
return it.second;
}
return 0;
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset *preset) const;
const std::string& get_vendor(const Preset *preset) const;
template<class F> void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) {
for (auto preset : presets) {
const Preset& prst = *(preset);
const Preset& prntr = *printer;
if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
static const std::string& get_filament_vendor(const Preset *preset);
static const std::string& get_material_type(const Preset *preset);
static const std::string& get_material_vendor(const Preset *preset);
};
struct PrinterPickerEvent;
@ -344,6 +285,10 @@ struct PageMaterials: ConfigWizardPage
std::string empty_printers_label;
bool first_paint = { false };
static const std::string EMPTY;
static const std::string TEMPLATES;
// notify user first time they choose template profile
bool template_shown = { false };
bool notification_shown = { false };
int last_hovered_item = { -1 } ;
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
@ -368,6 +313,82 @@ struct PageMaterials: ConfigWizardPage
virtual void on_activate() override;
};
struct Materials
{
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// String is alias of material, size_t number of compatible counters
std::vector<std::pair<std::string, size_t>> compatibility_counter;
std::set<std::string> types;
std::set<const Preset*> printers;
Materials(Technology technology) : technology(technology) {}
void push(const Preset* preset);
void add_printer(const Preset* preset);
void clear();
bool containts(const Preset* preset) const {
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
return std::find_if(presets.begin(), presets.end(),
[preset](const Preset* element) { return element == preset; }) != presets.end();
}
bool get_omnipresent(const Preset* preset) {
return get_printer_counter(preset) == printers.size();
}
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
std::vector<const Preset*> ret_vec;
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it)->alias == name)
ret_vec.push_back((*it));
}
return ret_vec;
}
size_t get_printer_counter(const Preset* preset) {
for (auto it : compatibility_counter) {
if (it.first == preset->alias)
return it.second;
}
return 0;
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset* preset) const;
const std::string& get_vendor(const Preset* preset) const;
template<class F> void filter_presets(const Preset* printer, const std::string& printer_name, const std::string& type, const std::string& vendor, F cb) {
for (auto preset : presets) {
const Preset& prst = *(preset);
const Preset& prntr = *printer;
if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor) &&
prst.vendor && !prst.vendor->templates_profile) {
cb(preset);
}
else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor && prst.vendor->templates_profile &&
(type.empty() || get_type(preset) == type) &&
(vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset* preset);
static const std::string& get_filament_vendor(const Preset* preset);
static const std::string& get_material_type(const Preset* preset);
static const std::string& get_material_vendor(const Preset* preset);
};
struct PageCustom: ConfigWizardPage
{
PageCustom(ConfigWizard *parent);
@ -608,9 +629,11 @@ struct ConfigWizard::priv
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page
bool custom_printer_selected { false };
bool custom_printer_selected { false }; // New custom printer is requested
bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
bool only_sla_mode { false };
bool template_profile_selected { false }; // This bool has one purpose - to tell that template profile should be installed if its not (because it cannot be added to appconfig)
wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr;

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