mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-04 07:40:38 +08:00
Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_sequential
This commit is contained in:
commit
17be299612
4
deps/wxWidgets/wxWidgets.cmake
vendored
4
deps/wxWidgets/wxWidgets.cmake
vendored
@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for
|
||||
endif()
|
||||
|
||||
prusaslicer_add_cmake_project(wxWidgets
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/4fd2120c913c20c3bb66ee9d01d8ff5087a8b90a.zip
|
||||
URL_HASH SHA256=5b59e8b4dccf73e109c6588f6a69bcfe4e02e930af53c43d5d1329c1f3d83ec9
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/0b49beaacce17d90f0c370ecd73221abd089667a.zip
|
||||
URL_HASH SHA256=8fa978a76d6bd811b30eecc5124186b9ad54290b820f3a354e85bfa9dae6a5ce
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG
|
||||
CMAKE_ARGS
|
||||
-DwxBUILD_PRECOMP=ON
|
||||
|
@ -237,14 +237,14 @@ documentation_link = https://help.prusa3d.com/article/prusaslicer-printables-com
|
||||
weight = 3
|
||||
|
||||
[hint:Cut tool]
|
||||
text = Cut tool\nDid you know that you can cut a model at any angle and even create aligning pins with the updated Cut tool? Learn more in the documentation.
|
||||
text = Cut tool\nDid you know that you can cut a model at any angle and even create aligning pins with the updated <a>Cut tool</a>? Learn more in the documentation.
|
||||
documentation_link = https://help.prusa3d.com/article/cut-tool_1779
|
||||
hypertext_type = gizmo
|
||||
hypertext_gizmo_item = cut
|
||||
weight = 3
|
||||
|
||||
[hint:Measurement tool]
|
||||
text = Measurement tool\nDid you know that you can measure the distances between points, edges and planes, the radius of a hole or the angle between edges or planes? Learn more in the documentation.
|
||||
text = Measurement tool\nDid you know that you can <a>measure</a> the distances between points, edges and planes, the radius of a hole or the angle between edges or planes? Learn more in the documentation.
|
||||
documentation_link = https://help.prusa3d.com/article/measurement-tool_399451
|
||||
hypertext_type = gizmo
|
||||
hypertext_gizmo_item = measure
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
min_slic3r_version = 2.4.1-beta3
|
||||
0.0.7 Correct missing profile improvement from 0.0.6 to set ensure_vertical_shell_thickness = 0 (off).
|
||||
0.0.6 Correct start gcode, match profile and firmware settings, and other profile improvements.
|
||||
0.0.5 Correct Marlin Error accumulation for Ditto printer profiles.
|
||||
0.0.4 Correct Marlin Error accumulation
|
||||
|
@ -5,7 +5,7 @@
|
||||
name = BIBO
|
||||
# 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 = 0.0.6
|
||||
config_version = 0.0.7
|
||||
# Where to get the updates from?
|
||||
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/BIBO/
|
||||
|
||||
@ -38,7 +38,7 @@ compatible_printers =
|
||||
complete_objects = 0
|
||||
dont_support_bridges = 1
|
||||
elefant_foot_compensation = 0.3
|
||||
ensure_vertical_shell_thickness = 1
|
||||
ensure_vertical_shell_thickness = 0
|
||||
external_fill_pattern = rectilinear
|
||||
external_perimeters_first = 0
|
||||
external_perimeter_extrusion_width = 0.40
|
||||
|
@ -1,7 +1,10 @@
|
||||
min_slic3r_version = 2.6.0-alpha1
|
||||
1.7.0-alpha0 Added profiles for Print With Smile filaments.
|
||||
1.6.0-alpha2 Added profile for Prusament PETG Carbon Fiber and Fiberthree F3 PA-GF30 Pro. Updated acceleration settings for Prusa MINI.
|
||||
1.6.0-alpha1 Updated FW version notification. Decreased min layer time for PLA.
|
||||
1.6.0-alpha0 Default top fill set to monotonic lines. Updated infill/perimeter overlap values. Updated output filename format. Enabled dynamic overhang speeds.
|
||||
min_slic3r_version = 2.5.0-alpha0
|
||||
1.5.7 Added filament profile for Prusament PETG Carbon Fiber and Fiberthree F3 PA-GF30 Pro.
|
||||
1.5.6 Updated FW version notification (MK2.5/MK3 family). Added filament profile for Kimya PEBA-S.
|
||||
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.
|
||||
|
@ -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.6.0-alpha1
|
||||
config_version = 1.7.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%
|
||||
@ -145,7 +145,6 @@ bridge_flow_ratio = 1
|
||||
bridge_speed = 25
|
||||
brim_width = 0
|
||||
brim_separation = 0.1
|
||||
clip_multipart_objects = 1
|
||||
compatible_printers =
|
||||
complete_objects = 0
|
||||
default_acceleration = 1000
|
||||
@ -268,8 +267,10 @@ fill_pattern = grid
|
||||
travel_speed = 150
|
||||
wipe_tower = 0
|
||||
default_acceleration = 1000
|
||||
first_layer_acceleration = 800
|
||||
infill_acceleration = 1000
|
||||
first_layer_acceleration = 600
|
||||
infill_acceleration = 1500
|
||||
solid_infill_acceleration = 1500
|
||||
top_solid_infill_acceleration = 800
|
||||
bridge_acceleration = 1000
|
||||
support_material_speed = 40
|
||||
max_print_speed = 150
|
||||
@ -373,7 +374,9 @@ fill_pattern = gyroid
|
||||
fill_density = 15%
|
||||
travel_speed = 150
|
||||
perimeter_acceleration = 800
|
||||
infill_acceleration = 1000
|
||||
infill_acceleration = 1500
|
||||
solid_infill_acceleration = 1500
|
||||
top_solid_infill_acceleration = 800
|
||||
bridge_acceleration = 1000
|
||||
first_layer_acceleration = 800
|
||||
default_acceleration = 1250
|
||||
@ -1283,6 +1286,10 @@ support_material_extrusion_width = 0.35
|
||||
bridge_acceleration = 300
|
||||
support_material_contact_distance = 0.1
|
||||
raft_contact_distance = 0.1
|
||||
infill_acceleration = 1000
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
external_perimeter_acceleration = 300
|
||||
|
||||
[print:0.07mm ULTRADETAIL @MINI]
|
||||
inherits = *0.07mm*; *MINI*
|
||||
@ -1298,6 +1305,10 @@ support_material_extrusion_width = 0.35
|
||||
bridge_acceleration = 300
|
||||
support_material_contact_distance = 0.1
|
||||
raft_contact_distance = 0.1
|
||||
infill_acceleration = 1000
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
external_perimeter_acceleration = 300
|
||||
|
||||
[print:0.10mm DETAIL @MINI]
|
||||
inherits = *0.10mm*; *MINI*
|
||||
@ -1314,6 +1325,11 @@ fill_pattern = gyroid
|
||||
fill_density = 15%
|
||||
perimeters = 3
|
||||
support_material_xy_spacing = 60%
|
||||
infill_acceleration = 1200
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
perimeter_acceleration = 700
|
||||
external_perimeter_acceleration = 600
|
||||
|
||||
[print:0.15mm QUALITY @MINI]
|
||||
inherits = *0.15mm*; *MINI*
|
||||
@ -1326,6 +1342,8 @@ top_solid_infill_speed = 40
|
||||
fill_pattern = gyroid
|
||||
fill_density = 15%
|
||||
support_material_xy_spacing = 60%
|
||||
perimeter_acceleration = 900
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.15mm SPEED @MINI]
|
||||
inherits = *0.15mm*; *MINI*
|
||||
@ -1336,6 +1354,8 @@ infill_speed = 140
|
||||
solid_infill_speed = 140
|
||||
top_solid_infill_speed = 40
|
||||
support_material_xy_spacing = 60%
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.20mm QUALITY @MINI]
|
||||
inherits = *0.20mm*; *MINI*
|
||||
@ -1348,6 +1368,8 @@ top_solid_infill_speed = 40
|
||||
fill_pattern = gyroid
|
||||
fill_density = 15%
|
||||
support_material_xy_spacing = 60%
|
||||
perimeter_acceleration = 900
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.20mm SPEED @MINI]
|
||||
inherits = *0.20mm*; *MINI*
|
||||
@ -1359,6 +1381,8 @@ max_print_speed = 150
|
||||
solid_infill_speed = 140
|
||||
top_solid_infill_speed = 40
|
||||
support_material_xy_spacing = 60%
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.25mm DRAFT @MINI]
|
||||
inherits = *0.25mm*; *MINI*
|
||||
@ -1378,6 +1402,8 @@ top_infill_extrusion_width = 0.4
|
||||
support_material_xy_spacing = 60%
|
||||
support_material_contact_distance = 0.2
|
||||
raft_contact_distance = 0.2
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
# MINI - 0.25mm nozzle
|
||||
|
||||
@ -1389,6 +1415,10 @@ fill_density = 20%
|
||||
support_material_speed = 30
|
||||
support_material_contact_distance = 0.07
|
||||
raft_contact_distance = 0.07
|
||||
infill_acceleration = 1000
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
external_perimeter_acceleration = 300
|
||||
|
||||
[print:0.07mm ULTRADETAIL @0.25 nozzle MINI]
|
||||
inherits = *0.07mm*; *0.25nozzle*; *MINI*
|
||||
@ -1401,6 +1431,10 @@ fill_pattern = grid
|
||||
fill_density = 20%
|
||||
support_material_contact_distance = 0.07
|
||||
raft_contact_distance = 0.07
|
||||
infill_acceleration = 1000
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
external_perimeter_acceleration = 300
|
||||
|
||||
[print:0.10mm DETAIL @0.25 nozzle MINI]
|
||||
inherits = *0.10mm*; *0.25nozzleMINI*; *MINI*
|
||||
@ -1409,6 +1443,10 @@ fill_pattern = grid
|
||||
fill_density = 20%
|
||||
support_material_contact_distance = 0.07
|
||||
raft_contact_distance = 0.07
|
||||
infill_acceleration = 1200
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
external_perimeter_acceleration = 500
|
||||
|
||||
[print:0.15mm QUALITY @0.25 nozzle MINI]
|
||||
inherits = *0.15mm*; *0.25nozzleMINI*; *MINI*
|
||||
@ -1421,6 +1459,10 @@ perimeter_extrusion_width = 0.27
|
||||
external_perimeter_extrusion_width = 0.27
|
||||
infill_extrusion_width = 0.27
|
||||
solid_infill_extrusion_width = 0.27
|
||||
infill_acceleration = 1500
|
||||
solid_infill_acceleration = 1000
|
||||
top_solid_infill_acceleration = 800
|
||||
external_perimeter_acceleration = 500
|
||||
|
||||
# MINI - 0.6mm nozzle
|
||||
|
||||
@ -1433,11 +1475,17 @@ max_print_speed = 100
|
||||
perimeter_speed = 45
|
||||
solid_infill_speed = 70
|
||||
top_solid_infill_speed = 45
|
||||
infill_extrusion_width = 0.65
|
||||
solid_infill_extrusion_width = 0.65
|
||||
perimeter_extrusion_width = 0.6
|
||||
external_perimeter_extrusion_width = 0.6
|
||||
infill_extrusion_width = 0.6
|
||||
solid_infill_extrusion_width = 0.6
|
||||
top_infill_extrusion_width = 0.5
|
||||
support_material_contact_distance = 0.22
|
||||
raft_contact_distance = 0.22
|
||||
bridge_flow_ratio = 1
|
||||
top_solid_infill_acceleration = 800
|
||||
perimeter_acceleration = 900
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.20mm DETAIL @0.6 nozzle MINI]
|
||||
inherits = *0.20mm*; *0.6nozzleMINI*
|
||||
@ -1448,11 +1496,16 @@ max_print_speed = 100
|
||||
perimeter_speed = 45
|
||||
solid_infill_speed = 70
|
||||
top_solid_infill_speed = 45
|
||||
infill_extrusion_width = 0.65
|
||||
solid_infill_extrusion_width = 0.65
|
||||
perimeter_extrusion_width = 0.6
|
||||
external_perimeter_extrusion_width = 0.6
|
||||
infill_extrusion_width = 0.6
|
||||
solid_infill_extrusion_width = 0.6
|
||||
top_infill_extrusion_width = 0.5
|
||||
support_material_contact_distance = 0.22
|
||||
raft_contact_distance = 0.22
|
||||
bridge_flow_ratio = 1
|
||||
perimeter_acceleration = 900
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.30mm QUALITY @0.6 nozzle MINI]
|
||||
inherits = *0.30mm*; *0.6nozzleMINI*
|
||||
@ -1465,9 +1518,12 @@ solid_infill_speed = 65
|
||||
top_solid_infill_speed = 45
|
||||
external_perimeter_extrusion_width = 0.68
|
||||
perimeter_extrusion_width = 0.68
|
||||
top_infill_extrusion_width = 0.55
|
||||
support_material_contact_distance = 0.25
|
||||
raft_contact_distance = 0.25
|
||||
bridge_flow_ratio = 1
|
||||
perimeter_acceleration = 900
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.35mm SPEED @0.6 nozzle MINI]
|
||||
inherits = *0.35mm*; *0.6nozzleMINI*
|
||||
@ -1483,6 +1539,8 @@ perimeter_extrusion_width = 0.68
|
||||
support_material_contact_distance = 0.25
|
||||
raft_contact_distance = 0.25
|
||||
bridge_flow_ratio = 0.95
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.40mm DRAFT @0.6 nozzle MINI]
|
||||
inherits = *0.40mm*; *0.6nozzleMINI*
|
||||
@ -1500,6 +1558,8 @@ solid_infill_extrusion_width = 0.68
|
||||
support_material_contact_distance = 0.25
|
||||
raft_contact_distance = 0.25
|
||||
bridge_flow_ratio = 0.95
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
# MINI - 0.8mm nozzle
|
||||
|
||||
@ -1508,13 +1568,17 @@ inherits = 0.30mm DETAIL @0.8 nozzle
|
||||
compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]==0.8
|
||||
perimeter_speed = 35
|
||||
external_perimeter_speed = 25
|
||||
infill_acceleration = 1000
|
||||
infill_speed = 50
|
||||
max_print_speed = 80
|
||||
solid_infill_speed = 45
|
||||
top_solid_infill_speed = 35
|
||||
support_material_speed = 40
|
||||
travel_speed = 150
|
||||
infill_acceleration = 1500
|
||||
solid_infill_acceleration = 1500
|
||||
top_solid_infill_acceleration = 800
|
||||
perimeter_acceleration = 900
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.40mm QUALITY @0.8 nozzle MINI]
|
||||
inherits = 0.40mm QUALITY @0.8 nozzle
|
||||
@ -1525,11 +1589,15 @@ solid_infill_speed = 40
|
||||
top_solid_infill_speed = 30
|
||||
support_material_speed = 40
|
||||
travel_speed = 150
|
||||
infill_acceleration = 1500
|
||||
solid_infill_acceleration = 1500
|
||||
top_solid_infill_acceleration = 800
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
[print:0.55mm DRAFT @0.8 nozzle MINI]
|
||||
inherits = 0.55mm DRAFT @0.8 nozzle
|
||||
compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]==0.8
|
||||
infill_acceleration = 1000
|
||||
infill_speed = 40
|
||||
solid_infill_speed = 40
|
||||
support_material_speed = 35
|
||||
@ -1539,6 +1607,11 @@ top_solid_infill_speed = 28
|
||||
external_perimeter_extrusion_width = 1
|
||||
perimeter_extrusion_width = 1
|
||||
travel_speed = 150
|
||||
infill_acceleration = 1500
|
||||
solid_infill_acceleration = 1500
|
||||
top_solid_infill_acceleration = 800
|
||||
perimeter_acceleration = 1000
|
||||
external_perimeter_acceleration = 800
|
||||
|
||||
# XXXXXXxxXXXXXXXXXXXXXX
|
||||
# XXX--- filament ---XXX
|
||||
@ -3865,6 +3938,17 @@ filament_spool_weight = 201
|
||||
filament_type = PETG
|
||||
compatible_printers_condition = nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber]
|
||||
inherits = Prusament PETG
|
||||
filament_vendor = Prusa Polymers
|
||||
first_layer_temperature = 260
|
||||
temperature = 265
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_density = 1.27
|
||||
filament_colour = #BBBBBB
|
||||
compatible_printers_condition = nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
|
||||
|
||||
[filament:Prusa PETG @0.6 nozzle]
|
||||
inherits = *PET06*
|
||||
renamed_from = "Prusa PET 0.6 nozzle"; "Prusa PETG 0.6 nozzle"
|
||||
@ -3883,6 +3967,14 @@ filament_density = 1.27
|
||||
filament_spool_weight = 201
|
||||
filament_type = PETG
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @0.6 nozzle]
|
||||
inherits = Prusament PETG @0.6 nozzle
|
||||
first_layer_temperature = 260
|
||||
temperature = 265
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Filament PM PETG @0.6 nozzle]
|
||||
inherits = *PET06*
|
||||
renamed_from = "Plasty Mladec PETG @0.6 nozzle"
|
||||
@ -3976,6 +4068,14 @@ filament_cost = 36.29
|
||||
filament_density = 1.27
|
||||
filament_spool_weight = 201
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @MMU2]
|
||||
inherits = Prusament PETG @MMU2
|
||||
first_layer_temperature = 260
|
||||
temperature = 260
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Generic PETG @MMU2 0.6 nozzle]
|
||||
inherits = *PET MMU2 06*
|
||||
renamed_from = "Generic PET MMU2 0.6 nozzle"; "Generic PETG MMU2 0.6 nozzle"
|
||||
@ -3995,6 +4095,14 @@ filament_cost = 36.29
|
||||
filament_density = 1.27
|
||||
filament_spool_weight = 201
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @MMU2 0.6 nozzle]
|
||||
inherits = Prusament PETG @MMU2 0.6 nozzle
|
||||
first_layer_temperature = 260
|
||||
temperature = 260
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Filament PM PETG @MMU2 0.6 nozzle]
|
||||
inherits = *PET MMU2 06*
|
||||
renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle"
|
||||
@ -4079,6 +4187,93 @@ bed_temperature = 30
|
||||
filament_retract_length = 0
|
||||
extrusion_multiplier = 1.16
|
||||
|
||||
[filament:Print With Smile PLA]
|
||||
inherits = *PLA*
|
||||
filament_vendor = Print With Smile
|
||||
filament_cost = 535
|
||||
filament_density = 1.24
|
||||
filament_spool_weight = 0
|
||||
filament_colour = #FFFF6F
|
||||
filament_max_volumetric_speed = 15
|
||||
first_layer_temperature = 205
|
||||
temperature = 205
|
||||
|
||||
[filament:Print With Smile PETG]
|
||||
inherits = *PET*
|
||||
filament_vendor = Print With Smile
|
||||
filament_cost = 590
|
||||
filament_density = 1.27
|
||||
filament_spool_weight = 0
|
||||
filament_colour = #4D9398
|
||||
filament_max_volumetric_speed = 8
|
||||
temperature = 245
|
||||
first_layer_bed_temperature = 85
|
||||
first_layer_temperature = 240
|
||||
bed_temperature = 90
|
||||
|
||||
[filament:Print With Smile PETG @MINI]
|
||||
inherits = Print With Smile PETG; *PETMINI*
|
||||
|
||||
[filament:Print With Smile ASA]
|
||||
inherits = Ultrafuse ASA
|
||||
filament_vendor = Print With Smile
|
||||
filament_cost = 625
|
||||
first_layer_temperature = 250
|
||||
temperature = 250
|
||||
first_layer_bed_temperature = 105
|
||||
bed_temperature = 110
|
||||
filament_type = ASA
|
||||
max_fan_speed = 40
|
||||
bridge_fan_speed = 70
|
||||
filament_max_volumetric_speed = 11
|
||||
|
||||
[filament:Print With Smile ASA @MINI]
|
||||
inherits = Print With Smile ASA; *ABSMINI*
|
||||
|
||||
[filament:Print With Smile ABS]
|
||||
inherits = *ABSC*
|
||||
filament_vendor = Print With Smile
|
||||
filament_cost = 535
|
||||
filament_density = 1.08
|
||||
first_layer_temperature = 240
|
||||
temperature = 240
|
||||
|
||||
[filament:Print With Smile ABS @MINI]
|
||||
inherits = Print With Smile ABS; *ABSMINI*
|
||||
|
||||
[filament:Print With Smile PETG CF]
|
||||
inherits = Extrudr XPETG CF
|
||||
filament_vendor = Print With Smile
|
||||
filament_cost = 899
|
||||
filament_density = 1.29
|
||||
filament_notes =
|
||||
first_layer_temperature = 260
|
||||
temperature = 260
|
||||
first_layer_bed_temperature = 85
|
||||
bed_temperature = 85
|
||||
max_fan_speed = 55
|
||||
bridge_fan_speed = 60
|
||||
filament_max_volumetric_speed = 5
|
||||
|
||||
[filament:Print With Smile PETG CF @MINI]
|
||||
inherits = Print With Smile PETG CF; *PETMINI*
|
||||
|
||||
[filament:Print With Smile TPU96A]
|
||||
inherits = *FLEX*
|
||||
filament_vendor = Print With Smile
|
||||
filament_cost = 1200
|
||||
filament_density = 1.31
|
||||
extrusion_multiplier = 1.1
|
||||
filament_max_volumetric_speed = 1.35
|
||||
fan_always_on = 1
|
||||
cooling = 0
|
||||
max_fan_speed = 60
|
||||
min_fan_speed = 60
|
||||
disable_fan_first_layers = 4
|
||||
full_fan_speed_layer = 6
|
||||
filament_retract_length = 1.2
|
||||
filament_deretract_speed = 20
|
||||
|
||||
[filament:Fiberlogy Easy PLA]
|
||||
inherits = *PLA*
|
||||
renamed_from = Fiberlogy PLA
|
||||
@ -4680,6 +4875,23 @@ fan_always_on = 1
|
||||
max_fan_speed = 15
|
||||
min_fan_speed = 15
|
||||
|
||||
[filament:Fiberthree F3 PA-GF30 Pro]
|
||||
inherits = Prusament PC Blend Carbon Fiber
|
||||
filament_vendor = Fiberthree
|
||||
filament_cost = 208.01
|
||||
filament_density = 1.35
|
||||
extrusion_multiplier = 1.03
|
||||
first_layer_temperature = 275
|
||||
temperature = 285
|
||||
first_layer_bed_temperature = 90
|
||||
bed_temperature = 90
|
||||
fan_below_layer_time = 10
|
||||
compatible_printers_condition = printer_model!="MINI" and ! single_extruder_multi_material
|
||||
max_fan_speed = 15
|
||||
min_fan_speed = 15
|
||||
filament_type = PA
|
||||
filament_max_volumetric_speed = 6
|
||||
|
||||
[filament:Taulman T-Glase]
|
||||
inherits = *PET*
|
||||
filament_vendor = Taulman
|
||||
@ -4889,6 +5101,13 @@ renamed_from = "Prusa PET MMU1"; "Prusa PETG MMU1"
|
||||
[filament:Prusament PETG @MMU1]
|
||||
inherits = Prusament PETG; *PETMMU1*
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @MMU1]
|
||||
inherits = Prusament PETG @MMU1
|
||||
first_layer_temperature = 260
|
||||
temperature = 265
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Taulman T-Glase @MMU1]
|
||||
inherits = Taulman T-Glase; *PETMMU1*
|
||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode"
|
||||
@ -5016,6 +5235,19 @@ fan_always_on = 1
|
||||
max_fan_speed = 15
|
||||
min_fan_speed = 15
|
||||
|
||||
[filament:Fiberthree F3 PA-GF30 Pro @MINI]
|
||||
inherits = Fiberthree F3 PA-GF30 Pro
|
||||
filament_vendor = Fiberthree
|
||||
first_layer_temperature = 275
|
||||
temperature = 280
|
||||
compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI"
|
||||
filament_retract_length = nil
|
||||
filament_retract_speed = nil
|
||||
filament_retract_lift = nil
|
||||
filament_retract_before_travel = nil
|
||||
filament_wipe = nil
|
||||
filament_type = PA
|
||||
|
||||
[filament:Kimya ABS Carbon @MINI]
|
||||
inherits = Kimya ABS Carbon; *ABSMINI*
|
||||
filament_max_volumetric_speed = 6
|
||||
@ -5043,6 +5275,15 @@ inherits = Verbatim ABS; *ABSMINI*
|
||||
inherits = Prusament PETG; *PETMINI*
|
||||
compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @MINI]
|
||||
inherits = Prusament PETG @MINI
|
||||
first_layer_temperature = 260
|
||||
temperature = 265
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]>=0.4 and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
|
||||
|
||||
[filament:Kimya PETG Carbon @MINI]
|
||||
inherits = Kimya PETG Carbon; *PETMINI*
|
||||
filament_max_volumetric_speed = 6
|
||||
@ -5053,6 +5294,14 @@ compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI
|
||||
[filament:Prusament PETG @0.6 nozzle MINI]
|
||||
inherits = Prusament PETG; *PETMINI06*
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @0.6 nozzle MINI]
|
||||
inherits = Prusament PETG @0.6 nozzle MINI
|
||||
first_layer_temperature = 260
|
||||
temperature = 265
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Generic PETG @0.6 nozzle MINI]
|
||||
inherits = Generic PETG; *PETMINI06*
|
||||
renamed_from = "Generic PET 0.6 nozzle MINI"; "Generic PETG 0.6 nozzle MINI"
|
||||
@ -5358,6 +5607,14 @@ filament_retract_lift = 0.2
|
||||
slowdown_below_layer_time = 20
|
||||
compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model!="MK2SMM" and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @0.8 nozzle]
|
||||
inherits = Prusament PETG @0.8 nozzle
|
||||
first_layer_temperature = 265
|
||||
temperature = 270
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Prusament ASA @0.8 nozzle]
|
||||
inherits = Prusament ASA
|
||||
first_layer_temperature = 265
|
||||
@ -5437,6 +5694,14 @@ temperature = 240
|
||||
slowdown_below_layer_time = 20
|
||||
filament_ramming_parameters = "120 140 5.51613 5.6129 5.70968 5.77419 5.77419 5.74194 5.80645 5.93548 6.06452 6.19355 6.3871 6.74194 7.25806 7.87097 8.54839 9.22581 10 10.8387| 0.05 5.5032 0.45 5.63868 0.95 5.8 1.45 5.7839 1.95 6.02257 2.45 6.25811 2.95 7.08395 3.45 8.43875 3.95 9.92258 4.45 11.3419 4.95 7.6"
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @MMU2 0.8 nozzle]
|
||||
inherits = Prusament PETG @MMU2 0.8 nozzle
|
||||
first_layer_temperature = 265
|
||||
temperature = 270
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Generic PLA @MMU2 0.8 nozzle]
|
||||
inherits = Generic PLA @MMU2
|
||||
compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material
|
||||
@ -5520,6 +5785,14 @@ filament_retract_lift = 0.25
|
||||
slowdown_below_layer_time = 20
|
||||
compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model=="MINI"
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber @0.8 nozzle MINI]
|
||||
inherits = Prusament PETG @0.8 nozzle MINI
|
||||
first_layer_temperature = 265
|
||||
temperature = 270
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Prusament ASA @0.8 nozzle MINI]
|
||||
inherits = Prusament ASA @MINI
|
||||
first_layer_temperature = 265
|
||||
@ -10039,7 +10312,7 @@ default_filament_profile = "Prusament PLA"
|
||||
default_print_profile = 0.15mm QUALITY @MINI
|
||||
gcode_flavor = marlin2
|
||||
machine_max_acceleration_e = 5000
|
||||
machine_max_acceleration_extruding = 1250
|
||||
machine_max_acceleration_extruding = 2000
|
||||
machine_max_acceleration_retracting = 1250
|
||||
machine_max_acceleration_travel = 2500
|
||||
machine_max_acceleration_x = 2500
|
||||
|
@ -1,2 +1,3 @@
|
||||
min_slic3r_version = 2.6.0-alpha0
|
||||
1.0.1 Added Prusament PETG Carbon Fiber, Fiberthree F3 PA-GF30 Pro.
|
||||
1.0.0 Initial
|
||||
|
@ -2,14 +2,12 @@
|
||||
|
||||
[vendor]
|
||||
name = Templates
|
||||
config_version = 1.0.0
|
||||
config_version = 1.0.1
|
||||
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/
|
||||
templates_profile = 1
|
||||
|
||||
## Generic filament profiles
|
||||
|
||||
# Print profiles for the Prusa Research printers.
|
||||
|
||||
[filament:*common*]
|
||||
cooling = 1
|
||||
compatible_printers =
|
||||
@ -1547,6 +1545,16 @@ filament_density = 1.27
|
||||
filament_spool_weight = 201
|
||||
filament_type = PETG
|
||||
|
||||
[filament:Prusament PETG Carbon Fiber]
|
||||
inherits = Prusament PETG
|
||||
filament_vendor = Prusa Polymers
|
||||
first_layer_temperature = 260
|
||||
temperature = 265
|
||||
extrusion_multiplier = 1.03
|
||||
filament_cost = 54.99
|
||||
filament_density = 1.27
|
||||
filament_colour = #BBBBBB
|
||||
|
||||
[filament:Prusa PLA]
|
||||
inherits = *PLA*
|
||||
filament_vendor = Made for Prusa
|
||||
@ -2055,6 +2063,21 @@ fan_always_on = 1
|
||||
max_fan_speed = 15
|
||||
min_fan_speed = 15
|
||||
|
||||
[filament:Fiberthree F3 PA-GF30 Pro]
|
||||
inherits = Prusament PC Blend Carbon Fiber
|
||||
filament_vendor = Fiberthree
|
||||
filament_cost = 208.01
|
||||
filament_density = 1.35
|
||||
extrusion_multiplier = 1.03
|
||||
first_layer_temperature = 275
|
||||
temperature = 285
|
||||
first_layer_bed_temperature = 90
|
||||
bed_temperature = 90
|
||||
fan_below_layer_time = 10
|
||||
max_fan_speed = 15
|
||||
min_fan_speed = 15
|
||||
filament_type = PA
|
||||
|
||||
[filament:Taulman T-Glase]
|
||||
inherits = *PET*
|
||||
filament_vendor = Taulman
|
||||
|
BIN
resources/profiles/TriLAB/AQD_thumbnail.png
Normal file
BIN
resources/profiles/TriLAB/AQD_thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
@ -382,7 +382,7 @@ int CLI::run(int argc, char **argv)
|
||||
} else if (opt_key == "align_xy") {
|
||||
const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
|
||||
for (auto &model : m_models) {
|
||||
BoundingBoxf3 bb = model.bounding_box();
|
||||
BoundingBoxf3 bb = model.bounding_box_exact();
|
||||
// this affects volumes:
|
||||
model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z());
|
||||
}
|
||||
@ -423,7 +423,7 @@ int CLI::run(int argc, char **argv)
|
||||
} else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
|
||||
std::vector<Model> new_models;
|
||||
for (auto &model : m_models) {
|
||||
model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0
|
||||
model.translate(0, 0, -model.bounding_box_exact().min.z()); // align to z = 0
|
||||
size_t num_objects = model.objects.size();
|
||||
for (size_t i = 0; i < num_objects; ++ i) {
|
||||
|
||||
|
@ -108,6 +108,10 @@ inline cInt Round(double val)
|
||||
return static_cast<cInt>((val < 0) ? (val - 0.5) : (val + 0.5));
|
||||
}
|
||||
|
||||
// Overriding the Eigen operators because we don't want to compare Z coordinate if IntPoint is 3 dimensional.
|
||||
inline bool operator==(const IntPoint &l, const IntPoint &r) { return l.x() == r.x() && l.y() == r.y(); }
|
||||
inline bool operator!=(const IntPoint &l, const IntPoint &r) { return l.x() != r.x() || l.y() != r.y(); }
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// PolyTree methods ...
|
||||
//------------------------------------------------------------------------------
|
||||
@ -178,19 +182,25 @@ double Area(const Path &poly)
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
double Area(const OutRec &outRec)
|
||||
double Area(const OutPt *op)
|
||||
{
|
||||
OutPt *op = outRec.Pts;
|
||||
const OutPt *startOp = op;
|
||||
if (!op) return 0;
|
||||
double a = 0;
|
||||
do {
|
||||
a += (double)(op->Prev->Pt.x() + op->Pt.x()) * (double)(op->Prev->Pt.y() - op->Pt.y());
|
||||
op = op->Next;
|
||||
} while (op != outRec.Pts);
|
||||
} while (op != startOp);
|
||||
return a * 0.5;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
double Area(const OutRec &outRec)
|
||||
{
|
||||
return Area(outRec.Pts);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
|
||||
{
|
||||
OutPt *pp2 = pp;
|
||||
@ -524,27 +534,32 @@ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
|
||||
p = btmPt2->Next;
|
||||
while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next;
|
||||
double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
|
||||
return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
|
||||
|
||||
if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
|
||||
std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
|
||||
return Area(btmPt1) > 0; //if otherwise identical use orientation
|
||||
else
|
||||
return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Called by GetLowermostRec()
|
||||
OutPt* GetBottomPt(OutPt *pp)
|
||||
{
|
||||
OutPt* dups = 0;
|
||||
OutPt* dups = nullptr;
|
||||
OutPt* p = pp->Next;
|
||||
while (p != pp)
|
||||
{
|
||||
if (p->Pt.y() > pp->Pt.y())
|
||||
{
|
||||
pp = p;
|
||||
dups = 0;
|
||||
dups = nullptr;
|
||||
}
|
||||
else if (p->Pt.y() == pp->Pt.y() && p->Pt.x() <= pp->Pt.x())
|
||||
{
|
||||
if (p->Pt.x() < pp->Pt.x())
|
||||
{
|
||||
dups = 0;
|
||||
dups = nullptr;
|
||||
pp = p;
|
||||
} else
|
||||
{
|
||||
@ -565,6 +580,7 @@ OutPt* GetBottomPt(OutPt *pp)
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool Pt2IsBetweenPt1AndPt3(const IntPoint &pt1,
|
||||
@ -4093,19 +4109,40 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path
|
||||
for (int i = 0; i < polynode.ChildCount(); ++i)
|
||||
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
|
||||
}
|
||||
|
||||
void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths)
|
||||
{
|
||||
bool match = true;
|
||||
if (nodetype == ntClosed) match = !polynode.IsOpen();
|
||||
else if (nodetype == ntOpen) return;
|
||||
|
||||
if (!polynode.Contour.empty() && match)
|
||||
paths.push_back(std::move(polynode.Contour));
|
||||
for (int i = 0; i < polynode.ChildCount(); ++i)
|
||||
AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
|
||||
{
|
||||
paths.resize(0);
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
AddPolyNodeToPaths(polytree, ntAny, paths);
|
||||
}
|
||||
|
||||
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths)
|
||||
{
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
AddPolyNodeToPaths(std::move(polytree), ntAny, paths);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
|
||||
{
|
||||
paths.resize(0);
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
AddPolyNodeToPaths(polytree, ntClosed, paths);
|
||||
}
|
||||
@ -4113,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
|
||||
|
||||
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
|
||||
{
|
||||
paths.resize(0);
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
//Open paths are top level only, so ...
|
||||
for (int i = 0; i < polytree.ChildCount(); ++i)
|
||||
|
@ -206,6 +206,7 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool
|
||||
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
|
||||
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
|
||||
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
|
||||
|
||||
|
@ -871,7 +871,7 @@ template<class P> auto rcend(const P& p) -> decltype(_backward(cbegin(p)))
|
||||
|
||||
template<class P> TPoint<P> front(const P& p) { return *shapelike::cbegin(p); }
|
||||
template<class P> TPoint<P> back (const P& p) {
|
||||
return *backward(shapelike::cend(p));
|
||||
return *std::prev(shapelike::cend(p));
|
||||
}
|
||||
|
||||
// Optional, does nothing by default
|
||||
|
@ -157,26 +157,34 @@ template<class RawShape> class EdgeCache {
|
||||
|
||||
void createCache(const RawShape& sh) {
|
||||
{ // For the contour
|
||||
auto first = shapelike::cbegin(sh);
|
||||
auto next = std::next(first);
|
||||
auto endit = shapelike::cend(sh);
|
||||
auto first = sl::cbegin(sh);
|
||||
auto endit = sl::cend(sh);
|
||||
auto next = first == endit ? endit : std::next(first);
|
||||
|
||||
contour_.distances.reserve(shapelike::contourVertexCount(sh));
|
||||
contour_.distances.reserve(sl::contourVertexCount(sh));
|
||||
|
||||
while(next != endit) {
|
||||
contour_.emap.emplace_back(*(first++), *(next++));
|
||||
contour_.full_distance += length(contour_.emap.back());
|
||||
contour_.distances.emplace_back(contour_.full_distance);
|
||||
}
|
||||
|
||||
if constexpr (ClosureTypeV<RawShape> == Closure::OPEN) {
|
||||
if (sl::contourVertexCount(sh) > 0) {
|
||||
contour_.emap.emplace_back(sl::back(sh), sl::front(sh));
|
||||
contour_.full_distance += length(contour_.emap.back());
|
||||
contour_.distances.emplace_back(contour_.full_distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& h : shapelike::holes(sh)) { // For the holes
|
||||
auto first = h.begin();
|
||||
auto next = std::next(first);
|
||||
auto endit = h.end();
|
||||
auto first = sl::cbegin(h);
|
||||
auto endit = sl::cend(h);
|
||||
auto next = first == endit ? endit :std::next(first);
|
||||
|
||||
ContourCache hc;
|
||||
hc.distances.reserve(endit - first);
|
||||
hc.distances.reserve(sl::contourVertexCount(h));
|
||||
|
||||
while(next != endit) {
|
||||
hc.emap.emplace_back(*(first++), *(next++));
|
||||
@ -184,6 +192,14 @@ template<class RawShape> class EdgeCache {
|
||||
hc.distances.emplace_back(hc.full_distance);
|
||||
}
|
||||
|
||||
if constexpr (ClosureTypeV<RawShape> == Closure::OPEN) {
|
||||
if (sl::contourVertexCount(h) > 0) {
|
||||
hc.emap.emplace_back(sl::back(sh), sl::front(sh));
|
||||
hc.full_distance += length(hc.emap.back());
|
||||
hc.distances.emplace_back(hc.full_distance);
|
||||
}
|
||||
}
|
||||
|
||||
holes_.emplace_back(std::move(hc));
|
||||
}
|
||||
}
|
||||
@ -206,7 +222,6 @@ template<class RawShape> class EdgeCache {
|
||||
contour_.corners.reserve(N / S + 1);
|
||||
contour_.corners.emplace_back(0.0);
|
||||
auto N_1 = N-1;
|
||||
contour_.corners.emplace_back(0.0);
|
||||
for(size_t i = 0; i < N_1; i += S) {
|
||||
contour_.corners.emplace_back(
|
||||
contour_.distances.at(i) / contour_.full_distance);
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||
|
||||
// Definition of the ray intersection hit structure.
|
||||
@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>;
|
||||
using Tree2d = Tree<2, double>;
|
||||
using Tree3d = Tree<3, double>;
|
||||
|
||||
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
|
||||
// to build an AABBTree over coord_t 2D bounding boxes.
|
||||
class BoundingBoxWrapper {
|
||||
public:
|
||||
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
|
||||
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
|
539
src/libslic3r/Algorithm/RegionExpansion.cpp
Normal file
539
src/libslic3r/Algorithm/RegionExpansion.cpp
Normal file
@ -0,0 +1,539 @@
|
||||
#include "RegionExpansion.hpp"
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/ClipperZUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta)
|
||||
inline double clipper_round_offset_error(double offset, double arc_tolerance)
|
||||
{
|
||||
static constexpr const double def_arc_tolerance = 0.25;
|
||||
const double y =
|
||||
arc_tolerance <= 0 ?
|
||||
def_arc_tolerance :
|
||||
arc_tolerance > offset * def_arc_tolerance ?
|
||||
offset * def_arc_tolerance :
|
||||
arc_tolerance;
|
||||
double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI);
|
||||
return offset * (1. - cos(M_PI / steps));
|
||||
}
|
||||
|
||||
RegionExpansionParameters RegionExpansionParameters::build(
|
||||
// Scaled expansion value
|
||||
float full_expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_expansion_steps)
|
||||
{
|
||||
assert(full_expansion > 0);
|
||||
assert(expansion_step > 0);
|
||||
assert(max_nr_expansion_steps > 0);
|
||||
|
||||
RegionExpansionParameters out;
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
// The expansion should not be too tiny, but also small enough, so the following expansion will
|
||||
// compensate for tiny_expansion and bring the wave back to the boundary without producing
|
||||
// ugly cusps where it touches the boundary.
|
||||
out.tiny_expansion = std::min(0.25f * full_expansion, scaled<float>(0.05f));
|
||||
size_t nsteps = size_t(ceil((full_expansion - out.tiny_expansion) / expansion_step));
|
||||
if (max_nr_expansion_steps > 0)
|
||||
nsteps = std::min(nsteps, max_nr_expansion_steps);
|
||||
assert(nsteps > 0);
|
||||
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
|
||||
if (nsteps > 1 && 0.25 * out.initial_step < out.tiny_expansion) {
|
||||
// Decrease the step size by lowering number of steps.
|
||||
nsteps = std::max<size_t>(1, (floor((full_expansion - out.tiny_expansion) / (4. * out.tiny_expansion))));
|
||||
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
|
||||
}
|
||||
if (0.25 * out.initial_step < out.tiny_expansion || nsteps == 1) {
|
||||
out.tiny_expansion = 0.2f * full_expansion;
|
||||
out.initial_step = 0.8f * full_expansion;
|
||||
}
|
||||
out.other_step = out.initial_step;
|
||||
out.num_other_steps = nsteps - 1;
|
||||
|
||||
// Accuracy of the offsetter for wave propagation.
|
||||
out.arc_tolerance = scaled<double>(0.1);
|
||||
out.shortest_edge_length = out.initial_step * ClipperOffsetShortestEdgeFactor;
|
||||
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation. Needs to be in sync with the offsetter accuracy.
|
||||
// Clipper positive round offset should rather offset less than more.
|
||||
// Still a little bit of additional offset was added.
|
||||
out.max_inflation = (out.tiny_expansion + nsteps * out.initial_step) * 1.1;
|
||||
// (clipper_round_offset_error(out.tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(out.initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath.
|
||||
// The expanded contours are then opened (the first point is repeated at the end).
|
||||
static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened(
|
||||
const ExPolygons &src, const float expansion, coord_t &base_idx)
|
||||
{
|
||||
ClipperLib_Z::Paths out;
|
||||
out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
ClipperLib::ClipperOffset offsetter;
|
||||
offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor;
|
||||
ClipperLib::Paths expansion_cache;
|
||||
for (const ExPolygon &expoly : src) {
|
||||
for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) {
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
offsetter.Clear();
|
||||
offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon);
|
||||
expansion_cache.clear();
|
||||
offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion);
|
||||
append(out, ClipperZUtils::to_zpaths<true>(expansion_cache, base_idx));
|
||||
}
|
||||
++ base_idx;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Paths were created by splitting closed polygons into open paths and then by clipping them.
|
||||
// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons.
|
||||
// Those ends are sorted lexicographically in "splits".
|
||||
// Reconnect those split pieces.
|
||||
static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector<std::pair<ClipperLib_Z::IntPoint, int>> &splits)
|
||||
{
|
||||
for (auto it_path = paths.begin(); it_path != paths.end(); ) {
|
||||
ClipperLib_Z::Path &path = *it_path;
|
||||
assert(path.size() >= 2);
|
||||
bool merged = false;
|
||||
if (path.size() >= 2) {
|
||||
const ClipperLib_Z::IntPoint &front = path.front();
|
||||
const ClipperLib_Z::IntPoint &back = path.back();
|
||||
// The path before clipping was supposed to cross the clipping boundary or be fully out of it.
|
||||
// Thus the clipped contour is supposed to become open, with one exception: The anchor expands into a closed hole.
|
||||
if (front.x() != back.x() || front.y() != back.y()) {
|
||||
// Look up the ends in "splits", possibly join the contours.
|
||||
// "splits" maps into the other piece connected to the same end point.
|
||||
auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair<ClipperLib_Z::IntPoint, int>* {
|
||||
auto it = std::lower_bound(splits.begin(), splits.end(), pt,
|
||||
[](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); });
|
||||
return it != splits.end() && it->first == pt ? &(*it) : nullptr;
|
||||
};
|
||||
auto *end = find_end(front);
|
||||
bool end_front = true;
|
||||
if (! end) {
|
||||
end_front = false;
|
||||
end = find_end(back);
|
||||
}
|
||||
if (end) {
|
||||
// This segment ends at a split point of the source closed contour before clipping.
|
||||
if (end->second == -1) {
|
||||
// Open end was found, not matched yet.
|
||||
end->second = int(it_path - paths.begin());
|
||||
} else {
|
||||
// Open end was found and matched with end->second
|
||||
ClipperLib_Z::Path &other_path = paths[end->second];
|
||||
polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front);
|
||||
if (std::next(it_path) == paths.end()) {
|
||||
paths.pop_back();
|
||||
break;
|
||||
}
|
||||
path = std::move(paths.back());
|
||||
paths.pop_back();
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! merged)
|
||||
++ it_path;
|
||||
}
|
||||
}
|
||||
|
||||
using AABBTreeBBoxes = AABBTreeIndirect::Tree<2, coord_t>;
|
||||
|
||||
static AABBTreeBBoxes build_aabb_tree_over_expolygons(const ExPolygons &expolygons)
|
||||
{
|
||||
// Calculate bounding boxes of internal slices.
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(expolygons.size());
|
||||
for (size_t i = 0; i < expolygons.size(); ++ i)
|
||||
bboxes.emplace_back(i, get_extents(expolygons[i].contour));
|
||||
// Build AABB tree over bounding boxes of boundary expolygons.
|
||||
AABBTreeBBoxes out;
|
||||
out.build_modify_input(bboxes);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int sample_in_expolygons(
|
||||
// AABB tree over boundary expolygons
|
||||
const AABBTreeBBoxes &aabb_tree,
|
||||
const ExPolygons &expolygons,
|
||||
const Point &sample)
|
||||
{
|
||||
int out = -1;
|
||||
AABBTreeIndirect::traverse(aabb_tree,
|
||||
[&sample](const AABBTreeBBoxes::Node &node) {
|
||||
return node.bbox.contains(sample);
|
||||
},
|
||||
[&expolygons, &sample, &out](const AABBTreeBBoxes::Node &node) {
|
||||
assert(node.is_leaf());
|
||||
assert(node.is_valid());
|
||||
if (expolygons[node.idx].contains(sample)) {
|
||||
out = int(node.idx);
|
||||
// Stop traversal.
|
||||
return false;
|
||||
}
|
||||
// Continue traversal.
|
||||
return true;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<WaveSeed> wave_seeds(
|
||||
// Source regions that are supposed to touch the boundary.
|
||||
const ExPolygons &src,
|
||||
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
|
||||
const ExPolygons &boundary,
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
float tiny_expansion,
|
||||
// Sort output by boundary ID and source ID.
|
||||
bool sorted)
|
||||
{
|
||||
assert(tiny_expansion > 0);
|
||||
|
||||
if (src.empty())
|
||||
return {};
|
||||
|
||||
using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection;
|
||||
using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections;
|
||||
|
||||
ClipperLib_Z::Paths segments;
|
||||
Intersections intersections;
|
||||
|
||||
coord_t idx_boundary_begin = 1;
|
||||
coord_t idx_boundary_end = idx_boundary_begin;
|
||||
coord_t idx_src_end;
|
||||
|
||||
{
|
||||
ClipperLib_Z::Clipper zclipper;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
zclipper.ZFillFunction(visitor.clipper_callback());
|
||||
// as closed contours
|
||||
zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true);
|
||||
// as open contours
|
||||
std::vector<std::pair<ClipperLib_Z::IntPoint, int>> zsrc_splits;
|
||||
{
|
||||
idx_src_end = idx_boundary_end;
|
||||
ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end);
|
||||
zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false);
|
||||
zsrc_splits.reserve(zsrc.size());
|
||||
for (const ClipperLib_Z::Path &path : zsrc) {
|
||||
assert(path.size() >= 2);
|
||||
assert(path.front() == path.back());
|
||||
zsrc_splits.emplace_back(path.front(), -1);
|
||||
}
|
||||
std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); });
|
||||
}
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), segments);
|
||||
merge_splits(segments, zsrc_splits);
|
||||
}
|
||||
|
||||
// AABBTree over bounding boxes of boundaries.
|
||||
// Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point
|
||||
// with the boundary and all Z coordinates of the closed contour point to the source contour.
|
||||
AABBTreeBBoxes aabb_tree;
|
||||
|
||||
// Sort paths into their respective islands.
|
||||
// Each src x boundary will be processed (wave expanded) independently.
|
||||
// Multiple pieces of a single src may intersect the same boundary.
|
||||
WaveSeeds out;
|
||||
out.reserve(segments.size());
|
||||
int iseed = 0;
|
||||
for (const ClipperLib_Z::Path &path : segments) {
|
||||
assert(path.size() >= 2);
|
||||
const ClipperLib_Z::IntPoint &front = path.front();
|
||||
const ClipperLib_Z::IntPoint &back = path.back();
|
||||
// Both ends of a seed segment are supposed to be inside a single boundary expolygon.
|
||||
// Thus as long as the seed contour is not closed, it should be open at a boundary point.
|
||||
assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) ||
|
||||
//(front.z() < 0 && back.z() < 0));
|
||||
// Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created.
|
||||
(front.z() < 0 || back.z() < 0));
|
||||
const Intersection *intersection = nullptr;
|
||||
auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) {
|
||||
return is.first >= 1 && is.first < idx_boundary_end &&
|
||||
is.second >= idx_boundary_end && is.second < idx_src_end;
|
||||
};
|
||||
if (front.z() < 0) {
|
||||
const Intersection &is = intersections[- front.z() - 1];
|
||||
assert(intersection_point_valid(is));
|
||||
if (intersection_point_valid(is))
|
||||
intersection = &is;
|
||||
}
|
||||
if (! intersection && back.z() < 0) {
|
||||
const Intersection &is = intersections[- back.z() - 1];
|
||||
assert(intersection_point_valid(is));
|
||||
if (intersection_point_valid(is))
|
||||
intersection = &is;
|
||||
}
|
||||
if (intersection) {
|
||||
// The path intersects the boundary contour at least at one side.
|
||||
out.push_back({ uint32_t(intersection->second - idx_boundary_end), uint32_t(intersection->first - 1), ClipperZUtils::from_zpath(path) });
|
||||
} else {
|
||||
// This should be a closed contour.
|
||||
assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end);
|
||||
// Find a source boundary expolygon of one sample of this closed path.
|
||||
if (aabb_tree.empty())
|
||||
aabb_tree = build_aabb_tree_over_expolygons(boundary);
|
||||
int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y()));
|
||||
// Boundary that contains the sample point was found.
|
||||
assert(boundary_id >= 0);
|
||||
if (boundary_id >= 0)
|
||||
out.push_back({ uint32_t(front.z() - idx_boundary_end), uint32_t(boundary_id), ClipperZUtils::from_zpath(path) });
|
||||
}
|
||||
++ iseed;
|
||||
}
|
||||
|
||||
if (sorted)
|
||||
// Sort the seeds by their intersection boundary and source contour.
|
||||
std::sort(out.begin(), out.end(), lower_by_boundary_and_src);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
out.reserve(polylines.size());
|
||||
ClipperLib::Paths out_this;
|
||||
for (const ClipperLib::Path &path : polylines) {
|
||||
assert(path.size() >= 2);
|
||||
co.Clear();
|
||||
co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : ClipperLib::etOpenRound);
|
||||
co.Execute(out_this, offset);
|
||||
append(out, std::move(out_this));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Input polygons may consist of multiple expolygons, even nested expolygons.
|
||||
// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive
|
||||
// clipping operation, thus it is not being done here.
|
||||
static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset)
|
||||
{
|
||||
ClipperLib::Paths out;
|
||||
out.reserve(polygons.size());
|
||||
ClipperLib::Paths out_this;
|
||||
for (const ClipperLib::Path &polygon : polygons) {
|
||||
co.Clear();
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
// contours will be CCW oriented even though the input paths are CW oriented.
|
||||
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
|
||||
co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon);
|
||||
bool ccw = ClipperLib::Orientation(polygon);
|
||||
co.Execute(out_this, ccw ? offset : - offset);
|
||||
if (! ccw) {
|
||||
// Reverse the resulting contours.
|
||||
for (ClipperLib::Path &path : out_this)
|
||||
std::reverse(path.begin(), path.end());
|
||||
}
|
||||
append(out, std::move(out_this));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
clipper.AddPaths(wavefront, ClipperLib::ptSubject, true);
|
||||
clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true);
|
||||
ClipperLib::Paths out;
|
||||
clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive);
|
||||
return out;
|
||||
}
|
||||
|
||||
static Polygons propagate_wave_from_boundary(
|
||||
ClipperLib::ClipperOffset &co,
|
||||
// Seed of the wave: Open polylines very close to the boundary.
|
||||
const ClipperLib::Paths &seed,
|
||||
// Boundary inside which the waveform will propagate.
|
||||
const ExPolygon &boundary,
|
||||
// How much to inflate the seed lines to produce the first wave area.
|
||||
const float initial_step,
|
||||
// How much to inflate the first wave area and the successive wave areas in each step.
|
||||
const float other_step,
|
||||
// Number of inflate steps after the initial step.
|
||||
const size_t num_other_steps,
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation.
|
||||
const float max_inflation)
|
||||
{
|
||||
assert(! seed.empty() && seed.front().size() >= 2);
|
||||
Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents<true>(seed).inflated(max_inflation));
|
||||
ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping);
|
||||
// Now offset the remaining
|
||||
for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset)
|
||||
polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping);
|
||||
return to_polygons(polygons);
|
||||
}
|
||||
|
||||
// Resulting regions are sorted by boundary id and source id.
|
||||
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
std::vector<RegionExpansion> out;
|
||||
ClipperLib::Paths paths;
|
||||
ClipperLib::ClipperOffset co;
|
||||
co.ArcTolerance = params.arc_tolerance;
|
||||
co.ShortestEdgeLength = params.shortest_edge_length;
|
||||
for (auto it_seed = seeds.begin(); it_seed != seeds.end();) {
|
||||
auto it = it_seed;
|
||||
paths.clear();
|
||||
for (; it != seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it)
|
||||
paths.emplace_back(it->path);
|
||||
// Propagate the wavefront while clipping it with the trimmed boundary.
|
||||
// Collect the expanded polygons, merge them with the source polygons.
|
||||
RegionExpansion re;
|
||||
for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation))
|
||||
out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary });
|
||||
it_seed = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
return propagate_waves(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
|
||||
}
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps)
|
||||
{
|
||||
return propagate_waves(src, boundary, RegionExpansionParameters::build(expansion, expansion_step, max_nr_steps));
|
||||
}
|
||||
|
||||
// Returns regions per source ExPolygon expanded into boundary.
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
std::vector<RegionExpansion> expanded = propagate_waves(seeds, boundary, params);
|
||||
assert(std::is_sorted(seeds.begin(), seeds.end(), [](const auto &l, const auto &r){ return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); }));
|
||||
Polygons acc;
|
||||
std::vector<RegionExpansionEx> out;
|
||||
for (auto it = expanded.begin(); it != expanded.end(); ) {
|
||||
auto it2 = it;
|
||||
acc.clear();
|
||||
for (; it2 != expanded.end() && it2->boundary_id == it->boundary_id && it2->src_id == it->src_id; ++ it2)
|
||||
acc.emplace_back(std::move(it2->polygon));
|
||||
size_t size = it2 - it;
|
||||
if (size == 1)
|
||||
out.push_back({ ExPolygon{std::move(acc.front())}, it->src_id, it->boundary_id });
|
||||
else {
|
||||
ExPolygons expolys = union_ex(acc);
|
||||
reserve_more_power_of_2(out, expolys.size());
|
||||
for (ExPolygon &ex : expolys)
|
||||
out.push_back({ std::move(ex), it->src_id, it->boundary_id });
|
||||
}
|
||||
it = it2;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns regions per source ExPolygon expanded into boundary.
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(
|
||||
// Source regions that are supposed to touch the boundary.
|
||||
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
|
||||
const ExPolygons &src,
|
||||
const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float full_expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_expansion_steps)
|
||||
{
|
||||
auto params = RegionExpansionParameters::build(full_expansion, expansion_step, max_nr_expansion_steps);
|
||||
return propagate_waves_ex(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
|
||||
}
|
||||
|
||||
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps)
|
||||
{
|
||||
std::vector<Polygons> out(src.size(), Polygons{});
|
||||
for (RegionExpansion &r : propagate_waves(src, boundary, expansion, expansion_step, max_nr_steps))
|
||||
out[r.src_id].emplace_back(std::move(r.polygon));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms)
|
||||
{
|
||||
// expanded regions are sorted by boundary id and source id
|
||||
std::vector<RegionExpansion> expanded = propagate_waves(src, boundary, params);
|
||||
// expanded regions will be merged into source regions, thus they will be re-sorted by source id.
|
||||
std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; });
|
||||
uint32_t last = 0;
|
||||
Polygons acc;
|
||||
ExPolygons out;
|
||||
out.reserve(src.size());
|
||||
for (auto it = expanded.begin(); it != expanded.end();) {
|
||||
for (; last < it->src_id; ++ last)
|
||||
out.emplace_back(std::move(src[last]));
|
||||
acc.clear();
|
||||
assert(it->src_id == last);
|
||||
for (; it != expanded.end() && it->src_id == last; ++ it)
|
||||
acc.emplace_back(std::move(it->polygon));
|
||||
//FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon
|
||||
ExPolygon &src_ex = src[last ++];
|
||||
assert(! src_ex.contour.empty());
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
BoundingBox bbox = get_extents(acc);
|
||||
bbox.merge(get_extents(src_ex));
|
||||
SVG svg(debug_out_path("expand_merge_expolygons-failed-union=%d.svg", iRun ++).c_str(), bbox);
|
||||
svg.draw(acc);
|
||||
svg.draw_outline(acc, "black", scale_(0.05));
|
||||
svg.draw(src_ex, "red");
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
Point sample = src_ex.contour.front();
|
||||
append(acc, to_polygons(std::move(src_ex)));
|
||||
ExPolygons merged = union_safety_offset_ex(acc);
|
||||
// Expanding one expolygon by waves should not change connectivity of the source expolygon:
|
||||
// Single expolygon should be produced possibly with increased number of holes.
|
||||
if (merged.size() > 1) {
|
||||
// assert(merged.size() == 1);
|
||||
// There is something wrong with the initial waves. Most likely the bridge was not valid at all
|
||||
// or the boundary region was very close to some bridge edge, but not really touching.
|
||||
// Pick only a single merged expolygon, which contains one sample point of the source expolygon.
|
||||
auto aabb_tree = build_aabb_tree_over_expolygons(merged);
|
||||
int id = sample_in_expolygons(aabb_tree, merged, sample);
|
||||
assert(id != -1);
|
||||
if (id != -1)
|
||||
out.emplace_back(std::move(merged[id]));
|
||||
} else if (merged.size() == 1)
|
||||
out.emplace_back(std::move(merged.front()));
|
||||
}
|
||||
for (; last < uint32_t(src.size()); ++ last)
|
||||
out.emplace_back(std::move(src[last]));
|
||||
return out;
|
||||
}
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
114
src/libslic3r/Algorithm/RegionExpansion.hpp
Normal file
114
src/libslic3r/Algorithm/RegionExpansion.hpp
Normal file
@ -0,0 +1,114 @@
|
||||
#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
|
||||
#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
struct RegionExpansionParameters
|
||||
{
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
float tiny_expansion;
|
||||
// How much to inflate the seed lines to produce the first wave area.
|
||||
float initial_step;
|
||||
// How much to inflate the first wave area and the successive wave areas in each step.
|
||||
float other_step;
|
||||
// Number of inflate steps after the initial step.
|
||||
size_t num_other_steps;
|
||||
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
|
||||
// clipping during wave propagation.
|
||||
float max_inflation;
|
||||
|
||||
// Accuracy of the offsetter for wave propagation.
|
||||
double arc_tolerance;
|
||||
double shortest_edge_length;
|
||||
|
||||
static RegionExpansionParameters build(
|
||||
// Scaled expansion value
|
||||
float full_expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_expansion_steps);
|
||||
};
|
||||
|
||||
struct WaveSeed {
|
||||
uint32_t src;
|
||||
uint32_t boundary;
|
||||
Points path;
|
||||
};
|
||||
using WaveSeeds = std::vector<WaveSeed>;
|
||||
|
||||
inline bool lower_by_boundary_and_src(const WaveSeed &l, const WaveSeed &r)
|
||||
{
|
||||
return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src);
|
||||
}
|
||||
|
||||
inline bool lower_by_src_and_boundary(const WaveSeed &l, const WaveSeed &r)
|
||||
{
|
||||
return l.src < r.src || (l.src == r.src && l.boundary < r.boundary);
|
||||
}
|
||||
|
||||
// Expand src slightly outwards to intersect boundaries, trim the offsetted src polylines by the boundaries.
|
||||
// Return the trimmed paths annotated with their origin (source of the path, index of the boundary region).
|
||||
WaveSeeds wave_seeds(
|
||||
// Source regions that are supposed to touch the boundary.
|
||||
const ExPolygons &src,
|
||||
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
|
||||
const ExPolygons &boundary,
|
||||
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
|
||||
float tiny_expansion,
|
||||
bool sorted);
|
||||
|
||||
struct RegionExpansion
|
||||
{
|
||||
Polygon polygon;
|
||||
uint32_t src_id;
|
||||
uint32_t boundary_id;
|
||||
};
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
|
||||
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps);
|
||||
|
||||
struct RegionExpansionEx
|
||||
{
|
||||
ExPolygon expolygon;
|
||||
uint32_t src_id;
|
||||
uint32_t boundary_id;
|
||||
};
|
||||
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
|
||||
std::vector<RegionExpansionEx> propagate_waves_ex(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps);
|
||||
|
||||
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
|
||||
// Scaled expansion value
|
||||
float expansion,
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled).
|
||||
float expansion_step,
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
size_t max_nr_steps);
|
||||
|
||||
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters ¶ms);
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */
|
@ -38,16 +38,16 @@ class AnyPtr {
|
||||
}
|
||||
|
||||
public:
|
||||
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
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>>>
|
||||
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>>>
|
||||
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>>>
|
||||
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))}
|
||||
{}
|
||||
|
||||
@ -59,16 +59,16 @@ public:
|
||||
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>>>
|
||||
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>>>
|
||||
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>>>
|
||||
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>>>
|
||||
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); }
|
||||
|
@ -85,7 +85,13 @@ template<class PConf>
|
||||
void fill_config(PConf& pcfg, const ArrangeParams ¶ms) {
|
||||
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
switch (params.alignment) {
|
||||
case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break;
|
||||
case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break;
|
||||
case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break;
|
||||
case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break;
|
||||
case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break;
|
||||
}
|
||||
|
||||
// Start placing the items from the center of the print bed
|
||||
pcfg.starting_point = PConf::Alignment::CENTER;
|
||||
@ -593,23 +599,27 @@ template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
|
||||
auto parea = poly_area(bed);
|
||||
|
||||
if ((1.0 - parea / area(bb)) < 1e-3)
|
||||
return fn(bb);
|
||||
return fn(RectangleBed{bb});
|
||||
else if (!std::isnan(circ.radius()))
|
||||
return fn(circ);
|
||||
else
|
||||
return fn(Polygon(bed));
|
||||
return fn(IrregularBed{ExPolygon(bed)});
|
||||
}
|
||||
}
|
||||
|
||||
bool is_box(const Points &bed)
|
||||
{
|
||||
return !bed.empty() &&
|
||||
((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3);
|
||||
}
|
||||
|
||||
template<>
|
||||
void arrange(ArrangePolygons & items,
|
||||
const ArrangePolygons &excludes,
|
||||
const Points & bed,
|
||||
const ArrangeParams & params)
|
||||
{
|
||||
call_with_bed(bed, [&](const auto &bin) {
|
||||
arrange(items, excludes, bin, params);
|
||||
});
|
||||
arrange(items, excludes, to_arrange_bed(bed), params);
|
||||
}
|
||||
|
||||
template<class BedT>
|
||||
@ -649,5 +659,97 @@ template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, c
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
ArrangeBed to_arrange_bed(const Points &bedpts)
|
||||
{
|
||||
ArrangeBed ret;
|
||||
|
||||
call_with_bed(bedpts, [&](const auto &bed) {
|
||||
ret = bed;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void arrange(ArrangePolygons &items,
|
||||
const ArrangePolygons &excludes,
|
||||
const SegmentedRectangleBed &bed,
|
||||
const ArrangeParams ¶ms)
|
||||
{
|
||||
arrange(items, excludes, bed.bb, params);
|
||||
|
||||
if (! excludes.empty())
|
||||
return;
|
||||
|
||||
auto it = std::max_element(items.begin(), items.end(),
|
||||
[](auto &i1, auto &i2) {
|
||||
return i1.bed_idx < i2.bed_idx;
|
||||
});
|
||||
|
||||
size_t beds = 0;
|
||||
if (it != items.end())
|
||||
beds = it->bed_idx + 1;
|
||||
|
||||
std::vector<BoundingBox> pilebb(beds);
|
||||
|
||||
for (auto &itm : items) {
|
||||
if (itm.bed_idx >= 0)
|
||||
pilebb[itm.bed_idx].merge(get_extents(itm.transformed_poly()));
|
||||
}
|
||||
|
||||
auto piecesz = unscaled(bed.bb).size();
|
||||
piecesz.x() /= bed.segments.x();
|
||||
piecesz.y() /= bed.segments.y();
|
||||
|
||||
for (size_t bedidx = 0; bedidx < beds; ++bedidx) {
|
||||
BoundingBox bb;
|
||||
auto pilesz = unscaled(pilebb[bedidx]).size();
|
||||
bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x());
|
||||
bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y());
|
||||
switch (params.alignment) {
|
||||
case Pivots::BottomLeft:
|
||||
bb.translate(bed.bb.min - bb.min);
|
||||
break;
|
||||
case Pivots::TopRight:
|
||||
bb.translate(bed.bb.max - bb.max);
|
||||
break;
|
||||
case Pivots::BottomRight: {
|
||||
Point bedref{bed.bb.max.x(), bed.bb.min.y()};
|
||||
Point bbref {bb.max.x(), bb.min.y()};
|
||||
bb.translate(bedref - bbref);
|
||||
break;
|
||||
}
|
||||
case Pivots::TopLeft: {
|
||||
Point bedref{bed.bb.min.x(), bed.bb.max.y()};
|
||||
Point bbref {bb.min.x(), bb.max.y()};
|
||||
bb.translate(bedref - bbref);
|
||||
break;
|
||||
}
|
||||
case Pivots::Center: {
|
||||
bb.translate(bed.bb.center() - bb.center());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2crd d = bb.center() - pilebb[bedidx].center();
|
||||
|
||||
auto bedbb = bed.bb;
|
||||
bedbb.offset(-params.min_bed_distance);
|
||||
auto pilebbx = pilebb[bedidx];
|
||||
pilebbx.translate(d);
|
||||
|
||||
Point corr{0, 0};
|
||||
corr.x() = -std::min(0, pilebbx.min.x() - bedbb.min.x())
|
||||
-std::max(0, pilebbx.max.x() - bedbb.max.x());
|
||||
corr.y() = -std::min(0, pilebbx.min.y() - bedbb.min.y())
|
||||
-std::max(0, pilebbx.max.y() - bedbb.max.y());
|
||||
|
||||
d += corr;
|
||||
|
||||
for (auto &itm : items)
|
||||
if (itm.bed_idx == bedidx)
|
||||
itm.translation += d;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace arr
|
||||
} // namespace Slic3r
|
||||
|
@ -3,12 +3,25 @@
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBox;
|
||||
|
||||
namespace arrangement {
|
||||
|
||||
/// Representing an unbounded bed.
|
||||
struct InfiniteBed {
|
||||
Point center;
|
||||
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||
};
|
||||
|
||||
struct RectangleBed {
|
||||
BoundingBox bb;
|
||||
};
|
||||
|
||||
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
||||
class CircleBed {
|
||||
Point center_;
|
||||
@ -22,12 +35,28 @@ public:
|
||||
inline const Point& center() const { return center_; }
|
||||
};
|
||||
|
||||
/// Representing an unbounded bed.
|
||||
struct InfiniteBed {
|
||||
Point center;
|
||||
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||
struct SegmentedRectangleBed {
|
||||
Vec<2, size_t> segments;
|
||||
BoundingBox bb;
|
||||
|
||||
SegmentedRectangleBed (const BoundingBox &bb,
|
||||
size_t segments_x,
|
||||
size_t segments_y)
|
||||
: segments{segments_x, segments_y}
|
||||
, bb{bb}
|
||||
{}
|
||||
};
|
||||
|
||||
struct IrregularBed {
|
||||
ExPolygon poly;
|
||||
};
|
||||
|
||||
//enum BedType { Infinite, Rectangle, Circle, SegmentedRectangle, Irregular };
|
||||
|
||||
using ArrangeBed = boost::variant<InfiniteBed, RectangleBed, CircleBed, SegmentedRectangleBed, IrregularBed>;
|
||||
|
||||
ArrangeBed to_arrange_bed(const Points &bedpts);
|
||||
|
||||
/// A logical bed representing an object not being arranged. Either the arrange
|
||||
/// has not yet successfully run on this ArrangePolygon or it could not fit the
|
||||
/// object due to overly large size or invalid geometry.
|
||||
@ -75,6 +104,10 @@ struct ArrangePolygon {
|
||||
|
||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
||||
|
||||
enum class Pivots {
|
||||
Center, TopLeft, BottomLeft, BottomRight, TopRight
|
||||
};
|
||||
|
||||
struct ArrangeParams {
|
||||
|
||||
/// The minimum distance which is allowed for any
|
||||
@ -93,6 +126,12 @@ struct ArrangeParams {
|
||||
|
||||
bool allow_rotations = false;
|
||||
|
||||
/// Final alignment of the merged pile after arrangement
|
||||
Pivots alignment = Pivots::Center;
|
||||
|
||||
/// Starting position hint for the arrangement
|
||||
Pivots starting_point = Pivots::Center;
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned)> progressind;
|
||||
@ -127,12 +166,32 @@ extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excl
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams ¶ms)
|
||||
{
|
||||
arrange(items, excludes, bed.bb, params);
|
||||
}
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams ¶ms)
|
||||
{
|
||||
arrange(items, excludes, bed.poly.contour, params);
|
||||
}
|
||||
|
||||
void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams ¶ms)
|
||||
{
|
||||
auto call_arrange = [&](const auto &realbed) { arrange(items, excludes, realbed, params); };
|
||||
boost::apply_visitor(call_arrange, bed);
|
||||
}
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
|
||||
bool is_box(const Points &bed);
|
||||
|
||||
}} // namespace Slic3r::arrangement
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
||||
|
@ -22,24 +22,9 @@ public:
|
||||
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It> >
|
||||
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
{
|
||||
if (from == to) {
|
||||
this->defined = false;
|
||||
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
} else {
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointClass::Scalar>();
|
||||
this->max = this->min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
this->min = this->min.cwiseMin(vec);
|
||||
this->max = this->max.cwiseMax(vec);
|
||||
}
|
||||
this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y());
|
||||
}
|
||||
}
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
: BoundingBoxBase(points.begin(), points.end())
|
||||
@ -70,6 +55,30 @@ public:
|
||||
}
|
||||
bool operator==(const BoundingBoxBase<PointClass> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
|
||||
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); }
|
||||
|
||||
private:
|
||||
// to access construct()
|
||||
friend BoundingBox get_extents<false>(const Points &pts);
|
||||
friend BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
// The output bounding box is expected to be set to "undefined" initially.
|
||||
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
|
||||
static void construct(BoundingBoxType &out, It from, It to)
|
||||
{
|
||||
if (from != to) {
|
||||
auto it = from;
|
||||
out.min = it->template cast<typename PointClass::Scalar>();
|
||||
out.max = out.min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
out.min = out.min.cwiseMin(vec);
|
||||
out.max = out.max.cwiseMax(vec);
|
||||
}
|
||||
out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class PointClass>
|
||||
@ -226,9 +235,18 @@ inline bool empty(const BoundingBox3Base<VT> &bb)
|
||||
}
|
||||
|
||||
inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
|
||||
inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
|
||||
|
||||
template<class T = coord_t>
|
||||
BoundingBoxBase<Vec<2, T>> scaled(const BoundingBoxf &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = coord_t>
|
||||
BoundingBox3Base<Vec<3, T>> scaled(const BoundingBoxf3 &bb) { return {scaled<T>(bb.min), scaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = double>
|
||||
BoundingBoxBase<Vec<2, T>> unscaled(const BoundingBox &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
|
||||
|
||||
template<class T = double>
|
||||
BoundingBox3Base<Vec<3, T>> unscaled(const BoundingBox3 &bb) { return {unscaled<T>(bb.min), unscaled<T>(bb.max)}; }
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBoxBase<Tin> &b)
|
||||
|
@ -75,12 +75,9 @@ private:
|
||||
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
|
||||
|
||||
if (floating_polylines.empty()) {
|
||||
if (floating_edges.empty()) {
|
||||
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
|
||||
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
|
||||
@ -91,7 +88,6 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
|
||||
}
|
||||
|
||||
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
|
||||
Lines floating_edges = to_lines(floating_polylines);
|
||||
std::unordered_map<double, Vec2d> directions{};
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d normal = l.normal().cast<double>().normalized();
|
||||
@ -125,6 +121,13 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
|
||||
return {result_dir, min_cost};
|
||||
};
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Lines floating_edges = to_lines(diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON))));
|
||||
return detect_bridging_direction(floating_edges, overhang_area);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -641,7 +641,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
// perform operation
|
||||
ClipperLib_Z::PolyTree loops_trimmed_tree;
|
||||
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(loops_trimmed_tree), loops_trimmed);
|
||||
}
|
||||
|
||||
// Third, produce the extrusions, sorted by the source loop indices.
|
||||
|
@ -22,6 +22,8 @@ set(SLIC3R_SOURCES
|
||||
AABBTreeLines.hpp
|
||||
AABBMesh.hpp
|
||||
AABBMesh.cpp
|
||||
Algorithm/RegionExpansion.cpp
|
||||
Algorithm/RegionExpansion.hpp
|
||||
AnyPtr.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
@ -36,6 +38,7 @@ set(SLIC3R_SOURCES
|
||||
clipper.hpp
|
||||
ClipperUtils.cpp
|
||||
ClipperUtils.hpp
|
||||
ClipperZUtils.hpp
|
||||
Color.cpp
|
||||
Color.hpp
|
||||
Config.cpp
|
||||
@ -79,6 +82,8 @@ set(SLIC3R_SOURCES
|
||||
Fill/FillBase.hpp
|
||||
Fill/FillConcentric.cpp
|
||||
Fill/FillConcentric.hpp
|
||||
Fill/FillEnsuring.cpp
|
||||
Fill/FillEnsuring.hpp
|
||||
Fill/FillHoneycomb.cpp
|
||||
Fill/FillHoneycomb.hpp
|
||||
Fill/FillGyroid.cpp
|
||||
|
@ -26,7 +26,6 @@ MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
|
||||
MeshBoolean::cgal::CGALMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
auto tr = get_transform(csgpart);
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
|
@ -8,8 +8,6 @@
|
||||
#include "SVG.hpp"
|
||||
#endif /* CLIPPER_UTILS_DEBUG */
|
||||
|
||||
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef CLIPPER_UTILS_DEBUG
|
||||
@ -267,7 +265,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.ShortestEdgeLength = std::abs(offset * ClipperOffsetShortestEdgeFactor);
|
||||
for (const ClipperLib::Path &path : paths) {
|
||||
co.Clear();
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
@ -414,7 +412,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
|
||||
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
|
||||
co.Execute(contours, delta);
|
||||
}
|
||||
@ -435,7 +433,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
|
||||
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
|
||||
ClipperLib::Paths out2;
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
@ -680,6 +678,13 @@ Slic3r::Polygons union_(const Slic3r::ExPolygons &subject)
|
||||
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2)
|
||||
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); }
|
||||
Slic3r::Polygons union_(Slic3r::Polygons &&subject, const Slic3r::Polygons &subject2) {
|
||||
if (subject.empty())
|
||||
return subject2;
|
||||
if (subject2.empty())
|
||||
return std::move(subject);
|
||||
return union_(subject, subject2);
|
||||
}
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2)
|
||||
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(subject2), ApplySafetyOffset::No); }
|
||||
|
||||
@ -713,6 +718,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
@ -1055,7 +1062,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
|
||||
};
|
||||
|
||||
// Minimum edge length, squared.
|
||||
double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
|
||||
double lmin = *std::max_element(deltas.begin(), deltas.end()) * ClipperOffsetShortestEdgeFactor;
|
||||
double l2min = lmin * lmin;
|
||||
// Minimum angle to consider two edges to be parallel.
|
||||
// Vojtech's estimate.
|
||||
|
@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl
|
||||
// Miter limit is ignored for jtSquare.
|
||||
static constexpr const double DefaultLineMiterLimit = 0.;
|
||||
|
||||
// Decimation factor applied on input contour when doing offset, multiplied by the offset distance.
|
||||
static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005;
|
||||
|
||||
enum class ApplySafetyOffset {
|
||||
No,
|
||||
Yes
|
||||
@ -370,6 +373,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d
|
||||
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
|
||||
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
@ -430,6 +435,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
@ -601,6 +607,6 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::
|
||||
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
#endif // slic3r_ClipperUtils_hpp_
|
||||
|
143
src/libslic3r/ClipperZUtils.hpp
Normal file
143
src/libslic3r/ClipperZUtils.hpp
Normal file
@ -0,0 +1,143 @@
|
||||
#ifndef slic3r_ClipperZUtils_hpp_
|
||||
#define slic3r_ClipperZUtils_hpp_
|
||||
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace ClipperZUtils {
|
||||
|
||||
using ZPoint = ClipperLib_Z::IntPoint;
|
||||
using ZPoints = ClipperLib_Z::Path;
|
||||
using ZPath = ClipperLib_Z::Path;
|
||||
using ZPaths = ClipperLib_Z::Paths;
|
||||
|
||||
inline bool zpoint_lower(const ZPoint &l, const ZPoint &r)
|
||||
{
|
||||
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z())));
|
||||
}
|
||||
|
||||
// Convert a single path to path with a given Z coordinate.
|
||||
// If Open, then duplicate the first point at the end.
|
||||
template<bool Open = false>
|
||||
inline ZPath to_zpath(const Points &path, coord_t z)
|
||||
{
|
||||
ZPath out;
|
||||
if (! path.empty()) {
|
||||
out.reserve((path.size() + Open) ? 1 : 0);
|
||||
for (const Point &p : path)
|
||||
out.emplace_back(p.x(), p.y(), z);
|
||||
if (Open)
|
||||
out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple paths to paths with a given Z coordinate.
|
||||
// If Open, then duplicate the first point of each path at its end.
|
||||
template<bool Open = false>
|
||||
inline ZPaths to_zpaths(const std::vector<Points> &paths, coord_t z)
|
||||
{
|
||||
ZPaths out;
|
||||
out.reserve(paths.size());
|
||||
for (const Points &path : paths)
|
||||
out.emplace_back(to_zpath<Open>(path, z));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple expolygons into z-paths with Z specified by an index of the source expolygon
|
||||
// offsetted by base_index.
|
||||
// If Open, then duplicate the first point of each path at its end.
|
||||
template<bool Open = false>
|
||||
inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx)
|
||||
{
|
||||
ZPaths out;
|
||||
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
for (const ExPolygon &expoly : src) {
|
||||
out.emplace_back(to_zpath<Open>(expoly.contour.points, base_idx));
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
out.emplace_back(to_zpath<Open>(hole.points, base_idx));
|
||||
++ base_idx;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert a single path to path with a given Z coordinate.
|
||||
// If Open, then duplicate the first point at the end.
|
||||
template<bool Open = false>
|
||||
inline Points from_zpath(const ZPoints &path)
|
||||
{
|
||||
Points out;
|
||||
if (! path.empty()) {
|
||||
out.reserve((path.size() + Open) ? 1 : 0);
|
||||
for (const ZPoint &p : path)
|
||||
out.emplace_back(p.x(), p.y());
|
||||
if (Open)
|
||||
out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple paths to paths with a given Z coordinate.
|
||||
// If Open, then duplicate the first point of each path at its end.
|
||||
template<bool Open = false>
|
||||
inline void from_zpaths(const ZPaths &paths, std::vector<Points> &out)
|
||||
{
|
||||
out.reserve(out.size() + paths.size());
|
||||
for (const ZPoints &path : paths)
|
||||
out.emplace_back(from_zpath<Open>(path));
|
||||
}
|
||||
template<bool Open = false>
|
||||
inline std::vector<Points> from_zpaths(const ZPaths &paths)
|
||||
{
|
||||
std::vector<Points> out;
|
||||
from_zpaths(paths, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
class ClipperZIntersectionVisitor {
|
||||
public:
|
||||
using Intersection = std::pair<coord_t, coord_t>;
|
||||
using Intersections = std::vector<Intersection>;
|
||||
ClipperZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {}
|
||||
void reset() { m_intersections.clear(); }
|
||||
void operator()(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) {
|
||||
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
|
||||
coord_t *begin = srcs;
|
||||
coord_t *end = srcs + 4;
|
||||
//FIXME bubble sort manually?
|
||||
std::sort(begin, end);
|
||||
end = std::unique(begin, end);
|
||||
if (begin + 1 == end) {
|
||||
// Self intersection may happen on source contour. Just copy the Z value.
|
||||
pt.z() = *begin;
|
||||
} else {
|
||||
assert(begin + 2 == end);
|
||||
if (begin + 2 <= end) {
|
||||
// store a -1 based negative index into the "intersections" vector here.
|
||||
m_intersections.emplace_back(srcs[0], srcs[1]);
|
||||
pt.z() = -coord_t(m_intersections.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
ClipperLib_Z::ZFillCallback clipper_callback() {
|
||||
return [this](const ZPoint &e1bot, const ZPoint &e1top,
|
||||
const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt)
|
||||
{ return (*this)(e1bot, e1top, e2bot, e2top, pt); };
|
||||
}
|
||||
|
||||
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
|
||||
|
||||
private:
|
||||
std::vector<std::pair<coord_t, coord_t>> &m_intersections;
|
||||
};
|
||||
|
||||
} // namespace ClipperZUtils
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_ClipperZUtils_hpp_
|
@ -303,7 +303,7 @@ template <class T>
|
||||
class ConfigOptionSingle : public ConfigOption {
|
||||
public:
|
||||
T value;
|
||||
explicit ConfigOptionSingle(T value) : value(value) {}
|
||||
explicit ConfigOptionSingle(T value) : value(std::move(value)) {}
|
||||
operator T() const { return this->value; }
|
||||
|
||||
void set(const ConfigOption *rhs) override
|
||||
@ -847,9 +847,9 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
|
||||
class ConfigOptionString : public ConfigOptionSingle<std::string>
|
||||
{
|
||||
public:
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
|
||||
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
|
||||
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>(std::string{}) {}
|
||||
explicit ConfigOptionString(std::string value) : ConfigOptionSingle<std::string>(std::move(value)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coString; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionString(*this); }
|
||||
@ -1736,7 +1736,7 @@ private:
|
||||
void set_values(const std::initializer_list<std::string_view> il) {
|
||||
m_values.clear();
|
||||
m_values.reserve(il.size());
|
||||
for (const std::string_view p : il)
|
||||
for (const std::string_view& p : il)
|
||||
m_values.emplace_back(p);
|
||||
assert(m_labels.empty() || m_labels.size() == m_values.size());
|
||||
}
|
||||
@ -1745,7 +1745,7 @@ private:
|
||||
m_values.reserve(il.size());
|
||||
m_labels.clear();
|
||||
m_labels.reserve(il.size());
|
||||
for (const std::pair<std::string_view, std::string_view> p : il) {
|
||||
for (const std::pair<std::string_view, std::string_view>& p : il) {
|
||||
m_values.emplace_back(p.first);
|
||||
m_labels.emplace_back(p.second);
|
||||
}
|
||||
@ -1753,7 +1753,7 @@ private:
|
||||
void set_labels(const std::initializer_list<std::string_view> il) {
|
||||
m_labels.clear();
|
||||
m_labels.reserve(il.size());
|
||||
for (const std::string_view p : il)
|
||||
for (const std::string_view& p : il)
|
||||
m_labels.emplace_back(p);
|
||||
assert(m_values.empty() || m_labels.size() == m_values.size());
|
||||
}
|
||||
@ -1762,9 +1762,9 @@ private:
|
||||
// Check whether def.enum_values contains all the values of def.enum_keys_map and
|
||||
// that they are sorted by their ordinary values.
|
||||
m_values_ordinary = true;
|
||||
for (const std::pair<std::string, int>& key : *m_enum_keys_map) {
|
||||
assert(key.second >= 0);
|
||||
if (key.second >= this->values().size() || this->value(key.second) != key.first) {
|
||||
for (const auto& [enum_name, enum_int] : *m_enum_keys_map) {
|
||||
assert(enum_int >= 0);
|
||||
if (enum_int >= int(this->values().size()) || this->value(enum_int) != enum_name) {
|
||||
m_values_ordinary = false;
|
||||
break;
|
||||
}
|
||||
|
@ -1098,7 +1098,13 @@ namespace priv {
|
||||
/// Track source of intersection
|
||||
/// Help for anotate inner and outer faces
|
||||
/// </summary>
|
||||
struct Visitor {
|
||||
struct Visitor : public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor<CutMesh> {
|
||||
Visitor(const CutMesh &object, const CutMesh &shape, EdgeShapeMap edge_shape_map,
|
||||
FaceShapeMap face_shape_map, VertexShapeMap vert_shape_map, bool* is_valid) :
|
||||
object(object), shape(shape), edge_shape_map(edge_shape_map), face_shape_map(face_shape_map),
|
||||
vert_shape_map(vert_shape_map), is_valid(is_valid)
|
||||
{}
|
||||
|
||||
const CutMesh &object;
|
||||
const CutMesh &shape;
|
||||
|
||||
@ -1160,16 +1166,6 @@ struct Visitor {
|
||||
/// <param name="v">New added vertex</param>
|
||||
/// <param name="tm">Affected mesh</param>
|
||||
void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm);
|
||||
|
||||
// Not used visitor functions
|
||||
void before_subface_creations(FI /* f_old */, CutMesh &/* mesh */){}
|
||||
void after_subface_created(FI /* f_new */, CutMesh &/* mesh */) {}
|
||||
void after_subface_creations(CutMesh&) {}
|
||||
void before_subface_created(CutMesh&) {}
|
||||
void before_edge_split(HI /* h */, CutMesh& /* tm */) {}
|
||||
void edge_split(HI /* hnew */, CutMesh& /* tm */) {}
|
||||
void after_edge_split() {}
|
||||
void add_retriangulation_edge(HI /* h */, CutMesh& /* tm */) {}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -15,10 +15,12 @@
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillConcentric.hpp"
|
||||
|
||||
#include "FillEnsuring.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
|
||||
|
||||
struct SurfaceFillParams
|
||||
{
|
||||
// Zero based extruder ID.
|
||||
@ -159,7 +161,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
// Calculate the actual flow we'll be using for this infill.
|
||||
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
|
||||
params.flow = params.bridge ?
|
||||
layerm.bridging_flow(extrusion_role) :
|
||||
// Always enable thick bridges for internal bridges.
|
||||
layerm.bridging_flow(extrusion_role, surface.is_bridge() && ! surface.is_external()) :
|
||||
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
|
||||
|
||||
// Calculate flow spacing for infill pattern generation.
|
||||
@ -302,6 +305,46 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect narrow internal solid infill area and use ipEnsuring pattern instead.
|
||||
{
|
||||
std::vector<char> narrow_expolygons;
|
||||
static constexpr const auto narrow_pattern = ipEnsuring;
|
||||
for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
size_t num_expolygons = fill.expolygons.size();
|
||||
narrow_expolygons.clear();
|
||||
narrow_expolygons.reserve(num_expolygons);
|
||||
// Detect narrow expolygons.
|
||||
int num_narrow = 0;
|
||||
for (const ExPolygon &ex : fill.expolygons) {
|
||||
bool narrow = offset_ex(ex, -scaled<float>(NarrowInfillAreaThresholdMM)).empty();
|
||||
num_narrow += int(narrow);
|
||||
narrow_expolygons.emplace_back(narrow);
|
||||
}
|
||||
if (num_narrow == num_expolygons) {
|
||||
// All expolygons are narrow, change the fill pattern.
|
||||
fill.params.pattern = narrow_pattern;
|
||||
} else if (num_narrow > 0) {
|
||||
// Some expolygons are narrow, split the fills.
|
||||
params = fill.params;
|
||||
params.pattern = narrow_pattern;
|
||||
surface_fills.emplace_back(params);
|
||||
SurfaceFill &old_fill = surface_fills[surface_fill_id];
|
||||
SurfaceFill &new_fill = surface_fills.back();
|
||||
new_fill.region_id = old_fill.region_id;
|
||||
new_fill.surface.surface_type = stInternalSolid;
|
||||
new_fill.surface.thickness = old_fill.surface.thickness;
|
||||
new_fill.expolygons.reserve(num_narrow);
|
||||
for (size_t i = 0; i < narrow_expolygons.size(); ++ i)
|
||||
if (narrow_expolygons[i])
|
||||
new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i]));
|
||||
old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(),
|
||||
[&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }),
|
||||
old_fill.expolygons.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return surface_fills;
|
||||
}
|
||||
|
||||
@ -441,14 +484,28 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
size_t first_object_layer_id = this->object()->get_layer(0)->id();
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
//skip patterns for which additional input is nullptr
|
||||
switch (surface_fill.params.pattern) {
|
||||
case ipLightning: if (lightning_generator == nullptr) continue; break;
|
||||
case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break;
|
||||
case ipSupportCubic: if (support_fill_octree == nullptr) continue; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
|
||||
f->set_bounding_box(bbox);
|
||||
f->layer_id = this->id();
|
||||
// Layer ID is used for orienting the infill in alternating directions.
|
||||
// Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent
|
||||
// from raft.
|
||||
f->layer_id = this->id() - first_object_layer_id;
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
if (surface_fill.params.pattern == ipLightning) {
|
||||
auto *lf = dynamic_cast<FillLightning::Filler*>(f.get());
|
||||
@ -456,11 +513,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
|
||||
}
|
||||
|
||||
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) {
|
||||
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get());
|
||||
assert(fill_concentric != nullptr);
|
||||
fill_concentric->print_config = &this->object()->print()->config();
|
||||
fill_concentric->print_object_config = &this->object()->config();
|
||||
if (surface_fill.params.pattern == ipEnsuring) {
|
||||
auto *fill_ensuring = dynamic_cast<FillEnsuring *>(f.get());
|
||||
assert(fill_ensuring != nullptr);
|
||||
fill_ensuring->print_region_config = &m_regions[surface_fill.region_id]->region().config();
|
||||
}
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
@ -490,7 +546,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.anchor_length = surface_fill.params.anchor_length;
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric;
|
||||
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring;
|
||||
params.layer_height = layerm.layer()->height;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
@ -591,6 +647,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
#endif
|
||||
}
|
||||
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
|
||||
{
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this);
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
const auto resolution = this->object()->print()->config().gcode_resolution.value;
|
||||
|
||||
Polylines sparse_infill_polylines{};
|
||||
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
// skip patterns for which additional input is nullptr
|
||||
switch (surface_fill.params.pattern) {
|
||||
case ipLightning: continue; break;
|
||||
case ipAdaptiveCubic: continue; break;
|
||||
case ipSupportCubic: continue; break;
|
||||
case ipCount: continue; break;
|
||||
case ipSupportBase: continue; break;
|
||||
case ipEnsuring: continue; break;
|
||||
case ipRectilinear:
|
||||
case ipMonotonic:
|
||||
case ipMonotonicLines:
|
||||
case ipAlignedRectilinear:
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars:
|
||||
case ipCubic:
|
||||
case ipLine:
|
||||
case ipConcentric:
|
||||
case ipHoneycomb:
|
||||
case ip3DHoneycomb:
|
||||
case ipGyroid:
|
||||
case ipHilbertCurve:
|
||||
case ipArchimedeanChords:
|
||||
case ipOctagramSpiral: break;
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
|
||||
f->set_bounding_box(bbox);
|
||||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
// f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
double link_max_length = 0.;
|
||||
if (!surface_fill.params.bridge) {
|
||||
#if 0
|
||||
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
|
||||
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
|
||||
#else
|
||||
if (surface_fill.params.density > 80.) // 80%
|
||||
link_max_length = 3. * f->spacing;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Maximum length of the perimeter segment linking two infill lines.
|
||||
f->link_max_length = (coord_t) scale_(link_max_length);
|
||||
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
|
||||
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
|
||||
|
||||
LayerRegion &layerm = *m_regions[surface_fill.region_id];
|
||||
|
||||
// apply half spacing using this flow's own spacing and generate infill
|
||||
FillParams params;
|
||||
params.density = float(0.01 * surface_fill.params.density);
|
||||
params.dont_adjust = false; // surface_fill.params.dont_adjust;
|
||||
params.anchor_length = surface_fill.params.anchor_length;
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
params.use_arachne = false;
|
||||
params.layer_height = layerm.layer()->height;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
try {
|
||||
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
|
||||
sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end());
|
||||
} catch (InfillFailedException &) {}
|
||||
}
|
||||
}
|
||||
|
||||
return sparse_infill_polylines;
|
||||
}
|
||||
|
||||
// Create ironing extrusions over top surfaces.
|
||||
void Layer::make_ironing()
|
||||
{
|
||||
@ -698,7 +842,11 @@ void Layer::make_ironing()
|
||||
FillRectilinear fill;
|
||||
FillParams fill_params;
|
||||
fill.set_bounding_box(this->object()->bounding_box());
|
||||
fill.layer_id = this->id();
|
||||
// Layer ID is used for orienting the infill in alternating directions.
|
||||
// Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent
|
||||
// from raft.
|
||||
//FIXME ironing does not take fill angle into account. Shall it? Does it matter?
|
||||
fill.layer_id = this->id() - this->object()->get_layer(0)->id();
|
||||
fill.z = this->print_z;
|
||||
fill.overlap = 0;
|
||||
fill_params.density = 1.;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillAdaptive.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillEnsuring.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@ -50,6 +51,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
||||
case ipSupportCubic: return new FillAdaptive::Filler();
|
||||
case ipSupportBase: return new FillSupportBase();
|
||||
case ipLightning: return new FillLightning::Filler();
|
||||
case ipEnsuring: return new FillEnsuring();
|
||||
default: throw Slic3r::InvalidArgument("unknown type");
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,10 @@ public:
|
||||
// Octree builds on mesh for usage in the adaptive cubic infill
|
||||
FillAdaptive::Octree* adapt_fill_octree = nullptr;
|
||||
|
||||
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
|
||||
const PrintConfig *print_config = nullptr;
|
||||
const PrintObjectConfig *print_object_config = nullptr;
|
||||
|
||||
public:
|
||||
virtual ~Fill() {}
|
||||
virtual Fill* clone() const = 0;
|
||||
|
@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms,
|
||||
continue;
|
||||
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
||||
if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) {
|
||||
thick_polyline.points.pop_back();
|
||||
assert(thick_polyline.points.size() * 2 == thick_polyline.width.size());
|
||||
int nearest_idx = nearest_point_index(thick_polyline.points, last_pos);
|
||||
std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end());
|
||||
std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end());
|
||||
thick_polyline.points.emplace_back(thick_polyline.points.front());
|
||||
}
|
||||
if (extrusion->is_closed)
|
||||
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
|
||||
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
||||
last_pos = thick_polylines_out.back().last_point();
|
||||
}
|
||||
|
@ -26,11 +26,6 @@ protected:
|
||||
ThickPolylines &thick_polylines_out) override;
|
||||
|
||||
bool no_sort() const override { return true; }
|
||||
|
||||
const PrintConfig *print_config = nullptr;
|
||||
const PrintObjectConfig *print_object_config = nullptr;
|
||||
|
||||
friend class Layer;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
82
src/libslic3r/Fill/FillEnsuring.cpp
Normal file
82
src/libslic3r/Fill/FillEnsuring.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Arachne/WallToolPaths.hpp"
|
||||
|
||||
#include "FillEnsuring.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
assert(params.use_arachne);
|
||||
assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr);
|
||||
|
||||
const coord_t scaled_spacing = scaled<coord_t>(this->spacing);
|
||||
|
||||
// Perform offset.
|
||||
Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled<float>(this->overlap)) : ExPolygons{surface->expolygon};
|
||||
// Create the infills for each of the regions.
|
||||
ThickPolylines thick_polylines_out;
|
||||
for (ExPolygon &ex_poly : expp) {
|
||||
Point bbox_size = ex_poly.contour.bounding_box().size();
|
||||
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1;
|
||||
Polygons polygons = to_polygons(ex_poly);
|
||||
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config);
|
||||
if (std::vector<Arachne::VariableWidthLines> loops = wall_tool_paths.getToolPaths(); !loops.empty()) {
|
||||
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
|
||||
for (Arachne::VariableWidthLines &loop : loops) {
|
||||
if (loop.empty())
|
||||
continue;
|
||||
for (const Arachne::ExtrusionLine &wall : loop)
|
||||
all_extrusions.emplace_back(&wall);
|
||||
}
|
||||
|
||||
// Split paths using a nearest neighbor search.
|
||||
size_t firts_poly_idx = thick_polylines_out.size();
|
||||
Point last_pos(0, 0);
|
||||
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
|
||||
if (extrusion->empty())
|
||||
continue;
|
||||
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
||||
if (thick_polyline.length() == 0.)
|
||||
//FIXME this should not happen.
|
||||
continue;
|
||||
assert(thick_polyline.size() > 1);
|
||||
assert(thick_polyline.length() > 0.);
|
||||
//assert(thick_polyline.points.size() == thick_polyline.width.size());
|
||||
if (extrusion->is_closed)
|
||||
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
|
||||
|
||||
assert(thick_polyline.size() > 1);
|
||||
//assert(thick_polyline.points.size() == thick_polyline.width.size());
|
||||
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
||||
last_pos = thick_polylines_out.back().last_point();
|
||||
}
|
||||
|
||||
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
// Keep valid paths only.
|
||||
size_t j = firts_poly_idx;
|
||||
for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
|
||||
assert(thick_polylines_out[i].size() > 1);
|
||||
assert(thick_polylines_out[i].length() > 0.);
|
||||
//assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size());
|
||||
thick_polylines_out[i].clip_end(this->loop_clipping);
|
||||
assert(thick_polylines_out[i].size() > 1);
|
||||
if (thick_polylines_out[i].is_valid()) {
|
||||
if (j < i)
|
||||
thick_polylines_out[j] = std::move(thick_polylines_out[i]);
|
||||
++j;
|
||||
}
|
||||
}
|
||||
if (j < thick_polylines_out.size())
|
||||
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
|
||||
}
|
||||
}
|
||||
|
||||
return thick_polylines_out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
30
src/libslic3r/Fill/FillEnsuring.hpp
Normal file
30
src/libslic3r/Fill/FillEnsuring.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef slic3r_FillEnsuring_hpp_
|
||||
#define slic3r_FillEnsuring_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillEnsuring : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill *clone() const override { return new FillEnsuring(*this); }
|
||||
~FillEnsuring() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override { return {}; };
|
||||
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out);
|
||||
|
||||
bool no_sort() const override { return true; }
|
||||
|
||||
// PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill.
|
||||
const PrintRegionConfig *print_region_config = nullptr;
|
||||
|
||||
friend class Layer;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillEnsuring_hpp_
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintRegionConfig;
|
||||
class Surface;
|
||||
|
||||
class FillRectilinear : public Fill
|
||||
|
@ -137,9 +137,7 @@ static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
|
||||
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
|
||||
static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches";
|
||||
static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters";
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume";
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
|
||||
static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed";
|
||||
static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets";
|
||||
@ -906,36 +904,18 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) {
|
||||
ModelObject* o = model.objects[obj_id];
|
||||
for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) {
|
||||
ModelVolume* v = o->volumes[vol_id];
|
||||
if (v->source.input_file.empty())
|
||||
v->source.input_file = v->name.empty() ? filename : v->name;
|
||||
v->source.input_file = filename;
|
||||
if (v->source.volume_idx == -1)
|
||||
v->source.volume_idx = vol_id;
|
||||
if (v->source.object_idx == -1)
|
||||
v->source.object_idx = obj_id;
|
||||
}
|
||||
}
|
||||
#else
|
||||
int object_idx = 0;
|
||||
for (ModelObject* o : model.objects) {
|
||||
int volume_idx = 0;
|
||||
for (ModelVolume* v : o->volumes) {
|
||||
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) {
|
||||
v->source.input_file = filename;
|
||||
if (v->source.volume_idx == -1)
|
||||
v->source.volume_idx = volume_idx;
|
||||
if (v->source.object_idx == -1)
|
||||
v->source.object_idx = object_idx;
|
||||
}
|
||||
++volume_idx;
|
||||
}
|
||||
++object_idx;
|
||||
}
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
|
||||
// // fixes the min z of the model if negative
|
||||
// model.adjust_min_z();
|
||||
@ -2287,10 +2267,8 @@ namespace Slic3r {
|
||||
volume->source.is_converted_from_inches = metadata.value == "1";
|
||||
else if (metadata.key == SOURCE_IN_METERS_KEY)
|
||||
volume->source.is_converted_from_meters = metadata.value == "1";
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY)
|
||||
volume->source.is_from_builtin_objects = metadata.value == "1";
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
else
|
||||
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
||||
}
|
||||
@ -3328,10 +3306,8 @@ namespace Slic3r {
|
||||
stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
else if (volume->source.is_converted_from_meters)
|
||||
stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
if (volume->source.is_from_builtin_objects)
|
||||
stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
}
|
||||
|
||||
// stores volume's config data
|
||||
|
@ -657,11 +657,7 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||
if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform)
|
||||
m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
|
||||
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
if (m_volume->source.input_file.empty()) {
|
||||
#else
|
||||
if (m_volume->source.input_file.empty() && m_volume->type() == ModelVolumeType::MODEL_PART) {
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
m_volume->source.object_idx = (int)m_model.objects.size() - 1;
|
||||
m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
|
||||
m_volume->center_geometry_after_creation();
|
||||
@ -818,10 +814,8 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||
m_volume->source.is_converted_from_inches = m_value[1] == "1";
|
||||
else if (strcmp(opt_key, "source_in_meters") == 0)
|
||||
m_volume->source.is_converted_from_meters = m_value[1] == "1";
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
else if (strcmp(opt_key, "source_is_builtin_volume") == 0)
|
||||
m_volume->source.is_from_builtin_objects = m_value[1] == "1";
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
}
|
||||
}
|
||||
else if (m_path.size() == 3) {
|
||||
@ -922,11 +916,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut
|
||||
unsigned int counter = 0;
|
||||
for (ModelVolume* v : o->volumes) {
|
||||
++counter;
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
if (v->source.input_file.empty())
|
||||
#else
|
||||
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART)
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
v->source.input_file = path;
|
||||
if (v->name.empty()) {
|
||||
v->name = o->name;
|
||||
@ -1075,11 +1065,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti
|
||||
|
||||
for (ModelObject *o : model->objects)
|
||||
for (ModelVolume *v : o->volumes)
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
if (v->source.input_file.empty())
|
||||
#else
|
||||
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART)
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
v->source.input_file = path;
|
||||
|
||||
return true;
|
||||
@ -1270,10 +1256,8 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
|
||||
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
|
||||
else if (volume->source.is_converted_from_meters)
|
||||
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
|
||||
#if ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
if (volume->source.is_from_builtin_objects)
|
||||
stream << " <metadata type=\"slic3r.source_is_builtin_volume\">1</metadata>\n";
|
||||
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
|
||||
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
for (size_t i = 0; i < its.indices.size(); ++i) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
#include "Config.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "GCode/ExtrusionProcessor.hpp"
|
||||
#include "I18N.hpp"
|
||||
@ -24,6 +25,7 @@
|
||||
#include <cstdlib>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -1003,18 +1005,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
std::sort(zs.begin(), zs.end());
|
||||
m_layer_count += (unsigned int)(object->instances().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
|
||||
}
|
||||
} else {
|
||||
// Print all objects with the same print_z together.
|
||||
std::vector<coordf_t> zs;
|
||||
for (auto object : print.objects()) {
|
||||
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
|
||||
for (auto layer : object->layers())
|
||||
zs.push_back(layer->print_z);
|
||||
for (auto layer : object->support_layers())
|
||||
zs.push_back(layer->print_z);
|
||||
}
|
||||
std::sort(zs.begin(), zs.end());
|
||||
m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
||||
@ -1032,6 +1022,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
m_pressure_equalizer = make_unique<PressureEqualizer>(print.config());
|
||||
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
|
||||
|
||||
if (print.config().avoid_crossing_curled_overhangs){
|
||||
this->m_avoid_crossing_curled_overhangs.init_bed_shape(get_bed_shape(print.config()));
|
||||
}
|
||||
|
||||
// Write information on the generator.
|
||||
file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str());
|
||||
|
||||
@ -1138,6 +1132,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
this->set_extruders(tool_ordering.all_extruders());
|
||||
// Order object instances using a nearest neighbor search.
|
||||
print_object_instances_ordering = chain_print_object_instances(print);
|
||||
m_layer_count = tool_ordering.layer_tools().size();
|
||||
}
|
||||
if (initial_extruder_id == (unsigned int)-1) {
|
||||
// Nothing to print!
|
||||
@ -2107,8 +2102,12 @@ LayerResult GCode::process_layer(
|
||||
if (this->config().avoid_crossing_curled_overhangs) {
|
||||
m_avoid_crossing_curled_overhangs.clear();
|
||||
for (const ObjectLayerToPrint &layer_to_print : layers) {
|
||||
m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin())));
|
||||
m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin())));
|
||||
if (layer_to_print.object() == nullptr)
|
||||
continue;
|
||||
for (const auto &instance : layer_to_print.object()->instances()) {
|
||||
m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.object_layer, instance.shift);
|
||||
m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.support_layer, instance.shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2859,16 +2858,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
);
|
||||
}
|
||||
|
||||
bool variable_speed = false;
|
||||
bool variable_speed_or_fan_speed = false;
|
||||
std::vector<ProcessedPoint> new_points{};
|
||||
if (this->m_config.enable_dynamic_overhang_speeds && !this->on_first_layer() && path.role().is_perimeter()) {
|
||||
if ((this->m_config.enable_dynamic_overhang_speeds || this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) &&
|
||||
!this->on_first_layer() && path.role().is_perimeter()) {
|
||||
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {{100, ConfigOptionFloatOrPercent{speed, false}}};
|
||||
if (this->m_config.enable_dynamic_overhang_speeds) {
|
||||
overhangs_with_speeds = {{0, m_config.overhang_speed_0},
|
||||
{25, m_config.overhang_speed_1},
|
||||
{50, m_config.overhang_speed_2},
|
||||
{75, m_config.overhang_speed_3},
|
||||
{100, ConfigOptionFloatOrPercent{speed, false}}};
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, ConfigOptionInts>> overhang_w_fan_speeds = {{100, ConfigOptionInts{0}}};
|
||||
if (this->m_config.enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) {
|
||||
overhang_w_fan_speeds = {{0, m_config.overhang_fan_speed_0},
|
||||
{25, m_config.overhang_fan_speed_1},
|
||||
{50, m_config.overhang_fan_speed_2},
|
||||
{75, m_config.overhang_fan_speed_3},
|
||||
{100, ConfigOptionInts{0}}};
|
||||
}
|
||||
|
||||
double external_perim_reference_speed = std::min(m_config.get_abs_value("external_perimeter_speed"),
|
||||
std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm,
|
||||
m_config.max_volumetric_speed.value / path.mm3_per_mm));
|
||||
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, m_config.overhang_overlap_levels,
|
||||
m_config.dynamic_overhang_speeds,
|
||||
external_perim_reference_speed, speed);
|
||||
variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint &p) { return p.speed != speed; });
|
||||
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds,
|
||||
m_writer.extruder()->id(), external_perim_reference_speed,
|
||||
speed);
|
||||
variable_speed_or_fan_speed = std::any_of(new_points.begin(), new_points.end(),
|
||||
[speed](const ProcessedPoint &p) { return p.speed != speed || p.fan_speed != 0; });
|
||||
}
|
||||
|
||||
double F = speed * 60; // convert mm/sec to mm/min
|
||||
@ -2922,19 +2941,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
+ float_to_string_decimal_point(m_last_height) + "\n";
|
||||
}
|
||||
|
||||
std::string comment;
|
||||
std::string cooling_marker_setspeed_comments;
|
||||
if (m_enable_cooling_markers) {
|
||||
if (path.role().is_bridge())
|
||||
if (path.role().is_bridge() &&
|
||||
(!path.role().is_perimeter() || !this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())))
|
||||
gcode += ";_BRIDGE_FAN_START\n";
|
||||
else
|
||||
comment = ";_EXTRUDE_SET_SPEED";
|
||||
cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED";
|
||||
if (path.role() == ExtrusionRole::ExternalPerimeter)
|
||||
comment += ";_EXTERNAL_PERIMETER";
|
||||
cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER";
|
||||
}
|
||||
|
||||
if (!variable_speed) {
|
||||
if (!variable_speed_or_fan_speed) {
|
||||
// F is mm per minute.
|
||||
gcode += m_writer.set_speed(F, "", comment);
|
||||
gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments);
|
||||
double path_length = 0.;
|
||||
std::string comment;
|
||||
if (m_config.gcode_comments) {
|
||||
@ -2957,21 +2977,28 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
|
||||
marked_comment = description;
|
||||
marked_comment += description_bridge;
|
||||
}
|
||||
double last_set_speed = new_points[0].speed * 60.0;
|
||||
gcode += m_writer.set_speed(last_set_speed, "", comment);
|
||||
double last_set_speed = new_points[0].speed * 60.0;
|
||||
double last_set_fan_speed = new_points[0].fan_speed;
|
||||
gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments);
|
||||
gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
|
||||
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
|
||||
for (size_t i = 1; i < new_points.size(); i++) {
|
||||
const ProcessedPoint& processed_point = new_points[i];
|
||||
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
|
||||
const double line_length = (p - prev).norm();
|
||||
const ProcessedPoint &processed_point = new_points[i];
|
||||
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
|
||||
const double line_length = (p - prev).norm();
|
||||
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment);
|
||||
prev = p;
|
||||
prev = p;
|
||||
double new_speed = processed_point.speed * 60.0;
|
||||
if (last_set_speed != new_speed) {
|
||||
gcode += m_writer.set_speed(new_speed, "", comment);
|
||||
gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments);
|
||||
last_set_speed = new_speed;
|
||||
}
|
||||
if (last_set_fan_speed != processed_point.fan_speed) {
|
||||
last_set_fan_speed = processed_point.fan_speed;
|
||||
gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
|
||||
}
|
||||
}
|
||||
gcode += ";_RESET_FAN_SPEED\n";
|
||||
}
|
||||
|
||||
if (m_enable_cooling_markers)
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "../GCode.hpp"
|
||||
#include "CoolingBuffer.hpp"
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@ -59,6 +60,9 @@ struct CoolingLine
|
||||
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
|
||||
// cannot have its speed adjusted. This should not happen (sic!).
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 12,
|
||||
// Custom fan speed (introduced for overhang fan speed)
|
||||
TYPE_SET_FAN_SPEED = 1 << 13,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 14,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
|
||||
@ -88,6 +92,8 @@ struct CoolingLine
|
||||
float time;
|
||||
// Maximum duration of this segment.
|
||||
float time_max;
|
||||
// Requested fan speed
|
||||
int fan_speed;
|
||||
// If marked with the "slowdown" flag, the line has been slowed down.
|
||||
bool slowdown;
|
||||
};
|
||||
@ -372,7 +378,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
@ -491,13 +498,25 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
bool has_S = pos_S > 0;
|
||||
bool has_P = pos_P > 0;
|
||||
if (has_S || has_P) {
|
||||
auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
|
||||
if (has_P)
|
||||
line.time *= 0.001f;
|
||||
} else
|
||||
line.time = 0;
|
||||
line.time_max = line.time;
|
||||
} else if (boost::contains(sline, ";_SET_FAN_SPEED")) {
|
||||
auto speed_start = sline.find_last_of('D');
|
||||
int speed = 0;
|
||||
for (char num : sline.substr(speed_start + 1)) {
|
||||
speed = speed * 10 + (num - '0');
|
||||
}
|
||||
line.type = CoolingLine::TYPE_SET_FAN_SPEED;
|
||||
line.fan_speed = speed;
|
||||
} else if (boost::contains(sline, ";_RESET_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_RESET_FAN_SPEED;
|
||||
}
|
||||
|
||||
if (line.type != 0)
|
||||
adjustment->lines.emplace_back(std::move(line));
|
||||
}
|
||||
@ -730,10 +749,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
new_gcode.reserve(gcode.size() * 2);
|
||||
bool bridge_fan_control = false;
|
||||
int bridge_fan_speed = 0;
|
||||
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() {
|
||||
auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed]() {
|
||||
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
|
||||
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
|
||||
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
|
||||
std::pair<int, int> custom_fan_speed_limits{fan_speed_new, 100 };
|
||||
int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers);
|
||||
// Is the fan speed ramp enabled?
|
||||
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
|
||||
@ -750,11 +770,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
if (layer_time < slowdown_below_layer_time) {
|
||||
// Layer time very short. Enable the fan to a full throttle.
|
||||
fan_speed_new = max_fan_speed;
|
||||
custom_fan_speed_limits.first = fan_speed_new;
|
||||
} else if (layer_time < fan_below_layer_time) {
|
||||
// Layer time quite short. Enable the fan proportionally according to the current layer time.
|
||||
assert(layer_time >= slowdown_below_layer_time);
|
||||
double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
|
||||
fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
|
||||
custom_fan_speed_limits.first = fan_speed_new;
|
||||
}
|
||||
}
|
||||
bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
|
||||
@ -763,6 +785,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255);
|
||||
custom_fan_speed_limits.second = fan_speed_new;
|
||||
}
|
||||
#undef EXTRUDER_CONFIG
|
||||
bridge_fan_control = bridge_fan_speed > fan_speed_new;
|
||||
@ -775,11 +798,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
m_fan_speed = fan_speed_new;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
|
||||
}
|
||||
custom_fan_speed_limits.first = std::min(custom_fan_speed_limits.first, custom_fan_speed_limits.second);
|
||||
return custom_fan_speed_limits;
|
||||
};
|
||||
|
||||
const char *pos = gcode.c_str();
|
||||
int current_feedrate = 0;
|
||||
change_extruder_set_fan();
|
||||
std::pair<int,int> fan_speed_limits = change_extruder_set_fan();
|
||||
for (const CoolingLine *line : lines) {
|
||||
const char *line_start = gcode.c_str() + line->line_start;
|
||||
const char *line_end = gcode.c_str() + line->line_end;
|
||||
@ -790,9 +815,17 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder);
|
||||
if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) {
|
||||
m_current_extruder = new_extruder;
|
||||
change_extruder_set_fan();
|
||||
fan_speed_limits = change_extruder_set_fan();
|
||||
}
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
} else if (line->type & CoolingLine::TYPE_SET_FAN_SPEED) {
|
||||
int new_speed = std::clamp(line->fan_speed, fan_speed_limits.first, fan_speed_limits.second);
|
||||
if (m_fan_speed != new_speed) {
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, new_speed);
|
||||
m_fan_speed = new_speed;
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_RESET_FAN_SPEED){
|
||||
fan_speed_limits = change_extruder_set_fan();
|
||||
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
|
||||
if (bridge_fan_control)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed);
|
||||
@ -816,7 +849,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
if (line->slowdown)
|
||||
new_feedrate = int(floor(60. * line->feedrate + 0.5));
|
||||
else
|
||||
auto res = std::from_chars(fpos, line_end, new_feedrate);
|
||||
//auto res =
|
||||
std::from_chars(fpos, line_end, new_feedrate);
|
||||
if (new_feedrate == current_feedrate) {
|
||||
// No need to change the F value.
|
||||
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
@ -238,6 +239,7 @@ struct ProcessedPoint
|
||||
{
|
||||
Point p;
|
||||
float speed = 1.0f;
|
||||
int fan_speed = 0;
|
||||
};
|
||||
|
||||
class ExtrusionQualityEstimator
|
||||
@ -257,34 +259,27 @@ public:
|
||||
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
|
||||
}
|
||||
|
||||
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
|
||||
const ConfigOptionPercents &overlaps,
|
||||
const ConfigOptionFloatsOrPercents &speeds,
|
||||
float ext_perimeter_speed,
|
||||
float original_speed)
|
||||
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
|
||||
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
|
||||
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
|
||||
size_t extruder_id,
|
||||
float ext_perimeter_speed,
|
||||
float original_speed)
|
||||
{
|
||||
size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size());
|
||||
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
|
||||
std::vector<std::pair<float, float>> speed_sections;
|
||||
for (size_t i = 0; i < speed_sections_count; i++) {
|
||||
float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0));
|
||||
float speed = speeds.get_at(i).percent ? (speed_base * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value;
|
||||
speed_sections.push_back({distance, speed});
|
||||
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
|
||||
float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
|
||||
overhangs_w_speeds[i].second.value;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
std::sort(speed_sections.begin(), speed_sections.end(),
|
||||
[](const std::pair<float, float> &a, const std::pair<float, float> &b) {
|
||||
if (a.first == b.first) {
|
||||
return a.second > b.second;
|
||||
}
|
||||
return a.first < b.first; });
|
||||
|
||||
std::pair<float, float> last_section{INFINITY, 0};
|
||||
for (auto §ion : speed_sections) {
|
||||
if (section.first == last_section.first) {
|
||||
section.second = last_section.second;
|
||||
} else {
|
||||
last_section = section;
|
||||
}
|
||||
std::map<float, float> fan_speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
|
||||
float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
std::vector<ExtendedPoint> extended_points =
|
||||
@ -296,28 +291,26 @@ public:
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
|
||||
float final_speed;
|
||||
if (distance <= speed_sections.front().first) {
|
||||
final_speed = original_speed;
|
||||
} else if (distance >= speed_sections.back().first) {
|
||||
final_speed = speed_sections.back().second;
|
||||
} else {
|
||||
size_t section_idx = 0;
|
||||
while (distance > speed_sections[section_idx + 1].first) {
|
||||
section_idx++;
|
||||
}
|
||||
float t = (distance - speed_sections[section_idx].first) /
|
||||
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
|
||||
t = std::clamp(t, 0.0f, 1.0f);
|
||||
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
return final_speed;
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
|
||||
float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance));
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
|
||||
interpolate_speed(speed_sections, next.distance));
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
|
||||
interpolate_speed(fan_speed_sections, next.distance));
|
||||
|
||||
processed_points.push_back({scaled(curr.position), extrusion_speed});
|
||||
processed_points.push_back({scaled(curr.position), extrusion_speed, int(fan_speed)});
|
||||
}
|
||||
return processed_points;
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProces
|
||||
{ EProducer::Slic3r, "generated by Slic3r" },
|
||||
{ EProducer::SuperSlicer, "generated by SuperSlicer" },
|
||||
{ EProducer::Cura, "Cura_SteamEngine" },
|
||||
{ EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" },
|
||||
{ EProducer::Simplify3D, "generated by Simplify3D(R)" },
|
||||
{ EProducer::CraftWare, "CraftWare" },
|
||||
{ EProducer::ideaMaker, "ideaMaker" },
|
||||
{ EProducer::KissSlicer, "KISSlicer" },
|
||||
@ -2025,10 +2025,10 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ; layer
|
||||
tag = " layer";
|
||||
// ; layer | ;layer
|
||||
tag = "layer";
|
||||
pos = cmt.find(tag);
|
||||
if (pos == 0) {
|
||||
if (pos == 0 || pos == 1) {
|
||||
// skip lines "; layer end"
|
||||
const std::string_view data = cmt.substr(pos + tag.length());
|
||||
size_t end_start = data.find("end");
|
||||
@ -2402,7 +2402,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
if (m_forced_height > 0.0f)
|
||||
m_height = m_forced_height;
|
||||
else if (m_layer_id == 0)
|
||||
m_height = (m_end_position[Z] <= double(m_first_layer_height)) ? m_end_position[Z] : m_first_layer_height;
|
||||
m_height = m_first_layer_height + m_z_offset;
|
||||
else if (line.comment() != INTERNAL_G2G3_TAG){
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0)
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
@ -3378,9 +3378,9 @@ void GCodeProcessor::process_T(const std::string_view command)
|
||||
extra_time += m_kissslicer_toolchange_time_correction;
|
||||
simulate_st_synchronize(extra_time);
|
||||
|
||||
// specific to single extruder multi material, set the extruder temperature
|
||||
// if not done yet
|
||||
if (m_single_extruder_multi_material && m_extruder_temps[m_extruder_id] == 0.0f)
|
||||
// specific to single extruder multi material, set the new extruder temperature
|
||||
// to match the old one
|
||||
if (m_single_extruder_multi_material)
|
||||
m_extruder_temps[m_extruder_id] = m_extruder_temps[old_extruder_id];
|
||||
|
||||
m_result.extruders_count = std::max<size_t>(m_result.extruders_count, m_extruder_id + 1);
|
||||
|
@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &
|
||||
if (surface.is_internal())
|
||||
m_internal_islands.emplace_back(&surface.expolygon);
|
||||
// Calculate bounding boxes of internal slices.
|
||||
class BBoxWrapper {
|
||||
public:
|
||||
BBoxWrapper(const size_t idx, const BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const AABBTree::BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
AABBTree::BoundingBox m_bbox;
|
||||
};
|
||||
std::vector<BBoxWrapper> bboxes;
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(m_internal_islands.size());
|
||||
for (size_t i = 0; i < m_internal_islands.size(); ++ i)
|
||||
bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));
|
||||
|
@ -929,7 +929,7 @@ void WipeTower::toolchange_Unload(
|
||||
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:
|
||||
// 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
|
||||
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)
|
||||
@ -937,8 +937,8 @@ void WipeTower::toolchange_Unload(
|
||||
else
|
||||
writer.set_position(pos);
|
||||
|
||||
writer.resume_preview()
|
||||
.flush_planner_queue();
|
||||
writer.resume_preview()
|
||||
.flush_planner_queue();
|
||||
}
|
||||
|
||||
// Change the tool, set a speed override for soluble and flex materials.
|
||||
|
@ -720,28 +720,26 @@ void Transformation::reset()
|
||||
}
|
||||
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
void Transformation::reset_rotation()
|
||||
{
|
||||
const Geometry::TransformationSVD svd(*this);
|
||||
m_matrix = get_offset_matrix() * Transform3d(svd.v * svd.s * svd.v.transpose()) * svd.mirror_matrix();
|
||||
}
|
||||
|
||||
void Transformation::reset_scaling_factor()
|
||||
{
|
||||
const Geometry::TransformationSVD svd(*this);
|
||||
m_matrix = get_offset_matrix() * Transform3d(svd.u) * Transform3d(svd.v.transpose()) * svd.mirror_matrix();
|
||||
}
|
||||
|
||||
void Transformation::reset_skew()
|
||||
{
|
||||
Matrix3d rotation;
|
||||
Matrix3d scale;
|
||||
m_matrix.computeRotationScaling(&rotation, &scale);
|
||||
auto new_scale_factor = [](const Matrix3d& s) {
|
||||
return pow(s(0, 0) * s(1, 1) * s(2, 2), 1. / 3.); // scale average
|
||||
};
|
||||
|
||||
const double average_scale = std::cbrt(scale(0, 0) * scale(1, 1) * scale(2, 2));
|
||||
|
||||
scale(0, 0) = is_left_handed() ? -average_scale : average_scale;
|
||||
scale(1, 1) = average_scale;
|
||||
scale(2, 2) = average_scale;
|
||||
|
||||
scale(0, 1) = 0.0;
|
||||
scale(0, 2) = 0.0;
|
||||
scale(1, 0) = 0.0;
|
||||
scale(1, 2) = 0.0;
|
||||
scale(2, 0) = 0.0;
|
||||
scale(2, 1) = 0.0;
|
||||
|
||||
const Vec3d offset = get_offset();
|
||||
m_matrix = rotation * scale;
|
||||
m_matrix.translation() = offset;
|
||||
const Geometry::TransformationSVD svd(*this);
|
||||
m_matrix = get_offset_matrix() * Transform3d(svd.u) * scale_transform(new_scale_factor(svd.s)) * Transform3d(svd.v.transpose()) * svd.mirror_matrix();
|
||||
}
|
||||
|
||||
Transform3d Transformation::get_matrix_no_offset() const
|
||||
@ -838,6 +836,55 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
|
||||
}
|
||||
#endif // !ENABLE_WORLD_COORDINATE
|
||||
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
TransformationSVD::TransformationSVD(const Transform3d& trafo)
|
||||
{
|
||||
const auto &m0 = trafo.matrix().block<3, 3>(0, 0);
|
||||
mirror = m0.determinant() < 0.0;
|
||||
|
||||
Matrix3d m;
|
||||
if (mirror)
|
||||
m = m0 * Eigen::DiagonalMatrix<double, 3, 3>(-1.0, 1.0, 1.0);
|
||||
else
|
||||
m = m0;
|
||||
const Eigen::JacobiSVD<Matrix3d> svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV);
|
||||
u = svd.matrixU();
|
||||
v = svd.matrixV();
|
||||
s = svd.singularValues().asDiagonal();
|
||||
|
||||
scale = !s.isApprox(Matrix3d::Identity());
|
||||
anisotropic_scale = ! is_approx(s(0, 0), s(1, 1)) || ! is_approx(s(1, 1), s(2, 2));
|
||||
rotation = !v.isApprox(u);
|
||||
|
||||
if (anisotropic_scale) {
|
||||
rotation_90_degrees = true;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const Vec3d row = v.row(i).cwiseAbs();
|
||||
const size_t num_zeros = is_approx(row[0], 0.) + is_approx(row[1], 0.) + is_approx(row[2], 0.);
|
||||
const size_t num_ones = is_approx(row[0], 1.) + is_approx(row[1], 1.) + is_approx(row[2], 1.);
|
||||
if (num_zeros != 2 || num_ones != 1) {
|
||||
rotation_90_degrees = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Detect skew by brute force: check if the axes are still orthogonal after transformation
|
||||
const Matrix3d trafo_linear = trafo.linear();
|
||||
const std::array<Vec3d, 3> axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
|
||||
std::array<Vec3d, 3> transformed_axes;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
transformed_axes[i] = trafo_linear * axes[i];
|
||||
}
|
||||
skew = std::abs(transformed_axes[0].dot(transformed_axes[1])) > EPSILON ||
|
||||
std::abs(transformed_axes[1].dot(transformed_axes[2])) > EPSILON ||
|
||||
std::abs(transformed_axes[2].dot(transformed_axes[0])) > EPSILON;
|
||||
|
||||
// This following old code does not work under all conditions. The v matrix can become non diagonal (see SPE-1492)
|
||||
// skew = ! rotation_90_degrees;
|
||||
} else
|
||||
skew = false;
|
||||
}
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
|
||||
// For parsing a transformation matrix from 3MF / AMF.
|
||||
Transform3d transform3d_from_string(const std::string& transform_str)
|
||||
{
|
||||
@ -883,4 +930,30 @@ double rotation_diff_z(const Transform3d &trafo_from, const Transform3d &trafo_t
|
||||
return atan2(vx.y(), vx.x());
|
||||
}
|
||||
|
||||
bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2)
|
||||
{
|
||||
if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON)
|
||||
// One of the object is higher than the other above the build plate (or below the build plate).
|
||||
return false;
|
||||
Matrix3d m1 = t1.matrix().block<3, 3>(0, 0);
|
||||
Matrix3d m2 = t2.matrix().block<3, 3>(0, 0);
|
||||
Matrix3d m = m2.inverse() * m1;
|
||||
Vec3d z = m.block<3, 1>(0, 2);
|
||||
if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON)
|
||||
// Z direction or length changed.
|
||||
return false;
|
||||
// Z still points in the same direction and it has the same length.
|
||||
Vec3d x = m.block<3, 1>(0, 0);
|
||||
Vec3d y = m.block<3, 1>(0, 1);
|
||||
if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON)
|
||||
return false;
|
||||
double lx2 = x.squaredNorm();
|
||||
double ly2 = y.squaredNorm();
|
||||
if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON)
|
||||
return false;
|
||||
// Verify whether the vectors x, y are still perpendicular.
|
||||
double d = x.dot(y);
|
||||
return std::abs(d * d) < EPSILON * lx2 * ly2;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::Geometry
|
||||
|
@ -492,8 +492,8 @@ public:
|
||||
void reset();
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
void reset_offset() { set_offset(Vec3d::Zero()); }
|
||||
void reset_rotation() { set_rotation(Vec3d::Zero()); }
|
||||
void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); }
|
||||
void reset_rotation();
|
||||
void reset_scaling_factor();
|
||||
void reset_mirror() { set_mirror(Vec3d::Ones()); }
|
||||
void reset_skew();
|
||||
|
||||
@ -538,6 +538,27 @@ private:
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
};
|
||||
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
struct TransformationSVD
|
||||
{
|
||||
Matrix3d u = Matrix3d::Identity();
|
||||
Matrix3d s = Matrix3d::Identity();
|
||||
Matrix3d v = Matrix3d::Identity();
|
||||
|
||||
bool mirror{ false };
|
||||
bool scale{ false };
|
||||
bool anisotropic_scale{ false };
|
||||
bool rotation{ false };
|
||||
bool rotation_90_degrees{ false };
|
||||
bool skew{ false };
|
||||
|
||||
explicit TransformationSVD(const Transformation& trafo) : TransformationSVD(trafo.get_matrix()) {}
|
||||
explicit TransformationSVD(const Transform3d& trafo);
|
||||
|
||||
Eigen::DiagonalMatrix<double, 3, 3> mirror_matrix() const { return Eigen::DiagonalMatrix<double, 3, 3>(this->mirror ? -1. : 1., 1., 1.); }
|
||||
};
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
|
||||
// For parsing a transformation matrix from 3MF / AMF.
|
||||
extern Transform3d transform3d_from_string(const std::string& transform_str);
|
||||
|
||||
@ -563,6 +584,13 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
|
||||
return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z());
|
||||
}
|
||||
|
||||
// Returns true if one transformation may be converted into another transformation by
|
||||
// rotation around Z and by mirroring in X / Y only. Two objects sharing such transformation
|
||||
// may share support structures and they share Z height.
|
||||
bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2);
|
||||
inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transformation &t1, const Transformation &t2)
|
||||
{ return trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(t1.get_matrix(), t2.get_matrix()); }
|
||||
|
||||
template <class Tout = double, class Tin>
|
||||
std::pair<Tout, Tout> dir_to_spheric(const Vec<3, Tin> &n, Tout norm = 1.)
|
||||
{
|
||||
|
@ -444,7 +444,9 @@ private:
|
||||
|
||||
MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) :
|
||||
m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width)
|
||||
{}
|
||||
{
|
||||
(void)m_expolygon; // supress unused variable warning
|
||||
}
|
||||
|
||||
void MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
@ -468,9 +470,6 @@ void MedialAxis::build(ThickPolylines* polylines)
|
||||
}
|
||||
*/
|
||||
|
||||
//typedef const VD::vertex_type vert_t;
|
||||
using edge_t = const VD::edge_type;
|
||||
|
||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||
// note: this keeps twins, so it inserts twice the number of the valid edges
|
||||
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "JumpPointSearch.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "libslic3r/AStar.hpp"
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
@ -181,17 +182,18 @@ public:
|
||||
void JPSPathFinder::clear()
|
||||
{
|
||||
inpassable.clear();
|
||||
obstacle_max = Pixel(std::numeric_limits<coord_t>::min(), std::numeric_limits<coord_t>::min());
|
||||
obstacle_min = Pixel(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max());
|
||||
max_search_box.max = Pixel(std::numeric_limits<coord_t>::min(), std::numeric_limits<coord_t>::min());
|
||||
max_search_box.min = Pixel(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max());
|
||||
add_obstacles(bed_shape);
|
||||
}
|
||||
|
||||
void JPSPathFinder::add_obstacles(const Lines &obstacles)
|
||||
{
|
||||
auto store_obstacle = [&](coord_t x, coord_t y) {
|
||||
obstacle_max.x() = std::max(obstacle_max.x(), x);
|
||||
obstacle_max.y() = std::max(obstacle_max.y(), y);
|
||||
obstacle_min.x() = std::min(obstacle_min.x(), x);
|
||||
obstacle_min.y() = std::min(obstacle_min.y(), y);
|
||||
max_search_box.max.x() = std::max(max_search_box.max.x(), x);
|
||||
max_search_box.max.y() = std::max(max_search_box.max.y(), y);
|
||||
max_search_box.min.x() = std::min(max_search_box.min.x(), x);
|
||||
max_search_box.min.y() = std::min(max_search_box.min.y(), y);
|
||||
inpassable.insert(Pixel{x, y});
|
||||
return true;
|
||||
};
|
||||
@ -240,9 +242,9 @@ Polyline JPSPathFinder::find_path(const Point &p0, const Point &p1)
|
||||
});
|
||||
}
|
||||
|
||||
BoundingBox search_box({start, end, obstacle_max, obstacle_min});
|
||||
search_box.max += Pixel(1, 1);
|
||||
search_box.min -= Pixel(1, 1);
|
||||
BoundingBox search_box = max_search_box;
|
||||
search_box.max -= Pixel(1, 1);
|
||||
search_box.min += Pixel(1, 1);
|
||||
|
||||
BoundingBox bounding_square(Points{start, end});
|
||||
bounding_square.max += Pixel(5, 5);
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
@ -16,18 +17,19 @@ class JPSPathFinder
|
||||
using Pixel = Point;
|
||||
std::unordered_set<Pixel, PointHash> inpassable;
|
||||
coordf_t print_z;
|
||||
Pixel obstacle_min;
|
||||
Pixel obstacle_max;
|
||||
BoundingBox max_search_box;
|
||||
Lines bed_shape;
|
||||
|
||||
const coord_t resolution = scaled(1.5);
|
||||
Pixel pixelize(const Point &p) { return p / resolution; }
|
||||
Point unpixelize(const Pixel &p) { return p * resolution; }
|
||||
|
||||
public:
|
||||
JPSPathFinder() { clear(); };
|
||||
void clear();
|
||||
void add_obstacles(const Lines &obstacles);
|
||||
void add_obstacles(const Layer* layer, const Point& global_origin);
|
||||
JPSPathFinder() = default;
|
||||
void init_bed_shape(const Points &bed_shape) { this->bed_shape = (to_lines(Polygon{bed_shape})); };
|
||||
void clear();
|
||||
void add_obstacles(const Lines &obstacles);
|
||||
void add_obstacles(const Layer *layer, const Point &global_origin);
|
||||
Polyline find_path(const Point &start, const Point &end);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "Layer.hpp"
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include "ClipperZUtils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "Fill/Fill.hpp"
|
||||
@ -53,22 +53,7 @@ void Layer::make_slices()
|
||||
this->lslices = slices;
|
||||
}
|
||||
|
||||
// prepare lslices ordered by print order
|
||||
this->lslice_indices_sorted_by_print_order.clear();
|
||||
this->lslice_indices_sorted_by_print_order.reserve(lslices.size());
|
||||
// prepare ordering points
|
||||
Points ordering_points;
|
||||
ordering_points.reserve( this->lslices.size());
|
||||
for (const ExPolygon &ex : this->lslices)
|
||||
ordering_points.push_back(ex.contour.first_point());
|
||||
|
||||
// sort slices
|
||||
std::vector<Points::size_type> order = chain_points(ordering_points);
|
||||
|
||||
// populate slices vector
|
||||
for (size_t i : order) {
|
||||
this->lslice_indices_sorted_by_print_order.emplace_back(i);
|
||||
}
|
||||
this->lslice_indices_sorted_by_print_order = chain_expolygons(this->lslices);
|
||||
}
|
||||
|
||||
// used by Layer::build_up_down_graph()
|
||||
@ -105,7 +90,7 @@ static void connect_layer_slices(
|
||||
const coord_t offset_below,
|
||||
const coord_t offset_above
|
||||
#ifndef NDEBUG
|
||||
, const coord_t offset_end
|
||||
, const coord_t offset_end
|
||||
#endif // NDEBUG
|
||||
)
|
||||
{
|
||||
@ -127,9 +112,7 @@ static void connect_layer_slices(
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
auto assert_intersection_valid = [this](int i, int j) {
|
||||
assert(i != j);
|
||||
if (i > j)
|
||||
std::swap(i, j);
|
||||
assert(i < j);
|
||||
assert(i >= m_offset_below);
|
||||
assert(i < m_offset_above);
|
||||
assert(j >= m_offset_above);
|
||||
@ -140,35 +123,47 @@ static void connect_layer_slices(
|
||||
if (polynode.Contour.size() >= 3) {
|
||||
// If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect.
|
||||
// Otherwise the contour is fully inside another contour.
|
||||
int32_t i = 0, j = 0;
|
||||
int32_t i = -1, j = -1;
|
||||
for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) {
|
||||
const bool first = icontour == 0;
|
||||
const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
|
||||
const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
|
||||
if (contour.size() >= 3) {
|
||||
if (first) {
|
||||
i = contour.front().z();
|
||||
j = i;
|
||||
if (i < 0) {
|
||||
std::tie(i, j) = m_intersections[-i - 1];
|
||||
assert(assert_intersection_valid(i, j));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
for (const ClipperLib_Z::IntPoint& pt : contour) {
|
||||
for (const ClipperLib_Z::IntPoint &pt : contour) {
|
||||
j = pt.z();
|
||||
if (j < 0) {
|
||||
std::tie(i, j) = m_intersections[-j - 1];
|
||||
const auto &intersection = m_intersections[-j - 1];
|
||||
assert(intersection.first <= intersection.second);
|
||||
if (intersection.second < m_offset_above) {
|
||||
// Ignore intersection of polygons on the 1st layer.
|
||||
assert(intersection.first >= m_offset_below);
|
||||
j = i;
|
||||
} else if (intersection.first >= m_offset_above) {
|
||||
// Ignore intersection of polygons on the 2nd layer
|
||||
assert(intersection.second < m_offset_end);
|
||||
j = i;
|
||||
} else {
|
||||
std::tie(i, j) = m_intersections[-j - 1];
|
||||
assert(assert_intersection_valid(i, j));
|
||||
goto end;
|
||||
}
|
||||
} else if (i == -1) {
|
||||
// First source contour of this expolygon was found.
|
||||
i = j;
|
||||
} else if (i != j) {
|
||||
// Second source contour of this expolygon was found.
|
||||
if (i > j)
|
||||
std::swap(i, j);
|
||||
assert(assert_intersection_valid(i, j));
|
||||
goto end;
|
||||
}
|
||||
else if (i != j)
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
end:
|
||||
bool found = false;
|
||||
if (i == j) {
|
||||
if (i == -1) {
|
||||
// This should not happen. It may only happen if the source contours had just self intersections or intersections with contours at the same layer.
|
||||
assert(false);
|
||||
} else if (i == j) {
|
||||
// The contour is completely inside another contour.
|
||||
Point pt(polynode.Contour.front().x(), polynode.Contour.front().y());
|
||||
if (i < m_offset_above) {
|
||||
@ -202,8 +197,6 @@ static void connect_layer_slices(
|
||||
}
|
||||
} else {
|
||||
assert(assert_intersection_valid(i, j));
|
||||
if (i > j)
|
||||
std::swap(i, j);
|
||||
i -= m_offset_below;
|
||||
j -= m_offset_above;
|
||||
assert(i >= 0 && i < m_below.lslices_ex.size());
|
||||
@ -309,48 +302,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above)
|
||||
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
|
||||
#endif // NDEBUG
|
||||
|
||||
class ZFill {
|
||||
public:
|
||||
ZFill() = default;
|
||||
void reset() { m_intersections.clear(); }
|
||||
void operator()(
|
||||
const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top,
|
||||
const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top,
|
||||
ClipperLib_Z::IntPoint& pt) {
|
||||
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
|
||||
coord_t* begin = srcs;
|
||||
coord_t* end = srcs + 4;
|
||||
std::sort(begin, end);
|
||||
end = std::unique(begin, end);
|
||||
if (begin + 1 == end) {
|
||||
// Self intersection may happen on source contour. Just copy the Z value.
|
||||
pt.z() = *begin;
|
||||
} else {
|
||||
assert(begin + 2 == end);
|
||||
if (begin + 2 <= end) {
|
||||
// store a -1 based negative index into the "intersections" vector here.
|
||||
m_intersections.emplace_back(srcs[0], srcs[1]);
|
||||
pt.z() = -coord_t(m_intersections.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
|
||||
|
||||
private:
|
||||
std::vector<std::pair<coord_t, coord_t>> m_intersections;
|
||||
} zfill;
|
||||
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
ClipperLib_Z::PolyTree result;
|
||||
clipper.ZFillFunction(
|
||||
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
|
||||
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt)
|
||||
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
|
||||
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
clipper.ZFillFunction(visitor.clipper_callback());
|
||||
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
|
||||
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
|
||||
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
|
||||
connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset
|
||||
connect_layer_slices(below, above, result, intersections, paths_below_offset, paths_above_offset
|
||||
#ifndef NDEBUG
|
||||
, paths_end
|
||||
#endif // NDEBUG
|
||||
|
@ -134,7 +134,7 @@ public:
|
||||
|
||||
Flow flow(FlowRole role) const;
|
||||
Flow flow(FlowRole role, double layer_height) const;
|
||||
Flow bridging_flow(FlowRole role) const;
|
||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
@ -318,7 +318,7 @@ public:
|
||||
|
||||
Layer *upper_layer;
|
||||
Layer *lower_layer;
|
||||
bool slicing_errors;
|
||||
// bool slicing_errors;
|
||||
coordf_t slice_z; // Z used for slicing in unscaled coordinates
|
||||
coordf_t print_z; // Z used for printing in unscaled coordinates
|
||||
coordf_t height; // layer height in unscaled coordinates
|
||||
@ -369,6 +369,7 @@ public:
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring() const;
|
||||
void make_ironing();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
@ -387,7 +388,8 @@ protected:
|
||||
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
||||
|
||||
Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
|
||||
upper_layer(nullptr), lower_layer(nullptr), slicing_errors(false),
|
||||
upper_layer(nullptr), lower_layer(nullptr),
|
||||
//slicing_errors(false),
|
||||
slice_z(slice_z), print_z(print_z), height(height),
|
||||
m_id(id), m_object(object) {}
|
||||
virtual ~Layer();
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Surface.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Algorithm/RegionExpansion.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
@ -26,12 +27,12 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const
|
||||
return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0);
|
||||
}
|
||||
|
||||
Flow LayerRegion::bridging_flow(FlowRole role) const
|
||||
Flow LayerRegion::bridging_flow(FlowRole role, bool force_thick_bridges) const
|
||||
{
|
||||
const PrintRegion ®ion = this->region();
|
||||
const PrintRegionConfig ®ion_config = region.config();
|
||||
const PrintObject &print_object = *this->layer()->object();
|
||||
if (print_object.config().thick_bridges) {
|
||||
if (print_object.config().thick_bridges || force_thick_bridges) {
|
||||
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
|
||||
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
|
||||
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
|
||||
@ -139,6 +140,275 @@ void LayerRegion::make_perimeters(
|
||||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces.
|
||||
static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, SurfaceType surface_type, double &thickness)
|
||||
{
|
||||
size_t cnt = 0;
|
||||
for (const Surface &surface : surfaces)
|
||||
if (surface.surface_type == surface_type) {
|
||||
++ cnt;
|
||||
thickness = surface.thickness;
|
||||
}
|
||||
if (cnt == 0)
|
||||
return {};
|
||||
|
||||
ExPolygons out;
|
||||
out.reserve(cnt);
|
||||
for (Surface &surface : surfaces)
|
||||
if (surface.surface_type == surface_type)
|
||||
out.emplace_back(std::move(surface.expolygon));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||
// detect bridges.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
Surfaces expand_bridges_detect_orientations(
|
||||
Surfaces &surfaces,
|
||||
ExPolygons &shells,
|
||||
const Algorithm::RegionExpansionParameters &expansion_params)
|
||||
{
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
double thickness;
|
||||
ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, stBottomBridge, thickness);
|
||||
if (bridges_ex.empty())
|
||||
return {};
|
||||
|
||||
// Calculate bridge anchors and their expansions in their respective shell region.
|
||||
WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params.tiny_expansion, true);
|
||||
std::vector<RegionExpansionEx> bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params);
|
||||
|
||||
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
|
||||
struct Bridge {
|
||||
ExPolygon expolygon;
|
||||
uint32_t group_id;
|
||||
std::vector<RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
||||
double angle = -1;
|
||||
};
|
||||
std::vector<Bridge> bridges;
|
||||
{
|
||||
bridges.reserve(bridges_ex.size());
|
||||
uint32_t group_id = 0;
|
||||
for (ExPolygon &ex : bridges_ex)
|
||||
bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() });
|
||||
bridges_ex.clear();
|
||||
}
|
||||
|
||||
// Group the bridge surfaces by overlaps.
|
||||
auto group_id = [&bridges](uint32_t src_id) {
|
||||
uint32_t group_id = bridges[src_id].group_id;
|
||||
while (group_id != src_id) {
|
||||
src_id = group_id;
|
||||
group_id = bridges[src_id].group_id;
|
||||
}
|
||||
bridges[src_id].group_id = group_id;
|
||||
return group_id;
|
||||
};
|
||||
|
||||
{
|
||||
// Cache of bboxes per expansion boundary.
|
||||
std::vector<BoundingBox> bboxes;
|
||||
// Detect overlaps of bridge anchors inside their respective shell regions.
|
||||
// bridge_expansions are sorted by boundary id and source id.
|
||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) {
|
||||
// For each boundary region:
|
||||
auto it_begin = it;
|
||||
auto it_end = std::next(it_begin);
|
||||
for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ;
|
||||
bboxes.clear();
|
||||
bboxes.reserve(it_end - it_begin);
|
||||
for (auto it2 = it_begin; it2 != it_end; ++ it2)
|
||||
bboxes.emplace_back(get_extents(it2->expolygon.contour));
|
||||
// For each bridge anchor of the current source:
|
||||
for (; it != it_end; ++ it) {
|
||||
// A grup id for this bridge.
|
||||
for (auto it2 = std::next(it); it2 != it_end; ++ it2)
|
||||
if (it->src_id != it2->src_id &&
|
||||
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
|
||||
// One may ignore holes, they are irrelevant for intersection test.
|
||||
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
|
||||
// The two bridge regions intersect. Give them the same group id.
|
||||
uint32_t id = group_id(it->src_id);
|
||||
uint32_t id2 = group_id(it2->src_id);
|
||||
bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect bridge directions.
|
||||
{
|
||||
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
|
||||
auto it_bridge_anchor = bridge_anchors.begin();
|
||||
Lines lines;
|
||||
Polygons anchor_areas;
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||
Bridge &bridge = bridges[bridge_id];
|
||||
// lines.clear();
|
||||
anchor_areas.clear();
|
||||
int32_t last_anchor_id = -1;
|
||||
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
|
||||
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
|
||||
last_anchor_id = int(it_bridge_anchor->boundary);
|
||||
append(anchor_areas, to_polygons(shells[last_anchor_id]));
|
||||
}
|
||||
// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) {
|
||||
// reserve_more_power_of_2(lines, polyline.size() - 1);
|
||||
// for (size_t i = 1; i < polyline.size(); ++ i)
|
||||
// lines.push_back({ polyline[i - 1], polyline[1] });
|
||||
// }
|
||||
}
|
||||
lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))));
|
||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
|
||||
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||
// #if 1
|
||||
// coordf_t stroke_width = scale_(0.06);
|
||||
// BoundingBox bbox = get_extents(initial);
|
||||
// bbox.offset(scale_(1.));
|
||||
// ::Slic3r::SVG
|
||||
// svg(debug_out_path(("bridge"+std::to_string(bridges[idx_last].bridge_angle)+"_"+std::to_string(this->layer()->bottom_z())).c_str()),
|
||||
// bbox);
|
||||
|
||||
// svg.draw(initial, "cyan");
|
||||
// svg.draw(to_lines(lower_layer->lslices), "green", stroke_width);
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
|
||||
Surfaces out;
|
||||
{
|
||||
Polygons acc;
|
||||
Surface templ{ stBottomBridge, {} };
|
||||
std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) {
|
||||
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
|
||||
});
|
||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
|
||||
bridges[it->src_id].bridge_expansion_begin = it;
|
||||
uint32_t src_id = it->src_id;
|
||||
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
|
||||
}
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||
acc.clear();
|
||||
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
|
||||
if (group_id(bridge_id) == bridge_id) {
|
||||
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
|
||||
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
|
||||
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
|
||||
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
|
||||
append(acc, to_polygons(std::move(it_bridge_expansion->expolygon)));
|
||||
}
|
||||
//FIXME try to be smart and pick the best bridging angle for all?
|
||||
templ.bridge_angle = bridges[bridge_id].angle;
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
for (ExPolygon &ex : union_safety_offset_ex(acc))
|
||||
out.emplace_back(templ, std::move(ex));
|
||||
}
|
||||
}
|
||||
|
||||
// Clip the shells by the expanded bridges.
|
||||
shells = diff_ex(shells, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
static Surfaces expand_merge_surfaces(
|
||||
Surfaces &surfaces,
|
||||
SurfaceType surface_type,
|
||||
ExPolygons &shells,
|
||||
const Algorithm::RegionExpansionParameters ¶ms,
|
||||
const double bridge_angle = -1.)
|
||||
{
|
||||
double thickness;
|
||||
ExPolygons src = fill_surfaces_extract_expolygons(surfaces, surface_type, thickness);
|
||||
if (src.empty())
|
||||
return {};
|
||||
|
||||
std::vector<ExPolygon> expanded = expand_merge_expolygons(std::move(src), shells, params);
|
||||
// Trim the shells by the expanded expolygons.
|
||||
shells = diff_ex(shells, expanded);
|
||||
|
||||
Surface templ{ surface_type, {} };
|
||||
templ.bridge_angle = bridge_angle;
|
||||
Surfaces out;
|
||||
out.reserve(expanded.size());
|
||||
for (auto &expoly : expanded)
|
||||
out.emplace_back(templ, std::move(expoly));
|
||||
return out;
|
||||
}
|
||||
|
||||
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
|
||||
{
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Width of the perimeters.
|
||||
float shell_width = 0;
|
||||
if (int num_perimeters = this->region().config().perimeters; num_perimeters > 0) {
|
||||
Flow external_perimeter_flow = this->flow(frExternalPerimeter);
|
||||
Flow perimeter_flow = this->flow(frPerimeter);
|
||||
shell_width += 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing();
|
||||
shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1);
|
||||
} else {
|
||||
// TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation
|
||||
shell_width = float(SCALED_EPSILON);
|
||||
}
|
||||
|
||||
// Scaled expansions of the respective external surfaces.
|
||||
float expansion_top = shell_width * sqrt(2.);
|
||||
float expansion_bottom = expansion_top;
|
||||
float expansion_bottom_bridge = expansion_top;
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps.
|
||||
static constexpr const float expansion_step = scaled<float>(0.1);
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
static constexpr const size_t max_nr_expansion_steps = 5;
|
||||
|
||||
// Expand the top / bottom / bridge surfaces into the shell thickness solid infills.
|
||||
double layer_thickness;
|
||||
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, stInternalSolid, layer_thickness));
|
||||
|
||||
SurfaceCollection bridges;
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
||||
const double custom_angle = this->region().config().bridge_angle.value;
|
||||
const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
||||
bridges.surfaces = custom_angle > 0 ?
|
||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) :
|
||||
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
|
||||
Algorithm::RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps));
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
|
||||
Algorithm::RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps));
|
||||
|
||||
m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternalSolid });
|
||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + bridges.size() + bottoms.size() + tops.size());
|
||||
Surface solid_templ(stInternalSolid, {});
|
||||
solid_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(shells), solid_templ);
|
||||
m_fill_surfaces.append(std::move(bridges.surfaces));
|
||||
m_fill_surfaces.append(std::move(bottoms));
|
||||
m_fill_surfaces.append(std::move(tops));
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
#else
|
||||
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
|
||||
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
@ -146,10 +416,11 @@ void LayerRegion::make_perimeters(
|
||||
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
|
||||
{
|
||||
const bool has_infill = this->region().config().fill_density.value > 0.;
|
||||
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
// const float margin = scaled<float>(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset
|
||||
@ -164,7 +435,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
Surfaces internal;
|
||||
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
|
||||
Polygons fill_boundaries = to_polygons(this->fill_expolygons());
|
||||
Polygons lower_layer_covered_tmp;
|
||||
|
||||
// Collect top surfaces and internal surfaces.
|
||||
// Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
|
||||
@ -174,33 +444,42 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
// Voids are sparse infills if infill rate is zero.
|
||||
Polygons voids;
|
||||
for (const Surface &surface : this->fill_surfaces()) {
|
||||
if (surface.is_top()) {
|
||||
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
|
||||
// This gives the priority to bottom surfaces.
|
||||
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
|
||||
// Grown by 3mm.
|
||||
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottomBridge) {
|
||||
if (! surface.empty())
|
||||
assert(! surface.empty());
|
||||
if (! surface.empty()) {
|
||||
if (surface.is_top()) {
|
||||
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
|
||||
// This gives the priority to bottom surfaces.
|
||||
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
|
||||
// Grown by 3mm.
|
||||
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottomBridge) {
|
||||
bridges.emplace_back(surface);
|
||||
}
|
||||
if (surface.is_internal()) {
|
||||
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
|
||||
if (! has_infill && lower_layer != nullptr)
|
||||
polygons_append(voids, surface.expolygon);
|
||||
internal.emplace_back(std::move(surface));
|
||||
} else {
|
||||
assert(surface.is_internal());
|
||||
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
|
||||
if (! has_infill && lower_layer != nullptr)
|
||||
polygons_append(voids, surface.expolygon);
|
||||
internal.emplace_back(std::move(surface));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! has_infill && lower_layer != nullptr && ! voids.empty()) {
|
||||
if (! voids.empty()) {
|
||||
// There are some voids (empty infill regions) on this layer. Usually one does not want to expand
|
||||
// any infill into these voids, with the exception the expanded infills are supported by layers below
|
||||
// with nonzero inill.
|
||||
assert(! has_infill && lower_layer != nullptr);
|
||||
// Remove voids from fill_boundaries, that are not supported by the layer below.
|
||||
Polygons lower_layer_covered_tmp;
|
||||
if (lower_layer_covered == nullptr) {
|
||||
lower_layer_covered = &lower_layer_covered_tmp;
|
||||
lower_layer_covered_tmp = to_polygons(lower_layer->lslices);
|
||||
}
|
||||
if (! lower_layer_covered->empty())
|
||||
// Allow the top / bottom surfaces to expand into the voids of this layer if supported by the layer below.
|
||||
voids = diff(voids, *lower_layer_covered);
|
||||
fill_boundaries = diff(fill_boundaries, voids);
|
||||
if (! voids.empty())
|
||||
fill_boundaries = diff(fill_boundaries, voids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,13 +503,12 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
|
||||
SVG svg(debug_out_path("4_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
|
||||
svg.draw(fill_boundaries_ex);
|
||||
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
|
||||
// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
{
|
||||
@ -253,7 +531,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
if (idx_island == -1) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
|
||||
} else {
|
||||
// Found an island, to which this bridge region belongs. Trim it,
|
||||
// Found an island, to which this bridge region belongs. Trim the expanded bridging region
|
||||
// with its source region, so it does not overflow into a neighbor region.
|
||||
polys = intersection(polys, fill_boundaries_ex[idx_island]);
|
||||
}
|
||||
bridge_bboxes.push_back(get_extents(polys));
|
||||
@ -371,10 +650,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
|
||||
Surfaces new_surfaces;
|
||||
{
|
||||
// Merge top and bottom in a single collection.
|
||||
surfaces_append(top, std::move(bottom));
|
||||
// Intersect the grown surfaces with the actual fill boundaries.
|
||||
Polygons bottom_polygons = to_polygons(bottom);
|
||||
// Merge top and bottom in a single collection.
|
||||
surfaces_append(top, std::move(bottom));
|
||||
for (size_t i = 0; i < top.size(); ++ i) {
|
||||
Surface &s1 = top[i];
|
||||
if (s1.empty())
|
||||
@ -422,9 +701,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
m_fill_surfaces.surfaces = std::move(new_surfaces);
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
#endif
|
||||
|
||||
void LayerRegion::prepare_fill_surfaces()
|
||||
{
|
||||
|
@ -323,14 +323,30 @@ bool Model::add_default_instances()
|
||||
}
|
||||
|
||||
// this returns the bounding box of the *transformed* instances
|
||||
BoundingBoxf3 Model::bounding_box() const
|
||||
BoundingBoxf3 Model::bounding_box_approx() const
|
||||
{
|
||||
BoundingBoxf3 bb;
|
||||
for (ModelObject *o : this->objects)
|
||||
bb.merge(o->bounding_box());
|
||||
bb.merge(o->bounding_box_approx());
|
||||
return bb;
|
||||
}
|
||||
|
||||
BoundingBoxf3 Model::bounding_box_exact() const
|
||||
{
|
||||
BoundingBoxf3 bb;
|
||||
for (ModelObject *o : this->objects)
|
||||
bb.merge(o->bounding_box_exact());
|
||||
return bb;
|
||||
}
|
||||
|
||||
double Model::max_z() const
|
||||
{
|
||||
double z = 0;
|
||||
for (ModelObject *o : this->objects)
|
||||
z = std::max(z, o->max_z());
|
||||
return z;
|
||||
}
|
||||
|
||||
unsigned int Model::update_print_volume_state(const BuildVolume &build_volume)
|
||||
{
|
||||
unsigned int num_printable = 0;
|
||||
@ -377,7 +393,7 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
|
||||
ModelObject* object = this->objects.front();
|
||||
object->clear_instances();
|
||||
|
||||
Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones();
|
||||
Vec3d ext_size = object->bounding_box_exact().size() + dist * Vec3d::Ones();
|
||||
|
||||
for (size_t x_copy = 1; x_copy <= x; ++x_copy) {
|
||||
for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
|
||||
@ -548,13 +564,13 @@ void Model::adjust_min_z()
|
||||
if (objects.empty())
|
||||
return;
|
||||
|
||||
if (bounding_box().min(2) < 0.0)
|
||||
if (this->bounding_box_exact().min.z() < 0.0)
|
||||
{
|
||||
for (ModelObject* obj : objects)
|
||||
{
|
||||
if (obj != nullptr)
|
||||
{
|
||||
coordf_t obj_min_z = obj->bounding_box().min(2);
|
||||
coordf_t obj_min_z = obj->min_z();
|
||||
if (obj_min_z < 0.0)
|
||||
obj->translate_instances(Vec3d(0.0, 0.0, -obj_min_z));
|
||||
}
|
||||
@ -627,12 +643,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
||||
this->printable = rhs.printable;
|
||||
this->origin_translation = rhs.origin_translation;
|
||||
this->cut_id.copy(rhs.cut_id);
|
||||
m_bounding_box = rhs.m_bounding_box;
|
||||
m_bounding_box_valid = rhs.m_bounding_box_valid;
|
||||
m_raw_bounding_box = rhs.m_raw_bounding_box;
|
||||
m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid;
|
||||
m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box;
|
||||
m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid;
|
||||
this->copy_transformation_caches(rhs);
|
||||
|
||||
this->clear_volumes();
|
||||
this->volumes.reserve(rhs.volumes.size());
|
||||
@ -668,12 +679,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
|
||||
this->layer_height_profile = std::move(rhs.layer_height_profile);
|
||||
this->printable = std::move(rhs.printable);
|
||||
this->origin_translation = std::move(rhs.origin_translation);
|
||||
m_bounding_box = std::move(rhs.m_bounding_box);
|
||||
m_bounding_box_valid = std::move(rhs.m_bounding_box_valid);
|
||||
m_raw_bounding_box = rhs.m_raw_bounding_box;
|
||||
m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid;
|
||||
m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box;
|
||||
m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid;
|
||||
this->copy_transformation_caches(rhs);
|
||||
|
||||
this->clear_volumes();
|
||||
this->volumes = std::move(rhs.volumes);
|
||||
@ -864,16 +870,72 @@ void ModelObject::clear_instances()
|
||||
|
||||
// Returns the bounding box of the transformed instances.
|
||||
// This bounding box is approximate and not snug.
|
||||
const BoundingBoxf3& ModelObject::bounding_box() const
|
||||
const BoundingBoxf3& ModelObject::bounding_box_approx() const
|
||||
{
|
||||
if (! m_bounding_box_valid) {
|
||||
m_bounding_box_valid = true;
|
||||
if (! m_bounding_box_approx_valid) {
|
||||
m_bounding_box_approx_valid = true;
|
||||
BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box();
|
||||
m_bounding_box.reset();
|
||||
m_bounding_box_approx.reset();
|
||||
for (const ModelInstance *i : this->instances)
|
||||
m_bounding_box.merge(i->transform_bounding_box(raw_bbox));
|
||||
m_bounding_box_approx.merge(i->transform_bounding_box(raw_bbox));
|
||||
}
|
||||
return m_bounding_box_approx;
|
||||
}
|
||||
|
||||
// Returns the bounding box of the transformed instances.
|
||||
// This bounding box is approximate and not snug.
|
||||
const BoundingBoxf3& ModelObject::bounding_box_exact() const
|
||||
{
|
||||
if (! m_bounding_box_exact_valid) {
|
||||
m_bounding_box_exact_valid = true;
|
||||
m_min_max_z_valid = true;
|
||||
m_bounding_box_exact.reset();
|
||||
for (size_t i = 0; i < this->instances.size(); ++ i)
|
||||
m_bounding_box_exact.merge(this->instance_bounding_box(i));
|
||||
}
|
||||
return m_bounding_box_exact;
|
||||
}
|
||||
|
||||
double ModelObject::min_z() const
|
||||
{
|
||||
const_cast<ModelObject*>(this)->update_min_max_z();
|
||||
return m_bounding_box_exact.min.z();
|
||||
}
|
||||
|
||||
double ModelObject::max_z() const
|
||||
{
|
||||
const_cast<ModelObject*>(this)->update_min_max_z();
|
||||
return m_bounding_box_exact.max.z();
|
||||
}
|
||||
|
||||
void ModelObject::update_min_max_z()
|
||||
{
|
||||
assert(! this->instances.empty());
|
||||
if (! m_min_max_z_valid && ! this->instances.empty()) {
|
||||
m_min_max_z_valid = true;
|
||||
const Transform3d mat_instance = this->instances.front()->get_transformation().get_matrix();
|
||||
double global_min_z = std::numeric_limits<double>::max();
|
||||
double global_max_z = - std::numeric_limits<double>::max();
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part()) {
|
||||
const Transform3d m = mat_instance * v->get_matrix();
|
||||
const Vec3d row_z = m.linear().row(2).cast<double>();
|
||||
const double shift_z = m.translation().z();
|
||||
double this_min_z = std::numeric_limits<double>::max();
|
||||
double this_max_z = - std::numeric_limits<double>::max();
|
||||
for (const Vec3f &p : v->mesh().its.vertices) {
|
||||
double z = row_z.dot(p.cast<double>());
|
||||
this_min_z = std::min(this_min_z, z);
|
||||
this_max_z = std::max(this_max_z, z);
|
||||
}
|
||||
this_min_z += shift_z;
|
||||
this_max_z += shift_z;
|
||||
global_min_z = std::min(global_min_z, this_min_z);
|
||||
global_max_z = std::max(global_max_z, this_max_z);
|
||||
}
|
||||
m_bounding_box_exact.min.z() = global_min_z;
|
||||
m_bounding_box_exact.max.z() = global_max_z;
|
||||
}
|
||||
return m_bounding_box;
|
||||
}
|
||||
|
||||
// A mesh containing all transformed instances of this object.
|
||||
@ -1031,19 +1093,19 @@ void ModelObject::ensure_on_bed(bool allow_negative_z)
|
||||
|
||||
if (allow_negative_z) {
|
||||
if (parts_count() == 1) {
|
||||
const double min_z = get_min_z();
|
||||
const double max_z = get_max_z();
|
||||
const double min_z = this->min_z();
|
||||
const double max_z = this->max_z();
|
||||
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
|
||||
z_offset = -min_z;
|
||||
}
|
||||
else {
|
||||
const double max_z = get_max_z();
|
||||
const double max_z = this->max_z();
|
||||
if (max_z < SINKING_MIN_Z_THRESHOLD)
|
||||
z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
|
||||
}
|
||||
}
|
||||
else
|
||||
z_offset = -get_min_z();
|
||||
z_offset = -this->min_z();
|
||||
|
||||
if (z_offset != 0.0)
|
||||
translate_instances(z_offset * Vec3d::UnitZ());
|
||||
@ -1070,8 +1132,10 @@ void ModelObject::translate(double x, double y, double z)
|
||||
v->translate(x, y, z);
|
||||
}
|
||||
|
||||
if (m_bounding_box_valid)
|
||||
m_bounding_box.translate(x, y, z);
|
||||
if (m_bounding_box_approx_valid)
|
||||
m_bounding_box_approx.translate(x, y, z);
|
||||
if (m_bounding_box_exact_valid)
|
||||
m_bounding_box_exact.translate(x, y, z);
|
||||
}
|
||||
|
||||
void ModelObject::scale(const Vec3d &versor)
|
||||
@ -1241,10 +1305,10 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
|
||||
break;
|
||||
}
|
||||
|
||||
if (connector_attributes.style == CutConnectorStyle::Prizm)
|
||||
if (connector_attributes.style == CutConnectorStyle::Prism)
|
||||
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else if (connector_attributes.type == CutConnectorType::Plug)
|
||||
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
|
||||
connector_mesh = its_make_frustum(1.0, 1.0, (2 * PI / sectorCount));
|
||||
else
|
||||
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
|
||||
|
||||
@ -1347,12 +1411,7 @@ void ModelVolume::apply_tolerance()
|
||||
return;
|
||||
|
||||
Vec3d sf = get_scaling_factor();
|
||||
/*
|
||||
// correct Z offset in respect to the new size
|
||||
Vec3d pos = vol->get_offset();
|
||||
pos[Z] += sf[Z] * 0.5 * vol->cut_info.height_tolerance;
|
||||
vol->set_offset(pos);
|
||||
*/
|
||||
|
||||
// make a "hole" wider
|
||||
sf[X] += double(cut_info.radius_tolerance);
|
||||
sf[Y] += double(cut_info.radius_tolerance);
|
||||
@ -1361,9 +1420,39 @@ void ModelVolume::apply_tolerance()
|
||||
sf[Z] += double(cut_info.height_tolerance);
|
||||
|
||||
set_scaling_factor(sf);
|
||||
|
||||
// correct offset in respect to the new depth
|
||||
Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
|
||||
if (rot_norm.norm() != 0.0)
|
||||
rot_norm.normalize();
|
||||
|
||||
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
|
||||
if (cut_info.connector_type == CutConnectorType::Plug)
|
||||
z_offset -= 0.05; // add small Z offset to better preview
|
||||
|
||||
set_offset(get_offset() + rot_norm * z_offset);
|
||||
}
|
||||
|
||||
void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
@ -1373,39 +1462,53 @@ void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttri
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
vol->apply_tolerance();
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume* vol = upper->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
vol->apply_tolerance();
|
||||
else
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume* vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
}
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Dowel &&
|
||||
attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_for_cut(&dowel);
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject* dowel{ nullptr };
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume* vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the dowels
|
||||
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
|
||||
// Compute the displacement (in instance coordinates) to be applied to place the dowels
|
||||
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 0.0));
|
||||
|
||||
dowels.push_back(dowel);
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
volume->apply_tolerance();
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1418,6 +1521,11 @@ void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& i
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Geometry::Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
@ -1427,25 +1535,8 @@ void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& i
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {})
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume* vol = object->add_volume(mesh);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
|
||||
void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
@ -1459,23 +1550,20 @@ void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d&
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
volume->reset_mesh();
|
||||
// Reset volume transformation except for offset
|
||||
const Vec3d offset = volume->get_offset();
|
||||
volume->set_transformation(Geometry::Transformation());
|
||||
volume->set_offset(offset);
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace)
|
||||
{
|
||||
// Perform cut
|
||||
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
{
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
@ -1606,7 +1694,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix,
|
||||
if (volume->cut_info.is_processed)
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
|
||||
else
|
||||
process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace);
|
||||
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels, local_dowels_displace);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
|
||||
@ -1847,32 +1935,6 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
double ModelObject::get_min_z() const
|
||||
{
|
||||
if (instances.empty())
|
||||
return 0.0;
|
||||
else {
|
||||
double min_z = DBL_MAX;
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
min_z = std::min(min_z, get_instance_min_z(i));
|
||||
}
|
||||
return min_z;
|
||||
}
|
||||
}
|
||||
|
||||
double ModelObject::get_max_z() const
|
||||
{
|
||||
if (instances.empty())
|
||||
return 0.0;
|
||||
else {
|
||||
double max_z = -DBL_MAX;
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
max_z = std::max(max_z, get_instance_max_z(i));
|
||||
}
|
||||
return max_z;
|
||||
}
|
||||
}
|
||||
|
||||
double ModelObject::get_instance_min_z(size_t instance_idx) const
|
||||
{
|
||||
double min_z = DBL_MAX;
|
||||
@ -2249,7 +2311,7 @@ void ModelVolume::scale(const Vec3d& scaling_factors)
|
||||
|
||||
void ModelObject::scale_to_fit(const Vec3d &size)
|
||||
{
|
||||
Vec3d orig_size = this->bounding_box().size();
|
||||
Vec3d orig_size = this->bounding_box_exact().size();
|
||||
double factor = std::min(
|
||||
size.x() / orig_size.x(),
|
||||
std::min(
|
||||
@ -2354,37 +2416,6 @@ void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) cons
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
}
|
||||
|
||||
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const
|
||||
{
|
||||
// Rotate around mesh origin.
|
||||
TriangleMesh copy(mesh);
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
copy.transform(get_transformation().get_rotation_matrix());
|
||||
#else
|
||||
copy.transform(get_matrix(true, false, true, true));
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
BoundingBoxf3 bbox = copy.bounding_box();
|
||||
|
||||
if (!empty(bbox)) {
|
||||
// Scale the bounding box along the three axes.
|
||||
for (unsigned int i = 0; i < 3; ++i)
|
||||
{
|
||||
if (std::abs(get_scaling_factor((Axis)i)-1.0) > EPSILON)
|
||||
{
|
||||
bbox.min(i) *= get_scaling_factor((Axis)i);
|
||||
bbox.max(i) *= get_scaling_factor((Axis)i);
|
||||
}
|
||||
}
|
||||
|
||||
// Translate the bounding box.
|
||||
if (! dont_translate) {
|
||||
bbox.min += get_offset();
|
||||
bbox.max += get_offset();
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
|
||||
{
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
|
@ -168,7 +168,7 @@ private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Create an object for deserialization, don't allocate IDs for ModelMaterial and its config.
|
||||
ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
|
||||
ModelMaterial() : ObjectBase(-1), config(-1) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid());
|
||||
Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
|
||||
@ -228,7 +228,7 @@ enum class CutConnectorType : int {
|
||||
};
|
||||
|
||||
enum class CutConnectorStyle : int {
|
||||
Prizm
|
||||
Prism
|
||||
, Frustum
|
||||
, Undef
|
||||
//,Claw
|
||||
@ -246,7 +246,7 @@ enum class CutConnectorShape : int {
|
||||
struct CutConnectorAttributes
|
||||
{
|
||||
CutConnectorType type{ CutConnectorType::Plug };
|
||||
CutConnectorStyle style{ CutConnectorStyle::Prizm };
|
||||
CutConnectorStyle style{ CutConnectorStyle::Prism };
|
||||
CutConnectorShape shape{ CutConnectorShape::Circle };
|
||||
|
||||
CutConnectorAttributes() {}
|
||||
@ -343,7 +343,7 @@ public:
|
||||
// The pairs of <z, layer_height> are packed into a 1D array.
|
||||
LayerHeightProfile layer_height_profile;
|
||||
// Whether or not this object is printable
|
||||
bool printable;
|
||||
bool printable { true };
|
||||
|
||||
// This vector holds position of selected support points for SLA. The data are
|
||||
// saved in mesh coordinates to allow using them for several instances.
|
||||
@ -397,11 +397,22 @@ public:
|
||||
void delete_last_instance();
|
||||
void clear_instances();
|
||||
|
||||
// Returns the bounding box of the transformed instances.
|
||||
// This bounding box is approximate and not snug.
|
||||
// This bounding box is being cached.
|
||||
const BoundingBoxf3& bounding_box() const;
|
||||
void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; }
|
||||
// Returns the bounding box of the transformed instances. This bounding box is approximate and not snug, it is being cached.
|
||||
const BoundingBoxf3& bounding_box_approx() const;
|
||||
// Returns an exact bounding box of the transformed instances. The result it is being cached.
|
||||
const BoundingBoxf3& bounding_box_exact() const;
|
||||
// Return minimum / maximum of a printable object transformed into the world coordinate system.
|
||||
// All instances share the same min / max Z.
|
||||
double min_z() const;
|
||||
double max_z() const;
|
||||
|
||||
void invalidate_bounding_box() {
|
||||
m_bounding_box_approx_valid = false;
|
||||
m_bounding_box_exact_valid = false;
|
||||
m_min_max_z_valid = false;
|
||||
m_raw_bounding_box_valid = false;
|
||||
m_raw_mesh_bounding_box_valid = false;
|
||||
}
|
||||
|
||||
// A mesh containing all transformed instances of this object.
|
||||
TriangleMesh mesh() const;
|
||||
@ -459,10 +470,13 @@ public:
|
||||
void synchronize_model_after_cut();
|
||||
void apply_cut_attributes(ModelObjectCutAttributes attributes);
|
||||
void clone_for_cut(ModelObject **obj);
|
||||
void process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
|
||||
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
|
||||
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
|
||||
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
|
||||
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
|
||||
@ -474,8 +488,6 @@ public:
|
||||
// Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
|
||||
void bake_xy_rotation_into_meshes(size_t instance_idx);
|
||||
|
||||
double get_min_z() const;
|
||||
double get_max_z() const;
|
||||
double get_instance_min_z(size_t instance_idx) const;
|
||||
double get_instance_max_z(size_t instance_idx) const;
|
||||
|
||||
@ -497,14 +509,13 @@ public:
|
||||
private:
|
||||
friend class Model;
|
||||
// This constructor assigns new ID to this ModelObject and its config.
|
||||
explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()),
|
||||
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
|
||||
explicit ModelObject(Model* model) : m_model(model), origin_translation(Vec3d::Zero())
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->layer_height_profile.id().valid());
|
||||
}
|
||||
explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
|
||||
explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), origin_translation(Vec3d::Zero())
|
||||
{
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
@ -582,15 +593,31 @@ private:
|
||||
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
|
||||
|
||||
// Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
|
||||
Model *m_model = nullptr;
|
||||
Model *m_model { nullptr };
|
||||
|
||||
// Bounding box, cached.
|
||||
mutable BoundingBoxf3 m_bounding_box;
|
||||
mutable bool m_bounding_box_valid;
|
||||
mutable BoundingBoxf3 m_bounding_box_approx;
|
||||
mutable bool m_bounding_box_approx_valid { false };
|
||||
mutable BoundingBoxf3 m_bounding_box_exact;
|
||||
mutable bool m_bounding_box_exact_valid { false };
|
||||
mutable bool m_min_max_z_valid { false };
|
||||
mutable BoundingBoxf3 m_raw_bounding_box;
|
||||
mutable bool m_raw_bounding_box_valid;
|
||||
mutable bool m_raw_bounding_box_valid { false };
|
||||
mutable BoundingBoxf3 m_raw_mesh_bounding_box;
|
||||
mutable bool m_raw_mesh_bounding_box_valid;
|
||||
mutable bool m_raw_mesh_bounding_box_valid { false };
|
||||
|
||||
// Only use this method if now the source and dest ModelObjects are equal, for example they were synchronized by Print::apply().
|
||||
void copy_transformation_caches(const ModelObject &src) {
|
||||
m_bounding_box_approx = src.m_bounding_box_approx;
|
||||
m_bounding_box_approx_valid = src.m_bounding_box_approx_valid;
|
||||
m_bounding_box_exact = src.m_bounding_box_exact;
|
||||
m_bounding_box_exact_valid = src.m_bounding_box_exact_valid;
|
||||
m_min_max_z_valid = src.m_min_max_z_valid;
|
||||
m_raw_bounding_box = src.m_raw_bounding_box;
|
||||
m_raw_bounding_box_valid = src.m_raw_bounding_box_valid;
|
||||
m_raw_mesh_bounding_box = src.m_raw_mesh_bounding_box;
|
||||
m_raw_mesh_bounding_box_valid = src.m_raw_mesh_bounding_box_valid;
|
||||
}
|
||||
|
||||
// Called by Print::apply() to set the model pointer after making a copy.
|
||||
friend class Print;
|
||||
@ -602,8 +629,7 @@ private:
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
|
||||
ModelObject() :
|
||||
ObjectBase(-1), config(-1), layer_height_profile(-1),
|
||||
m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
|
||||
ObjectBase(-1), config(-1), layer_height_profile(-1) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->layer_height_profile.id().invalid());
|
||||
@ -614,12 +640,17 @@ private:
|
||||
Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile);
|
||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper,
|
||||
sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
|
||||
m_bounding_box_approx, m_bounding_box_approx_valid,
|
||||
m_bounding_box_exact, m_bounding_box_exact_valid, m_min_max_z_valid,
|
||||
m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid,
|
||||
cut_connectors, cut_id);
|
||||
}
|
||||
|
||||
// Called by Print::validate() from the UI thread.
|
||||
unsigned int update_instances_print_volume_state(const BuildVolume &build_volume);
|
||||
|
||||
// Called by min_z(), max_z()
|
||||
void update_min_max_z();
|
||||
};
|
||||
|
||||
enum class EnforcerBlockerType : int8_t {
|
||||
@ -1103,7 +1134,7 @@ public:
|
||||
// flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
|
||||
ModelInstanceEPrintVolumeState print_volume_state;
|
||||
// Whether or not this instance is printable
|
||||
bool printable;
|
||||
bool printable { true };
|
||||
|
||||
ModelObject* get_object() const { return this->object; }
|
||||
|
||||
@ -1153,9 +1184,7 @@ public:
|
||||
|
||||
// To be called on an external mesh
|
||||
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
|
||||
// Calculate a bounding box of a transformed mesh. To be called on an external mesh.
|
||||
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
|
||||
// Transform an external bounding box.
|
||||
// Transform an external bounding box, thus the resulting bounding box is no more snug.
|
||||
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
|
||||
// Transform an external vector.
|
||||
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
|
||||
@ -1198,7 +1227,7 @@ private:
|
||||
ModelObject* object;
|
||||
|
||||
// Constructor, which assigns a new unique ID.
|
||||
explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); }
|
||||
explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), object(object) { assert(this->id().valid()); }
|
||||
// Constructor, which assigns a new unique ID.
|
||||
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); }
|
||||
@ -1313,8 +1342,12 @@ public:
|
||||
void delete_material(t_model_material_id material_id);
|
||||
void clear_materials();
|
||||
bool add_default_instances();
|
||||
// Returns approximate axis aligned bounding box of this model
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
// Returns approximate axis aligned bounding box of this model.
|
||||
BoundingBoxf3 bounding_box_approx() const;
|
||||
// Returns exact axis aligned bounding box of this model.
|
||||
BoundingBoxf3 bounding_box_exact() const;
|
||||
// Return maximum height of all printable objects.
|
||||
double max_z() const;
|
||||
// Set the print_volume_state of PrintObject::instances,
|
||||
// return total number of printable objects.
|
||||
unsigned int update_print_volume_state(const BuildVolume &build_volume);
|
||||
|
@ -29,10 +29,12 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <math.h>
|
||||
#include <ostream>
|
||||
#include <stack>
|
||||
@ -433,10 +435,12 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
|
||||
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
|
||||
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
|
||||
|
||||
ClipperLib_Z::PolyTree clipped_polytree;
|
||||
ClipperLib_Z::Paths clipped_paths;
|
||||
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths);
|
||||
{
|
||||
ClipperLib_Z::PolyTree clipped_polytree;
|
||||
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
|
||||
}
|
||||
|
||||
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero.
|
||||
// For those vertices, we must assign value based on the subject.
|
||||
@ -661,187 +665,176 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co
|
||||
// find out if paths touch - at least one point of one path is within limit distance of second path
|
||||
bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, double limit_distance)
|
||||
{
|
||||
AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()};
|
||||
AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()};
|
||||
|
||||
for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) {
|
||||
if (lines_two.distance_from_lines<false>(path_one.polyline.points[pt_idx]) < limit_distance) { return true; }
|
||||
}
|
||||
|
||||
AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()};
|
||||
for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) {
|
||||
if (lines_one.distance_from_lines<false>(path_two.polyline.points[pt_idx]) < limit_distance) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ExtrusionPaths reconnect_extrusion_paths(const ExtrusionPaths& paths, double limit_distance) {
|
||||
if (paths.empty()) return paths;
|
||||
Polylines reconnect_polylines(const Polylines &polylines, double limit_distance)
|
||||
{
|
||||
if (polylines.empty())
|
||||
return polylines;
|
||||
|
||||
std::unordered_map<size_t, ExtrusionPath> connected;
|
||||
connected.reserve(paths.size());
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
if (!paths[i].empty()) {
|
||||
connected.emplace(i, paths[i]);
|
||||
std::unordered_map<size_t, Polyline> connected;
|
||||
connected.reserve(polylines.size());
|
||||
for (size_t i = 0; i < polylines.size(); i++) {
|
||||
if (!polylines[i].empty()) {
|
||||
connected.emplace(i, polylines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t a = 0; a < paths.size(); a++) {
|
||||
for (size_t a = 0; a < polylines.size(); a++) {
|
||||
if (connected.find(a) == connected.end()) {
|
||||
continue;
|
||||
}
|
||||
ExtrusionPath &base = connected.at(a);
|
||||
for (size_t b = a + 1; b < paths.size(); b++) {
|
||||
Polyline &base = connected.at(a);
|
||||
for (size_t b = a + 1; b < polylines.size(); b++) {
|
||||
if (connected.find(b) == connected.end()) {
|
||||
continue;
|
||||
}
|
||||
ExtrusionPath &next = connected.at(b);
|
||||
Polyline &next = connected.at(b);
|
||||
if ((base.last_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
|
||||
base.polyline.append(std::move(next.polyline));
|
||||
base.append(std::move(next));
|
||||
connected.erase(b);
|
||||
} else if ((base.last_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
|
||||
base.polyline.points.insert(base.polyline.points.end(), next.polyline.points.rbegin(), next.polyline.points.rend());
|
||||
base.points.insert(base.points.end(), next.points.rbegin(), next.points.rend());
|
||||
connected.erase(b);
|
||||
} else if ((base.first_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
|
||||
next.polyline.append(std::move(base.polyline));
|
||||
next.append(std::move(base));
|
||||
base = std::move(next);
|
||||
base.reverse();
|
||||
connected.erase(b);
|
||||
} else if ((base.first_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
|
||||
base.reverse();
|
||||
base.polyline.append(std::move(next.polyline));
|
||||
base.append(std::move(next));
|
||||
base.reverse();
|
||||
connected.erase(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExtrusionPaths result;
|
||||
for (auto& ext : connected) {
|
||||
Polylines result;
|
||||
for (auto &ext : connected) {
|
||||
result.push_back(std::move(ext.second));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExtrusionPaths sort_and_connect_extra_perimeters(const std::vector<ExtrusionPaths> &extra_perims, double extrusion_spacing)
|
||||
ExtrusionPaths sort_extra_perimeters(ExtrusionPaths extra_perims, int index_of_first_unanchored, double extrusion_spacing)
|
||||
{
|
||||
std::vector<ExtrusionPaths> connected_shells;
|
||||
connected_shells.reserve(extra_perims.size());
|
||||
for (const ExtrusionPaths &ps : extra_perims) {
|
||||
// this will also filter away empty paths
|
||||
connected_shells.push_back(reconnect_extrusion_paths(ps, 1.0 * extrusion_spacing));
|
||||
if (extra_perims.empty()) return {};
|
||||
|
||||
std::vector<std::unordered_set<size_t>> dependencies(extra_perims.size());
|
||||
for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
|
||||
for (size_t prev_path_idx = 0; prev_path_idx < path_idx; prev_path_idx++) {
|
||||
if (paths_touch(extra_perims[path_idx], extra_perims[prev_path_idx], extrusion_spacing * 1.5f)) {
|
||||
dependencies[path_idx].insert(prev_path_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Pidx
|
||||
{
|
||||
size_t shell;
|
||||
size_t path;
|
||||
bool operator==(const Pidx &rhs) const { return shell == rhs.shell && path == rhs.path; }
|
||||
};
|
||||
struct PidxHash
|
||||
{
|
||||
size_t operator()(const Pidx &i) const { return std::hash<size_t>{}(i.shell) ^ std::hash<size_t>{}(i.path); }
|
||||
};
|
||||
|
||||
auto get_path = [&](Pidx i) { return connected_shells[i.shell][i.path]; };
|
||||
std::vector<bool> processed(extra_perims.size(), false);
|
||||
for (size_t path_idx = 0; path_idx < index_of_first_unanchored; path_idx++) {
|
||||
processed[path_idx] = true;
|
||||
}
|
||||
|
||||
std::vector<std::unordered_map<Pidx, std::unordered_set<Pidx, PidxHash>, PidxHash>> dependencies;
|
||||
for (size_t shell = 0; shell < connected_shells.size(); shell++) {
|
||||
dependencies.push_back({});
|
||||
auto ¤t_shell = dependencies[shell];
|
||||
for (size_t path = 0; path < connected_shells[shell].size(); path++) {
|
||||
Pidx current_path{shell, path};
|
||||
std::unordered_set<Pidx, PidxHash> current_dependencies{};
|
||||
if (shell > 0) {
|
||||
for (const auto &prev_path : dependencies[shell - 1]) {
|
||||
if (paths_touch(get_path(current_path), get_path(prev_path.first), extrusion_spacing * 1.5f)) {
|
||||
current_dependencies.insert(prev_path.first);
|
||||
};
|
||||
for (size_t i = index_of_first_unanchored; i < extra_perims.size(); i++) {
|
||||
bool change = false;
|
||||
for (size_t path_idx = index_of_first_unanchored; path_idx < extra_perims.size(); path_idx++) {
|
||||
if (processed[path_idx])
|
||||
continue;
|
||||
auto processed_dep = std::find_if(dependencies[path_idx].begin(), dependencies[path_idx].end(),
|
||||
[&](size_t dep) { return processed[dep]; });
|
||||
if (processed_dep != dependencies[path_idx].end()) {
|
||||
for (auto it = dependencies[path_idx].begin(); it != dependencies[path_idx].end();) {
|
||||
if (!processed[*it]) {
|
||||
dependencies[*it].insert(path_idx);
|
||||
dependencies[path_idx].erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
processed[path_idx] = true;
|
||||
change = true;
|
||||
}
|
||||
current_shell[current_path] = current_dependencies;
|
||||
}
|
||||
if (!change) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Point current_point{};
|
||||
for (const ExtrusionPaths &ps : connected_shells) {
|
||||
for (const ExtrusionPath &p : ps) {
|
||||
if (!p.empty()) {
|
||||
current_point = p.first_point();
|
||||
goto first_point_found;
|
||||
}
|
||||
}
|
||||
}
|
||||
first_point_found:
|
||||
Point current_point = extra_perims.begin()->first_point();
|
||||
|
||||
ExtrusionPaths sorted_paths{};
|
||||
Pidx npidx = Pidx{size_t(-1), 0};
|
||||
Pidx next_pidx = npidx;
|
||||
bool reverse = false;
|
||||
size_t null_idx = size_t(-1);
|
||||
size_t next_idx = null_idx;
|
||||
bool reverse = false;
|
||||
while (true) {
|
||||
if (next_pidx == npidx) { // find next pidx to print
|
||||
if (next_idx == null_idx) { // find next pidx to print
|
||||
double dist = std::numeric_limits<double>::max();
|
||||
for (size_t shell = 0; shell < dependencies.size(); shell++) {
|
||||
for (const auto &p : dependencies[shell]) {
|
||||
if (!p.second.empty())
|
||||
continue;
|
||||
const auto &path = get_path(p.first);
|
||||
double dist_a = (path.first_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_a < dist) {
|
||||
dist = dist_a;
|
||||
next_pidx = p.first;
|
||||
reverse = false;
|
||||
}
|
||||
double dist_b = (path.last_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_b < dist) {
|
||||
dist = dist_b;
|
||||
next_pidx = p.first;
|
||||
reverse = true;
|
||||
}
|
||||
for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
|
||||
if (!dependencies[path_idx].empty())
|
||||
continue;
|
||||
const auto &path = extra_perims[path_idx];
|
||||
double dist_a = (path.first_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_a < dist) {
|
||||
dist = dist_a;
|
||||
next_idx = path_idx;
|
||||
reverse = false;
|
||||
}
|
||||
double dist_b = (path.last_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_b < dist) {
|
||||
dist = dist_b;
|
||||
next_idx = path_idx;
|
||||
reverse = true;
|
||||
}
|
||||
}
|
||||
if (next_pidx == npidx) {
|
||||
break;
|
||||
if (next_idx == null_idx) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// we have valid next_pidx, add it to the sorted paths, update dependencies, update current point and potentialy set new next_pidx
|
||||
ExtrusionPath path = get_path(next_pidx);
|
||||
// we have valid next_idx, add it to the sorted paths, update dependencies, update current point and potentialy set new next_idx
|
||||
ExtrusionPath path = extra_perims[next_idx];
|
||||
if (reverse) {
|
||||
path.reverse();
|
||||
}
|
||||
sorted_paths.push_back(path);
|
||||
assert(dependencies[next_idx].empty());
|
||||
dependencies[next_idx].insert(null_idx);
|
||||
current_point = sorted_paths.back().last_point();
|
||||
if (next_pidx.shell < dependencies.size() - 1) {
|
||||
for (auto &p : dependencies[next_pidx.shell + 1]) {
|
||||
p.second.erase(next_pidx);
|
||||
}
|
||||
for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
|
||||
dependencies[path_idx].erase(next_idx);
|
||||
}
|
||||
dependencies[next_pidx.shell].erase(next_pidx);
|
||||
// check current and next shell for next pidx
|
||||
double dist = std::numeric_limits<double>::max();
|
||||
size_t current_shell = next_pidx.shell;
|
||||
next_pidx = npidx;
|
||||
for (size_t shell = current_shell; shell < std::min(current_shell + 2, dependencies.size()); shell++) {
|
||||
for (const auto &p : dependencies[shell]) {
|
||||
if (!p.second.empty())
|
||||
continue;
|
||||
const ExtrusionPath &next_path = get_path(p.first);
|
||||
double dist_a = (next_path.first_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_a < dist) {
|
||||
dist = dist_a;
|
||||
next_pidx = p.first;
|
||||
reverse = false;
|
||||
}
|
||||
double dist_b = (next_path.last_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_b < dist) {
|
||||
dist = dist_b;
|
||||
next_pidx = p.first;
|
||||
reverse = true;
|
||||
}
|
||||
double dist = std::numeric_limits<double>::max();
|
||||
next_idx = null_idx;
|
||||
|
||||
for (size_t path_idx = next_idx + 1; path_idx < extra_perims.size(); path_idx++) {
|
||||
if (!dependencies[path_idx].empty()) {
|
||||
continue;
|
||||
}
|
||||
const ExtrusionPath &next_path = extra_perims[path_idx];
|
||||
double dist_a = (next_path.first_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_a < dist) {
|
||||
dist = dist_a;
|
||||
next_idx = path_idx;
|
||||
reverse = false;
|
||||
}
|
||||
double dist_b = (next_path.last_point() - current_point).cast<double>().squaredNorm();
|
||||
if (dist_b < dist) {
|
||||
dist = dist_b;
|
||||
next_idx = path_idx;
|
||||
reverse = true;
|
||||
}
|
||||
}
|
||||
if (dist > scaled(5.0)) {
|
||||
next_pidx = npidx;
|
||||
next_idx = null_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -873,61 +866,27 @@ first_point_found:
|
||||
// #define EXTRA_PERIM_DEBUG_FILES
|
||||
// Function will generate extra perimeters clipped over nonbridgeable areas of the provided surface and returns both the new perimeters and
|
||||
// Polygons filled by those clipped perimeters
|
||||
std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over_overhangs(ExPolygons infill_area,
|
||||
const Polygons &lower_slices_polygons,
|
||||
const Flow &overhang_flow,
|
||||
double scaled_resolution,
|
||||
std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over_overhangs(ExPolygons infill_area,
|
||||
const Polygons &lower_slices_polygons,
|
||||
int perimeter_count,
|
||||
const Flow &overhang_flow,
|
||||
double scaled_resolution,
|
||||
const PrintObjectConfig &object_config,
|
||||
const PrintConfig &print_config)
|
||||
{
|
||||
coord_t anchors_size = scale_(EXTERNAL_INFILL_MARGIN);
|
||||
coord_t anchors_size = std::min(coord_t(scale_(EXTERNAL_INFILL_MARGIN)), overhang_flow.scaled_spacing() * (perimeter_count + 1));
|
||||
|
||||
BoundingBox infill_area_bb = get_extents(infill_area).inflated(SCALED_EPSILON);
|
||||
Polygons optimized_lower_slices = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons, infill_area_bb);
|
||||
Polygons overhangs = diff(infill_area, optimized_lower_slices);
|
||||
|
||||
if (overhangs.empty()) { return {}; }
|
||||
Polygons anchors = intersection(infill_area, optimized_lower_slices);
|
||||
Polygons inset_anchors; // anchored area inset by the anchor length
|
||||
{
|
||||
std::vector<double> deltas{anchors_size * 0.15 + 0.5 * overhang_flow.scaled_spacing(),
|
||||
anchors_size * 0.33 + 0.5 * overhang_flow.scaled_spacing(),
|
||||
anchors_size * 0.66 + 0.5 * overhang_flow.scaled_spacing(), anchors_size * 1.00};
|
||||
|
||||
std::vector<Polygons> anchor_areas_w_delta_anchor_size{};
|
||||
for (double delta : deltas) {
|
||||
// for each delta, store anchors without the delta region around overhangs
|
||||
anchor_areas_w_delta_anchor_size.push_back(diff(anchors, expand(overhangs, delta, EXTRA_PERIMETER_OFFSET_PARAMETERS)));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size() - 1; i++) {
|
||||
// Then, clip off each anchor area by the next area expanded back to original size, so that this smaller anchor region is only where larger wouldnt fit
|
||||
anchor_areas_w_delta_anchor_size[i] = diff(anchor_areas_w_delta_anchor_size[i], expand(anchor_areas_w_delta_anchor_size[i + 1],
|
||||
deltas[i + 1], EXTRA_PERIMETER_OFFSET_PARAMETERS));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size(); i++) {
|
||||
inset_anchors = union_(inset_anchors, anchor_areas_w_delta_anchor_size[i]);
|
||||
}
|
||||
|
||||
inset_anchors = expand(inset_anchors, 0.1*overhang_flow.scaled_width());
|
||||
|
||||
#ifdef EXTRA_PERIM_DEBUG_FILES
|
||||
{
|
||||
std::vector<std::string> colors = {"blue", "purple", "orange", "red"};
|
||||
BoundingBox bbox = get_extents(anchors);
|
||||
bbox.offset(scale_(1.));
|
||||
::Slic3r::SVG svg(debug_out_path("anchored").c_str(), bbox);
|
||||
for (const Line &line : to_lines(inset_anchors)) svg.draw(line, "green", scale_(0.2));
|
||||
for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size(); i++) {
|
||||
for (const Line &line : to_lines(anchor_areas_w_delta_anchor_size[i])) svg.draw(line, colors[i], scale_(0.1));
|
||||
}
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Polygons inset_overhang_area = diff(infill_area, inset_anchors);
|
||||
AABBTreeLines::LinesDistancer<Line> lower_layer_aabb_tree{to_lines(optimized_lower_slices)};
|
||||
Polygons anchors = intersection(infill_area, optimized_lower_slices);
|
||||
Polygons inset_anchors = diff(anchors,
|
||||
expand(overhangs, anchors_size + 0.1 * overhang_flow.scaled_width(), EXTRA_PERIMETER_OFFSET_PARAMETERS));
|
||||
Polygons inset_overhang_area = diff(infill_area, inset_anchors);
|
||||
|
||||
#ifdef EXTRA_PERIM_DEBUG_FILES
|
||||
{
|
||||
@ -942,7 +901,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
|
||||
Polygons inset_overhang_area_left_unfilled;
|
||||
|
||||
std::vector<std::vector<ExtrusionPaths>> extra_perims; // overhang region -> shell -> shell parts
|
||||
std::vector<ExtrusionPaths> extra_perims; // overhang region -> extrusion paths
|
||||
for (const ExPolygon &overhang : union_ex(to_expolygons(inset_overhang_area))) {
|
||||
Polygons overhang_to_cover = to_polygons(overhang);
|
||||
Polygons expanded_overhang_to_cover = expand(overhang_to_cover, 1.1 * overhang_flow.scaled_spacing());
|
||||
@ -954,9 +913,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
overhang_to_cover.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
extra_perims.emplace_back();
|
||||
std::vector<ExtrusionPaths> &overhang_region = extra_perims.back();
|
||||
ExtrusionPaths &overhang_region = extra_perims.emplace_back();
|
||||
|
||||
Polygons anchoring = intersection(expanded_overhang_to_cover, inset_anchors);
|
||||
Polygons perimeter_polygon = offset(union_(expand(overhang_to_cover, 0.1 * overhang_flow.scaled_spacing()), anchoring),
|
||||
@ -1002,9 +959,9 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width
|
||||
Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover);
|
||||
if (!shrinked.empty()) {
|
||||
overhang_region.emplace_back();
|
||||
extrusion_paths_append(overhang_region.back(), perimeter, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(),
|
||||
overhang_flow.width(), overhang_flow.height());
|
||||
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
|
||||
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
|
||||
overhang_flow.height());
|
||||
}
|
||||
|
||||
Polylines fills;
|
||||
@ -1015,15 +972,15 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
}
|
||||
if (!fills.empty()) {
|
||||
fills = intersection_pl(fills, shrinked_overhang_to_cover);
|
||||
overhang_region.emplace_back();
|
||||
extrusion_paths_append(overhang_region.back(), fills, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(),
|
||||
overhang_flow.width(), overhang_flow.height());
|
||||
extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()),
|
||||
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
|
||||
overhang_flow.height());
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
overhang_region.emplace_back();
|
||||
extrusion_paths_append(overhang_region.back(), perimeter, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(),
|
||||
overhang_flow.width(), overhang_flow.height());
|
||||
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
|
||||
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
|
||||
overhang_flow.height());
|
||||
}
|
||||
|
||||
if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; }
|
||||
@ -1057,16 +1014,23 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
for (const Line &line : to_lines(inset_overhang_area_left_unfilled)) svg.draw(line, "red", scale_(0.05));
|
||||
svg.Close();
|
||||
#endif
|
||||
overhang_region.erase(std::remove_if(overhang_region.begin(), overhang_region.end(),
|
||||
[](const ExtrusionPath &p) { return p.empty(); }),
|
||||
overhang_region.end());
|
||||
|
||||
std::reverse(overhang_region.begin(), overhang_region.end()); // reverse the order, It shall be printed from inside out
|
||||
if (!overhang_region.empty()) {
|
||||
auto is_anchored = [&lower_layer_aabb_tree](const ExtrusionPath &path) {
|
||||
return lower_layer_aabb_tree.distance_from_lines<true>(path.first_point()) <= 0 ||
|
||||
lower_layer_aabb_tree.distance_from_lines<true>(path.last_point()) <= 0;
|
||||
};
|
||||
std::reverse(overhang_region.begin(), overhang_region.end());
|
||||
auto first_unanchored = std::stable_partition(overhang_region.begin(), overhang_region.end(), is_anchored);
|
||||
int index_of_first_unanchored = first_unanchored - overhang_region.begin();
|
||||
overhang_region = sort_extra_perimeters(overhang_region, index_of_first_unanchored, overhang_flow.scaled_spacing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExtrusionPaths> result{};
|
||||
for (const std::vector<ExtrusionPaths> &paths : extra_perims) {
|
||||
result.push_back(sort_and_connect_extra_perimeters(paths, overhang_flow.scaled_spacing()));
|
||||
}
|
||||
|
||||
#ifdef EXTRA_PERIM_DEBUG_FILES
|
||||
BoundingBox bbox = get_extents(inset_overhang_area);
|
||||
bbox.offset(scale_(2.));
|
||||
@ -1079,7 +1043,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
|
||||
inset_overhang_area_left_unfilled = union_(inset_overhang_area_left_unfilled);
|
||||
|
||||
return {result, diff(inset_overhang_area, inset_overhang_area_left_unfilled)};
|
||||
return {extra_perims, diff(inset_overhang_area, inset_overhang_area_left_unfilled)};
|
||||
}
|
||||
|
||||
// Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper
|
||||
@ -1313,6 +1277,7 @@ void PerimeterGenerator::process_arachne(
|
||||
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
|
||||
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
|
||||
lower_slices_polygons_cache,
|
||||
loop_number + 1,
|
||||
params.overhang_flow, params.scaled_resolution,
|
||||
params.object_config, params.print_config);
|
||||
if (!extra_perimeters.empty()) {
|
||||
@ -1607,6 +1572,7 @@ void PerimeterGenerator::process_classic(
|
||||
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
|
||||
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
|
||||
lower_slices_polygons_cache,
|
||||
loop_number + 1,
|
||||
params.overhang_flow, params.scaled_resolution,
|
||||
params.object_config, params.print_config);
|
||||
if (!extra_perimeters.empty()) {
|
||||
|
@ -185,75 +185,108 @@ namespace client
|
||||
template<typename Iterator>
|
||||
struct expr
|
||||
{
|
||||
expr() : type(TYPE_EMPTY) {}
|
||||
explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; }
|
||||
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; }
|
||||
explicit expr(int i) : type(TYPE_INT) { data.i = i; }
|
||||
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; }
|
||||
explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; }
|
||||
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; }
|
||||
explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); }
|
||||
explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); }
|
||||
expr() {}
|
||||
explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; }
|
||||
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; }
|
||||
explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; }
|
||||
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_INT), it_range(it_begin, it_end) { m_data.i = i; }
|
||||
explicit expr(double d) : m_type(TYPE_DOUBLE) { m_data.d = d; }
|
||||
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; }
|
||||
explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
|
||||
explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
|
||||
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
|
||||
type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); }
|
||||
expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range)
|
||||
{ if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); }
|
||||
explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range)
|
||||
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
|
||||
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end)
|
||||
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
|
||||
~expr() { this->reset(); }
|
||||
|
||||
expr &operator=(const expr &rhs)
|
||||
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
|
||||
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
|
||||
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
|
||||
explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {}
|
||||
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
|
||||
{
|
||||
m_data.set(rhs.m_data);
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
}
|
||||
expr &operator=(const expr &rhs)
|
||||
{
|
||||
this->type = rhs.type;
|
||||
this->it_range = rhs.it_range;
|
||||
if (rhs.type == TYPE_STRING)
|
||||
this->data.s = new std::string(*rhs.data.s);
|
||||
else
|
||||
this->data.set(rhs.data);
|
||||
return *this;
|
||||
if (rhs.type() == TYPE_STRING) {
|
||||
this->set_s(rhs.s());
|
||||
} else {
|
||||
m_type = rhs.type();
|
||||
m_data.set(rhs.m_data);
|
||||
}
|
||||
this->it_range = rhs.it_range;
|
||||
return *this;
|
||||
}
|
||||
|
||||
expr &operator=(expr &&rhs)
|
||||
{
|
||||
type = rhs.type;
|
||||
this->it_range = rhs.it_range;
|
||||
data.set(rhs.data);
|
||||
rhs.type = TYPE_EMPTY;
|
||||
if (this != &rhs) {
|
||||
this->reset();
|
||||
m_type = rhs.type();
|
||||
this->it_range = rhs.it_range;
|
||||
m_data.set(rhs.m_data);
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (this->type == TYPE_STRING)
|
||||
delete data.s;
|
||||
this->type = TYPE_EMPTY;
|
||||
if (this->type() == TYPE_STRING)
|
||||
delete m_data.s;
|
||||
m_type = TYPE_EMPTY;
|
||||
}
|
||||
~expr() { reset(); }
|
||||
|
||||
bool& b() { return data.b; }
|
||||
bool b() const { return data.b; }
|
||||
void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; }
|
||||
int& i() { return data.i; }
|
||||
int i() const { return data.i; }
|
||||
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
|
||||
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
|
||||
int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); }
|
||||
double& d() { return data.d; }
|
||||
double d() const { return data.d; }
|
||||
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
|
||||
double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); }
|
||||
std::string& s() { return *data.s; }
|
||||
const std::string& s() const { return *data.s; }
|
||||
void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; }
|
||||
void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; }
|
||||
enum Type {
|
||||
TYPE_EMPTY = 0,
|
||||
TYPE_BOOL,
|
||||
TYPE_INT,
|
||||
TYPE_DOUBLE,
|
||||
TYPE_STRING,
|
||||
};
|
||||
Type type() const { return m_type; }
|
||||
|
||||
bool& b() { return m_data.b; }
|
||||
bool b() const { return m_data.b; }
|
||||
void set_b(bool v) { this->reset(); this->set_b_lite(v); }
|
||||
void set_b_lite(bool v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.b = v; m_data.set(tmp); m_type = TYPE_BOOL; }
|
||||
int& i() { return m_data.i; }
|
||||
int i() const { return m_data.i; }
|
||||
void set_i(int v) { this->reset(); set_i_lite(v); }
|
||||
void set_i_lite(int v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.i = v; m_data.set(tmp); m_type = TYPE_INT; }
|
||||
int as_i() const { return this->type() == TYPE_INT ? this->i() : int(this->d()); }
|
||||
int as_i_rounded() const { return this->type() == TYPE_INT ? this->i() : int(std::round(this->d())); }
|
||||
double& d() { return m_data.d; }
|
||||
double d() const { return m_data.d; }
|
||||
void set_d(double v) { this->reset(); this->set_d_lite(v); }
|
||||
void set_d_lite(double v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.d = v; m_data.set(tmp); m_type = TYPE_DOUBLE; }
|
||||
double as_d() const { return this->type() == TYPE_DOUBLE ? this->d() : double(this->i()); }
|
||||
std::string& s() { return *m_data.s; }
|
||||
const std::string& s() const { return *m_data.s; }
|
||||
void set_s(const std::string &s) {
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = s;
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(s));
|
||||
}
|
||||
void set_s(std::string &&s) {
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = std::move(s);
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(std::move(s)));
|
||||
}
|
||||
void set_s(const char *s) {
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = s;
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(s));
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::string out;
|
||||
switch (type) {
|
||||
case TYPE_BOOL: out = data.b ? "true" : "false"; break;
|
||||
case TYPE_INT: out = std::to_string(data.i); break;
|
||||
switch (this->type()) {
|
||||
case TYPE_BOOL: out = this->b() ? "true" : "false"; break;
|
||||
case TYPE_INT: out = std::to_string(this->i()); break;
|
||||
case TYPE_DOUBLE:
|
||||
#if 0
|
||||
// The default converter produces trailing zeros after the decimal point.
|
||||
@ -263,48 +296,24 @@ namespace client
|
||||
// It seems to be doing what the old boost::to_string() did.
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << data.d;
|
||||
ss << this->d();
|
||||
out = ss.str();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case TYPE_STRING: out = *data.s; break;
|
||||
case TYPE_STRING: out = this->s(); break;
|
||||
default: break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
union Data {
|
||||
// Raw image of the other data members.
|
||||
// The C++ compiler will consider a possible aliasing of char* with any other union member,
|
||||
// therefore copying the raw data is safe.
|
||||
char raw[8];
|
||||
bool b;
|
||||
int i;
|
||||
double d;
|
||||
std::string *s;
|
||||
|
||||
// Copy the largest member variable through char*, which will alias with all other union members by default.
|
||||
void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); }
|
||||
} data;
|
||||
|
||||
enum Type {
|
||||
TYPE_EMPTY = 0,
|
||||
TYPE_BOOL,
|
||||
TYPE_INT,
|
||||
TYPE_DOUBLE,
|
||||
TYPE_STRING,
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
||||
// Range of input iterators covering this expression.
|
||||
// Used for throwing parse exceptions.
|
||||
boost::iterator_range<Iterator> it_range;
|
||||
|
||||
expr unary_minus(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_INT :
|
||||
return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
@ -319,7 +328,7 @@ namespace client
|
||||
|
||||
expr unary_integer(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_INT:
|
||||
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
@ -334,7 +343,7 @@ namespace client
|
||||
|
||||
expr round(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_INT:
|
||||
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
@ -349,7 +358,7 @@ namespace client
|
||||
|
||||
expr unary_not(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_BOOL:
|
||||
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
|
||||
default:
|
||||
@ -362,23 +371,20 @@ namespace client
|
||||
|
||||
expr &operator+=(const expr &rhs)
|
||||
{
|
||||
if (this->type == TYPE_STRING) {
|
||||
if (this->type() == TYPE_STRING) {
|
||||
// Convert the right hand side to string and append.
|
||||
*this->data.s += rhs.to_string();
|
||||
} else if (rhs.type == TYPE_STRING) {
|
||||
*m_data.s += rhs.to_string();
|
||||
} else if (rhs.type() == TYPE_STRING) {
|
||||
// Conver the left hand side to string, append rhs.
|
||||
this->data.s = new std::string(this->to_string() + rhs.s());
|
||||
this->type = TYPE_STRING;
|
||||
this->set_s(this->to_string() + rhs.s());
|
||||
} else {
|
||||
const char *err_msg = "Cannot add non-numeric types.";
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
rhs.throw_if_not_numeric(err_msg);
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() + rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i += rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() + rhs.as_d());
|
||||
else
|
||||
m_data.i += rhs.i();
|
||||
}
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
@ -389,12 +395,10 @@ namespace client
|
||||
const char *err_msg = "Cannot subtract non-numeric types.";
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
rhs.throw_if_not_numeric(err_msg);
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() - rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i -= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() - rhs.as_d());
|
||||
else
|
||||
m_data.i -= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -404,12 +408,10 @@ namespace client
|
||||
const char *err_msg = "Cannot multiply with non-numeric type.";
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
rhs.throw_if_not_numeric(err_msg);
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() * rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i *= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() * rhs.as_d());
|
||||
else
|
||||
m_data.i *= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -418,14 +420,12 @@ namespace client
|
||||
{
|
||||
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
|
||||
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
|
||||
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
rhs.throw_exception("Division by zero");
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() / rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i /= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() / rhs.as_d());
|
||||
else
|
||||
m_data.i /= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -434,14 +434,12 @@ namespace client
|
||||
{
|
||||
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
|
||||
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
|
||||
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
rhs.throw_exception("Division by zero");
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = std::fmod(this->as_d(), rhs.as_d());
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i %= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(std::fmod(this->as_d(), rhs.as_d()));
|
||||
else
|
||||
m_data.i %= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -453,14 +451,14 @@ namespace client
|
||||
|
||||
static void evaluate_boolean(expr &self, bool &out)
|
||||
{
|
||||
if (self.type != TYPE_BOOL)
|
||||
if (self.type() != TYPE_BOOL)
|
||||
self.throw_exception("Not a boolean expression");
|
||||
out = self.b();
|
||||
}
|
||||
|
||||
static void evaluate_boolean_to_string(expr &self, std::string &out)
|
||||
{
|
||||
if (self.type != TYPE_BOOL)
|
||||
if (self.type() != TYPE_BOOL)
|
||||
self.throw_exception("Not a boolean expression");
|
||||
out = self.b() ? "true" : "false";
|
||||
}
|
||||
@ -469,31 +467,31 @@ namespace client
|
||||
static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
|
||||
{
|
||||
bool value = false;
|
||||
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
|
||||
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
|
||||
if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) &&
|
||||
(rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) {
|
||||
// Both types are numeric.
|
||||
switch (op) {
|
||||
case '=':
|
||||
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
|
||||
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
|
||||
(std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
|
||||
break;
|
||||
case '<':
|
||||
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
|
||||
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
|
||||
(lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
|
||||
break;
|
||||
case '>':
|
||||
default:
|
||||
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
|
||||
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
|
||||
(lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
|
||||
break;
|
||||
}
|
||||
} else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
|
||||
} else if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
|
||||
// Both type are bool.
|
||||
if (op != '=')
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
|
||||
value = lhs.b() == rhs.b();
|
||||
} else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
|
||||
} else if (lhs.type() == TYPE_STRING || rhs.type() == TYPE_STRING) {
|
||||
// One type is string, the other could be converted to string.
|
||||
value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
|
||||
(op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string());
|
||||
@ -502,8 +500,7 @@ namespace client
|
||||
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
|
||||
}
|
||||
lhs.reset();
|
||||
lhs.type = TYPE_BOOL;
|
||||
lhs.data.b = invert ? ! value : value;
|
||||
lhs.set_b_lite(invert ? ! value : value);
|
||||
}
|
||||
// Compare operators, store the result into lhs.
|
||||
static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); }
|
||||
@ -528,15 +525,14 @@ namespace client
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
throw_if_not_numeric(param2);
|
||||
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
|
||||
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) {
|
||||
double d = 0.;
|
||||
switch (fun) {
|
||||
case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break;
|
||||
case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break;
|
||||
default: param1.throw_exception("Internal error: invalid function");
|
||||
}
|
||||
param1.data.d = d;
|
||||
param1.type = TYPE_DOUBLE;
|
||||
param1.set_d_lite(d);
|
||||
} else {
|
||||
int i = 0;
|
||||
switch (fun) {
|
||||
@ -544,8 +540,7 @@ namespace client
|
||||
case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break;
|
||||
default: param1.throw_exception("Internal error: invalid function");
|
||||
}
|
||||
param1.data.i = i;
|
||||
param1.type = TYPE_INT;
|
||||
param1.set_i_lite(i);
|
||||
}
|
||||
}
|
||||
// Store the result into param1.
|
||||
@ -557,13 +552,10 @@ namespace client
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
throw_if_not_numeric(param2);
|
||||
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
|
||||
param1.data.d = std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng);
|
||||
param1.type = TYPE_DOUBLE;
|
||||
} else {
|
||||
param1.data.i = std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng);
|
||||
param1.type = TYPE_INT;
|
||||
}
|
||||
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE)
|
||||
param1.set_d_lite(std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng));
|
||||
else
|
||||
param1.set_i_lite(std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng));
|
||||
}
|
||||
|
||||
// Store the result into param1.
|
||||
@ -572,10 +564,10 @@ namespace client
|
||||
static void digits(expr ¶m1, expr ¶m2, expr ¶m3)
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
if (param2.type != TYPE_INT)
|
||||
if (param2.type() != TYPE_INT)
|
||||
param2.throw_exception("digits: second parameter must be integer");
|
||||
bool has_decimals = param3.type != TYPE_EMPTY;
|
||||
if (has_decimals && param3.type != TYPE_INT)
|
||||
bool has_decimals = param3.type() != TYPE_EMPTY;
|
||||
if (has_decimals && param3.type() != TYPE_INT)
|
||||
param3.throw_exception("digits: third parameter must be integer");
|
||||
|
||||
char buf[256];
|
||||
@ -593,7 +585,7 @@ namespace client
|
||||
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
|
||||
{
|
||||
const std::string *subject = nullptr;
|
||||
if (lhs.type == TYPE_STRING) {
|
||||
if (lhs.type() == TYPE_STRING) {
|
||||
// One type is string, the other could be converted to string.
|
||||
subject = &lhs.s();
|
||||
} else {
|
||||
@ -604,9 +596,7 @@ namespace client
|
||||
bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
|
||||
if (op == '!')
|
||||
result = ! result;
|
||||
lhs.reset();
|
||||
lhs.type = TYPE_BOOL;
|
||||
lhs.data.b = result;
|
||||
lhs.set_b(result);
|
||||
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
|
||||
// Syntax error in the regular expression
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
@ -620,21 +610,20 @@ namespace client
|
||||
static void logical_op(expr &lhs, expr &rhs, char op)
|
||||
{
|
||||
bool value = false;
|
||||
if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
|
||||
if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
|
||||
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
|
||||
} else {
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
|
||||
}
|
||||
lhs.type = TYPE_BOOL;
|
||||
lhs.data.b = value;
|
||||
lhs.set_b_lite(value);
|
||||
}
|
||||
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
|
||||
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
|
||||
|
||||
static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2)
|
||||
{
|
||||
if (lhs.type != TYPE_BOOL)
|
||||
if (lhs.type() != TYPE_BOOL)
|
||||
lhs.throw_exception("Not a boolean expression");
|
||||
if (lhs.b())
|
||||
lhs = std::move(rhs1);
|
||||
@ -658,9 +647,25 @@ namespace client
|
||||
|
||||
void throw_if_not_numeric(const char *message) const
|
||||
{
|
||||
if (this->type != TYPE_INT && this->type != TYPE_DOUBLE)
|
||||
if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE)
|
||||
this->throw_exception(message);
|
||||
}
|
||||
|
||||
private:
|
||||
// This object will take ownership of the parameter string object "s".
|
||||
void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; }
|
||||
|
||||
Type m_type = TYPE_EMPTY;
|
||||
|
||||
union Data {
|
||||
bool b;
|
||||
int i;
|
||||
double d;
|
||||
std::string *s;
|
||||
|
||||
// Copy the largest member variable through char*, which will alias with all other union members by default.
|
||||
void set(const Data &rhs) { memcpy(this, &rhs, sizeof(rhs)); }
|
||||
} m_data;
|
||||
};
|
||||
|
||||
template<typename ITERATOR>
|
||||
@ -668,7 +673,7 @@ namespace client
|
||||
{
|
||||
typedef expr<ITERATOR> Expr;
|
||||
os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - ";
|
||||
switch (expression.type) {
|
||||
switch (expression.type()) {
|
||||
case Expr::TYPE_EMPTY: os << "empty"; break;
|
||||
case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
|
||||
case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break;
|
||||
@ -802,6 +807,7 @@ namespace client
|
||||
case coInt: output.set_i(opt.opt->getInt()); break;
|
||||
case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
|
||||
case coPercent: output.set_d(opt.opt->getFloat()); break;
|
||||
case coEnum:
|
||||
case coPoint: output.set_s(opt.opt->serialize()); break;
|
||||
case coBool: output.set_b(opt.opt->getBool()); break;
|
||||
case coFloatOrPercent:
|
||||
@ -869,18 +875,51 @@ namespace client
|
||||
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
|
||||
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx])); break;
|
||||
case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
|
||||
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
|
||||
default:
|
||||
ctx->throw_exception("Unknown vector variable type", opt.it_range);
|
||||
}
|
||||
output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end);
|
||||
}
|
||||
|
||||
// Return a boolean value, true if the scalar variable referenced by "opt" is nullable and it has a nil value.
|
||||
template <typename Iterator>
|
||||
static void is_nil_test_scalar(
|
||||
const MyContext *ctx,
|
||||
OptWithPos<Iterator> &opt,
|
||||
expr<Iterator> &output)
|
||||
{
|
||||
if (opt.opt->is_vector())
|
||||
ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
|
||||
output.set_b(opt.opt->is_nil());
|
||||
output.it_range = opt.it_range;
|
||||
}
|
||||
|
||||
// Return a boolean value, true if an element of a vector variable referenced by "opt[index]" is nullable and it has a nil value.
|
||||
template <typename Iterator>
|
||||
static void is_nil_test_vector(
|
||||
const MyContext *ctx,
|
||||
OptWithPos<Iterator> &opt,
|
||||
int &index,
|
||||
Iterator it_end,
|
||||
expr<Iterator> &output)
|
||||
{
|
||||
if (opt.opt->is_scalar())
|
||||
ctx->throw_exception("Referencing a scalar variable when vector is expected", opt.it_range);
|
||||
const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
|
||||
if (vec->empty())
|
||||
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
|
||||
size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index);
|
||||
output.set_b(static_cast<const ConfigOptionVectorBase*>(opt.opt)->is_nil(idx));
|
||||
output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end);
|
||||
}
|
||||
|
||||
// Verify that the expression returns an integer, which may be used
|
||||
// to address a vector.
|
||||
template <typename Iterator>
|
||||
static void evaluate_index(expr<Iterator> &expr_index, int &output)
|
||||
{
|
||||
if (expr_index.type != expr<Iterator>::TYPE_INT)
|
||||
if (expr_index.type() != expr<Iterator>::TYPE_INT)
|
||||
expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
|
||||
output = expr_index.i();
|
||||
}
|
||||
@ -973,6 +1012,7 @@ namespace client
|
||||
{ "unary_expression", "Expecting an expression." },
|
||||
{ "optional_parameter", "Expecting a closing brace or an optional parameter." },
|
||||
{ "scalar_variable_reference", "Expecting a scalar variable reference."},
|
||||
{ "is_nil_test", "Expecting a scalar variable reference."},
|
||||
{ "variable_reference", "Expecting a variable reference."},
|
||||
{ "regular_expression", "Expecting a regular expression."}
|
||||
};
|
||||
@ -1259,6 +1299,7 @@ namespace client
|
||||
[ px::bind(&expr<Iterator>::template digits<true>, _val, _2, _3) ]
|
||||
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
|
||||
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ]
|
||||
| (kw["is_nil"] > '(' > is_nil_test(_r1) > ')') [_val = _1]
|
||||
| (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
|
||||
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
|
||||
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
|
||||
@ -1286,6 +1327,15 @@ namespace client
|
||||
[ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ];
|
||||
variable_reference.name("variable reference");
|
||||
|
||||
is_nil_test =
|
||||
variable_reference(_r1)[_a=_1] >>
|
||||
(
|
||||
('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index<Iterator>, _1, _b)] > ']' >
|
||||
iter_pos[px::bind(&MyContext::is_nil_test_vector<Iterator>, _r1, _a, _b, _1, _val)])
|
||||
| eps[px::bind(&MyContext::is_nil_test_scalar<Iterator>, _r1, _a, _val)]
|
||||
);
|
||||
is_nil_test.name("is_nil test");
|
||||
|
||||
regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']];
|
||||
regular_expression.name("regular_expression");
|
||||
|
||||
@ -1295,6 +1345,7 @@ namespace client
|
||||
("zdigits")
|
||||
("if")
|
||||
("int")
|
||||
("is_nil")
|
||||
//("inf")
|
||||
("else")
|
||||
("elsif")
|
||||
@ -1329,6 +1380,7 @@ namespace client
|
||||
debug(optional_parameter);
|
||||
debug(scalar_variable_reference);
|
||||
debug(variable_reference);
|
||||
debug(is_nil_test);
|
||||
debug(regular_expression);
|
||||
}
|
||||
}
|
||||
@ -1374,6 +1426,8 @@ namespace client
|
||||
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit_encoding::space_type> scalar_variable_reference;
|
||||
// Rule to translate an identifier to a ConfigOption, or to fail.
|
||||
qi::rule<Iterator, OptWithPos<Iterator>(const MyContext*), spirit_encoding::space_type> variable_reference;
|
||||
// Evaluating whether a nullable variable is nil.
|
||||
qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit_encoding::space_type> is_nil_test;
|
||||
|
||||
qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit_encoding::space_type> if_else_output;
|
||||
// qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit_encoding::space_type> switch_output;
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
@ -38,6 +39,8 @@ public:
|
||||
|
||||
// Add new ConfigOption values to m_config.
|
||||
void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
|
||||
void set(const std::string &key, std::string_view value) { this->set(key, new ConfigOptionString(std::string(value))); }
|
||||
void set(const std::string &key, const char *value) { this->set(key, new ConfigOptionString(value)); }
|
||||
void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
|
||||
void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
|
||||
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }
|
||||
|
@ -84,18 +84,28 @@ Points collect_duplicates(Points pts /* Copy */)
|
||||
return duplicits;
|
||||
}
|
||||
|
||||
template<bool IncludeBoundary>
|
||||
BoundingBox get_extents(const Points &pts)
|
||||
{
|
||||
return BoundingBox(pts);
|
||||
BoundingBox out;
|
||||
BoundingBox::construct<IncludeBoundary>(out, pts.begin(), pts.end());
|
||||
return out;
|
||||
}
|
||||
template BoundingBox get_extents<false>(const Points &pts);
|
||||
template BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
template<bool IncludeBoundary>
|
||||
BoundingBox get_extents(const std::vector<Points> &pts)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const Points &p : pts)
|
||||
bbox.merge(get_extents(p));
|
||||
bbox.merge(get_extents<IncludeBoundary>(p));
|
||||
return bbox;
|
||||
}
|
||||
template BoundingBox get_extents<false>(const std::vector<Points> &pts);
|
||||
template BoundingBox get_extents<true>(const std::vector<Points> &pts);
|
||||
|
||||
BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
|
||||
{
|
||||
|
@ -237,8 +237,20 @@ inline Point lerp(const Point &a, const Point &b, double t)
|
||||
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
|
||||
}
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
template<bool IncludeBoundary = false>
|
||||
BoundingBox get_extents(const Points &pts);
|
||||
extern template BoundingBox get_extents<false>(const Points &pts);
|
||||
extern template BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
template<bool IncludeBoundary = false>
|
||||
BoundingBox get_extents(const std::vector<Points> &pts);
|
||||
extern template BoundingBox get_extents<false>(const std::vector<Points> &pts);
|
||||
extern template BoundingBox get_extents<true>(const std::vector<Points> &pts);
|
||||
|
||||
BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
|
||||
|
||||
int nearest_point_index(const Points &points, const Point &pt);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user