This commit is contained in:
enricoturri1966 2023-03-13 12:01:59 +01:00
commit 17be299612
206 changed files with 86606 additions and 68489 deletions

View File

@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for
endif() endif()
prusaslicer_add_cmake_project(wxWidgets prusaslicer_add_cmake_project(wxWidgets
URL https://github.com/prusa3d/wxWidgets/archive/4fd2120c913c20c3bb66ee9d01d8ff5087a8b90a.zip URL https://github.com/prusa3d/wxWidgets/archive/0b49beaacce17d90f0c370ecd73221abd089667a.zip
URL_HASH SHA256=5b59e8b4dccf73e109c6588f6a69bcfe4e02e930af53c43d5d1329c1f3d83ec9 URL_HASH SHA256=8fa978a76d6bd811b30eecc5124186b9ad54290b820f3a354e85bfa9dae6a5ce
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG
CMAKE_ARGS CMAKE_ARGS
-DwxBUILD_PRECOMP=ON -DwxBUILD_PRECOMP=ON

View File

@ -237,14 +237,14 @@ documentation_link = https://help.prusa3d.com/article/prusaslicer-printables-com
weight = 3 weight = 3
[hint:Cut tool] [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 documentation_link = https://help.prusa3d.com/article/cut-tool_1779
hypertext_type = gizmo hypertext_type = gizmo
hypertext_gizmo_item = cut hypertext_gizmo_item = cut
weight = 3 weight = 3
[hint:Measurement tool] [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 documentation_link = https://help.prusa3d.com/article/measurement-tool_399451
hypertext_type = gizmo hypertext_type = gizmo
hypertext_gizmo_item = measure hypertext_gizmo_item = measure

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
min_slic3r_version = 2.4.1-beta3 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.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.5 Correct Marlin Error accumulation for Ditto printer profiles.
0.0.4 Correct Marlin Error accumulation 0.0.4 Correct Marlin Error accumulation

View File

@ -5,7 +5,7 @@
name = BIBO name = BIBO
# Configuration version of this file. Config file will only be installed, if the config_version differs. # 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. # 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? # Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/BIBO/ 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 complete_objects = 0
dont_support_bridges = 1 dont_support_bridges = 1
elefant_foot_compensation = 0.3 elefant_foot_compensation = 0.3
ensure_vertical_shell_thickness = 1 ensure_vertical_shell_thickness = 0
external_fill_pattern = rectilinear external_fill_pattern = rectilinear
external_perimeters_first = 0 external_perimeters_first = 0
external_perimeter_extrusion_width = 0.40 external_perimeter_extrusion_width = 0.40

View File

@ -1,7 +1,10 @@
min_slic3r_version = 2.6.0-alpha1 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-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. 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 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.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.5 Added new Prusament Resin material profiles. Enabled g-code thumbnails for MK2.5 family printers.
1.5.4 Added material profiles for Prusament Resin BioBased60. 1.5.4 Added material profiles for Prusament Resin BioBased60.

View File

@ -5,7 +5,7 @@
name = Prusa Research name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs. # 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. # 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? # Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ 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% changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -145,7 +145,6 @@ bridge_flow_ratio = 1
bridge_speed = 25 bridge_speed = 25
brim_width = 0 brim_width = 0
brim_separation = 0.1 brim_separation = 0.1
clip_multipart_objects = 1
compatible_printers = compatible_printers =
complete_objects = 0 complete_objects = 0
default_acceleration = 1000 default_acceleration = 1000
@ -268,8 +267,10 @@ fill_pattern = grid
travel_speed = 150 travel_speed = 150
wipe_tower = 0 wipe_tower = 0
default_acceleration = 1000 default_acceleration = 1000
first_layer_acceleration = 800 first_layer_acceleration = 600
infill_acceleration = 1000 infill_acceleration = 1500
solid_infill_acceleration = 1500
top_solid_infill_acceleration = 800
bridge_acceleration = 1000 bridge_acceleration = 1000
support_material_speed = 40 support_material_speed = 40
max_print_speed = 150 max_print_speed = 150
@ -373,7 +374,9 @@ fill_pattern = gyroid
fill_density = 15% fill_density = 15%
travel_speed = 150 travel_speed = 150
perimeter_acceleration = 800 perimeter_acceleration = 800
infill_acceleration = 1000 infill_acceleration = 1500
solid_infill_acceleration = 1500
top_solid_infill_acceleration = 800
bridge_acceleration = 1000 bridge_acceleration = 1000
first_layer_acceleration = 800 first_layer_acceleration = 800
default_acceleration = 1250 default_acceleration = 1250
@ -1283,6 +1286,10 @@ support_material_extrusion_width = 0.35
bridge_acceleration = 300 bridge_acceleration = 300
support_material_contact_distance = 0.1 support_material_contact_distance = 0.1
raft_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] [print:0.07mm ULTRADETAIL @MINI]
inherits = *0.07mm*; *MINI* inherits = *0.07mm*; *MINI*
@ -1298,6 +1305,10 @@ support_material_extrusion_width = 0.35
bridge_acceleration = 300 bridge_acceleration = 300
support_material_contact_distance = 0.1 support_material_contact_distance = 0.1
raft_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] [print:0.10mm DETAIL @MINI]
inherits = *0.10mm*; *MINI* inherits = *0.10mm*; *MINI*
@ -1314,6 +1325,11 @@ fill_pattern = gyroid
fill_density = 15% fill_density = 15%
perimeters = 3 perimeters = 3
support_material_xy_spacing = 60% 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] [print:0.15mm QUALITY @MINI]
inherits = *0.15mm*; *MINI* inherits = *0.15mm*; *MINI*
@ -1326,6 +1342,8 @@ top_solid_infill_speed = 40
fill_pattern = gyroid fill_pattern = gyroid
fill_density = 15% fill_density = 15%
support_material_xy_spacing = 60% support_material_xy_spacing = 60%
perimeter_acceleration = 900
external_perimeter_acceleration = 800
[print:0.15mm SPEED @MINI] [print:0.15mm SPEED @MINI]
inherits = *0.15mm*; *MINI* inherits = *0.15mm*; *MINI*
@ -1336,6 +1354,8 @@ infill_speed = 140
solid_infill_speed = 140 solid_infill_speed = 140
top_solid_infill_speed = 40 top_solid_infill_speed = 40
support_material_xy_spacing = 60% support_material_xy_spacing = 60%
perimeter_acceleration = 1000
external_perimeter_acceleration = 800
[print:0.20mm QUALITY @MINI] [print:0.20mm QUALITY @MINI]
inherits = *0.20mm*; *MINI* inherits = *0.20mm*; *MINI*
@ -1348,6 +1368,8 @@ top_solid_infill_speed = 40
fill_pattern = gyroid fill_pattern = gyroid
fill_density = 15% fill_density = 15%
support_material_xy_spacing = 60% support_material_xy_spacing = 60%
perimeter_acceleration = 900
external_perimeter_acceleration = 800
[print:0.20mm SPEED @MINI] [print:0.20mm SPEED @MINI]
inherits = *0.20mm*; *MINI* inherits = *0.20mm*; *MINI*
@ -1359,6 +1381,8 @@ max_print_speed = 150
solid_infill_speed = 140 solid_infill_speed = 140
top_solid_infill_speed = 40 top_solid_infill_speed = 40
support_material_xy_spacing = 60% support_material_xy_spacing = 60%
perimeter_acceleration = 1000
external_perimeter_acceleration = 800
[print:0.25mm DRAFT @MINI] [print:0.25mm DRAFT @MINI]
inherits = *0.25mm*; *MINI* inherits = *0.25mm*; *MINI*
@ -1378,6 +1402,8 @@ top_infill_extrusion_width = 0.4
support_material_xy_spacing = 60% support_material_xy_spacing = 60%
support_material_contact_distance = 0.2 support_material_contact_distance = 0.2
raft_contact_distance = 0.2 raft_contact_distance = 0.2
perimeter_acceleration = 1000
external_perimeter_acceleration = 800
# MINI - 0.25mm nozzle # MINI - 0.25mm nozzle
@ -1389,6 +1415,10 @@ fill_density = 20%
support_material_speed = 30 support_material_speed = 30
support_material_contact_distance = 0.07 support_material_contact_distance = 0.07
raft_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] [print:0.07mm ULTRADETAIL @0.25 nozzle MINI]
inherits = *0.07mm*; *0.25nozzle*; *MINI* inherits = *0.07mm*; *0.25nozzle*; *MINI*
@ -1401,6 +1431,10 @@ fill_pattern = grid
fill_density = 20% fill_density = 20%
support_material_contact_distance = 0.07 support_material_contact_distance = 0.07
raft_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] [print:0.10mm DETAIL @0.25 nozzle MINI]
inherits = *0.10mm*; *0.25nozzleMINI*; *MINI* inherits = *0.10mm*; *0.25nozzleMINI*; *MINI*
@ -1409,6 +1443,10 @@ fill_pattern = grid
fill_density = 20% fill_density = 20%
support_material_contact_distance = 0.07 support_material_contact_distance = 0.07
raft_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] [print:0.15mm QUALITY @0.25 nozzle MINI]
inherits = *0.15mm*; *0.25nozzleMINI*; *MINI* inherits = *0.15mm*; *0.25nozzleMINI*; *MINI*
@ -1421,6 +1459,10 @@ perimeter_extrusion_width = 0.27
external_perimeter_extrusion_width = 0.27 external_perimeter_extrusion_width = 0.27
infill_extrusion_width = 0.27 infill_extrusion_width = 0.27
solid_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 # MINI - 0.6mm nozzle
@ -1433,11 +1475,17 @@ max_print_speed = 100
perimeter_speed = 45 perimeter_speed = 45
solid_infill_speed = 70 solid_infill_speed = 70
top_solid_infill_speed = 45 top_solid_infill_speed = 45
infill_extrusion_width = 0.65 perimeter_extrusion_width = 0.6
solid_infill_extrusion_width = 0.65 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 support_material_contact_distance = 0.22
raft_contact_distance = 0.22 raft_contact_distance = 0.22
bridge_flow_ratio = 1 bridge_flow_ratio = 1
top_solid_infill_acceleration = 800
perimeter_acceleration = 900
external_perimeter_acceleration = 800
[print:0.20mm DETAIL @0.6 nozzle MINI] [print:0.20mm DETAIL @0.6 nozzle MINI]
inherits = *0.20mm*; *0.6nozzleMINI* inherits = *0.20mm*; *0.6nozzleMINI*
@ -1448,11 +1496,16 @@ max_print_speed = 100
perimeter_speed = 45 perimeter_speed = 45
solid_infill_speed = 70 solid_infill_speed = 70
top_solid_infill_speed = 45 top_solid_infill_speed = 45
infill_extrusion_width = 0.65 perimeter_extrusion_width = 0.6
solid_infill_extrusion_width = 0.65 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 support_material_contact_distance = 0.22
raft_contact_distance = 0.22 raft_contact_distance = 0.22
bridge_flow_ratio = 1 bridge_flow_ratio = 1
perimeter_acceleration = 900
external_perimeter_acceleration = 800
[print:0.30mm QUALITY @0.6 nozzle MINI] [print:0.30mm QUALITY @0.6 nozzle MINI]
inherits = *0.30mm*; *0.6nozzleMINI* inherits = *0.30mm*; *0.6nozzleMINI*
@ -1465,9 +1518,12 @@ solid_infill_speed = 65
top_solid_infill_speed = 45 top_solid_infill_speed = 45
external_perimeter_extrusion_width = 0.68 external_perimeter_extrusion_width = 0.68
perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68
top_infill_extrusion_width = 0.55
support_material_contact_distance = 0.25 support_material_contact_distance = 0.25
raft_contact_distance = 0.25 raft_contact_distance = 0.25
bridge_flow_ratio = 1 bridge_flow_ratio = 1
perimeter_acceleration = 900
external_perimeter_acceleration = 800
[print:0.35mm SPEED @0.6 nozzle MINI] [print:0.35mm SPEED @0.6 nozzle MINI]
inherits = *0.35mm*; *0.6nozzleMINI* inherits = *0.35mm*; *0.6nozzleMINI*
@ -1483,6 +1539,8 @@ perimeter_extrusion_width = 0.68
support_material_contact_distance = 0.25 support_material_contact_distance = 0.25
raft_contact_distance = 0.25 raft_contact_distance = 0.25
bridge_flow_ratio = 0.95 bridge_flow_ratio = 0.95
perimeter_acceleration = 1000
external_perimeter_acceleration = 800
[print:0.40mm DRAFT @0.6 nozzle MINI] [print:0.40mm DRAFT @0.6 nozzle MINI]
inherits = *0.40mm*; *0.6nozzleMINI* inherits = *0.40mm*; *0.6nozzleMINI*
@ -1500,6 +1558,8 @@ solid_infill_extrusion_width = 0.68
support_material_contact_distance = 0.25 support_material_contact_distance = 0.25
raft_contact_distance = 0.25 raft_contact_distance = 0.25
bridge_flow_ratio = 0.95 bridge_flow_ratio = 0.95
perimeter_acceleration = 1000
external_perimeter_acceleration = 800
# MINI - 0.8mm nozzle # 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 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]==0.8
perimeter_speed = 35 perimeter_speed = 35
external_perimeter_speed = 25 external_perimeter_speed = 25
infill_acceleration = 1000
infill_speed = 50 infill_speed = 50
max_print_speed = 80 max_print_speed = 80
solid_infill_speed = 45 solid_infill_speed = 45
top_solid_infill_speed = 35 top_solid_infill_speed = 35
support_material_speed = 40 support_material_speed = 40
travel_speed = 150 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] [print:0.40mm QUALITY @0.8 nozzle MINI]
inherits = 0.40mm QUALITY @0.8 nozzle inherits = 0.40mm QUALITY @0.8 nozzle
@ -1525,11 +1589,15 @@ solid_infill_speed = 40
top_solid_infill_speed = 30 top_solid_infill_speed = 30
support_material_speed = 40 support_material_speed = 40
travel_speed = 150 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] [print:0.55mm DRAFT @0.8 nozzle MINI]
inherits = 0.55mm DRAFT @0.8 nozzle inherits = 0.55mm DRAFT @0.8 nozzle
compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]==0.8 compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]==0.8
infill_acceleration = 1000
infill_speed = 40 infill_speed = 40
solid_infill_speed = 40 solid_infill_speed = 40
support_material_speed = 35 support_material_speed = 35
@ -1539,6 +1607,11 @@ top_solid_infill_speed = 28
external_perimeter_extrusion_width = 1 external_perimeter_extrusion_width = 1
perimeter_extrusion_width = 1 perimeter_extrusion_width = 1
travel_speed = 150 travel_speed = 150
infill_acceleration = 1500
solid_infill_acceleration = 1500
top_solid_infill_acceleration = 800
perimeter_acceleration = 1000
external_perimeter_acceleration = 800
# XXXXXXxxXXXXXXXXXXXXXX # XXXXXXxxXXXXXXXXXXXXXX
# XXX--- filament ---XXX # XXX--- filament ---XXX
@ -3865,6 +3938,17 @@ filament_spool_weight = 201
filament_type = PETG 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) 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] [filament:Prusa PETG @0.6 nozzle]
inherits = *PET06* inherits = *PET06*
renamed_from = "Prusa PET 0.6 nozzle"; "Prusa PETG 0.6 nozzle" 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_spool_weight = 201
filament_type = PETG 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] [filament:Filament PM PETG @0.6 nozzle]
inherits = *PET06* inherits = *PET06*
renamed_from = "Plasty Mladec PETG @0.6 nozzle" renamed_from = "Plasty Mladec PETG @0.6 nozzle"
@ -3976,6 +4068,14 @@ filament_cost = 36.29
filament_density = 1.27 filament_density = 1.27
filament_spool_weight = 201 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] [filament:Generic PETG @MMU2 0.6 nozzle]
inherits = *PET MMU2 06* inherits = *PET MMU2 06*
renamed_from = "Generic PET MMU2 0.6 nozzle"; "Generic PETG MMU2 0.6 nozzle" 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_density = 1.27
filament_spool_weight = 201 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] [filament:Filament PM PETG @MMU2 0.6 nozzle]
inherits = *PET MMU2 06* inherits = *PET MMU2 06*
renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle" renamed_from = "Plasty Mladec PETG @MMU2 0.6 nozzle"
@ -4079,6 +4187,93 @@ bed_temperature = 30
filament_retract_length = 0 filament_retract_length = 0
extrusion_multiplier = 1.16 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] [filament:Fiberlogy Easy PLA]
inherits = *PLA* inherits = *PLA*
renamed_from = Fiberlogy PLA renamed_from = Fiberlogy PLA
@ -4680,6 +4875,23 @@ fan_always_on = 1
max_fan_speed = 15 max_fan_speed = 15
min_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] [filament:Taulman T-Glase]
inherits = *PET* inherits = *PET*
filament_vendor = Taulman filament_vendor = Taulman
@ -4889,6 +5101,13 @@ renamed_from = "Prusa PET MMU1"; "Prusa PETG MMU1"
[filament:Prusament PETG @MMU1] [filament:Prusament PETG @MMU1]
inherits = Prusament PETG; *PETMMU1* 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] [filament:Taulman T-Glase @MMU1]
inherits = Taulman T-Glase; *PETMMU1* inherits = Taulman T-Glase; *PETMMU1*
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" 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 max_fan_speed = 15
min_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] [filament:Kimya ABS Carbon @MINI]
inherits = Kimya ABS Carbon; *ABSMINI* inherits = Kimya ABS Carbon; *ABSMINI*
filament_max_volumetric_speed = 6 filament_max_volumetric_speed = 6
@ -5043,6 +5275,15 @@ inherits = Verbatim ABS; *ABSMINI*
inherits = Prusament PETG; *PETMINI* inherits = Prusament PETG; *PETMINI*
compatible_printers_condition = printer_model=="MINI" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 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] [filament:Kimya PETG Carbon @MINI]
inherits = Kimya PETG Carbon; *PETMINI* inherits = Kimya PETG Carbon; *PETMINI*
filament_max_volumetric_speed = 6 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] [filament:Prusament PETG @0.6 nozzle MINI]
inherits = Prusament PETG; *PETMINI06* 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] [filament:Generic PETG @0.6 nozzle MINI]
inherits = Generic PETG; *PETMINI06* inherits = Generic PETG; *PETMINI06*
renamed_from = "Generic PET 0.6 nozzle MINI"; "Generic PETG 0.6 nozzle MINI" 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 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) 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] [filament:Prusament ASA @0.8 nozzle]
inherits = Prusament ASA inherits = Prusament ASA
first_layer_temperature = 265 first_layer_temperature = 265
@ -5437,6 +5694,14 @@ temperature = 240
slowdown_below_layer_time = 20 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_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] [filament:Generic PLA @MMU2 0.8 nozzle]
inherits = Generic PLA @MMU2 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 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 slowdown_below_layer_time = 20
compatible_printers_condition = nozzle_diameter[0]==0.8 and printer_model=="MINI" 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] [filament:Prusament ASA @0.8 nozzle MINI]
inherits = Prusament ASA @MINI inherits = Prusament ASA @MINI
first_layer_temperature = 265 first_layer_temperature = 265
@ -10039,7 +10312,7 @@ default_filament_profile = "Prusament PLA"
default_print_profile = 0.15mm QUALITY @MINI default_print_profile = 0.15mm QUALITY @MINI
gcode_flavor = marlin2 gcode_flavor = marlin2
machine_max_acceleration_e = 5000 machine_max_acceleration_e = 5000
machine_max_acceleration_extruding = 1250 machine_max_acceleration_extruding = 2000
machine_max_acceleration_retracting = 1250 machine_max_acceleration_retracting = 1250
machine_max_acceleration_travel = 2500 machine_max_acceleration_travel = 2500
machine_max_acceleration_x = 2500 machine_max_acceleration_x = 2500

View File

@ -1,2 +1,3 @@
min_slic3r_version = 2.6.0-alpha0 min_slic3r_version = 2.6.0-alpha0
1.0.1 Added Prusament PETG Carbon Fiber, Fiberthree F3 PA-GF30 Pro.
1.0.0 Initial 1.0.0 Initial

View File

@ -2,14 +2,12 @@
[vendor] [vendor]
name = Templates 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/ config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Templates/
templates_profile = 1 templates_profile = 1
## Generic filament profiles ## Generic filament profiles
# Print profiles for the Prusa Research printers.
[filament:*common*] [filament:*common*]
cooling = 1 cooling = 1
compatible_printers = compatible_printers =
@ -1547,6 +1545,16 @@ filament_density = 1.27
filament_spool_weight = 201 filament_spool_weight = 201
filament_type = PETG 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] [filament:Prusa PLA]
inherits = *PLA* inherits = *PLA*
filament_vendor = Made for Prusa filament_vendor = Made for Prusa
@ -2055,6 +2063,21 @@ fan_always_on = 1
max_fan_speed = 15 max_fan_speed = 15
min_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] [filament:Taulman T-Glase]
inherits = *PET* inherits = *PET*
filament_vendor = Taulman filament_vendor = Taulman

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -382,7 +382,7 @@ int CLI::run(int argc, char **argv)
} else if (opt_key == "align_xy") { } else if (opt_key == "align_xy") {
const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value; const Vec2d &p = m_config.option<ConfigOptionPoint>("align_xy")->value;
for (auto &model : m_models) { for (auto &model : m_models) {
BoundingBoxf3 bb = model.bounding_box(); BoundingBoxf3 bb = model.bounding_box_exact();
// this affects volumes: // this affects volumes:
model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); 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") { } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") {
std::vector<Model> new_models; std::vector<Model> new_models;
for (auto &model : m_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(); size_t num_objects = model.objects.size();
for (size_t i = 0; i < num_objects; ++ i) { for (size_t i = 0; i < num_objects; ++ i) {

View File

@ -108,6 +108,10 @@ inline cInt Round(double val)
return static_cast<cInt>((val < 0) ? (val - 0.5) : (val + 0.5)); 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 ... // 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; if (!op) return 0;
double a = 0; double a = 0;
do { do {
a += (double)(op->Prev->Pt.x() + op->Pt.x()) * (double)(op->Prev->Pt.y() - op->Pt.y()); a += (double)(op->Prev->Pt.x() + op->Pt.x()) * (double)(op->Prev->Pt.y() - op->Pt.y());
op = op->Next; op = op->Next;
} while (op != outRec.Pts); } while (op != startOp);
return a * 0.5; return a * 0.5;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
double Area(const OutRec &outRec)
{
return Area(outRec.Pts);
}
//------------------------------------------------------------------------------
bool PointIsVertex(const IntPoint &Pt, OutPt *pp) bool PointIsVertex(const IntPoint &Pt, OutPt *pp)
{ {
OutPt *pp2 = pp; OutPt *pp2 = pp;
@ -524,6 +534,11 @@ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
p = btmPt2->Next; p = btmPt2->Next;
while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next;
double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
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); return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -531,20 +546,20 @@ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2)
// Called by GetLowermostRec() // Called by GetLowermostRec()
OutPt* GetBottomPt(OutPt *pp) OutPt* GetBottomPt(OutPt *pp)
{ {
OutPt* dups = 0; OutPt* dups = nullptr;
OutPt* p = pp->Next; OutPt* p = pp->Next;
while (p != pp) while (p != pp)
{ {
if (p->Pt.y() > pp->Pt.y()) if (p->Pt.y() > pp->Pt.y())
{ {
pp = p; pp = p;
dups = 0; dups = nullptr;
} }
else if (p->Pt.y() == pp->Pt.y() && p->Pt.x() <= pp->Pt.x()) else if (p->Pt.y() == pp->Pt.y() && p->Pt.x() <= pp->Pt.x())
{ {
if (p->Pt.x() < pp->Pt.x()) if (p->Pt.x() < pp->Pt.x())
{ {
dups = 0; dups = nullptr;
pp = p; pp = p;
} else } else
{ {
@ -565,6 +580,7 @@ OutPt* GetBottomPt(OutPt *pp)
} }
return pp; return pp;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
bool Pt2IsBetweenPt1AndPt3(const IntPoint &pt1, 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) for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); 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) void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
{ {
paths.resize(0); paths.clear();
paths.reserve(polytree.Total()); paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntAny, paths); 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) void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
{ {
paths.resize(0); paths.clear();
paths.reserve(polytree.Total()); paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntClosed, paths); AddPolyNodeToPaths(polytree, ntClosed, paths);
} }
@ -4113,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
{ {
paths.resize(0); paths.clear();
paths.reserve(polytree.Total()); paths.reserve(polytree.Total());
//Open paths are top level only, so ... //Open paths are top level only, so ...
for (int i = 0; i < polytree.ChildCount(); ++i) for (int i = 0; i < polytree.ChildCount(); ++i)

View File

@ -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 MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);

View File

@ -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> front(const P& p) { return *shapelike::cbegin(p); }
template<class P> TPoint<P> back (const P& 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 // Optional, does nothing by default

View File

@ -157,26 +157,34 @@ template<class RawShape> class EdgeCache {
void createCache(const RawShape& sh) { void createCache(const RawShape& sh) {
{ // For the contour { // For the contour
auto first = shapelike::cbegin(sh); auto first = sl::cbegin(sh);
auto next = std::next(first); auto endit = sl::cend(sh);
auto endit = shapelike::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) { while(next != endit) {
contour_.emap.emplace_back(*(first++), *(next++)); contour_.emap.emplace_back(*(first++), *(next++));
contour_.full_distance += length(contour_.emap.back()); contour_.full_distance += length(contour_.emap.back());
contour_.distances.emplace_back(contour_.full_distance); 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 for(auto& h : shapelike::holes(sh)) { // For the holes
auto first = h.begin(); auto first = sl::cbegin(h);
auto next = std::next(first); auto endit = sl::cend(h);
auto endit = h.end(); auto next = first == endit ? endit :std::next(first);
ContourCache hc; ContourCache hc;
hc.distances.reserve(endit - first); hc.distances.reserve(sl::contourVertexCount(h));
while(next != endit) { while(next != endit) {
hc.emap.emplace_back(*(first++), *(next++)); hc.emap.emplace_back(*(first++), *(next++));
@ -184,6 +192,14 @@ template<class RawShape> class EdgeCache {
hc.distances.emplace_back(hc.full_distance); 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)); holes_.emplace_back(std::move(hc));
} }
} }
@ -206,7 +222,6 @@ template<class RawShape> class EdgeCache {
contour_.corners.reserve(N / S + 1); contour_.corners.reserve(N / S + 1);
contour_.corners.emplace_back(0.0); contour_.corners.emplace_back(0.0);
auto N_1 = N-1; auto N_1 = N-1;
contour_.corners.emplace_back(0.0);
for(size_t i = 0; i < N_1; i += S) { for(size_t i = 0; i < N_1; i += S) {
contour_.corners.emplace_back( contour_.corners.emplace_back(
contour_.distances.at(i) / contour_.full_distance); contour_.distances.at(i) / contour_.full_distance);

View File

@ -13,6 +13,7 @@
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "BoundingBox.hpp"
#include "Utils.hpp" // for next_highest_power_of_2() #include "Utils.hpp" // for next_highest_power_of_2()
// Definition of the ray intersection hit structure. // Definition of the ray intersection hit structure.
@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>;
using Tree2d = Tree<2, double>; using Tree2d = Tree<2, double>;
using Tree3d = Tree<3, 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 { namespace detail {
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType> template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
struct RayIntersector { struct RayIntersector {

View 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 &params)
{
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 &params)
{
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 &params)
{
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 &params)
{
// 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

View 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 &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);
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 &params);
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 &params);
} // Algorithm
} // Slic3r
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */

View File

@ -38,16 +38,16 @@ class AnyPtr {
} }
public: 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} 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))} 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))} 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))} 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=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
AnyPtr &operator=(const AnyPtr &other) = delete; 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; } 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; } 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; } 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; } AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
const T &operator*() const { return *get_ptr(*this); } const T &operator*() const { return *get_ptr(*this); }

View File

@ -85,7 +85,13 @@ template<class PConf>
void fill_config(PConf& pcfg, const ArrangeParams &params) { void fill_config(PConf& pcfg, const ArrangeParams &params) {
// Align the arranged pile into the center of the bin // 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 // Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER; 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); auto parea = poly_area(bed);
if ((1.0 - parea / area(bb)) < 1e-3) if ((1.0 - parea / area(bb)) < 1e-3)
return fn(bb); return fn(RectangleBed{bb});
else if (!std::isnan(circ.radius())) else if (!std::isnan(circ.radius()))
return fn(circ); return fn(circ);
else 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<> template<>
void arrange(ArrangePolygons & items, void arrange(ArrangePolygons & items,
const ArrangePolygons &excludes, const ArrangePolygons &excludes,
const Points & bed, const Points & bed,
const ArrangeParams & params) const ArrangeParams & params)
{ {
call_with_bed(bed, [&](const auto &bin) { arrange(items, excludes, to_arrange_bed(bed), params);
arrange(items, excludes, bin, params);
});
} }
template<class BedT> 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 &params); template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params); template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
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 &params)
{
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 arr
} // namespace Slic3r } // namespace Slic3r

View File

@ -3,12 +3,25 @@
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include <boost/variant.hpp>
#include <libslic3r/BoundingBox.hpp>
namespace Slic3r { namespace Slic3r {
class BoundingBox; class BoundingBox;
namespace arrangement { 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. /// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
class CircleBed { class CircleBed {
Point center_; Point center_;
@ -22,12 +35,28 @@ public:
inline const Point& center() const { return center_; } inline const Point& center() const { return center_; }
}; };
/// Representing an unbounded bed. struct SegmentedRectangleBed {
struct InfiniteBed { Vec<2, size_t> segments;
Point center; BoundingBox bb;
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
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 /// 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 /// has not yet successfully run on this ArrangePolygon or it could not fit the
/// object due to overly large size or invalid geometry. /// object due to overly large size or invalid geometry.
@ -75,6 +104,10 @@ struct ArrangePolygon {
using ArrangePolygons = std::vector<ArrangePolygon>; using ArrangePolygons = std::vector<ArrangePolygon>;
enum class Pivots {
Center, TopLeft, BottomLeft, BottomRight, TopRight
};
struct ArrangeParams { struct ArrangeParams {
/// The minimum distance which is allowed for any /// The minimum distance which is allowed for any
@ -93,6 +126,12 @@ struct ArrangeParams {
bool allow_rotations = false; 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. /// Progress indicator callback called when an object gets packed.
/// The unsigned argument is the number of items remaining to pack. /// The unsigned argument is the number of items remaining to pack.
std::function<void(unsigned)> progressind; 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 &params); extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params); extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams &params)
{
arrange(items, excludes, bed.bb, params);
}
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams &params)
{
arrange(items, excludes, bed.poly.contour, params);
}
void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams &params)
{
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 &params = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
bool is_box(const Points &bed);
}} // namespace Slic3r::arrangement }} // namespace Slic3r::arrangement
#endif // MODELARRANGE_HPP #endif // MODELARRANGE_HPP

View File

@ -22,24 +22,9 @@ public:
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); } min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
template<class It, class = IteratorOnly<It> > template<class It, class = IteratorOnly<It>>
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) BoundingBoxBase(It from, It to)
{ { construct(*this, from, to); }
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());
}
}
BoundingBoxBase(const std::vector<PointClass> &points) BoundingBoxBase(const std::vector<PointClass> &points)
: BoundingBoxBase(points.begin(), points.end()) : 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->min == rhs.min && this->max == rhs.max; }
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); } 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> 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 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)}; } template<class T = coord_t>
inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } 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> template<class Tout, class Tin>
auto cast(const BoundingBoxBase<Tin> &b) auto cast(const BoundingBoxBase<Tin> &b)

View File

@ -75,12 +75,9 @@ private:
//return ideal bridge direction and unsupported bridge endpoints distance. //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); if (floating_edges.empty()) {
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
if (floating_polylines.empty()) {
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
auto [pc1, pc2] = compute_principal_components(overhang_area); 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 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 // 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{}; std::unordered_map<double, Vec2d> directions{};
for (const Line &l : floating_edges) { for (const Line &l : floating_edges) {
Vec2d normal = l.normal().cast<double>().normalized(); 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 {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);
}
} }

View File

@ -641,7 +641,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
// perform operation // perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree; ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); 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. // Third, produce the extrusions, sorted by the source loop indices.

View File

@ -22,6 +22,8 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp AABBTreeLines.hpp
AABBMesh.hpp AABBMesh.hpp
AABBMesh.cpp AABBMesh.cpp
Algorithm/RegionExpansion.cpp
Algorithm/RegionExpansion.hpp
AnyPtr.hpp AnyPtr.hpp
BoundingBox.cpp BoundingBox.cpp
BoundingBox.hpp BoundingBox.hpp
@ -36,6 +38,7 @@ set(SLIC3R_SOURCES
clipper.hpp clipper.hpp
ClipperUtils.cpp ClipperUtils.cpp
ClipperUtils.hpp ClipperUtils.hpp
ClipperZUtils.hpp
Color.cpp Color.cpp
Color.hpp Color.hpp
Config.cpp Config.cpp
@ -79,6 +82,8 @@ set(SLIC3R_SOURCES
Fill/FillBase.hpp Fill/FillBase.hpp
Fill/FillConcentric.cpp Fill/FillConcentric.cpp
Fill/FillConcentric.hpp Fill/FillConcentric.hpp
Fill/FillEnsuring.cpp
Fill/FillEnsuring.hpp
Fill/FillHoneycomb.cpp Fill/FillHoneycomb.cpp
Fill/FillHoneycomb.hpp Fill/FillHoneycomb.hpp
Fill/FillGyroid.cpp Fill/FillGyroid.cpp

View File

@ -26,7 +26,6 @@ MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
MeshBoolean::cgal::CGALMeshPtr ret; MeshBoolean::cgal::CGALMeshPtr ret;
indexed_triangle_set m = *its; indexed_triangle_set m = *its;
auto tr = get_transform(csgpart);
its_transform(m, get_transform(csgpart), true); its_transform(m, get_transform(csgpart), true);
try { try {

View File

@ -8,8 +8,6 @@
#include "SVG.hpp" #include "SVG.hpp"
#endif /* CLIPPER_UTILS_DEBUG */ #endif /* CLIPPER_UTILS_DEBUG */
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
namespace Slic3r { namespace Slic3r {
#ifdef CLIPPER_UTILS_DEBUG #ifdef CLIPPER_UTILS_DEBUG
@ -267,7 +265,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper
co.ArcTolerance = miterLimit; co.ArcTolerance = miterLimit;
else else
co.MiterLimit = miterLimit; 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) { for (const ClipperLib::Path &path : paths) {
co.Clear(); co.Clear();
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output // 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; co.ArcTolerance = miterLimit;
else else
co.MiterLimit = miterLimit; 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.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
co.Execute(contours, delta); co.Execute(contours, delta);
} }
@ -435,7 +433,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
co.ArcTolerance = miterLimit; co.ArcTolerance = miterLimit;
else else
co.MiterLimit = miterLimit; 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); co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out2; ClipperLib::Paths out2;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output // 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); } { return _clipper(ClipperLib::ctUnion, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No); }
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2) Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(subject2), ApplySafetyOffset::No); } { 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) Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2)
{ return _clipper(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonProvider(subject2), ApplySafetyOffset::No); } { 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); } { 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) 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); } { 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) 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); } { 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. // 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; double l2min = lmin * lmin;
// Minimum angle to consider two edges to be parallel. // Minimum angle to consider two edges to be parallel.
// Vojtech's estimate. // Vojtech's estimate.

View File

@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl
// Miter limit is ignored for jtSquare. // Miter limit is ignored for jtSquare.
static constexpr const double DefaultLineMiterLimit = 0.; 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 { enum class ApplySafetyOffset {
No, No,
Yes 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); } { 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) 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); } { 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. // 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. // 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::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::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::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::Polyline &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines diff_pl(const Slic3r::Polylines &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); 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_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.); 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_

View 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_

View File

@ -303,7 +303,7 @@ template <class T>
class ConfigOptionSingle : public ConfigOption { class ConfigOptionSingle : public ConfigOption {
public: public:
T value; T value;
explicit ConfigOptionSingle(T value) : value(value) {} explicit ConfigOptionSingle(T value) : value(std::move(value)) {}
operator T() const { return this->value; } operator T() const { return this->value; }
void set(const ConfigOption *rhs) override void set(const ConfigOption *rhs) override
@ -847,8 +847,8 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
class ConfigOptionString : public ConfigOptionSingle<std::string> class ConfigOptionString : public ConfigOptionSingle<std::string>
{ {
public: public:
ConfigOptionString() : ConfigOptionSingle<std::string>("") {} ConfigOptionString() : ConfigOptionSingle<std::string>(std::string{}) {}
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {} explicit ConfigOptionString(std::string value) : ConfigOptionSingle<std::string>(std::move(value)) {}
static ConfigOptionType static_type() { return coString; } static ConfigOptionType static_type() { return coString; }
ConfigOptionType type() const override { return static_type(); } ConfigOptionType type() const override { return static_type(); }
@ -1736,7 +1736,7 @@ private:
void set_values(const std::initializer_list<std::string_view> il) { void set_values(const std::initializer_list<std::string_view> il) {
m_values.clear(); m_values.clear();
m_values.reserve(il.size()); m_values.reserve(il.size());
for (const std::string_view p : il) for (const std::string_view& p : il)
m_values.emplace_back(p); m_values.emplace_back(p);
assert(m_labels.empty() || m_labels.size() == m_values.size()); assert(m_labels.empty() || m_labels.size() == m_values.size());
} }
@ -1745,7 +1745,7 @@ private:
m_values.reserve(il.size()); m_values.reserve(il.size());
m_labels.clear(); m_labels.clear();
m_labels.reserve(il.size()); 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_values.emplace_back(p.first);
m_labels.emplace_back(p.second); m_labels.emplace_back(p.second);
} }
@ -1753,7 +1753,7 @@ private:
void set_labels(const std::initializer_list<std::string_view> il) { void set_labels(const std::initializer_list<std::string_view> il) {
m_labels.clear(); m_labels.clear();
m_labels.reserve(il.size()); m_labels.reserve(il.size());
for (const std::string_view p : il) for (const std::string_view& p : il)
m_labels.emplace_back(p); m_labels.emplace_back(p);
assert(m_values.empty() || m_labels.size() == m_values.size()); 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 // Check whether def.enum_values contains all the values of def.enum_keys_map and
// that they are sorted by their ordinary values. // that they are sorted by their ordinary values.
m_values_ordinary = true; m_values_ordinary = true;
for (const std::pair<std::string, int>& key : *m_enum_keys_map) { for (const auto& [enum_name, enum_int] : *m_enum_keys_map) {
assert(key.second >= 0); assert(enum_int >= 0);
if (key.second >= this->values().size() || this->value(key.second) != key.first) { if (enum_int >= int(this->values().size()) || this->value(enum_int) != enum_name) {
m_values_ordinary = false; m_values_ordinary = false;
break; break;
} }

View File

@ -1098,7 +1098,13 @@ namespace priv {
/// Track source of intersection /// Track source of intersection
/// Help for anotate inner and outer faces /// Help for anotate inner and outer faces
/// </summary> /// </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 &object;
const CutMesh &shape; const CutMesh &shape;
@ -1160,16 +1166,6 @@ struct Visitor {
/// <param name="v">New added vertex</param> /// <param name="v">New added vertex</param>
/// <param name="tm">Affected mesh</param> /// <param name="tm">Affected mesh</param>
void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm); 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> /// <summary>

View File

@ -15,10 +15,12 @@
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
#include "FillLightning.hpp" #include "FillLightning.hpp"
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
#include "FillEnsuring.hpp"
namespace Slic3r { namespace Slic3r {
static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
struct SurfaceFillParams struct SurfaceFillParams
{ {
// Zero based extruder ID. // 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. // Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
params.flow = params.bridge ? 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); layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// Calculate flow spacing for infill pattern generation. // 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; return surface_fills;
} }
@ -441,14 +484,28 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
} }
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
size_t first_object_layer_id = this->object()->get_layer(0)->id();
for (SurfaceFill &surface_fill : surface_fills) { 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. // Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern)); std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox); 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->z = this->print_z;
f->angle = surface_fill.params.angle; 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) { if (surface_fill.params.pattern == ipLightning) {
auto *lf = dynamic_cast<FillLightning::Filler*>(f.get()); 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(); lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
} }
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) { if (surface_fill.params.pattern == ipEnsuring) {
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get()); auto *fill_ensuring = dynamic_cast<FillEnsuring *>(f.get());
assert(fill_concentric != nullptr); assert(fill_ensuring != nullptr);
fill_concentric->print_config = &this->object()->print()->config(); fill_ensuring->print_region_config = &m_regions[surface_fill.region_id]->region().config();
fill_concentric->print_object_config = &this->object()->config();
} }
// calculate flow spacing for infill pattern generation // 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 = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max; params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution; 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; params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) { for (ExPolygon &expoly : surface_fill.expolygons) {
@ -591,6 +647,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
#endif #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. // Create ironing extrusions over top surfaces.
void Layer::make_ironing() void Layer::make_ironing()
{ {
@ -698,7 +842,11 @@ void Layer::make_ironing()
FillRectilinear fill; FillRectilinear fill;
FillParams fill_params; FillParams fill_params;
fill.set_bounding_box(this->object()->bounding_box()); 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.z = this->print_z;
fill.overlap = 0; fill.overlap = 0;
fill_params.density = 1.; fill_params.density = 1.;

View File

@ -20,6 +20,7 @@
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
#include "FillAdaptive.hpp" #include "FillAdaptive.hpp"
#include "FillLightning.hpp" #include "FillLightning.hpp"
#include "FillEnsuring.hpp"
#include <boost/log/trivial.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 ipSupportCubic: return new FillAdaptive::Filler();
case ipSupportBase: return new FillSupportBase(); case ipSupportBase: return new FillSupportBase();
case ipLightning: return new FillLightning::Filler(); case ipLightning: return new FillLightning::Filler();
case ipEnsuring: return new FillEnsuring();
default: throw Slic3r::InvalidArgument("unknown type"); default: throw Slic3r::InvalidArgument("unknown type");
} }
} }

View File

@ -91,6 +91,10 @@ public:
// Octree builds on mesh for usage in the adaptive cubic infill // Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr; 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: public:
virtual ~Fill() {} virtual ~Fill() {}
virtual Fill* clone() const = 0; virtual Fill* clone() const = 0;

View File

@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams &params,
continue; continue;
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); 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()) { if (extrusion->is_closed)
thick_polyline.points.pop_back(); thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
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());
}
thick_polylines_out.emplace_back(std::move(thick_polyline)); thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point(); last_pos = thick_polylines_out.back().last_point();
} }

View File

@ -26,11 +26,6 @@ protected:
ThickPolylines &thick_polylines_out) override; ThickPolylines &thick_polylines_out) override;
bool no_sort() const override { return true; } bool no_sort() const override { return true; }
const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr;
friend class Layer;
}; };
} // namespace Slic3r } // namespace Slic3r

View 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 &params)
{
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

View 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 &params) override { return {}; };
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams &params) override;
protected:
void fill_surface_single_arachne(const Surface &surface, const FillParams &params, 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_

View File

@ -7,6 +7,7 @@
namespace Slic3r { namespace Slic3r {
class PrintRegionConfig;
class Surface; class Surface;
class FillRectilinear : public Fill class FillRectilinear : public Fill

View File

@ -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_OFFSET_Z_KEY = "source_offset_z";
static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches";
static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; 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"; 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_EDGES_FIXED = "edges_fixed";
static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; 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) { for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) {
ModelObject* o = model.objects[obj_id]; ModelObject* o = model.objects[obj_id];
for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) {
ModelVolume* v = o->volumes[vol_id]; ModelVolume* v = o->volumes[vol_id];
if (v->source.input_file.empty()) 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) if (v->source.volume_idx == -1)
v->source.volume_idx = vol_id; v->source.volume_idx = vol_id;
if (v->source.object_idx == -1) if (v->source.object_idx == -1)
v->source.object_idx = obj_id; 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 // // fixes the min z of the model if negative
// model.adjust_min_z(); // model.adjust_min_z();
@ -2287,10 +2267,8 @@ namespace Slic3r {
volume->source.is_converted_from_inches = metadata.value == "1"; volume->source.is_converted_from_inches = metadata.value == "1";
else if (metadata.key == SOURCE_IN_METERS_KEY) else if (metadata.key == SOURCE_IN_METERS_KEY)
volume->source.is_converted_from_meters = metadata.value == "1"; volume->source.is_converted_from_meters = metadata.value == "1";
#if ENABLE_RELOAD_FROM_DISK_REWORK
else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY)
volume->source.is_from_builtin_objects = metadata.value == "1"; volume->source.is_from_builtin_objects = metadata.value == "1";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
else else
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); 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"; stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
else if (volume->source.is_converted_from_meters) else if (volume->source.is_converted_from_meters)
stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (volume->source.is_from_builtin_objects) if (volume->source.is_from_builtin_objects)
stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
} }
// stores volume's config data // stores volume's config data

View File

@ -657,11 +657,7 @@ void AMFParserContext::endElement(const char * /* name */)
if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform) if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform)
m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (m_volume->source.input_file.empty()) { 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.object_idx = (int)m_model.objects.size() - 1;
m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
m_volume->center_geometry_after_creation(); 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"; m_volume->source.is_converted_from_inches = m_value[1] == "1";
else if (strcmp(opt_key, "source_in_meters") == 0) else if (strcmp(opt_key, "source_in_meters") == 0)
m_volume->source.is_converted_from_meters = m_value[1] == "1"; 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) else if (strcmp(opt_key, "source_is_builtin_volume") == 0)
m_volume->source.is_from_builtin_objects = m_value[1] == "1"; m_volume->source.is_from_builtin_objects = m_value[1] == "1";
#endif // ENABLE_RELOAD_FROM_DISK_REWORK
} }
} }
else if (m_path.size() == 3) { else if (m_path.size() == 3) {
@ -922,11 +916,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut
unsigned int counter = 0; unsigned int counter = 0;
for (ModelVolume* v : o->volumes) { for (ModelVolume* v : o->volumes) {
++counter; ++counter;
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (v->source.input_file.empty()) 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; v->source.input_file = path;
if (v->name.empty()) { if (v->name.empty()) {
v->name = o->name; v->name = o->name;
@ -1075,11 +1065,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti
for (ModelObject *o : model->objects) for (ModelObject *o : model->objects)
for (ModelVolume *v : o->volumes) for (ModelVolume *v : o->volumes)
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (v->source.input_file.empty()) 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; v->source.input_file = path;
return true; 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"; stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
else if (volume->source.is_converted_from_meters) else if (volume->source.is_converted_from_meters)
stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n"; stream << " <metadata type=\"slic3r.source_in_meters\">1</metadata>\n";
#if ENABLE_RELOAD_FROM_DISK_REWORK
if (volume->source.is_from_builtin_objects) if (volume->source.is_from_builtin_objects)
stream << " <metadata type=\"slic3r.source_is_builtin_volume\">1</metadata>\n"; 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); stream << std::setprecision(std::numeric_limits<float>::max_digits10);
const indexed_triangle_set &its = volume->mesh().its; const indexed_triangle_set &its = volume->mesh().its;
for (size_t i = 0; i < its.indices.size(); ++i) { for (size_t i = 0; i < its.indices.size(); ++i) {

View File

@ -1,3 +1,4 @@
#include "Config.hpp"
#include "libslic3r.h" #include "libslic3r.h"
#include "GCode/ExtrusionProcessor.hpp" #include "GCode/ExtrusionProcessor.hpp"
#include "I18N.hpp" #include "I18N.hpp"
@ -24,6 +25,7 @@
#include <cstdlib> #include <cstdlib>
#include <chrono> #include <chrono>
#include <math.h> #include <math.h>
#include <string>
#include <string_view> #include <string_view>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -1003,18 +1005,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
std::sort(zs.begin(), zs.end()); std::sort(zs.begin(), zs.end());
m_layer_count += (unsigned int)(object->instances().size() * (std::unique(zs.begin(), zs.end()) - zs.begin())); 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(); 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_pressure_equalizer = make_unique<PressureEqualizer>(print.config());
m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; 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. // Write information on the generator.
file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); 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()); this->set_extruders(tool_ordering.all_extruders());
// Order object instances using a nearest neighbor search. // Order object instances using a nearest neighbor search.
print_object_instances_ordering = chain_print_object_instances(print); print_object_instances_ordering = chain_print_object_instances(print);
m_layer_count = tool_ordering.layer_tools().size();
} }
if (initial_extruder_id == (unsigned int)-1) { if (initial_extruder_id == (unsigned int)-1) {
// Nothing to print! // Nothing to print!
@ -2107,8 +2102,12 @@ LayerResult GCode::process_layer(
if (this->config().avoid_crossing_curled_overhangs) { if (this->config().avoid_crossing_curled_overhangs) {
m_avoid_crossing_curled_overhangs.clear(); m_avoid_crossing_curled_overhangs.clear();
for (const ObjectLayerToPrint &layer_to_print : layers) { for (const ObjectLayerToPrint &layer_to_print : layers) {
m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin()))); if (layer_to_print.object() == nullptr)
m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin()))); 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{}; 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"), 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, std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm,
m_config.max_volumetric_speed.value / 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, new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds,
m_config.dynamic_overhang_speeds, m_writer.extruder()->id(), external_perim_reference_speed,
external_perim_reference_speed, speed); speed);
variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint &p) { return p.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 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"; + float_to_string_decimal_point(m_last_height) + "\n";
} }
std::string comment; std::string cooling_marker_setspeed_comments;
if (m_enable_cooling_markers) { 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"; gcode += ";_BRIDGE_FAN_START\n";
else else
comment = ";_EXTRUDE_SET_SPEED"; cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED";
if (path.role() == ExtrusionRole::ExternalPerimeter) 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. // 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.; double path_length = 0.;
std::string comment; std::string comment;
if (m_config.gcode_comments) { if (m_config.gcode_comments) {
@ -2958,21 +2978,28 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
marked_comment += description_bridge; marked_comment += description_bridge;
} }
double last_set_speed = new_points[0].speed * 60.0; double last_set_speed = new_points[0].speed * 60.0;
gcode += m_writer.set_speed(last_set_speed, "", comment); 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); Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
for (size_t i = 1; i < new_points.size(); i++) { for (size_t i = 1; i < new_points.size(); i++) {
const ProcessedPoint& processed_point = new_points[i]; const ProcessedPoint &processed_point = new_points[i];
Vec2d p = this->point_to_gcode_quantized(processed_point.p); Vec2d p = this->point_to_gcode_quantized(processed_point.p);
const double line_length = (p - prev).norm(); const double line_length = (p - prev).norm();
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment); 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; double new_speed = processed_point.speed * 60.0;
if (last_set_speed != new_speed) { 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; 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) if (m_enable_cooling_markers)
gcode += path.role().is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; gcode += path.role().is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";

View File

@ -1,5 +1,6 @@
#include "../GCode.hpp" #include "../GCode.hpp"
#include "CoolingBuffer.hpp" #include "CoolingBuffer.hpp"
#include <algorithm>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include <boost/log/trivial.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 // 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!). // cannot have its speed adjusted. This should not happen (sic!).
TYPE_ADJUSTABLE_EMPTY = 1 << 12, 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) : CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
@ -88,6 +92,8 @@ struct CoolingLine
float time; float time;
// Maximum duration of this segment. // Maximum duration of this segment.
float time_max; float time_max;
// Requested fan speed
int fan_speed;
// If marked with the "slowdown" flag, the line has been slowed down. // If marked with the "slowdown" flag, the line has been slowed down.
bool slowdown; bool slowdown;
}; };
@ -372,7 +378,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
if (axis != 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) { if (axis == 4) {
// Convert mm/min to mm/sec. // Convert mm/min to mm/sec.
new_pos[4] /= 60.f; 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_S = pos_S > 0;
bool has_P = pos_P > 0; bool has_P = pos_P > 0;
if (has_S || has_P) { 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) if (has_P)
line.time *= 0.001f; line.time *= 0.001f;
} else } else
line.time = 0; line.time = 0;
line.time_max = line.time; 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) if (line.type != 0)
adjustment->lines.emplace_back(std::move(line)); adjustment->lines.emplace_back(std::move(line));
} }
@ -730,10 +749,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
new_gcode.reserve(gcode.size() * 2); new_gcode.reserve(gcode.size() * 2);
bool bridge_fan_control = false; bool bridge_fan_control = false;
int bridge_fan_speed = 0; 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) #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; 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); int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers);
// Is the fan speed ramp enabled? // Is the fan speed ramp enabled?
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); 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) { if (layer_time < slowdown_below_layer_time) {
// Layer time very short. Enable the fan to a full throttle. // Layer time very short. Enable the fan to a full throttle.
fan_speed_new = max_fan_speed; fan_speed_new = max_fan_speed;
custom_fan_speed_limits.first = fan_speed_new;
} else if (layer_time < fan_below_layer_time) { } else if (layer_time < fan_below_layer_time) {
// Layer time quite short. Enable the fan proportionally according to the current layer time. // Layer time quite short. Enable the fan proportionally according to the current layer time.
assert(layer_time >= slowdown_below_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); 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); 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); 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); 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); 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); 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 #undef EXTRUDER_CONFIG
bridge_fan_control = bridge_fan_speed > fan_speed_new; 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; m_fan_speed = fan_speed_new;
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); 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(); const char *pos = gcode.c_str();
int current_feedrate = 0; int current_feedrate = 0;
change_extruder_set_fan(); std::pair<int,int> fan_speed_limits = change_extruder_set_fan();
for (const CoolingLine *line : lines) { for (const CoolingLine *line : lines) {
const char *line_start = gcode.c_str() + line->line_start; const char *line_start = gcode.c_str() + line->line_start;
const char *line_end = gcode.c_str() + line->line_end; 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); 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) { if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) {
m_current_extruder = new_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); 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) { } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
if (bridge_fan_control) if (bridge_fan_control)
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); 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) if (line->slowdown)
new_feedrate = int(floor(60. * line->feedrate + 0.5)); new_feedrate = int(floor(60. * line->feedrate + 0.5));
else 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) { if (new_feedrate == current_feedrate) {
// No need to change the F value. // 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.) if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)

View File

@ -17,6 +17,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <iterator>
#include <limits> #include <limits>
#include <numeric> #include <numeric>
#include <unordered_map> #include <unordered_map>
@ -238,6 +239,7 @@ struct ProcessedPoint
{ {
Point p; Point p;
float speed = 1.0f; float speed = 1.0f;
int fan_speed = 0;
}; };
class ExtrusionQualityEstimator class ExtrusionQualityEstimator
@ -258,33 +260,26 @@ public:
} }
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path, std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
const ConfigOptionPercents &overlaps, const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
const ConfigOptionFloatsOrPercents &speeds, const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
size_t extruder_id,
float ext_perimeter_speed, float ext_perimeter_speed,
float original_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; float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
std::vector<std::pair<float, float>> speed_sections; std::map<float, float> speed_sections;
for (size_t i = 0; i < speed_sections_count; i++) { for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0)); float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
float speed = speeds.get_at(i).percent ? (speed_base * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value; float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
speed_sections.push_back({distance, speed}); 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}; std::map<float, float> fan_speed_sections;
for (auto &section : speed_sections) { for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
if (section.first == last_section.first) { float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
section.second = last_section.second; float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
} else { fan_speed_sections[distance] = fan_speed;
last_section = section;
}
} }
std::vector<ExtendedPoint> extended_points = std::vector<ExtendedPoint> extended_points =
@ -296,28 +291,26 @@ public:
const ExtendedPoint &curr = extended_points[i]; const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
auto calculate_speed = [&speed_sections, &original_speed](float distance) { auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
float final_speed; auto upper_dist = values.lower_bound(distance);
if (distance <= speed_sections.front().first) { if (upper_dist == values.end()) {
final_speed = original_speed; return values.rbegin()->second;
} 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) / if (upper_dist == values.begin()) {
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first); return upper_dist->second;
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;
} }
return final_speed;
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; return processed_points;
} }

View File

@ -472,7 +472,7 @@ const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProces
{ EProducer::Slic3r, "generated by Slic3r" }, { EProducer::Slic3r, "generated by Slic3r" },
{ EProducer::SuperSlicer, "generated by SuperSlicer" }, { EProducer::SuperSlicer, "generated by SuperSlicer" },
{ EProducer::Cura, "Cura_SteamEngine" }, { EProducer::Cura, "Cura_SteamEngine" },
{ EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" }, { EProducer::Simplify3D, "generated by Simplify3D(R)" },
{ EProducer::CraftWare, "CraftWare" }, { EProducer::CraftWare, "CraftWare" },
{ EProducer::ideaMaker, "ideaMaker" }, { EProducer::ideaMaker, "ideaMaker" },
{ EProducer::KissSlicer, "KISSlicer" }, { EProducer::KissSlicer, "KISSlicer" },
@ -2025,10 +2025,10 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string_view comment)
return true; return true;
} }
// ; layer // ; layer | ;layer
tag = " layer"; tag = "layer";
pos = cmt.find(tag); pos = cmt.find(tag);
if (pos == 0) { if (pos == 0 || pos == 1) {
// skip lines "; layer end" // skip lines "; layer end"
const std::string_view data = cmt.substr(pos + tag.length()); const std::string_view data = cmt.substr(pos + tag.length());
size_t end_start = data.find("end"); 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) if (m_forced_height > 0.0f)
m_height = m_forced_height; m_height = m_forced_height;
else if (m_layer_id == 0) 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){ else if (line.comment() != INTERNAL_G2G3_TAG){
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0) 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; 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; extra_time += m_kissslicer_toolchange_time_correction;
simulate_st_synchronize(extra_time); simulate_st_synchronize(extra_time);
// specific to single extruder multi material, set the extruder temperature // specific to single extruder multi material, set the new extruder temperature
// if not done yet // to match the old one
if (m_single_extruder_multi_material && m_extruder_temps[m_extruder_id] == 0.0f) if (m_single_extruder_multi_material)
m_extruder_temps[m_extruder_id] = m_extruder_temps[old_extruder_id]; 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); m_result.extruders_count = std::max<size_t>(m_result.extruders_count, m_extruder_id + 1);

View File

@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &
if (surface.is_internal()) if (surface.is_internal())
m_internal_islands.emplace_back(&surface.expolygon); m_internal_islands.emplace_back(&surface.expolygon);
// Calculate bounding boxes of internal slices. // Calculate bounding boxes of internal slices.
class BBoxWrapper { std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
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;
bboxes.reserve(m_internal_islands.size()); bboxes.reserve(m_internal_islands.size());
for (size_t i = 0; i < m_internal_islands.size(); ++ i) for (size_t i = 0; i < m_internal_islands.size(); ++ i)
bboxes.emplace_back(i, get_extents(*m_internal_islands[i])); bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));

View File

@ -720,28 +720,26 @@ void Transformation::reset()
} }
#if ENABLE_WORLD_COORDINATE #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() void Transformation::reset_skew()
{ {
Matrix3d rotation; auto new_scale_factor = [](const Matrix3d& s) {
Matrix3d scale; return pow(s(0, 0) * s(1, 1) * s(2, 2), 1. / 3.); // scale average
m_matrix.computeRotationScaling(&rotation, &scale); };
const double average_scale = std::cbrt(scale(0, 0) * scale(1, 1) * scale(2, 2)); 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();
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;
} }
Transform3d Transformation::get_matrix_no_offset() const Transform3d Transformation::get_matrix_no_offset() const
@ -838,6 +836,55 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation
} }
#endif // !ENABLE_WORLD_COORDINATE #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. // For parsing a transformation matrix from 3MF / AMF.
Transform3d transform3d_from_string(const std::string& transform_str) 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()); 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 }} // namespace Slic3r::Geometry

View File

@ -492,8 +492,8 @@ public:
void reset(); void reset();
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE
void reset_offset() { set_offset(Vec3d::Zero()); } void reset_offset() { set_offset(Vec3d::Zero()); }
void reset_rotation() { set_rotation(Vec3d::Zero()); } void reset_rotation();
void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } void reset_scaling_factor();
void reset_mirror() { set_mirror(Vec3d::Ones()); } void reset_mirror() { set_mirror(Vec3d::Ones()); }
void reset_skew(); void reset_skew();
@ -538,6 +538,27 @@ private:
#endif // ENABLE_WORLD_COORDINATE #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. // For parsing a transformation matrix from 3MF / AMF.
extern Transform3d transform3d_from_string(const std::string& transform_str); 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()); 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> template <class Tout = double, class Tin>
std::pair<Tout, Tout> dir_to_spheric(const Vec<3, Tin> &n, Tout norm = 1.) std::pair<Tout, Tout> dir_to_spheric(const Vec<3, Tin> &n, Tout norm = 1.)
{ {

View File

@ -444,7 +444,9 @@ private:
MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) : 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) 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) 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) // 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 // note: this keeps twins, so it inserts twice the number of the valid edges
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{}); m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});

View File

@ -1,5 +1,6 @@
#include "JumpPointSearch.hpp" #include "JumpPointSearch.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "libslic3r/AStar.hpp" #include "libslic3r/AStar.hpp"
#include "libslic3r/KDTreeIndirect.hpp" #include "libslic3r/KDTreeIndirect.hpp"
@ -181,17 +182,18 @@ public:
void JPSPathFinder::clear() void JPSPathFinder::clear()
{ {
inpassable.clear(); inpassable.clear();
obstacle_max = Pixel(std::numeric_limits<coord_t>::min(), std::numeric_limits<coord_t>::min()); max_search_box.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.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) void JPSPathFinder::add_obstacles(const Lines &obstacles)
{ {
auto store_obstacle = [&](coord_t x, coord_t y) { auto store_obstacle = [&](coord_t x, coord_t y) {
obstacle_max.x() = std::max(obstacle_max.x(), x); max_search_box.max.x() = std::max(max_search_box.max.x(), x);
obstacle_max.y() = std::max(obstacle_max.y(), y); max_search_box.max.y() = std::max(max_search_box.max.y(), y);
obstacle_min.x() = std::min(obstacle_min.x(), x); max_search_box.min.x() = std::min(max_search_box.min.x(), x);
obstacle_min.y() = std::min(obstacle_min.y(), y); max_search_box.min.y() = std::min(max_search_box.min.y(), y);
inpassable.insert(Pixel{x, y}); inpassable.insert(Pixel{x, y});
return true; 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}); BoundingBox search_box = max_search_box;
search_box.max += Pixel(1, 1); search_box.max -= Pixel(1, 1);
search_box.min -= Pixel(1, 1); search_box.min += Pixel(1, 1);
BoundingBox bounding_square(Points{start, end}); BoundingBox bounding_square(Points{start, end});
bounding_square.max += Pixel(5, 5); bounding_square.max += Pixel(5, 5);

View File

@ -2,6 +2,7 @@
#define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ #define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "Polygon.hpp"
#include "libslic3r/Layer.hpp" #include "libslic3r/Layer.hpp"
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/Polyline.hpp" #include "libslic3r/Polyline.hpp"
@ -16,18 +17,19 @@ class JPSPathFinder
using Pixel = Point; using Pixel = Point;
std::unordered_set<Pixel, PointHash> inpassable; std::unordered_set<Pixel, PointHash> inpassable;
coordf_t print_z; coordf_t print_z;
Pixel obstacle_min; BoundingBox max_search_box;
Pixel obstacle_max; Lines bed_shape;
const coord_t resolution = scaled(1.5); const coord_t resolution = scaled(1.5);
Pixel pixelize(const Point &p) { return p / resolution; } Pixel pixelize(const Point &p) { return p / resolution; }
Point unpixelize(const Pixel &p) { return p * resolution; } Point unpixelize(const Pixel &p) { return p * resolution; }
public: public:
JPSPathFinder() { clear(); }; JPSPathFinder() = default;
void init_bed_shape(const Points &bed_shape) { this->bed_shape = (to_lines(Polygon{bed_shape})); };
void clear(); void clear();
void add_obstacles(const Lines &obstacles); void add_obstacles(const Lines &obstacles);
void add_obstacles(const Layer* layer, const Point& global_origin); void add_obstacles(const Layer *layer, const Point &global_origin);
Polyline find_path(const Point &start, const Point &end); Polyline find_path(const Point &start, const Point &end);
}; };

View File

@ -1,5 +1,5 @@
#include "Layer.hpp" #include "Layer.hpp"
#include <clipper/clipper_z.hpp> #include "ClipperZUtils.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "Fill/Fill.hpp" #include "Fill/Fill.hpp"
@ -53,22 +53,7 @@ void Layer::make_slices()
this->lslices = slices; this->lslices = slices;
} }
// prepare lslices ordered by print order this->lslice_indices_sorted_by_print_order = chain_expolygons(this->lslices);
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);
}
} }
// used by Layer::build_up_down_graph() // used by Layer::build_up_down_graph()
@ -127,9 +112,7 @@ static void connect_layer_slices(
{ {
#ifndef NDEBUG #ifndef NDEBUG
auto assert_intersection_valid = [this](int i, int j) { auto assert_intersection_valid = [this](int i, int j) {
assert(i != j); assert(i < j);
if (i > j)
std::swap(i, j);
assert(i >= m_offset_below); assert(i >= m_offset_below);
assert(i < m_offset_above); assert(i < m_offset_above);
assert(j >= m_offset_above); assert(j >= m_offset_above);
@ -140,35 +123,47 @@ static void connect_layer_slices(
if (polynode.Contour.size() >= 3) { 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. // 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. // 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) { for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) {
const bool first = icontour == 0; const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
if (contour.size() >= 3) { if (contour.size() >= 3) {
if (first) { for (const ClipperLib_Z::IntPoint &pt : contour) {
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) {
j = pt.z(); j = pt.z();
if (j < 0) { if (j < 0) {
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]; std::tie(i, j) = m_intersections[-j - 1];
assert(assert_intersection_valid(i, j)); assert(assert_intersection_valid(i, j));
goto end; goto end;
} }
else if (i != j) } 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; goto end;
} }
} }
} }
}
end: end:
bool found = false; 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. // The contour is completely inside another contour.
Point pt(polynode.Contour.front().x(), polynode.Contour.front().y()); Point pt(polynode.Contour.front().x(), polynode.Contour.front().y());
if (i < m_offset_above) { if (i < m_offset_above) {
@ -202,8 +197,6 @@ static void connect_layer_slices(
} }
} else { } else {
assert(assert_intersection_valid(i, j)); assert(assert_intersection_valid(i, j));
if (i > j)
std::swap(i, j);
i -= m_offset_below; i -= m_offset_below;
j -= m_offset_above; j -= m_offset_above;
assert(i >= 0 && i < m_below.lslices_ex.size()); 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()); coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
#endif // NDEBUG #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::Clipper clipper;
ClipperLib_Z::PolyTree result; ClipperLib_Z::PolyTree result;
clipper.ZFillFunction( ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) clipper.ZFillFunction(visitor.clipper_callback());
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true); clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true); clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); 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 #ifndef NDEBUG
, paths_end , paths_end
#endif // NDEBUG #endif // NDEBUG

View File

@ -134,7 +134,7 @@ public:
Flow flow(FlowRole role) const; Flow flow(FlowRole role) const;
Flow flow(FlowRole role, double layer_height) 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 slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces(); void prepare_fill_surfaces();
@ -318,7 +318,7 @@ public:
Layer *upper_layer; Layer *upper_layer;
Layer *lower_layer; Layer *lower_layer;
bool slicing_errors; // bool slicing_errors;
coordf_t slice_z; // Z used for slicing in unscaled coordinates coordf_t slice_z; // Z used for slicing in unscaled coordinates
coordf_t print_z; // Z used for printing in unscaled coordinates coordf_t print_z; // Z used for printing in unscaled coordinates
coordf_t height; // layer height 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. // Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); } 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); 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 make_ironing();
void export_region_slices_to_svg(const char *path) const; 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()>&); 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) : 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), slice_z(slice_z), print_z(print_z), height(height),
m_id(id), m_object(object) {} m_id(id), m_object(object) {}
virtual ~Layer(); virtual ~Layer();

View File

@ -8,6 +8,7 @@
#include "Surface.hpp" #include "Surface.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "SVG.hpp" #include "SVG.hpp"
#include "Algorithm/RegionExpansion.hpp"
#include <string> #include <string>
#include <map> #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); 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 &region = this->region(); const PrintRegion &region = this->region();
const PrintRegionConfig &region_config = region.config(); const PrintRegionConfig &region_config = region.config();
const PrintObject &print_object = *this->layer()->object(); 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. // 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. // 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. // 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 &params,
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, 3.
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. #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) 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 bool has_infill = this->region().config().fill_density.value > 0.;
// const float margin = scaled<float>(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN));
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #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 */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // 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; Surfaces internal;
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. // 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 fill_boundaries = to_polygons(this->fill_expolygons());
Polygons lower_layer_covered_tmp;
// Collect top surfaces and internal surfaces. // 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. // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
@ -174,6 +444,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Voids are sparse infills if infill rate is zero. // Voids are sparse infills if infill rate is zero.
Polygons voids; Polygons voids;
for (const Surface &surface : this->fill_surfaces()) { for (const Surface &surface : this->fill_surfaces()) {
assert(! surface.empty());
if (! surface.empty()) {
if (surface.is_top()) { if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces. // Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces. // This gives the priority to bottom surfaces.
@ -182,24 +454,31 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Grown by 3mm. // Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) { } else if (surface.surface_type == stBottomBridge) {
if (! surface.empty())
bridges.emplace_back(surface); bridges.emplace_back(surface);
} } else {
if (surface.is_internal()) { assert(surface.is_internal());
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr) if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon); polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface)); 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. // Remove voids from fill_boundaries, that are not supported by the layer below.
Polygons lower_layer_covered_tmp;
if (lower_layer_covered == nullptr) { if (lower_layer_covered == nullptr) {
lower_layer_covered = &lower_layer_covered_tmp; lower_layer_covered = &lower_layer_covered_tmp;
lower_layer_covered_tmp = to_polygons(lower_layer->lslices); lower_layer_covered_tmp = to_polygons(lower_layer->lslices);
} }
if (! lower_layer_covered->empty()) 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); voids = diff(voids, *lower_layer_covered);
if (! voids.empty())
fill_boundaries = diff(fill_boundaries, voids); 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 #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{ {
static int iRun = 0; 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(fill_boundaries_ex);
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
svg.Close(); svg.Close();
} }
// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
{ {
@ -253,7 +531,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
if (idx_island == -1) { if (idx_island == -1) {
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
} else { } 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]); polys = intersection(polys, fill_boundaries_ex[idx_island]);
} }
bridge_bboxes.push_back(get_extents(polys)); 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; 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. // Intersect the grown surfaces with the actual fill boundaries.
Polygons bottom_polygons = to_polygons(bottom); 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) { for (size_t i = 0; i < top.size(); ++ i) {
Surface &s1 = top[i]; Surface &s1 = top[i];
if (s1.empty()) 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); m_fill_surfaces.surfaces = std::move(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #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 /* SLIC3R_DEBUG_SLICE_PROCESSING */
} }
#endif
void LayerRegion::prepare_fill_surfaces() void LayerRegion::prepare_fill_surfaces()
{ {

View File

@ -323,14 +323,30 @@ bool Model::add_default_instances()
} }
// this returns the bounding box of the *transformed* instances // this returns the bounding box of the *transformed* instances
BoundingBoxf3 Model::bounding_box() const BoundingBoxf3 Model::bounding_box_approx() const
{ {
BoundingBoxf3 bb; BoundingBoxf3 bb;
for (ModelObject *o : this->objects) for (ModelObject *o : this->objects)
bb.merge(o->bounding_box()); bb.merge(o->bounding_box_approx());
return bb; 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 Model::update_print_volume_state(const BuildVolume &build_volume)
{ {
unsigned int num_printable = 0; 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(); ModelObject* object = this->objects.front();
object->clear_instances(); 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 x_copy = 1; x_copy <= x; ++x_copy) {
for (size_t y_copy = 1; y_copy <= y; ++y_copy) { for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
@ -548,13 +564,13 @@ void Model::adjust_min_z()
if (objects.empty()) if (objects.empty())
return; return;
if (bounding_box().min(2) < 0.0) if (this->bounding_box_exact().min.z() < 0.0)
{ {
for (ModelObject* obj : objects) for (ModelObject* obj : objects)
{ {
if (obj != nullptr) 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) if (obj_min_z < 0.0)
obj->translate_instances(Vec3d(0.0, 0.0, -obj_min_z)); 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->printable = rhs.printable;
this->origin_translation = rhs.origin_translation; this->origin_translation = rhs.origin_translation;
this->cut_id.copy(rhs.cut_id); this->cut_id.copy(rhs.cut_id);
m_bounding_box = rhs.m_bounding_box; this->copy_transformation_caches(rhs);
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->clear_volumes(); this->clear_volumes();
this->volumes.reserve(rhs.volumes.size()); 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->layer_height_profile = std::move(rhs.layer_height_profile);
this->printable = std::move(rhs.printable); this->printable = std::move(rhs.printable);
this->origin_translation = std::move(rhs.origin_translation); this->origin_translation = std::move(rhs.origin_translation);
m_bounding_box = std::move(rhs.m_bounding_box); this->copy_transformation_caches(rhs);
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->clear_volumes(); this->clear_volumes();
this->volumes = std::move(rhs.volumes); this->volumes = std::move(rhs.volumes);
@ -864,16 +870,72 @@ void ModelObject::clear_instances()
// Returns the bounding box of the transformed instances. // Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug. // 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) { if (! m_bounding_box_approx_valid) {
m_bounding_box_valid = true; m_bounding_box_approx_valid = true;
BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box(); BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box();
m_bounding_box.reset(); m_bounding_box_approx.reset();
for (const ModelInstance *i : this->instances) 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. // 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 (allow_negative_z) {
if (parts_count() == 1) { if (parts_count() == 1) {
const double min_z = get_min_z(); const double min_z = this->min_z();
const double max_z = get_max_z(); const double max_z = this->max_z();
if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0) if (min_z >= SINKING_Z_THRESHOLD || max_z < 0.0)
z_offset = -min_z; z_offset = -min_z;
} }
else { else {
const double max_z = get_max_z(); const double max_z = this->max_z();
if (max_z < SINKING_MIN_Z_THRESHOLD) if (max_z < SINKING_MIN_Z_THRESHOLD)
z_offset = SINKING_MIN_Z_THRESHOLD - max_z; z_offset = SINKING_MIN_Z_THRESHOLD - max_z;
} }
} }
else else
z_offset = -get_min_z(); z_offset = -this->min_z();
if (z_offset != 0.0) if (z_offset != 0.0)
translate_instances(z_offset * Vec3d::UnitZ()); translate_instances(z_offset * Vec3d::UnitZ());
@ -1070,8 +1132,10 @@ void ModelObject::translate(double x, double y, double z)
v->translate(x, y, z); v->translate(x, y, z);
} }
if (m_bounding_box_valid) if (m_bounding_box_approx_valid)
m_bounding_box.translate(x, y, z); 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) void ModelObject::scale(const Vec3d &versor)
@ -1241,10 +1305,10 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
break; 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)); connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug) 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 else
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount); connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
@ -1347,12 +1411,7 @@ void ModelVolume::apply_tolerance()
return; return;
Vec3d sf = get_scaling_factor(); 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 // make a "hole" wider
sf[X] += double(cut_info.radius_tolerance); sf[X] += double(cut_info.radius_tolerance);
sf[Y] += 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); sf[Z] += double(cut_info.height_tolerance);
set_scaling_factor(sf); 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) std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace)
{ {
assert(volume->cut_info.is_connector); assert(volume->cut_info.is_connector);
@ -1373,6 +1462,7 @@ void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttri
// ! Don't apply instance transformation for the conntectors. // ! Don't apply instance transformation for the conntectors.
// This transformation is already there // This transformation is already there
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume* vol = upper->add_volume(*volume); ModelVolume* vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix); vol->set_transformation(volume_matrix);
@ -1381,15 +1471,12 @@ void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttri
if (attributes.has(ModelObjectCutAttribute::KeepLower)) { if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume); ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix); vol->set_transformation(volume_matrix);
if (volume->cut_info.connector_type == CutConnectorType::Dowel)
vol->apply_tolerance();
else
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug // 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); vol->set_type(ModelVolumeType::MODEL_PART);
} }
if (volume->cut_info.connector_type == CutConnectorType::Dowel && }
attributes.has(ModelObjectCutAttribute::CreateDowels)) { else {
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr }; ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc. // Clone the object to duplicate instances, materials etc.
clone_for_cut(&dowel); clone_for_cut(&dowel);
@ -1407,6 +1494,22 @@ void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttri
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());
}
} }
void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
@ -1418,6 +1521,11 @@ void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& i
// to the modifier volume transformation to preserve their shape properly. // to the modifier volume transformation to preserve their shape properly.
volume->set_transformation(Geometry::Transformation(volume_matrix)); 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 // Some logic for the negative volumes/connectors. Add only needed modifiers
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); 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; 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); 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 = {}) void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
{ ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
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)
{ {
const auto volume_matrix = volume->get_matrix(); 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()); TriangleMesh mesh(volume->mesh());
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); 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);
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
{
indexed_triangle_set upper_its, lower_its; indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its); upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower)) if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its); 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;
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
// Add required cut parts to the objects // 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) if (volume->cut_info.is_processed)
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
else 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()) else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace); process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace);
@ -1847,32 +1935,6 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
this->invalidate_bounding_box(); 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 ModelObject::get_instance_min_z(size_t instance_idx) const
{ {
double min_z = DBL_MAX; double min_z = DBL_MAX;
@ -2249,7 +2311,7 @@ void ModelVolume::scale(const Vec3d& scaling_factors)
void ModelObject::scale_to_fit(const Vec3d &size) 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( double factor = std::min(
size.x() / orig_size.x(), size.x() / orig_size.x(),
std::min( std::min(
@ -2354,37 +2416,6 @@ void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) cons
#endif // ENABLE_WORLD_COORDINATE #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 BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
{ {
#if ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE

View File

@ -168,7 +168,7 @@ private:
friend class cereal::access; friend class cereal::access;
friend class UndoRedo::StackImpl; friend class UndoRedo::StackImpl;
// Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. // 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) { template<class Archive> void serialize(Archive &ar) {
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(this->id().invalid()); assert(this->config.id().invalid());
Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config); Internal::StaticSerializationWrapper<ModelConfigObject> config_wrapper(config);
@ -228,7 +228,7 @@ enum class CutConnectorType : int {
}; };
enum class CutConnectorStyle : int { enum class CutConnectorStyle : int {
Prizm Prism
, Frustum , Frustum
, Undef , Undef
//,Claw //,Claw
@ -246,7 +246,7 @@ enum class CutConnectorShape : int {
struct CutConnectorAttributes struct CutConnectorAttributes
{ {
CutConnectorType type{ CutConnectorType::Plug }; CutConnectorType type{ CutConnectorType::Plug };
CutConnectorStyle style{ CutConnectorStyle::Prizm }; CutConnectorStyle style{ CutConnectorStyle::Prism };
CutConnectorShape shape{ CutConnectorShape::Circle }; CutConnectorShape shape{ CutConnectorShape::Circle };
CutConnectorAttributes() {} CutConnectorAttributes() {}
@ -343,7 +343,7 @@ public:
// The pairs of <z, layer_height> are packed into a 1D array. // The pairs of <z, layer_height> are packed into a 1D array.
LayerHeightProfile layer_height_profile; LayerHeightProfile layer_height_profile;
// Whether or not this object is printable // 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 // This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances. // saved in mesh coordinates to allow using them for several instances.
@ -397,11 +397,22 @@ public:
void delete_last_instance(); void delete_last_instance();
void clear_instances(); void clear_instances();
// Returns the bounding box of the transformed instances. // Returns the bounding box of the transformed instances. This bounding box is approximate and not snug, it is being cached.
// This bounding box is approximate and not snug. const BoundingBoxf3& bounding_box_approx() const;
// This bounding box is being cached. // Returns an exact bounding box of the transformed instances. The result it is being cached.
const BoundingBoxf3& bounding_box() const; const BoundingBoxf3& bounding_box_exact() const;
void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } // 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. // A mesh containing all transformed instances of this object.
TriangleMesh mesh() const; TriangleMesh mesh() const;
@ -459,10 +470,13 @@ public:
void synchronize_model_after_cut(); void synchronize_model_after_cut();
void apply_cut_attributes(ModelObjectCutAttributes attributes); void apply_cut_attributes(ModelObjectCutAttributes attributes);
void clone_for_cut(ModelObject **obj); 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); std::vector<ModelObject*>& dowels, Vec3d& local_dowels_displace);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix, void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower); 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, void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace); ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, Vec3d& local_displace);
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes); 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. // 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); 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_min_z(size_t instance_idx) const;
double get_instance_max_z(size_t instance_idx) const; double get_instance_max_z(size_t instance_idx) const;
@ -497,14 +509,13 @@ public:
private: private:
friend class Model; friend class Model;
// This constructor assigns new ID to this ModelObject and its config. // This constructor assigns new ID to this ModelObject and its config.
explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), explicit ModelObject(Model* model) : m_model(model), origin_translation(Vec3d::Zero())
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{ {
assert(this->id().valid()); assert(this->id().valid());
assert(this->config.id().valid()); assert(this->config.id().valid());
assert(this->layer_height_profile.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->id().invalid());
assert(this->config.id().invalid()); assert(this->config.id().invalid());
@ -582,15 +593,31 @@ private:
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
// Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. // 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. // Bounding box, cached.
mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_bounding_box_approx;
mutable bool m_bounding_box_valid; 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 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 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. // Called by Print::apply() to set the model pointer after making a copy.
friend class Print; friend class Print;
@ -602,8 +629,7 @@ private:
friend class UndoRedo::StackImpl; friend class UndoRedo::StackImpl;
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
ModelObject() : ModelObject() :
ObjectBase(-1), config(-1), layer_height_profile(-1), 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) {
assert(this->id().invalid()); assert(this->id().invalid());
assert(this->config.id().invalid()); assert(this->config.id().invalid());
assert(this->layer_height_profile.id().invalid()); assert(this->layer_height_profile.id().invalid());
@ -614,12 +640,17 @@ private:
Internal::StaticSerializationWrapper<LayerHeightProfile> layer_heigth_profile_wrapper(layer_height_profile); 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, 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, 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); cut_connectors, cut_id);
} }
// Called by Print::validate() from the UI thread. // Called by Print::validate() from the UI thread.
unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); 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 { 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()) // 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; ModelInstanceEPrintVolumeState print_volume_state;
// Whether or not this instance is printable // Whether or not this instance is printable
bool printable; bool printable { true };
ModelObject* get_object() const { return this->object; } ModelObject* get_object() const { return this->object; }
@ -1153,9 +1184,7 @@ public:
// To be called on an external mesh // To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; 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. // Transform an external bounding box, thus the resulting bounding box is no more snug.
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
// Transform an external bounding box.
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
// Transform an external vector. // Transform an external vector.
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
@ -1198,7 +1227,7 @@ private:
ModelObject* object; ModelObject* object;
// Constructor, which assigns a new unique ID. // 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. // Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object, const ModelInstance &other) : 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()); } 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 delete_material(t_model_material_id material_id);
void clear_materials(); void clear_materials();
bool add_default_instances(); bool add_default_instances();
// Returns approximate axis aligned bounding box of this model // Returns approximate axis aligned bounding box of this model.
BoundingBoxf3 bounding_box() const; 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, // Set the print_volume_state of PrintObject::instances,
// return total number of printable objects. // return total number of printable objects.
unsigned int update_print_volume_state(const BuildVolume &build_volume); unsigned int update_print_volume_state(const BuildVolume &build_volume);

View File

@ -29,10 +29,12 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cassert> #include <cassert>
#include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <list>
#include <math.h> #include <math.h>
#include <ostream> #include <ostream>
#include <stack> #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.AddPath(subject, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true); clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree clipped_polytree;
ClipperLib_Z::Paths clipped_paths; ClipperLib_Z::Paths clipped_paths;
{
ClipperLib_Z::PolyTree clipped_polytree;
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths); ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
}
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero. // 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. // 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 // 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) 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()}; AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) { 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; } 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++) { 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; } if (lines_one.distance_from_lines<false>(path_two.polyline.points[pt_idx]) < limit_distance) { return true; }
} }
return false; return false;
} }
ExtrusionPaths reconnect_extrusion_paths(const ExtrusionPaths& paths, double limit_distance) { Polylines reconnect_polylines(const Polylines &polylines, double limit_distance)
if (paths.empty()) return paths; {
if (polylines.empty())
return polylines;
std::unordered_map<size_t, ExtrusionPath> connected; std::unordered_map<size_t, Polyline> connected;
connected.reserve(paths.size()); connected.reserve(polylines.size());
for (size_t i = 0; i < paths.size(); i++) { for (size_t i = 0; i < polylines.size(); i++) {
if (!paths[i].empty()) { if (!polylines[i].empty()) {
connected.emplace(i, paths[i]); 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()) { if (connected.find(a) == connected.end()) {
continue; continue;
} }
ExtrusionPath &base = connected.at(a); Polyline &base = connected.at(a);
for (size_t b = a + 1; b < paths.size(); b++) { for (size_t b = a + 1; b < polylines.size(); b++) {
if (connected.find(b) == connected.end()) { if (connected.find(b) == connected.end()) {
continue; 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) { 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); connected.erase(b);
} else if ((base.last_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { } 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); connected.erase(b);
} else if ((base.first_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { } 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 = std::move(next);
base.reverse(); base.reverse();
connected.erase(b); connected.erase(b);
} else if ((base.first_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { } else if ((base.first_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
base.reverse(); base.reverse();
base.polyline.append(std::move(next.polyline)); base.append(std::move(next));
base.reverse(); base.reverse();
connected.erase(b); connected.erase(b);
} }
} }
} }
ExtrusionPaths result; Polylines result;
for (auto& ext : connected) { for (auto &ext : connected) {
result.push_back(std::move(ext.second)); result.push_back(std::move(ext.second));
} }
return result; 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; if (extra_perims.empty()) return {};
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));
}
struct Pidx std::vector<std::unordered_set<size_t>> dependencies(extra_perims.size());
{ for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
size_t shell; for (size_t prev_path_idx = 0; prev_path_idx < path_idx; prev_path_idx++) {
size_t path; if (paths_touch(extra_perims[path_idx], extra_perims[prev_path_idx], extrusion_spacing * 1.5f)) {
bool operator==(const Pidx &rhs) const { return shell == rhs.shell && path == rhs.path; } dependencies[path_idx].insert(prev_path_idx);
};
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<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 &current_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);
};
} }
} }
current_shell[current_path] = current_dependencies;
}
} }
Point current_point{}; std::vector<bool> processed(extra_perims.size(), false);
for (const ExtrusionPaths &ps : connected_shells) { for (size_t path_idx = 0; path_idx < index_of_first_unanchored; path_idx++) {
for (const ExtrusionPath &p : ps) { processed[path_idx] = true;
if (!p.empty()) { }
current_point = p.first_point();
goto first_point_found; 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;
} }
first_point_found: }
if (!change) {
break;
}
}
Point current_point = extra_perims.begin()->first_point();
ExtrusionPaths sorted_paths{}; ExtrusionPaths sorted_paths{};
Pidx npidx = Pidx{size_t(-1), 0}; size_t null_idx = size_t(-1);
Pidx next_pidx = npidx; size_t next_idx = null_idx;
bool reverse = false; bool reverse = false;
while (true) { 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(); double dist = std::numeric_limits<double>::max();
for (size_t shell = 0; shell < dependencies.size(); shell++) { for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
for (const auto &p : dependencies[shell]) { if (!dependencies[path_idx].empty())
if (!p.second.empty())
continue; continue;
const auto &path = get_path(p.first); const auto &path = extra_perims[path_idx];
double dist_a = (path.first_point() - current_point).cast<double>().squaredNorm(); double dist_a = (path.first_point() - current_point).cast<double>().squaredNorm();
if (dist_a < dist) { if (dist_a < dist) {
dist = dist_a; dist = dist_a;
next_pidx = p.first; next_idx = path_idx;
reverse = false; reverse = false;
} }
double dist_b = (path.last_point() - current_point).cast<double>().squaredNorm(); double dist_b = (path.last_point() - current_point).cast<double>().squaredNorm();
if (dist_b < dist) { if (dist_b < dist) {
dist = dist_b; dist = dist_b;
next_pidx = p.first; next_idx = path_idx;
reverse = true; reverse = true;
} }
} }
} if (next_idx == null_idx) {
if (next_pidx == npidx) {
break; break;
} }
} else { } else {
// we have valid next_pidx, add it to the sorted paths, update dependencies, update current point and potentialy set new 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 = get_path(next_pidx); ExtrusionPath path = extra_perims[next_idx];
if (reverse) { if (reverse) {
path.reverse(); path.reverse();
} }
sorted_paths.push_back(path); sorted_paths.push_back(path);
assert(dependencies[next_idx].empty());
dependencies[next_idx].insert(null_idx);
current_point = sorted_paths.back().last_point(); current_point = sorted_paths.back().last_point();
if (next_pidx.shell < dependencies.size() - 1) { for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
for (auto &p : dependencies[next_pidx.shell + 1]) { dependencies[path_idx].erase(next_idx);
p.second.erase(next_pidx);
} }
}
dependencies[next_pidx.shell].erase(next_pidx);
// check current and next shell for next pidx
double dist = std::numeric_limits<double>::max(); double dist = std::numeric_limits<double>::max();
size_t current_shell = next_pidx.shell; next_idx = null_idx;
next_pidx = npidx;
for (size_t shell = current_shell; shell < std::min(current_shell + 2, dependencies.size()); shell++) { for (size_t path_idx = next_idx + 1; path_idx < extra_perims.size(); path_idx++) {
for (const auto &p : dependencies[shell]) { if (!dependencies[path_idx].empty()) {
if (!p.second.empty())
continue; continue;
const ExtrusionPath &next_path = get_path(p.first); }
const ExtrusionPath &next_path = extra_perims[path_idx];
double dist_a = (next_path.first_point() - current_point).cast<double>().squaredNorm(); double dist_a = (next_path.first_point() - current_point).cast<double>().squaredNorm();
if (dist_a < dist) { if (dist_a < dist) {
dist = dist_a; dist = dist_a;
next_pidx = p.first; next_idx = path_idx;
reverse = false; reverse = false;
} }
double dist_b = (next_path.last_point() - current_point).cast<double>().squaredNorm(); double dist_b = (next_path.last_point() - current_point).cast<double>().squaredNorm();
if (dist_b < dist) { if (dist_b < dist) {
dist = dist_b; dist = dist_b;
next_pidx = p.first; next_idx = path_idx;
reverse = true; reverse = true;
} }
} }
}
if (dist > scaled(5.0)) { if (dist > scaled(5.0)) {
next_pidx = npidx; next_idx = null_idx;
} }
} }
} }
@ -875,58 +868,24 @@ first_point_found:
// Polygons filled by those clipped perimeters // Polygons filled by those clipped perimeters
std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over_overhangs(ExPolygons infill_area, std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over_overhangs(ExPolygons infill_area,
const Polygons &lower_slices_polygons, const Polygons &lower_slices_polygons,
int perimeter_count,
const Flow &overhang_flow, const Flow &overhang_flow,
double scaled_resolution, double scaled_resolution,
const PrintObjectConfig &object_config, const PrintObjectConfig &object_config,
const PrintConfig &print_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); 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 optimized_lower_slices = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons, infill_area_bb);
Polygons overhangs = diff(infill_area, optimized_lower_slices); Polygons overhangs = diff(infill_area, optimized_lower_slices);
if (overhangs.empty()) { return {}; } if (overhangs.empty()) { return {}; }
AABBTreeLines::LinesDistancer<Line> lower_layer_aabb_tree{to_lines(optimized_lower_slices)};
Polygons anchors = intersection(infill_area, optimized_lower_slices); Polygons anchors = intersection(infill_area, optimized_lower_slices);
Polygons inset_anchors; // anchored area inset by the anchor length Polygons inset_anchors = diff(anchors,
{ expand(overhangs, anchors_size + 0.1 * overhang_flow.scaled_width(), EXTRA_PERIMETER_OFFSET_PARAMETERS));
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); Polygons inset_overhang_area = diff(infill_area, inset_anchors);
#ifdef EXTRA_PERIM_DEBUG_FILES #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; 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))) { for (const ExPolygon &overhang : union_ex(to_expolygons(inset_overhang_area))) {
Polygons overhang_to_cover = to_polygons(overhang); Polygons overhang_to_cover = to_polygons(overhang);
Polygons expanded_overhang_to_cover = expand(overhang_to_cover, 1.1 * overhang_flow.scaled_spacing()); 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()); overhang_to_cover.end());
continue; continue;
} }
ExtrusionPaths &overhang_region = extra_perims.emplace_back();
extra_perims.emplace_back();
std::vector<ExtrusionPaths> &overhang_region = extra_perims.back();
Polygons anchoring = intersection(expanded_overhang_to_cover, inset_anchors); 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), 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 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); Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover);
if (!shrinked.empty()) { if (!shrinked.empty()) {
overhang_region.emplace_back(); extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
extrusion_paths_append(overhang_region.back(), perimeter, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.width(), overhang_flow.height()); overhang_flow.height());
} }
Polylines fills; Polylines fills;
@ -1015,15 +972,15 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
} }
if (!fills.empty()) { if (!fills.empty()) {
fills = intersection_pl(fills, shrinked_overhang_to_cover); fills = intersection_pl(fills, shrinked_overhang_to_cover);
overhang_region.emplace_back(); extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()),
extrusion_paths_append(overhang_region.back(), fills, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.width(), overhang_flow.height()); overhang_flow.height());
} }
break; break;
} else { } else {
overhang_region.emplace_back(); extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
extrusion_paths_append(overhang_region.back(), perimeter, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.width(), overhang_flow.height()); overhang_flow.height());
} }
if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; } if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; }
@ -1057,14 +1014,21 @@ 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)); for (const Line &line : to_lines(inset_overhang_area_left_unfilled)) svg.draw(line, "red", scale_(0.05));
svg.Close(); svg.Close();
#endif #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 #ifdef EXTRA_PERIM_DEBUG_FILES
@ -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); 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 // 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 // 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, auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
lower_slices_polygons_cache, lower_slices_polygons_cache,
loop_number + 1,
params.overhang_flow, params.scaled_resolution, params.overhang_flow, params.scaled_resolution,
params.object_config, params.print_config); params.object_config, params.print_config);
if (!extra_perimeters.empty()) { 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 // 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, auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
lower_slices_polygons_cache, lower_slices_polygons_cache,
loop_number + 1,
params.overhang_flow, params.scaled_resolution, params.overhang_flow, params.scaled_resolution,
params.object_config, params.print_config); params.object_config, params.print_config);
if (!extra_perimeters.empty()) { if (!extra_perimeters.empty()) {

View File

@ -185,75 +185,108 @@ namespace client
template<typename Iterator> template<typename Iterator>
struct expr struct expr
{ {
expr() : type(TYPE_EMPTY) {} expr() {}
explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; } explicit expr(bool b) : m_type(TYPE_BOOL) { m_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(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) : type(TYPE_INT) { data.i = i; } explicit expr(int i) : m_type(TYPE_INT) { m_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(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) : type(TYPE_DOUBLE) { data.d = d; } explicit expr(double d) : m_type(TYPE_DOUBLE) { m_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(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) : type(TYPE_STRING) { data.s = new std::string(s); } explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
explicit expr(const std::string &s) : type(TYPE_STRING) { 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) : 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); } m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range) expr(const expr &rhs) : m_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); } { 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) : type(rhs.type), it_range(rhs.it_range) explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {}
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; } explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
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; } m_data.set(rhs.m_data);
~expr() { this->reset(); } rhs.m_type = TYPE_EMPTY;
}
expr &operator=(const expr &rhs) expr &operator=(const expr &rhs)
{ {
this->type = rhs.type; 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; 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; return *this;
} }
expr &operator=(expr &&rhs) expr &operator=(expr &&rhs)
{ {
type = rhs.type; if (this != &rhs) {
this->reset();
m_type = rhs.type();
this->it_range = rhs.it_range; this->it_range = rhs.it_range;
data.set(rhs.data); m_data.set(rhs.m_data);
rhs.type = TYPE_EMPTY; rhs.m_type = TYPE_EMPTY;
}
return *this; return *this;
} }
void reset() void reset()
{ {
if (this->type == TYPE_STRING) if (this->type() == TYPE_STRING)
delete data.s; delete m_data.s;
this->type = TYPE_EMPTY; m_type = TYPE_EMPTY;
} }
~expr() { reset(); }
bool& b() { return data.b; } enum Type {
bool b() const { return data.b; } TYPE_EMPTY = 0,
void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; } TYPE_BOOL,
int& i() { return data.i; } TYPE_INT,
int i() const { return data.i; } TYPE_DOUBLE,
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; } TYPE_STRING,
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())); } Type type() const { return m_type; }
double& d() { return data.d; }
double d() const { return data.d; } bool& b() { return m_data.b; }
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; } bool b() const { return m_data.b; }
double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); } void set_b(bool v) { this->reset(); this->set_b_lite(v); }
std::string& s() { return *data.s; } void set_b_lite(bool v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.b = v; m_data.set(tmp); m_type = TYPE_BOOL; }
const std::string& s() const { return *data.s; } int& i() { return m_data.i; }
void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; } int i() const { return m_data.i; }
void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; } 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 to_string() const
{ {
std::string out; std::string out;
switch (type) { switch (this->type()) {
case TYPE_BOOL: out = data.b ? "true" : "false"; break; case TYPE_BOOL: out = this->b() ? "true" : "false"; break;
case TYPE_INT: out = std::to_string(data.i); break; case TYPE_INT: out = std::to_string(this->i()); break;
case TYPE_DOUBLE: case TYPE_DOUBLE:
#if 0 #if 0
// The default converter produces trailing zeros after the decimal point. // 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. // It seems to be doing what the old boost::to_string() did.
{ {
std::ostringstream ss; std::ostringstream ss;
ss << data.d; ss << this->d();
out = ss.str(); out = ss.str();
} }
#endif #endif
break; break;
case TYPE_STRING: out = *data.s; break; case TYPE_STRING: out = this->s(); break;
default: break; default: break;
} }
return out; 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. // Range of input iterators covering this expression.
// Used for throwing parse exceptions. // Used for throwing parse exceptions.
boost::iterator_range<Iterator> it_range; boost::iterator_range<Iterator> it_range;
expr unary_minus(const Iterator start_pos) const expr unary_minus(const Iterator start_pos) const
{ {
switch (this->type) { switch (this->type()) {
case TYPE_INT : case TYPE_INT :
return expr<Iterator>(- this->i(), start_pos, this->it_range.end()); return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE: case TYPE_DOUBLE:
@ -319,7 +328,7 @@ namespace client
expr unary_integer(const Iterator start_pos) const expr unary_integer(const Iterator start_pos) const
{ {
switch (this->type) { switch (this->type()) {
case TYPE_INT: case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end()); return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE: case TYPE_DOUBLE:
@ -334,7 +343,7 @@ namespace client
expr round(const Iterator start_pos) const expr round(const Iterator start_pos) const
{ {
switch (this->type) { switch (this->type()) {
case TYPE_INT: case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end()); return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE: case TYPE_DOUBLE:
@ -349,7 +358,7 @@ namespace client
expr unary_not(const Iterator start_pos) const expr unary_not(const Iterator start_pos) const
{ {
switch (this->type) { switch (this->type()) {
case TYPE_BOOL: case TYPE_BOOL:
return expr<Iterator>(! this->b(), start_pos, this->it_range.end()); return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
default: default:
@ -362,23 +371,20 @@ namespace client
expr &operator+=(const expr &rhs) expr &operator+=(const expr &rhs)
{ {
if (this->type == TYPE_STRING) { if (this->type() == TYPE_STRING) {
// Convert the right hand side to string and append. // Convert the right hand side to string and append.
*this->data.s += rhs.to_string(); *m_data.s += rhs.to_string();
} else if (rhs.type == TYPE_STRING) { } else if (rhs.type() == TYPE_STRING) {
// Conver the left hand side to string, append rhs. // Conver the left hand side to string, append rhs.
this->data.s = new std::string(this->to_string() + rhs.s()); this->set_s(this->to_string() + rhs.s());
this->type = TYPE_STRING;
} else { } else {
const char *err_msg = "Cannot add non-numeric types."; const char *err_msg = "Cannot add non-numeric types.";
this->throw_if_not_numeric(err_msg); this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
double d = this->as_d() + rhs.as_d(); this->set_d_lite(this->as_d() + rhs.as_d());
this->data.d = d; else
this->type = TYPE_DOUBLE; m_data.i += rhs.i();
} else
this->data.i += rhs.i();
} }
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this; return *this;
@ -389,12 +395,10 @@ namespace client
const char *err_msg = "Cannot subtract non-numeric types."; const char *err_msg = "Cannot subtract non-numeric types.";
this->throw_if_not_numeric(err_msg); this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
double d = this->as_d() - rhs.as_d(); this->set_d_lite(this->as_d() - rhs.as_d());
this->data.d = d; else
this->type = TYPE_DOUBLE; m_data.i -= rhs.i();
} else
this->data.i -= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this; return *this;
} }
@ -404,12 +408,10 @@ namespace client
const char *err_msg = "Cannot multiply with non-numeric type."; const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg); this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
double d = this->as_d() * rhs.as_d(); this->set_d_lite(this->as_d() * rhs.as_d());
this->data.d = d; else
this->type = TYPE_DOUBLE; m_data.i *= rhs.i();
} else
this->data.i *= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this; return *this;
} }
@ -418,14 +420,12 @@ namespace client
{ {
this->throw_if_not_numeric("Cannot divide a non-numeric type."); this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with 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"); rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
double d = this->as_d() / rhs.as_d(); this->set_d_lite(this->as_d() / rhs.as_d());
this->data.d = d; else
this->type = TYPE_DOUBLE; m_data.i /= rhs.i();
} else
this->data.i /= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this; return *this;
} }
@ -434,14 +434,12 @@ namespace client
{ {
this->throw_if_not_numeric("Cannot divide a non-numeric type."); this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with 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"); rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
double d = std::fmod(this->as_d(), rhs.as_d()); this->set_d_lite(std::fmod(this->as_d(), rhs.as_d()));
this->data.d = d; else
this->type = TYPE_DOUBLE; m_data.i %= rhs.i();
} else
this->data.i %= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this; return *this;
} }
@ -453,14 +451,14 @@ namespace client
static void evaluate_boolean(expr &self, bool &out) 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"); self.throw_exception("Not a boolean expression");
out = self.b(); out = self.b();
} }
static void evaluate_boolean_to_string(expr &self, std::string &out) 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"); self.throw_exception("Not a boolean expression");
out = self.b() ? "true" : "false"; out = self.b() ? "true" : "false";
} }
@ -469,31 +467,31 @@ namespace client
static void compare_op(expr &lhs, expr &rhs, char op, bool invert) static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
{ {
bool value = false; bool value = false;
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) && if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) &&
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) { (rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) {
// Both types are numeric. // Both types are numeric.
switch (op) { switch (op) {
case '=': 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()); (std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
break; break;
case '<': 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()); (lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
break; break;
case '>': case '>':
default: 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()); (lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
break; 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. // Both type are bool.
if (op != '=') if (op != '=')
boost::throw_exception(qi::expectation_failure<Iterator>( boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
value = lhs.b() == rhs.b(); 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. // One type is string, the other could be converted to string.
value = (op == '=') ? (lhs.to_string() == rhs.to_string()) : value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
(op == '<') ? (lhs.to_string() < rhs.to_string()) : (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.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
} }
lhs.reset(); lhs.reset();
lhs.type = TYPE_BOOL; lhs.set_b_lite(invert ? ! value : value);
lhs.data.b = invert ? ! value : value;
} }
// Compare operators, store the result into lhs. // Compare operators, store the result into lhs.
static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); } 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(param1);
throw_if_not_numeric(param2); 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.; double d = 0.;
switch (fun) { switch (fun) {
case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break; 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; case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break;
default: param1.throw_exception("Internal error: invalid function"); default: param1.throw_exception("Internal error: invalid function");
} }
param1.data.d = d; param1.set_d_lite(d);
param1.type = TYPE_DOUBLE;
} else { } else {
int i = 0; int i = 0;
switch (fun) { switch (fun) {
@ -544,8 +540,7 @@ namespace client
case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break; case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break;
default: param1.throw_exception("Internal error: invalid function"); default: param1.throw_exception("Internal error: invalid function");
} }
param1.data.i = i; param1.set_i_lite(i);
param1.type = TYPE_INT;
} }
} }
// Store the result into param1. // Store the result into param1.
@ -557,13 +552,10 @@ namespace client
{ {
throw_if_not_numeric(param1); throw_if_not_numeric(param1);
throw_if_not_numeric(param2); throw_if_not_numeric(param2);
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) { if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE)
param1.data.d = std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng); param1.set_d_lite(std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng));
param1.type = TYPE_DOUBLE; else
} else { param1.set_i_lite(std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng));
param1.data.i = std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng);
param1.type = TYPE_INT;
}
} }
// Store the result into param1. // Store the result into param1.
@ -572,10 +564,10 @@ namespace client
static void digits(expr &param1, expr &param2, expr &param3) static void digits(expr &param1, expr &param2, expr &param3)
{ {
throw_if_not_numeric(param1); throw_if_not_numeric(param1);
if (param2.type != TYPE_INT) if (param2.type() != TYPE_INT)
param2.throw_exception("digits: second parameter must be integer"); param2.throw_exception("digits: second parameter must be integer");
bool has_decimals = param3.type != TYPE_EMPTY; bool has_decimals = param3.type() != TYPE_EMPTY;
if (has_decimals && param3.type != TYPE_INT) if (has_decimals && param3.type() != TYPE_INT)
param3.throw_exception("digits: third parameter must be integer"); param3.throw_exception("digits: third parameter must be integer");
char buf[256]; char buf[256];
@ -593,7 +585,7 @@ namespace client
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op) static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
{ {
const std::string *subject = nullptr; 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. // One type is string, the other could be converted to string.
subject = &lhs.s(); subject = &lhs.s();
} else { } else {
@ -604,9 +596,7 @@ namespace client
bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern)); bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
if (op == '!') if (op == '!')
result = ! result; result = ! result;
lhs.reset(); lhs.set_b(result);
lhs.type = TYPE_BOOL;
lhs.data.b = result;
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) { } catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
// Syntax error in the regular expression // Syntax error in the regular expression
boost::throw_exception(qi::expectation_failure<Iterator>( boost::throw_exception(qi::expectation_failure<Iterator>(
@ -620,21 +610,20 @@ namespace client
static void logical_op(expr &lhs, expr &rhs, char op) static void logical_op(expr &lhs, expr &rhs, char op)
{ {
bool value = false; 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()); value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
} else { } else {
boost::throw_exception(qi::expectation_failure<Iterator>( 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.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
} }
lhs.type = TYPE_BOOL; lhs.set_b_lite(value);
lhs.data.b = value;
} }
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); } 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 logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) 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"); lhs.throw_exception("Not a boolean expression");
if (lhs.b()) if (lhs.b())
lhs = std::move(rhs1); lhs = std::move(rhs1);
@ -658,9 +647,25 @@ namespace client
void throw_if_not_numeric(const char *message) const 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); 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> template<typename ITERATOR>
@ -668,7 +673,7 @@ namespace client
{ {
typedef expr<ITERATOR> Expr; typedef expr<ITERATOR> Expr;
os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - "; 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_EMPTY: os << "empty"; break;
case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break; case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; 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 coInt: output.set_i(opt.opt->getInt()); break;
case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break; case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
case coPercent: output.set_d(opt.opt->getFloat()); break; case coPercent: output.set_d(opt.opt->getFloat()); break;
case coEnum:
case coPoint: output.set_s(opt.opt->serialize()); break; case coPoint: output.set_s(opt.opt->serialize()); break;
case coBool: output.set_b(opt.opt->getBool()); break; case coBool: output.set_b(opt.opt->getBool()); break;
case coFloatOrPercent: case coFloatOrPercent:
@ -869,18 +875,51 @@ namespace client
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break; 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 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 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: default:
ctx->throw_exception("Unknown vector variable type", opt.it_range); ctx->throw_exception("Unknown vector variable type", opt.it_range);
} }
output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end); 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 // Verify that the expression returns an integer, which may be used
// to address a vector. // to address a vector.
template <typename Iterator> template <typename Iterator>
static void evaluate_index(expr<Iterator> &expr_index, int &output) 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."); expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
output = expr_index.i(); output = expr_index.i();
} }
@ -973,6 +1012,7 @@ namespace client
{ "unary_expression", "Expecting an expression." }, { "unary_expression", "Expecting an expression." },
{ "optional_parameter", "Expecting a closing brace or an optional parameter." }, { "optional_parameter", "Expecting a closing brace or an optional parameter." },
{ "scalar_variable_reference", "Expecting a scalar variable reference."}, { "scalar_variable_reference", "Expecting a scalar variable reference."},
{ "is_nil_test", "Expecting a scalar variable reference."},
{ "variable_reference", "Expecting a variable reference."}, { "variable_reference", "Expecting a variable reference."},
{ "regular_expression", "Expecting a regular expression."} { "regular_expression", "Expecting a regular expression."}
}; };
@ -1259,6 +1299,7 @@ namespace client
[ px::bind(&expr<Iterator>::template digits<true>, _val, _2, _3) ] [ px::bind(&expr<Iterator>::template digits<true>, _val, _2, _3) ]
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _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) ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
| (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
| (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _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) ]; [ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ];
variable_reference.name("variable reference"); 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 = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']];
regular_expression.name("regular_expression"); regular_expression.name("regular_expression");
@ -1295,6 +1345,7 @@ namespace client
("zdigits") ("zdigits")
("if") ("if")
("int") ("int")
("is_nil")
//("inf") //("inf")
("else") ("else")
("elsif") ("elsif")
@ -1329,6 +1380,7 @@ namespace client
debug(optional_parameter); debug(optional_parameter);
debug(scalar_variable_reference); debug(scalar_variable_reference);
debug(variable_reference); debug(variable_reference);
debug(is_nil_test);
debug(regular_expression); 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; 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. // Rule to translate an identifier to a ConfigOption, or to fail.
qi::rule<Iterator, OptWithPos<Iterator>(const MyContext*), spirit_encoding::space_type> variable_reference; 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<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; // qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit_encoding::space_type> switch_output;

View File

@ -5,6 +5,7 @@
#include <map> #include <map>
#include <random> #include <random>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
@ -38,6 +39,8 @@ public:
// Add new ConfigOption values to m_config. // 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, 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, 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, unsigned int value) { this->set(key, int(value)); }
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); } void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }

View File

@ -84,18 +84,28 @@ Points collect_duplicates(Points pts /* Copy */)
return duplicits; return duplicits;
} }
template<bool IncludeBoundary>
BoundingBox get_extents(const Points &pts) 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 get_extents(const std::vector<Points> &pts)
{ {
BoundingBox bbox; BoundingBox bbox;
for (const Points &p : pts) for (const Points &p : pts)
bbox.merge(get_extents(p)); bbox.merge(get_extents<IncludeBoundary>(p));
return bbox; 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) BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
{ {

View File

@ -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>(); 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); 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); 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); BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
int nearest_point_index(const Points &points, const Point &pt); int nearest_point_index(const Points &points, const Point &pt);

View File

@ -267,13 +267,17 @@ ThickLines ThickPolyline::thicklines() const
// Removes the given distance from the end of the ThickPolyline // Removes the given distance from the end of the ThickPolyline
void ThickPolyline::clip_end(double distance) void ThickPolyline::clip_end(double distance)
{ {
if (! this->empty()) {
assert(this->width.size() == (this->points.size() - 1) * 2);
while (distance > 0) { while (distance > 0) {
Vec2d last_point = this->last_point().cast<double>(); Vec2d last_point = this->last_point().cast<double>();
coordf_t last_width = this->width.back();
this->points.pop_back(); this->points.pop_back();
this->width.pop_back(); if (this->points.empty()) {
if (this->points.empty()) assert(this->width.empty());
break; break;
}
coordf_t last_width = this->width.back();
this->width.pop_back();
Vec2d vec = this->last_point().cast<double>() - last_point; Vec2d vec = this->last_point().cast<double>() - last_point;
coordf_t width_diff = this->width.back() - last_width; coordf_t width_diff = this->width.back() - last_width;
@ -289,7 +293,21 @@ void ThickPolyline::clip_end(double distance)
distance -= std::sqrt(vec_length_sqr); distance -= std::sqrt(vec_length_sqr);
} }
assert(this->width.size() == (this->points.size() - 1) * 2); }
assert(this->points.empty() ? this->width.empty() : this->width.size() == (this->points.size() - 1) * 2);
}
void ThickPolyline::start_at_index(int index)
{
assert(index >= 0 && index < this->points.size());
assert(this->points.front() == this->points.back() && this->width.front() == this->width.back());
if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) {
this->points.pop_back();
assert(this->points.size() * 2 == this->width.size());
std::rotate(this->points.begin(), this->points.begin() + index, this->points.end());
std::rotate(this->width.begin(), this->width.begin() + 2 * index, this->width.end());
this->points.emplace_back(this->points.front());
}
} }
double Polyline3::length() const double Polyline3::length() const

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