Merge branch 'master_27x'
@ -80,6 +80,27 @@ This is set in the property list file
|
||||
|
||||
To remove the limitation, simply delete the key `MinimumSDKVersion` from that file.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `CMath::CMath` target not found
|
||||
|
||||
At the moment (20.2.2024) PrusaSlicer cannot be built with CMake 3.28+. Use [CMake 3.27](https://github.com/Kitware/CMake/releases/tag/v3.27.9) instead.
|
||||
If you install the CMake application from [universal DMG](https://github.com/Kitware/CMake/releases/download/v3.27.9/cmake-3.27.9-macos-universal.dmg), you can invoke the CMake like this:
|
||||
|
||||
```
|
||||
/Applications/CMake.app/Contents/bin/cmake
|
||||
```
|
||||
|
||||
### Running `cmake -GXCode` fails with `No CMAKE_CXX_COMPILER could be found.`
|
||||
|
||||
- If XCode command line tools wasn't already installed, run:
|
||||
```
|
||||
sudo xcode-select --install
|
||||
```
|
||||
- If XCode command line tools are already installed, run:
|
||||
```
|
||||
sudo xcode-select --reset
|
||||
```
|
||||
|
||||
# TL; DR
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
min_slic3r_version = 2.6.0
|
||||
0.0.8 Support for the X3 and X4 series by porting settings from ArtillerySlicer V3.0.
|
||||
min_slic3r_version = 2.4.1-alpha0
|
||||
0.0.7 Added Eolas Prints filaments.
|
||||
0.0.6 Reduced retract_length for direct extruders
|
||||
|
@ -12,7 +12,7 @@
|
||||
name = Artillery
|
||||
# Configuration version of this file. Config file will only be installed, if the config_version differs.
|
||||
# This means, the server may force the PrusaSlicer configuration to be downgraded.
|
||||
config_version = 0.0.7
|
||||
config_version = 0.0.8
|
||||
# Where to get the updates from?
|
||||
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Artillery/
|
||||
# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
|
||||
@ -49,6 +49,46 @@ bed_model = bed-hornet.stl
|
||||
bed_texture = bed-hornet.png
|
||||
default_materials = Generic PLA @Artillery; Generic ABS @Artillery; Generic PETG @Artillery; Generic TPU @Artillery
|
||||
|
||||
[printer_model:X3 Plus]
|
||||
name = Artillery Sidewinder X3 Plus
|
||||
variants = 0.4
|
||||
technology = FFF
|
||||
bed_model = bed-x3-plus.stl
|
||||
bed_texture = bed-x1.png
|
||||
default_materials = Artillery ABS @X3; Artillery PETG @X3; Artillery PLA @X3; Artillery PLA HS @X3; Artillery PLA Silk @X3; Artillery TPU @X3
|
||||
|
||||
[printer_model:X3 Pro]
|
||||
name = Artillery Sidewinder X3 Pro
|
||||
variants = 0.4
|
||||
technology = FFF
|
||||
bed_model = bed-x3-pro.stl
|
||||
bed_texture = bed-x1.png
|
||||
default_materials = Artillery ABS @X3; Artillery PETG @X3; Artillery PLA @X3; Artillery PLA HS @X3; Artillery PLA Silk @X3; Artillery TPU @X3
|
||||
|
||||
[printer_model:X4 Plus]
|
||||
name = Artillery Sidewinder X4 Plus
|
||||
variants = 0.4
|
||||
technology = FFF
|
||||
bed_model = bed-x4-plus.stl
|
||||
bed_texture = bed-x1.png
|
||||
default_materials = Artillery ABS @X4; Artillery PETG @X4; Artillery PLA & PLA HS @X4; Artillery PLA+ @X4; Artillery PLA Silk @X4; Artillery TPU @X4
|
||||
|
||||
[printer_model:X4 Pro]
|
||||
name = Artillery Sidewinder X4 Pro
|
||||
variants = 0.4
|
||||
technology = FFF
|
||||
bed_model = bed-x4-pro.stl
|
||||
bed_texture = bed-x1.png
|
||||
default_materials = Artillery ABS @X4; Artillery PETG @X4; Artillery PLA & PLA HS @X4; Artillery PLA+ @X4; Artillery PLA Silk @X4; Artillery TPU @X4
|
||||
|
||||
[printer_model:X4 Max]
|
||||
name = Artillery Sidewinder X4 Max
|
||||
variants = 0.4
|
||||
technology = FFF
|
||||
bed_model = bed-x4-max.stl
|
||||
bed_texture = bed-x1.png
|
||||
default_materials = Artillery ABS @X4; Artillery PETG @X4; Artillery PLA & PLA HS @X4; Artillery PLA+ @X4; Artillery PLA Silk @X4; Artillery TPU @X4
|
||||
|
||||
# Common printer preset
|
||||
[printer:*common*]
|
||||
before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0
|
||||
@ -130,6 +170,100 @@ min_layer_height = 0.04
|
||||
printer_variant = 0.4
|
||||
default_print_profile = 0.20mm NORMAL @Artillery
|
||||
|
||||
[printer:*common_X3_X4*]
|
||||
autoemit_temperature_commands = 1
|
||||
between_objects_gcode =
|
||||
color_change_gcode = M600
|
||||
cooling_tube_length = 5
|
||||
cooling_tube_retraction = 91.5
|
||||
deretract_speed = 0
|
||||
extra_loading_move = -2
|
||||
extruder_colour = #FFFF00
|
||||
extruder_offset = 0x0
|
||||
high_current_on_filament_swap = 0
|
||||
host_type = prusalink
|
||||
inherits =
|
||||
machine_limits_usage = emit_to_gcode
|
||||
machine_max_acceleration_e = 10000,5000
|
||||
machine_max_acceleration_extruding = 10000,1250
|
||||
machine_max_acceleration_retracting = 10000,1250
|
||||
machine_max_jerk_z = 0.2,0.4
|
||||
machine_min_extruding_rate = 0,0
|
||||
machine_min_travel_rate = 0,0
|
||||
max_layer_height = 0.25
|
||||
min_layer_height = 0.07
|
||||
nozzle_diameter = 0.4
|
||||
parking_pos_retraction = 92
|
||||
pause_print_gcode = M601
|
||||
print_host =
|
||||
printer_settings_id =
|
||||
printer_technology = FFF
|
||||
printer_variant = 0.4
|
||||
printer_vendor =
|
||||
printhost_apikey =
|
||||
printhost_cafile =
|
||||
remaining_times = 0
|
||||
retract_before_wipe = 0%
|
||||
retract_layer_change = 0
|
||||
retract_length = 0.8
|
||||
retract_length_toolchange = 10
|
||||
retract_lift = 0.6
|
||||
retract_lift_above = 0
|
||||
retract_lift_below = 380
|
||||
retract_restart_extra = 0
|
||||
retract_restart_extra_toolchange = 0
|
||||
silent_mode = 1
|
||||
single_extruder_multi_material = 0
|
||||
template_custom_gcode =
|
||||
thumbnails = 256x256
|
||||
thumbnails_format = PNG
|
||||
toolchange_gcode =
|
||||
use_firmware_retraction = 0
|
||||
use_relative_e_distances = 1
|
||||
use_volumetric_e = 0
|
||||
variable_layer_height = 1
|
||||
wipe = 0
|
||||
z_offset = 0
|
||||
start_gcode = ; Initial setups\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM220 S100 ; reset speed factor to 100%\nM221 S100 ; reset extrusion rate to 100%\n\n; Set the heating\nM140 S[first_layer_bed_temperature] ; start bed heating\n\n; Home\nG1 Z3 F3000 ; move z up little to prevent scratching of surface\nG28 ; home all axes\n\n; Auto bed Leveling\n@BEDLEVELVISUALIZER\nG29 ; perform bed leveling\nM420 S1 Z3 ; reload and fade mesh bed leveling until it reach 3mm Z\n\n; Wait for final heating\nG1 Z10 F3000 ; move z up little to prevent scratching of surface\nM190 S[first_layer_bed_temperature] ; wait for the bed to heat up\nM109 S[first_layer_temperature] ; wait for the nozzle to heat up\n\n; Сlean nozzle\nG92 E0 ; reset extruder\nG1 X150 Y{print_bed_size[1]-0.25} Z0.25 F5000.0 ; move to start position\nG1 X100 Y{print_bed_size[1]-0.25} Z0.25 F1500.0 E30 ; draw the first line\nG1 Z10 F3000 ; move z up little to prevent scratching\nG12 C2 ; clean nozzle\nG1 X25 Y{print_bed_size[1]-0.5} Z0.25 F5000.0 ; move to side a little\nG1 X90 Y{print_bed_size[1]-0.5} Z0.25 F1500.0 E15 ; draw the second line\nG92 E0 ; reset extruder\n\nM221 S{if layer_height<0.075}100{else}95{endif}\n
|
||||
|
||||
[printer:*common_X3*]
|
||||
inherits = *common_X3_X4*
|
||||
default_filament_profile = Artillery PLA @X3
|
||||
default_print_profile = X3 Default (0.20mm) @Artillery
|
||||
before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0
|
||||
layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z]
|
||||
end_gcode = ; After G-Code\nG4 ; wait\nG92 E0 ; prepare to retract\nG1 E-1.5 F3000; retract to avoid stringing\n\n; Anti-stringing end wiggle\nG91 ; use relative coordinates\nG1 X1 Y1 F1200\n\n; Raise nozzle and present bed\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+120, max_print_height)}{endif} ; move print head up\nG90 ; use absolute coordinates\n\n; Reset print setting overrides\nM200 D0 ; disable volumetric e\nM220 S100 ; reset speed factor to 100%\nM221 S100 ; reset extrusion rate to 100%\n\n; Shut down printer\nM106 S0 ; turn-off fan\nM104 S0 ; turn-off hotend\nM140 S0 ; turn-off bed\nM150 P0 ; turn off led\nM84 ; disable motors\n
|
||||
gcode_flavor = marlin2
|
||||
machine_max_acceleration_travel = 3000,1250
|
||||
machine_max_acceleration_x = 3000,1000
|
||||
machine_max_acceleration_y = 3000,1000
|
||||
machine_max_acceleration_z = 500,200
|
||||
machine_max_feedrate_e = 120,120
|
||||
machine_max_feedrate_x = 500,200
|
||||
machine_max_feedrate_y = 500,200
|
||||
machine_max_feedrate_z = 12,12
|
||||
machine_max_jerk_e = 2.5,2.5
|
||||
machine_max_jerk_x = 10,10
|
||||
machine_max_jerk_y = 10,10
|
||||
retract_before_travel = 2
|
||||
retract_speed = 35
|
||||
|
||||
[printer:*common_X4*]
|
||||
inherits = *common_X3_X4*
|
||||
default_filament_profile = Artillery PLA & PLA HS @X4
|
||||
before_layer_gcode =
|
||||
layer_gcode =
|
||||
gcode_flavor = klipper
|
||||
machine_max_acceleration_travel = 1500,1250
|
||||
machine_max_acceleration_x = 10000,1000
|
||||
machine_max_acceleration_y = 10000,1000
|
||||
machine_max_acceleration_z = 200,200
|
||||
machine_max_feedrate_x = 500,500
|
||||
machine_max_feedrate_y = 500,500
|
||||
printer_notes =
|
||||
retract_before_travel = 1.5
|
||||
retract_speed = 35
|
||||
|
||||
[printer:Artillery Sidewinder X1]
|
||||
inherits = *common_STOCK_FW*
|
||||
renamed_from = "Sidewinder X1"
|
||||
@ -175,8 +309,61 @@ max_print_height = 250
|
||||
machine_limits_usage = time_estimate_only
|
||||
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Artillery\nPRINTER_MODEL_Hornet\nPRINTER_HAS_Bowden
|
||||
|
||||
[printer:Artillery Sidewinder X3 Plus]
|
||||
inherits = *common_X3*
|
||||
printer_model = X3 Plus
|
||||
bed_shape = 0x0,300x0,300x300,0x300
|
||||
max_print_height = 400
|
||||
printer_notes =
|
||||
|
||||
[printer:Artillery Sidewinder X3 Pro]
|
||||
inherits = *common_X3*
|
||||
printer_model = X3 Pro
|
||||
bed_shape = 0x0,240x0,240x240,0x240
|
||||
max_print_height = 260
|
||||
printer_notes =
|
||||
|
||||
[printer:Artillery Sidewinder X4 Plus]
|
||||
inherits = *common_X4*
|
||||
printer_model = X4 Plus
|
||||
bed_shape = 0x0,300x0,300x300,0x300
|
||||
default_print_profile = ArtillerySlicer X4 Plus PETG & PLA & PLA+ @Artillery
|
||||
machine_max_feedrate_e = 100,120
|
||||
machine_max_feedrate_z = 13,12
|
||||
machine_max_jerk_e = 10,2.5
|
||||
machine_max_jerk_x = 20,10
|
||||
machine_max_jerk_y = 20,10
|
||||
max_print_height = 400
|
||||
end_gcode = G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z1 ;Raise Z more\nG90 ;Absolute positionning\nG1 X5 Y280 F3000 ;Wipe out\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z
|
||||
|
||||
[printer:Artillery Sidewinder X4 Pro]
|
||||
inherits = *common_X4*
|
||||
printer_model = X4 Pro
|
||||
bed_shape = 0x0,240x0,240x240,0x240
|
||||
default_print_profile = ArtillerySlicer X4 Pro PETG & PLA & PLA+ @Artillery
|
||||
machine_max_feedrate_e = 120,120
|
||||
machine_max_feedrate_z = 13,13
|
||||
machine_max_jerk_e = 2.5,2.5
|
||||
machine_max_jerk_x = 10,10
|
||||
machine_max_jerk_y = 10,10
|
||||
max_print_height = 260
|
||||
end_gcode = G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z1 ;Raise Z more\nG90 ;Absolute positionning\nG1 X5 Y220 F3000 ;Wipe out\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z
|
||||
|
||||
[printer:Artillery Sidewinder X4 Max]
|
||||
inherits = *common_X4*
|
||||
printer_model = X4 Max
|
||||
bed_shape = 0x0,500x0,500x500,0x500
|
||||
default_print_profile = ArtillerySlicer X4 Max PETG & PLA & PLA+ @Artillery
|
||||
machine_max_feedrate_e = 100,120
|
||||
machine_max_feedrate_z = 13,12
|
||||
machine_max_jerk_e = 10,2.5
|
||||
machine_max_jerk_x = 20,10
|
||||
machine_max_jerk_y = 20,10
|
||||
max_print_height = 500
|
||||
end_gcode = G91 ;Relative positioning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z1 ;Raise Z more\nG90 ;Absolute positionning\nG1 X5 Y480 F3000;\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z
|
||||
|
||||
###########
|
||||
## PRINT ##
|
||||
## PRINT ##'
|
||||
###########
|
||||
# Common print preset
|
||||
[print:*common*]
|
||||
@ -400,6 +587,263 @@ compatible_printers_condition = printer_model=~/(X1|Genius|Hornet).*/ and nozzle
|
||||
inherits = *0.28mm*
|
||||
compatible_printers_condition = printer_model=~/(X1|Genius|Hornet).*/ and nozzle_diameter[0]==0.4
|
||||
|
||||
[print:*common_x3_x4*]
|
||||
avoid_crossing_curled_overhangs = 0
|
||||
avoid_crossing_perimeters_max_detour = 0
|
||||
bottom_fill_pattern = monotonic
|
||||
bottom_solid_layers = 3
|
||||
bottom_solid_min_thickness = 0
|
||||
bridge_angle = 0
|
||||
bridge_flow_ratio = 1
|
||||
bridge_speed = 30
|
||||
brim_separation = 0
|
||||
brim_type = outer_only
|
||||
brim_width = 0
|
||||
compatible_printers =
|
||||
compatible_printers_condition =
|
||||
complete_objects = 0
|
||||
dont_support_bridges = 1
|
||||
draft_shield = disabled
|
||||
elefant_foot_compensation = 0
|
||||
external_perimeters_first = 0
|
||||
extra_perimeters = 1
|
||||
extra_perimeters_on_overhangs = 0
|
||||
extruder_clearance_height = 20
|
||||
extruder_clearance_radius = 20
|
||||
extrusion_width = 0.45
|
||||
fill_angle = 45
|
||||
fill_pattern = alignedrectilinear
|
||||
first_layer_acceleration_over_raft = 0
|
||||
first_layer_height = 0.25
|
||||
first_layer_speed = 30
|
||||
first_layer_speed_over_raft = 30
|
||||
fuzzy_skin = none
|
||||
fuzzy_skin_point_dist = 0.8
|
||||
fuzzy_skin_thickness = 0.3
|
||||
gap_fill_enabled = 1
|
||||
gap_fill_speed = 20
|
||||
gcode_comments = 0
|
||||
gcode_label_objects = 0
|
||||
gcode_resolution = 0.0125
|
||||
gcode_substitutions =
|
||||
infill_acceleration = 0
|
||||
infill_anchor = 600%
|
||||
infill_anchor_max = 50
|
||||
infill_every_layers = 1
|
||||
infill_extruder = 1
|
||||
infill_extrusion_width = 0.45
|
||||
infill_first = 0
|
||||
inherits =
|
||||
interface_shells = 0
|
||||
ironing = 0
|
||||
ironing_flowrate = 15%
|
||||
ironing_spacing = 0.1
|
||||
ironing_speed = 15
|
||||
ironing_type = top
|
||||
layer_height = 0.2
|
||||
max_volumetric_extrusion_rate_slope_negative = 0
|
||||
max_volumetric_extrusion_rate_slope_positive = 0
|
||||
max_volumetric_speed = 0
|
||||
min_bead_width = 85%
|
||||
min_feature_size = 25%
|
||||
min_skirt_length = 0
|
||||
mmu_segmented_region_interlocking_depth = 0
|
||||
mmu_segmented_region_max_width = 0
|
||||
notes =
|
||||
only_retract_when_crossing_perimeters = 0
|
||||
ooze_prevention = 0
|
||||
output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode
|
||||
perimeter_acceleration = 1500
|
||||
perimeter_speed = 120
|
||||
seam_position = rear
|
||||
skirts = 0
|
||||
skirt_distance = 0
|
||||
small_perimeter_speed = 25
|
||||
solid_infill_extrusion_width = 0.45
|
||||
support_material_extrusion_width = 0.35
|
||||
top_infill_extrusion_width = 0.4
|
||||
travel_acceleration = 3000
|
||||
travel_speed = 250
|
||||
external_perimeter_extrusion_width = 0.4
|
||||
|
||||
[print:*common_x3*]
|
||||
inherits = *common_x3_x4*
|
||||
compatible_printers_condition = printer_model=~/(X3).*/ and nozzle_diameter[0]==0.4
|
||||
avoid_crossing_perimeters = 0
|
||||
bridge_acceleration = 1000
|
||||
default_acceleration = 3000
|
||||
enable_dynamic_overhang_speeds = 1
|
||||
external_perimeter_acceleration = 1500
|
||||
external_perimeter_extrusion_width = 0.45
|
||||
fill_density = 5%
|
||||
first_layer_acceleration = 0
|
||||
first_layer_extrusion_width = 0.42
|
||||
infill_overlap = 25%
|
||||
max_print_speed = 400
|
||||
solid_infill_speed = 150
|
||||
top_solid_infill_speed = 150
|
||||
|
||||
[print:X3 Default (0.20mm) @Artillery]
|
||||
inherits = *common_x3*
|
||||
first_layer_height = 0.2
|
||||
perimeters = 2
|
||||
seam_position = random
|
||||
infill_anchor = 400%
|
||||
small_perimeter_speed = 50%
|
||||
external_perimeter_speed = 120
|
||||
infill_speed = 150
|
||||
support_material_speed = 200
|
||||
bridge_speed = 70
|
||||
gap_fill_speed = 120
|
||||
overhang_speed_0 = 80%
|
||||
overhang_speed_1 = 50
|
||||
overhang_speed_2 = 30
|
||||
overhang_speed_3 = 10
|
||||
travel_speed = 350
|
||||
first_layer_speed = 50
|
||||
first_layer_acceleration = 1000
|
||||
extrusion_width = 0.4
|
||||
first_layer_extrusion_width = 0.5
|
||||
perimeter_extrusion_width = 0.4
|
||||
infill_overlap = 35%
|
||||
resolution = 0.012
|
||||
gcode_resolution = 0.012
|
||||
|
||||
[print:ArtillerySlicer X3 TPU @Artillery]
|
||||
inherits = *common_x3*
|
||||
external_perimeter_speed = 30
|
||||
infill_speed = 30
|
||||
|
||||
[print:*common_x4*]
|
||||
inherits = *common_x3_x4*
|
||||
bridge_acceleration = 3000
|
||||
external_perimeter_acceleration = 3000
|
||||
first_layer_acceleration = 3000
|
||||
first_layer_extrusion_width = 0.5
|
||||
infill_overlap = 75%
|
||||
|
||||
[print:*common_x4_max*]
|
||||
inherits = *common_x4*
|
||||
default_acceleration = 3000
|
||||
compatible_printers_condition = printer_model=~/(X4 Max).*/ and nozzle_diameter[0]==0.4
|
||||
|
||||
[print:*common_x4_plus*]
|
||||
inherits = *common_x4*
|
||||
default_acceleration = 5000
|
||||
compatible_printers_condition = printer_model=~/(X4 Plus).*/ and nozzle_diameter[0]==0.4
|
||||
|
||||
[print:*common_x4_pro*]
|
||||
inherits = *common_x4*
|
||||
default_acceleration = 10000
|
||||
compatible_printers_condition = printer_model=~/(X4 Pro).*/ and nozzle_diameter[0]==0.4
|
||||
|
||||
[print:*common_x4_ABS_PETG_PLA*]
|
||||
external_perimeter_speed = 50%
|
||||
max_print_speed = 300
|
||||
|
||||
[print:ArtillerySlicer X4 Max ABS @Artillery]
|
||||
inherits = *common_x4_max*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 100
|
||||
solid_infill_speed = 100
|
||||
top_solid_infill_speed = 100
|
||||
|
||||
[print:ArtillerySlicer X4 Plus ABS @Artillery]
|
||||
inherits = *common_x4_plus*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 100
|
||||
solid_infill_speed = 150
|
||||
top_solid_infill_speed = 150
|
||||
|
||||
[print:ArtillerySlicer X4 Pro ABS @Artillery]
|
||||
inherits = *common_x4_pro*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 100
|
||||
solid_infill_speed = 200
|
||||
top_solid_infill_speed = 200
|
||||
|
||||
[print:ArtillerySlicer X4 Max PETG & PLA & PLA+ @Artillery]
|
||||
inherits = *common_x4_max*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 100
|
||||
top_solid_infill_speed = 100
|
||||
|
||||
[print:ArtillerySlicer X4 Plus PETG & PLA & PLA+ @Artillery]
|
||||
inherits = *common_x4_plus*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 150
|
||||
top_solid_infill_speed = 150
|
||||
|
||||
[print:ArtillerySlicer X4 Pro PETG & PLA & PLA+ @Artillery]
|
||||
inherits = *common_x4_pro*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 300
|
||||
solid_infill_speed = 200
|
||||
top_solid_infill_speed = 200
|
||||
|
||||
[print:ArtillerySlicer X4 Max PLA HS @Artillery]
|
||||
inherits = *common_x4_max*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 1
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 100
|
||||
top_solid_infill_speed = 100
|
||||
|
||||
[print:ArtillerySlicer X4 Plus PLA HS @Artillery]
|
||||
inherits = *common_x4_plus*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 1
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 150
|
||||
top_solid_infill_speed = 150
|
||||
|
||||
[print:ArtillerySlicer X4 Pro PLA HS @Artillery]
|
||||
inherits = *common_x4_pro*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 1
|
||||
infill_speed = 300
|
||||
solid_infill_speed = 200
|
||||
top_solid_infill_speed = 200
|
||||
|
||||
[print:ArtillerySlicer X4 Max PLA Silk @Artillery]
|
||||
inherits = *common_x4_max*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 100
|
||||
top_solid_infill_speed = 100
|
||||
|
||||
[print:ArtillerySlicer X4 Plus PLA Silk @Artillery]
|
||||
inherits = *common_x4_plus*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 150
|
||||
top_solid_infill_speed = 150
|
||||
|
||||
[print:ArtillerySlicer X4 Pro PLA Silk @Artillery]
|
||||
inherits = *common_x4_pro*; *common_x4_ABS_PETG_PLA*
|
||||
avoid_crossing_perimeters = 0
|
||||
infill_speed = 150
|
||||
solid_infill_speed = 200
|
||||
top_solid_infill_speed = 200
|
||||
|
||||
[print:*common_x4_TPU*]
|
||||
avoid_crossing_perimeters = 0
|
||||
enable_dynamic_overhang_speeds = 0
|
||||
external_perimeter_speed = 30
|
||||
fill_density = 15%
|
||||
infill_speed = 30
|
||||
max_print_speed = 60
|
||||
solid_infill_speed = 30
|
||||
top_solid_infill_speed = 30
|
||||
|
||||
[print:ArtillerySlicer X4 Max TPU @Artillery]
|
||||
inherits = *common_x4_max*; *common_x4_TPU*
|
||||
|
||||
[print:ArtillerySlicer X4 Plus TPU @Artillery]
|
||||
inherits = *common_x4_plus*; *common_x4_TPU*
|
||||
|
||||
[print:ArtillerySlicer X4 Pro TPU @Artillery]
|
||||
inherits = *common_x4_pro*; *common_x4_TPU*
|
||||
|
||||
###############
|
||||
## FILAMENTS ##
|
||||
###############
|
||||
@ -419,6 +863,80 @@ min_print_speed = 15
|
||||
slowdown_below_layer_time = 15
|
||||
compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_Artillery.*/
|
||||
|
||||
|
||||
# Common filament preset
|
||||
[filament:*common_x3_x4*]
|
||||
bridge_fan_speed = 100
|
||||
compatible_printers =
|
||||
compatible_printers_condition =
|
||||
compatible_prints_condition =
|
||||
cooling = 1
|
||||
enable_dynamic_fan_speeds = 0
|
||||
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
|
||||
extrusion_multiplier = 0.98
|
||||
fan_always_on = 1
|
||||
fan_below_layer_time = 60
|
||||
filament_colour = #E2E2E2
|
||||
filament_cooling_final_speed = 3.4
|
||||
filament_cooling_initial_speed = 2.2
|
||||
filament_cooling_moves = 4
|
||||
filament_cost = 0
|
||||
filament_deretract_speed = nil
|
||||
filament_diameter = 1.75
|
||||
filament_load_time = 0
|
||||
filament_loading_speed = 28
|
||||
filament_loading_speed_start = 3
|
||||
filament_minimal_purge_on_wipe_tower = 15
|
||||
filament_multitool_ramming = 0
|
||||
filament_multitool_ramming_flow = 10
|
||||
filament_multitool_ramming_volume = 10
|
||||
filament_notes = ""
|
||||
filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6"
|
||||
filament_retract_before_wipe = nil
|
||||
filament_retract_layer_change = nil
|
||||
filament_retract_length_toolchange = nil
|
||||
filament_retract_lift = 0.1
|
||||
filament_retract_lift_above = nil
|
||||
filament_retract_lift_below = nil
|
||||
filament_retract_restart_extra = nil
|
||||
filament_retract_restart_extra_toolchange = nil
|
||||
filament_settings_id = ""
|
||||
filament_soluble = 0
|
||||
filament_spool_weight = 0
|
||||
filament_toolchange_delay = 0
|
||||
filament_unload_time = 0
|
||||
filament_unloading_speed = 90
|
||||
filament_unloading_speed_start = 100
|
||||
filament_vendor = Artillery
|
||||
full_fan_speed_layer = 0
|
||||
idle_temperature = nil
|
||||
inherits =
|
||||
max_fan_speed = 100
|
||||
min_fan_speed = 100
|
||||
min_print_speed = 10
|
||||
overhang_fan_speed_0 = 0
|
||||
overhang_fan_speed_1 = 0
|
||||
overhang_fan_speed_2 = 0
|
||||
overhang_fan_speed_3 = 0
|
||||
slowdown_below_layer_time = 5
|
||||
start_filament_gcode = ; Filament gcode
|
||||
|
||||
[filament:*common_x3*]
|
||||
inherits = *common_x3_x4*
|
||||
disable_fan_first_layers = 2
|
||||
filament_max_volumetric_speed = 0
|
||||
filament_retract_before_travel = 1
|
||||
filament_wipe = nil
|
||||
compatible_printers_condition = printer_model=~/(X3).*/ and nozzle_diameter[0]==0.4
|
||||
|
||||
[filament:*common_x4*]
|
||||
inherits = *common_x3_x4*
|
||||
disable_fan_first_layers = 1
|
||||
filament_retract_before_travel = 2
|
||||
filament_retract_speed = 40
|
||||
filament_wipe = 1
|
||||
compatible_printers_condition = printer_model=~/(X4).*/ and nozzle_diameter[0]==0.4
|
||||
|
||||
[filament:*PLA*]
|
||||
inherits = *common*
|
||||
bed_temperature = 60
|
||||
@ -573,4 +1091,148 @@ temperature = 230
|
||||
first_layer_bed_temperature = 30
|
||||
bed_temperature = 30
|
||||
filament_retract_length = 0
|
||||
extrusion_multiplier = 1.16
|
||||
extrusion_multiplier = 1.16
|
||||
|
||||
[filament:Artillery ABS @X3]
|
||||
inherits = *common_x3*
|
||||
bed_temperature = 100
|
||||
compatible_prints = X3 Default (0.20mm) @Artillery
|
||||
filament_density = 1.04
|
||||
filament_retract_length = 1
|
||||
filament_retract_speed = 40
|
||||
filament_type = ABS
|
||||
first_layer_bed_temperature = 100
|
||||
first_layer_temperature = 260
|
||||
temperature = 250
|
||||
|
||||
[filament:Artillery PETG @X3]
|
||||
inherits = *common_x3*
|
||||
bed_temperature = 90
|
||||
compatible_prints = X3 Default (0.20mm) @Artillery
|
||||
filament_density = 1.26
|
||||
filament_retract_length = 1.1
|
||||
filament_retract_speed = 40
|
||||
filament_type = PETG
|
||||
first_layer_bed_temperature = 90
|
||||
first_layer_temperature = 250
|
||||
temperature = 245
|
||||
|
||||
[filament:Artillery PLA @X3]
|
||||
inherits = *common_x3*
|
||||
bed_temperature = 60
|
||||
compatible_prints = X3 Default (0.20mm) @Artillery
|
||||
filament_density = 1.24
|
||||
filament_retract_length = 1
|
||||
filament_retract_speed = 60
|
||||
filament_type = PLA
|
||||
first_layer_bed_temperature = 60
|
||||
first_layer_temperature = 210
|
||||
temperature = 210
|
||||
|
||||
[filament:Artillery PLA HS @X3]
|
||||
inherits = *common_x3*
|
||||
bed_temperature = 60
|
||||
compatible_prints = X3 Default (0.20mm) @Artillery
|
||||
filament_density = 1.2
|
||||
filament_retract_length = 1.2
|
||||
filament_retract_speed = 60
|
||||
filament_type = PLA
|
||||
first_layer_bed_temperature = 60
|
||||
first_layer_temperature = 210
|
||||
temperature = 210
|
||||
|
||||
[filament:Artillery PLA Silk @X3]
|
||||
inherits = *common_x3*
|
||||
bed_temperature = 70
|
||||
compatible_prints = X3 Default (0.20mm) @Artillery
|
||||
filament_density = 1.2
|
||||
filament_retract_length = 1
|
||||
filament_retract_speed = 40
|
||||
filament_type = PLA
|
||||
first_layer_bed_temperature = 70
|
||||
first_layer_temperature = 210
|
||||
temperature = 210
|
||||
|
||||
[filament:Artillery TPU @X3]
|
||||
inherits = *common_x3*
|
||||
bed_temperature = 70
|
||||
compatible_prints = ArtillerySlicer X3 TPU @Artillery
|
||||
filament_density = 1.21
|
||||
filament_retract_length = 0.8
|
||||
filament_retract_speed = 40
|
||||
filament_type = TPU
|
||||
first_layer_bed_temperature = 70
|
||||
first_layer_temperature = 210
|
||||
temperature = 210
|
||||
|
||||
[filament:Artillery ABS @X4]
|
||||
inherits = *common_x4*
|
||||
bed_temperature = 100
|
||||
compatible_prints = ArtillerySlicer X4 Max ABS @Artillery; ArtillerySlicer X4 Plus ABS @Artillery; ArtillerySlicer X4 Pro ABS @Artillery
|
||||
filament_density = 1.04
|
||||
filament_max_volumetric_speed = 20
|
||||
filament_retract_length = 1.3
|
||||
filament_type = ABS
|
||||
first_layer_bed_temperature = 100
|
||||
first_layer_temperature = 260
|
||||
temperature = 260
|
||||
|
||||
[filament:Artillery PETG @X4]
|
||||
inherits = *common_x4*
|
||||
bed_temperature = 90
|
||||
compatible_prints = ArtillerySlicer X4 Max PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Plus PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Pro PETG & PLA & PLA+ @Artillery
|
||||
filament_density = 1.26
|
||||
filament_max_volumetric_speed = 20
|
||||
filament_retract_length = 1.3
|
||||
filament_type = PETG
|
||||
first_layer_bed_temperature = 90
|
||||
first_layer_temperature = 250
|
||||
temperature = 250
|
||||
|
||||
[filament:Artillery PLA & PLA HS @X4]
|
||||
inherits = *common_x4*
|
||||
compatible_prints = ArtillerySlicer X4 Max PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Plus PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Pro PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Max PLA HS @Artillery; ArtillerySlicer X4 Plus PLA HS @Artillery; ArtillerySlicer X4 Pro PLA HS @Artillery
|
||||
bed_temperature = 60
|
||||
filament_density = 1.2
|
||||
filament_max_volumetric_speed = 20
|
||||
filament_retract_length = 1.3
|
||||
filament_type = PLA
|
||||
first_layer_bed_temperature = 60
|
||||
first_layer_temperature = 210
|
||||
temperature = 210
|
||||
|
||||
[filament:Artillery PLA+ @X4]
|
||||
inherits = *common_x4*
|
||||
bed_temperature = 60
|
||||
compatible_prints = ArtillerySlicer X4 Max PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Plus PETG & PLA & PLA+ @Artillery; ArtillerySlicer X4 Pro PETG & PLA & PLA+ @Artillery
|
||||
filament_density = 1.25
|
||||
filament_max_volumetric_speed = 20
|
||||
filament_retract_length = 1.3
|
||||
filament_type = PLA
|
||||
first_layer_bed_temperature = 60
|
||||
first_layer_temperature = 210
|
||||
temperature = 210
|
||||
|
||||
[filament:Artillery PLA Silk @X4]
|
||||
inherits = *common_x4*
|
||||
bed_temperature = 60
|
||||
compatible_prints = ArtillerySlicer X4 Max PLA Silk @Artillery; ArtillerySlicer X4 Plus PLA Silk @Artillery; ArtillerySlicer X4 Pro PLA Silk @Artillery
|
||||
filament_density = 1.2
|
||||
filament_max_volumetric_speed = 20
|
||||
filament_retract_length = 1.2
|
||||
filament_type = PLA
|
||||
first_layer_bed_temperature = 70
|
||||
first_layer_temperature = 215
|
||||
temperature = 215
|
||||
|
||||
[filament:Artillery TPU @X4]
|
||||
inherits = *common_x4*
|
||||
bed_temperature = 70
|
||||
compatible_prints = ArtillerySlicer X4 Max TPU @Artillery; ArtillerySlicer X4 Plus TPU @Artillery; ArtillerySlicer X4 Pro TPU @Artillery
|
||||
filament_density = 1.21
|
||||
filament_max_volumetric_speed = 4
|
||||
filament_retract_length = 1.5
|
||||
filament_type = TPU
|
||||
first_layer_bed_temperature = 70
|
||||
first_layer_temperature = 215
|
||||
temperature = 215
|
||||
|
BIN
resources/profiles/Artillery/X3 Plus_thumbnail.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
resources/profiles/Artillery/X3 Pro_thumbnail.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
resources/profiles/Artillery/X4 Max_thumbnail.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
resources/profiles/Artillery/X4 Plus_thumbnail.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
resources/profiles/Artillery/X4 Pro_thumbnail.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
resources/profiles/Artillery/bed-x3-plus.stl
Normal file
BIN
resources/profiles/Artillery/bed-x3-pro.stl
Normal file
BIN
resources/profiles/Artillery/bed-x4-max.stl
Normal file
BIN
resources/profiles/Artillery/bed-x4-plus.stl
Normal file
BIN
resources/profiles/Artillery/bed-x4-pro.stl
Normal file
@ -1,4 +1,9 @@
|
||||
min_slic3r_version = 2.7.0-beta1
|
||||
1.11.12 Added profiles for Original Prusa MK3.5.
|
||||
1.11.11 Decreased speed of sparse infill in STRUCTURAL profiles. Decreased max volumetric speed for some PETG filaments for better sparse infill quality and reliability (XL/MK4/3.9/MINI - 0.4mm nozzle).
|
||||
1.11.10 Updated FW version notification (XL/MINI/MK4/MK3.9).
|
||||
1.11.9 Default nozzle diameter for Prusa XL changed to 0.4mm. Added material profile for Prusament Resin Model Ultra Violet.
|
||||
1.11.8 Updated cooling thresholds in selected filament profiles (MK4/XL).
|
||||
1.11.7 Updated binary g-code settings for PrusaSlicer 2.7.1. Binary g-code is not used by default in PrusaSlicer 2.7.0.
|
||||
1.11.6 Updated FW version notification (XL/MINI/MK4/MK3.9). Updated print profiles for 0.25mm nozzle (XLIS/MK4IS/MK3.9).
|
||||
1.11.5 Updated printer names and resources for XL/MINI Input Shaper (final firmware 5.1.0 was released).
|
||||
@ -26,6 +31,8 @@ min_slic3r_version = 2.6.2-alpha0
|
||||
1.11.0-alpha1 Updated ramming parameters. Updated start-gcode for XL Multi-Tool. Updated output filename format.
|
||||
1.11.0-alpha0 Binary g-code, arc fitting, QOI/PNG thumbnails, 90degree XL tower, XL specific filament variants.
|
||||
min_slic3r_version = 2.6.1-rc2
|
||||
1.10.11 Updated FW version notification (XL/MINI/MK4/MK3.9).
|
||||
1.10.10 Default nozzle diameter for Prusa XL changed to 0.4mm.
|
||||
1.10.9 Updated FW version notification (XL/MINI/MK4/MK3.9). Updated print profiles for 0.25mm nozzle (XLIS/MK4IS/MK3.9).
|
||||
1.10.8 Updated printer names and resources for XL/MINI Input Shaper (final firmware 5.1.0 was released).
|
||||
1.10.7 Added filament profile for Prusament rPLA. Updated start g-code for XL Multi-Tool.
|
||||
|
BIN
resources/profiles/PrusaResearch/MK35_thumbnail.png
Normal file
After Width: | Height: | Size: 66 KiB |
612
resources/profiles/PrusaResearch/mk35.svg
Normal file
@ -0,0 +1,612 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg3128"
|
||||
width="710.1"
|
||||
height="596.7"
|
||||
viewBox="0 0 710.1 596.7"
|
||||
version="1.1"
|
||||
sodipodi:docname="mk35.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#000000"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="2.7800779"
|
||||
inkscape:cx="470.67027"
|
||||
inkscape:cy="448.36873"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3128" />
|
||||
<defs
|
||||
id="defs18">
|
||||
<rect
|
||||
x="327.60001"
|
||||
y="200.93479"
|
||||
width="168.88696"
|
||||
height="82.408697"
|
||||
id="rect18" />
|
||||
</defs>
|
||||
<line
|
||||
id="line2794"
|
||||
x1=".7"
|
||||
y1=".7"
|
||||
x2=".7"
|
||||
y2="596"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2796"
|
||||
x1="142.4"
|
||||
y1=".7"
|
||||
x2="142.4"
|
||||
y2="595.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2798"
|
||||
x1="284.2"
|
||||
y1=".7"
|
||||
x2="284.2"
|
||||
y2="595.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2800"
|
||||
x1="709.4"
|
||||
y1="596"
|
||||
x2="709.4"
|
||||
y2=".7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2802"
|
||||
x1="1.2"
|
||||
y1="581.8"
|
||||
x2="709.4"
|
||||
y2="581.8"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2804"
|
||||
x1=".7"
|
||||
y1="596"
|
||||
x2="709.4"
|
||||
y2="596"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2806"
|
||||
x1="1.2"
|
||||
y1="440.1"
|
||||
x2="709.4"
|
||||
y2="440.1"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2808"
|
||||
x1="1.2"
|
||||
y1="298.4"
|
||||
x2="709.4"
|
||||
y2="298.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2810"
|
||||
x1="1.2"
|
||||
y1="156.6"
|
||||
x2="709.4"
|
||||
y2="156.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2812"
|
||||
x1="1.2"
|
||||
y1="14.9"
|
||||
x2="709.4"
|
||||
y2="14.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2814"
|
||||
x1="709.4"
|
||||
y1=".7"
|
||||
x2=".7"
|
||||
y2=".7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2816"
|
||||
x1="425.9"
|
||||
y1=".7"
|
||||
x2="425.9"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2818"
|
||||
x1="425.9"
|
||||
y1="522.6"
|
||||
x2="425.9"
|
||||
y2="595.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2820"
|
||||
x1="567.6"
|
||||
y1=".7"
|
||||
x2="567.6"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2822"
|
||||
x1="567.6"
|
||||
y1="522.5"
|
||||
x2="567.6"
|
||||
y2="527.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2824"
|
||||
x1="567.6"
|
||||
y1="548.6"
|
||||
x2="567.6"
|
||||
y2="595.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.4px;" />
|
||||
<line
|
||||
id="line2826"
|
||||
x1="85.8"
|
||||
y1=".7"
|
||||
x2="85.8"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2828"
|
||||
x1="114.1"
|
||||
y1=".7"
|
||||
x2="114.1"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2830"
|
||||
x1="170.8"
|
||||
y1=".7"
|
||||
x2="170.8"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2832"
|
||||
x1="199.1"
|
||||
y1=".7"
|
||||
x2="199.1"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2834"
|
||||
x1="227.5"
|
||||
y1=".7"
|
||||
x2="227.5"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2836"
|
||||
x1="255.8"
|
||||
y1=".7"
|
||||
x2="255.8"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2838"
|
||||
x1="312.5"
|
||||
y1=".7"
|
||||
x2="312.5"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2840"
|
||||
x1="340.9"
|
||||
y1=".7"
|
||||
x2="340.9"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2842"
|
||||
x1="681"
|
||||
y1=".7"
|
||||
x2="681"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2844"
|
||||
x1="29.1"
|
||||
y1=".7"
|
||||
x2="29.1"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2846"
|
||||
x1="57.4"
|
||||
y1=".7"
|
||||
x2="57.4"
|
||||
y2="595.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2848"
|
||||
x1="1.5"
|
||||
y1="468.4"
|
||||
x2="709.4"
|
||||
y2="468.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2850"
|
||||
x1="1.5"
|
||||
y1="496.8"
|
||||
x2="709.4"
|
||||
y2="496.8"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2852"
|
||||
x1="1.5"
|
||||
y1="525.1"
|
||||
x2="709.4"
|
||||
y2="525.1"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2854"
|
||||
x1="1.5"
|
||||
y1="553.5"
|
||||
x2="709.4"
|
||||
y2="553.5"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2856"
|
||||
x1="1.5"
|
||||
y1="411.7"
|
||||
x2="709.4"
|
||||
y2="411.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2858"
|
||||
x1="1.5"
|
||||
y1="383.4"
|
||||
x2="709.4"
|
||||
y2="383.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2860"
|
||||
x1="1.5"
|
||||
y1="355"
|
||||
x2="709.4"
|
||||
y2="355"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2862"
|
||||
x1="1.5"
|
||||
y1="326.7"
|
||||
x2="709.4"
|
||||
y2="326.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2864"
|
||||
x1="1.5"
|
||||
y1="270"
|
||||
x2="709.4"
|
||||
y2="270"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2866"
|
||||
x1="1.5"
|
||||
y1="241.7"
|
||||
x2="709.4"
|
||||
y2="241.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2868"
|
||||
x1="1.5"
|
||||
y1="213.3"
|
||||
x2="709.4"
|
||||
y2="213.3"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2870"
|
||||
x1="1.5"
|
||||
y1="185"
|
||||
x2="709.4"
|
||||
y2="185"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2872"
|
||||
x1="1.5"
|
||||
y1="128.3"
|
||||
x2="709.4"
|
||||
y2="128.3"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2874"
|
||||
x1="1.5"
|
||||
y1="99.9"
|
||||
x2="709.4"
|
||||
y2="99.9"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2876"
|
||||
x1="1.5"
|
||||
y1="71.6"
|
||||
x2="709.4"
|
||||
y2="71.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2878"
|
||||
x1="1.5"
|
||||
y1="43.2"
|
||||
x2="709.4"
|
||||
y2="43.2"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2880"
|
||||
x1="369.2"
|
||||
y1=".7"
|
||||
x2="369.2"
|
||||
y2="522.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2882"
|
||||
x1="369.2"
|
||||
y1="522.6"
|
||||
x2="369.2"
|
||||
y2="595.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2884"
|
||||
x1="397.6"
|
||||
y1=".7"
|
||||
x2="397.6"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2886"
|
||||
x1="397.5"
|
||||
y1="522.6"
|
||||
x2="397.5"
|
||||
y2="595.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2888"
|
||||
x1="454.2"
|
||||
y1=".7"
|
||||
x2="454.2"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2890"
|
||||
x1="454.3"
|
||||
y1="522.6"
|
||||
x2="454.3"
|
||||
y2="595.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2892"
|
||||
x1="482.6"
|
||||
y1=".7"
|
||||
x2="482.6"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2894"
|
||||
x1="482.6"
|
||||
y1="522.6"
|
||||
x2="482.6"
|
||||
y2="595.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2896"
|
||||
x1="510.9"
|
||||
y1=".7"
|
||||
x2="510.9"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2898"
|
||||
x1="510.9"
|
||||
y1="522.6"
|
||||
x2="510.9"
|
||||
y2="595.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2900"
|
||||
x1="539.3"
|
||||
y1=".7"
|
||||
x2="539.3"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2902"
|
||||
x1="539.3"
|
||||
y1="522.6"
|
||||
x2="539.3"
|
||||
y2="595.6"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2904"
|
||||
x1="596"
|
||||
y1=".7"
|
||||
x2="596"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2906"
|
||||
x1="624.3"
|
||||
y1=".7"
|
||||
x2="624.3"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2908"
|
||||
x1="652.7"
|
||||
y1=".7"
|
||||
x2="652.7"
|
||||
y2="499.4"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2910"
|
||||
x1="652.7"
|
||||
y1="522.5"
|
||||
x2="652.7"
|
||||
y2="527.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2912"
|
||||
x1="652.6"
|
||||
y1="548.4"
|
||||
x2="652.6"
|
||||
y2="596"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2914"
|
||||
x1="624.3"
|
||||
y1="522.5"
|
||||
x2="624.3"
|
||||
y2="527.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2916"
|
||||
x1="624.3"
|
||||
y1="548.4"
|
||||
x2="624.3"
|
||||
y2="596"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2918"
|
||||
x1="596"
|
||||
y1="522.5"
|
||||
x2="596"
|
||||
y2="527.7"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<line
|
||||
id="line2920"
|
||||
x1="596"
|
||||
y1="548.4"
|
||||
x2="596"
|
||||
y2="596"
|
||||
style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: .4px;" />
|
||||
<path
|
||||
id="path3098"
|
||||
d="m548.6,532.1h2.1v4.8h0c.1-.2.3-.4.5-.6.2-.2.4-.3.7-.5.2-.1.5-.2.8-.3.3,0,.5-.1.8,0,.7,0,1.3.1,1.9.4.5.2,1,.6,1.4,1.1.4.5.6,1,.8,1.6.2.6.3,1.3.3,1.9,0,.6,0,1.2-.2,1.8-.2.6-.4,1.1-.7,1.6-.7,1-1.8,1.5-3,1.5-.3,0-.6,0-1,0-.3,0-.6-.1-.9-.2-.3-.1-.5-.3-.8-.5-.2-.2-.4-.5-.6-.8h0v1.3h-2v-12.9Zm7.2,8.2c0-.4,0-.8-.2-1.2-.1-.4-.3-.7-.5-1-.2-.3-.5-.6-.8-.7-.3-.2-.7-.3-1.1-.3-.8,0-1.5.3-2,.9-.5.7-.7,1.6-.7,2.4,0,.4,0,.9.2,1.3.1.4.3.7.5,1,.2.3.5.5.8.7.3.2.7.3,1.1.2.4,0,.8,0,1.2-.3.3-.2.6-.4.8-.7.2-.3.4-.7.5-1,0-.4.1-.8.1-1.2Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3100"
|
||||
d="m558.5,535.6h2.2l2.4,7h0l2.4-7h2.1l-3.6,9.8c-.2.4-.3.8-.5,1.2-.2.4-.4.7-.6,1-.2.3-.5.5-.9.7-.4.2-.9.3-1.3.3-.5,0-1,0-1.4-.1v-1.7h.5c.2,0,.3,0,.5,0,.2,0,.4,0,.6,0,.1,0,.3-.1.4-.3.1-.1.2-.3.3-.4,0-.2.1-.4.2-.5l.2-.7-3.5-9.2Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3102"
|
||||
d="m581.1,540.8c0,.5,0,1.1-.1,1.6,0,.5-.3,1-.6,1.4-.3.4-.8.8-1.3,1-.7.3-1.5.4-2.2.4-.6,0-1.2,0-1.7-.3-.5-.2-.9-.5-1.2-.9-.3-.4-.6-.8-.7-1.3-.1-.5-.2-1.1-.2-1.6v-.7h2.2v.7c0,.6,0,1.2.4,1.7.3.4.9.7,1.4.6.3,0,.6,0,.9-.2.2-.1.4-.3.6-.5.1-.2.2-.5.3-.8,0-.4,0-.7,0-1.1v-8.8h2.2v8.7Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3104"
|
||||
d="m587.8,545.1c-.7,0-1.4-.1-2-.4-.6-.2-1.1-.6-1.5-1-.4-.4-.7-1-.9-1.5-.2-.6-.3-1.3-.3-2,0-.7,0-1.4.3-2,.2-.6.5-1.1.9-1.5.4-.4.9-.8,1.5-1,1.3-.5,2.7-.5,4,0,.6.2,1.1.6,1.5,1,.4.4.7,1,.9,1.5.2.6.3,1.3.3,2,0,.7,0,1.4-.3,2-.2.6-.5,1.1-.9,1.5-.4.4-.9.8-1.5,1-.6.3-1.3.4-2,.4Zm0-1.6c.4,0,.8,0,1.2-.3.3-.2.6-.4.8-.8.2-.3.4-.7.5-1.1.1-.4.2-.8.2-1.2,0-.4,0-.8-.2-1.2,0-.4-.3-.7-.5-1-.2-.3-.5-.6-.8-.8-.7-.4-1.6-.4-2.4,0-.3.2-.6.4-.8.8-.2.3-.4.6-.5,1,0,.4-.1.8-.2,1.2,0,.4,0,.8.2,1.2,0,.4.3.7.5,1.1.2.3.5.6.8.8.4.2.8.3,1.2.3h0Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3106"
|
||||
d="m595.7,541.9c0,.5.3,1,.7,1.3.4.2.9.4,1.4.4.2,0,.4,0,.7,0,.2,0,.5,0,.7-.2.2,0,.4-.2.5-.4.3-.4.2-.9,0-1.3-.2-.2-.4-.3-.7-.4-.3-.1-.7-.2-1-.3l-1.1-.2c-.4,0-.7-.2-1.1-.3-.4-.1-.7-.3-1-.5-.3-.2-.5-.5-.6-.9-.2-.4-.3-.8-.2-1.2,0-.5.1-.9.4-1.3.2-.3.6-.6.9-.8.4-.2.8-.4,1.3-.4.4,0,.9-.1,1.3-.1.5,0,.9,0,1.4.2.4,0,.8.3,1.2.5.4.2.7.5.9.9.2.4.4.9.4,1.3h-2.1c0-.4-.3-.8-.7-1-.4-.2-.8-.3-1.2-.2-.2,0-.3,0-.5,0-.2,0-.4,0-.6.1-.2,0-.3.2-.4.3-.1.1-.2.3-.2.5,0,.2,0,.5.3.6.2.2.4.3.7.4.3.1.7.2,1,.3l1.1.2c.4,0,.7.2,1.1.3.4.1.7.3,1,.5.3.2.5.5.7.8.2.4.3.8.3,1.2,0,.5-.1,1-.4,1.4-.3.4-.6.7-1,.9-.4.2-.9.4-1.3.5-.5.1-1,.2-1.5.2-.5,0-1.1,0-1.6-.2-.5-.1-.9-.3-1.3-.6-.4-.3-.7-.6-.9-1-.2-.4-.3-.9-.3-1.4h2Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3108"
|
||||
d="m605.1,540.8c0,.3,0,.7.2,1,0,.3.3.6.5.9.2.3.5.5.8.6.4.2.7.2,1.1.2.5,0,1.1,0,1.5-.3.4-.3.7-.7.8-1.2h1.9c0,.5-.3.9-.6,1.3-.3.4-.6.7-1,1-.4.3-.8.5-1.2.6-.5.1-1,.2-1.5.2-.7,0-1.3-.1-1.9-.4-.5-.2-1-.6-1.4-1-.4-.4-.7-1-.9-1.5-.2-.6-.3-1.3-.3-2,0-.6.1-1.3.3-1.9.2-.6.5-1.1.9-1.6.8-1,2-1.5,3.3-1.5.7,0,1.4.1,2,.5.6.3,1.1.7,1.5,1.2.4.5.7,1.1.8,1.7.2.7.2,1.3.1,2h-6.9Zm4.8-1.3c0-.3,0-.6-.2-.9-.1-.3-.3-.6-.5-.8-.2-.2-.4-.4-.7-.5-.3-.1-.6-.2-.9-.2-.3,0-.7,0-1,.2-.3.1-.5.3-.8.5-.2.2-.4.5-.5.8-.1.3-.2.7-.2,1h4.8Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3110"
|
||||
d="m612.6,535.6h1.5v-.8c0-.5,0-1,.2-1.4.1-.3.3-.6.6-.8.2-.2.5-.3.8-.4.3,0,.7-.1,1,0,.5,0,.9,0,1.4,0v1.6c-.1,0-.3,0-.4,0h-.5c-.3,0-.5,0-.7.2-.2.2-.3.5-.3.8v1h1.7v1.5h-1.7v7.8h-2v-7.8h-1.5v-1.5Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3112"
|
||||
d="m624.5,532.1h5.7c.8,0,1.5.1,2.2.4.5.2,1,.6,1.3,1,.3.4.5.8.6,1.3.1.4.2.9.2,1.3,0,.4,0,.9-.2,1.3-.1.5-.3.9-.6,1.3-.4.4-.8.8-1.3,1-.7.3-1.5.4-2.2.4h-3.4v4.9h-2.2v-12.9Zm2.2,6.1h3.3c.3,0,.5,0,.8-.1.3,0,.5-.2.7-.3.2-.2.4-.4.5-.7.1-.3.2-.7.2-1,0-.3,0-.7-.2-1-.2-.5-.7-.9-1.2-1-.3,0-.6,0-.8,0h-3.3v4.2Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3114"
|
||||
d="m636.2,535.6h1.9v1.8h0c0-.3.2-.5.4-.7.2-.2.4-.5.6-.7.2-.2.5-.4.8-.5.3-.1.6-.2.9-.2h.8v2h-.4c-.1,0-.3,0-.5,0-.7,0-1.3.3-1.8.8-.2.3-.4.6-.5,1-.1.4-.2.9-.2,1.4v4.4h-2v-9.3Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3116"
|
||||
d="m650.7,544.9h-2v-1.3h0c-.3.5-.7.9-1.1,1.1-.5.3-1,.4-1.5.4-1,0-1.9-.2-2.7-.9-.6-.8-.9-1.8-.8-2.7v-5.9h2v5.7c0,.6.1,1.2.5,1.7.3.3.8.5,1.3.5.4,0,.7,0,1.1-.2.3-.1.5-.3.7-.5.2-.2.3-.5.4-.8,0-.3.1-.7.1-1v-5.4h2v9.3Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3118"
|
||||
d="m654.4,541.9c0,.5.3,1,.7,1.2.4.2.9.4,1.4.4.2,0,.4,0,.7,0,.2,0,.5,0,.7-.2.2,0,.4-.2.5-.4.1-.2.2-.4.2-.6,0-.2-.1-.5-.3-.7-.2-.2-.4-.3-.7-.4-.3-.1-.7-.2-1-.3l-1.1-.2c-.4,0-.7-.2-1.1-.3-.4-.1-.7-.3-1-.5-.3-.2-.5-.5-.7-.8-.2-.4-.3-.8-.3-1.2,0-.5.1-.9.4-1.3.2-.3.6-.6.9-.8.4-.2.8-.4,1.3-.4.4,0,.9-.1,1.3-.1.5,0,.9,0,1.4.2.4.1.8.3,1.2.5.4.2.7.5.9.9.2.4.4.9.4,1.3h-2.1c0-.4-.3-.8-.7-1-.4-.2-.8-.3-1.2-.2-.2,0-.3,0-.5,0-.2,0-.4,0-.5.1-.2,0-.3.2-.4.3-.1.1-.2.3-.2.5,0,.2,0,.5.3.6.2.2.4.3.7.4.3.1.7.2,1,.3l1.1.2c.4,0,.7.2,1.1.3.4.1.7.3,1,.5.3.2.5.5.7.8.2.4.3.8.3,1.2,0,.5-.1,1-.4,1.4-.3.4-.6.7-1,.9-.4.2-.9.4-1.3.5-.5.1-1,.2-1.5.2-.6,0-1.1,0-1.6-.2-.5-.1-.9-.3-1.3-.6-.4-.3-.7-.6-.9-1-.2-.4-.3-.9-.3-1.4h2.1Z"
|
||||
style="fill: #fff;" />
|
||||
<path
|
||||
id="path3120"
|
||||
d="m670,542.8c0,.2,0,.4,0,.5,0,.1.2.2.4.2h.5v1.4h-.3c0,0-.3.2-.3.2h-.4c-.1,0-.2,0-.3,0-.3,0-.7,0-1-.2-.3-.2-.5-.5-.5-.9-.4.4-.9.7-1.5.9-.6.2-1.1.3-1.7.3-.4,0-.8,0-1.2-.2-.4-.1-.7-.3-1-.5-.3-.2-.5-.5-.7-.8-.2-.4-.3-.8-.3-1.2,0-.5,0-1,.3-1.4.2-.3.5-.6.8-.8.4-.2.7-.4,1.2-.4.4,0,.9-.2,1.3-.2.3,0,.7-.1,1.1-.2.3,0,.6,0,.9-.2.2,0,.4-.2.6-.3.2-.2.2-.4.2-.7,0-.2,0-.5-.2-.7-.1-.2-.3-.3-.5-.4-.2,0-.4-.2-.6-.2-.2,0-.4,0-.7,0-.5,0-1,.1-1.4.4-.4.3-.6.7-.6,1.1h-2c0-.5.2-1,.4-1.5.3-.4.6-.7,1-1,.4-.2.9-.4,1.3-.5.5-.1,1-.2,1.5-.2.5,0,.9,0,1.3.2.4,0,.8.2,1.2.5.3.2.6.5.8.8.2.4.3.8.3,1.2v4.8Zm-2.1-2.6c-.3.2-.7.3-1.2.4-.5,0-.9.1-1.4.2-.2,0-.4,0-.6.2-.2,0-.4.1-.5.3-.2.1-.3.3-.4.5,0,.2-.1.4-.1.7,0,.2,0,.4.2.6.1.2.3.3.5.4.2,0,.4.2.6.2.2,0,.4,0,.6,0,.2,0,.5,0,.7,0,.3,0,.5-.2.8-.3.2-.1.4-.3.6-.5.2-.2.2-.5.2-.8v-1.5Z"
|
||||
style="fill: #fff;" />
|
||||
<g
|
||||
id="g5">
|
||||
<path
|
||||
d="m596.7,502.2h5.6l3.2,9.6c.5,1.5.8,2.6.8,2.6h0s.3-1.1.8-2.6l3.2-9.6h5.6v17.8h-3.8v-14h0s-.4,1.7-1.1,3.8l-3.4,10.2h-2.9l-3.4-10.2c-.7-2.1-1.1-3.8-1.1-3.8h0v13.9h-3.5v-17.8Z"
|
||||
style="fill: #ed6b21;"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m623.3,520.1h-3.8v-17.8h3.8v7.9l6.5-7.9h4.3l-6.4,7.5,6.7,10.4h-4.4l-4.9-7.5-1.8,2.1v5.4Z"
|
||||
style="fill: #ed6b21;"
|
||||
id="path2" />
|
||||
<path
|
||||
style="font-weight:bold;font-size:26.6667px;font-family:Helvetica;-inkscape-font-specification:'Helvetica Bold';white-space:pre;fill:#ed6b20;stroke-width:1.00047"
|
||||
d="m 331.94857,220.21561 q 0,1.13281 0.36459,1.875 0.67708,1.36719 2.46094,1.36719 1.09375,0 1.90104,-0.74219 0.82032,-0.75521 0.82032,-2.16146 0,-1.86198 -1.51042,-2.48698 -0.85938,-0.35156 -2.70834,-0.35156 v -2.65626 q 1.8099,-0.026 2.52604,-0.35156 1.23699,-0.54687 1.23699,-2.21354 0,-1.08073 -0.63803,-1.75782 -0.625,-0.67708 -1.77083,-0.67708 -1.31511,0 -1.94011,0.83333 -0.61198,0.83334 -0.58594,2.22657 h -3.46354 q 0.0521,-1.40625 0.48177,-2.66928 0.45573,-1.10677 1.43229,-2.04427 0.72917,-0.66406 1.73178,-1.01563 1.0026,-0.35156 2.46094,-0.35156 2.70833,0 4.36198,1.40625 1.66667,1.39323 1.66667,3.75001 0,1.66667 -0.98959,2.8125 -0.625,0.71615 -1.30208,0.97656 0.50781,0 1.45833,0.8724 1.41928,1.31511 1.41928,3.59376 0,2.39583 -1.66667,4.21875 -1.65365,1.8099 -4.90886,1.8099 -4.01042,0 -5.57293,-2.61719 -0.82031,-1.39323 -0.91145,-3.64584 z m 12.14846,1.91407 h 3.94531 v 3.88021 h -3.94531 z m 10.14324,-1.17188 q 0.22135,1.21094 0.84635,1.875 0.625,0.65104 1.82292,0.65104 1.38021,0 2.09636,-0.96354 0.72917,-0.97656 0.72917,-2.44792 0,-1.44531 -0.67709,-2.4349 -0.67708,-1.0026 -2.10938,-1.0026 -0.67708,0 -1.17187,0.16927 -0.8724,0.3125 -1.31511,1.15885 l -3.33334,-0.15625 1.32813,-10.4297 h 10.40366 v 3.15105 h -7.72136 l -0.67709,4.12761 q 0.85938,-0.5599 1.34115,-0.74219 0.80729,-0.29948 1.96615,-0.29948 2.34375,0 4.08854,1.57552 1.7448,1.57553 1.7448,4.58334 0,2.61719 -1.67969,4.67449 -1.67969,2.05729 -5.02605,2.05729 -2.69532,0 -4.42709,-1.44531 -1.73177,-1.44532 -1.92708,-4.10157 z"
|
||||
id="text18"
|
||||
transform="matrix(0.98018322,0,0,0.94522957,313.88943,306.40108)"
|
||||
aria-label="3.5" />
|
||||
</g>
|
||||
<g
|
||||
id="g13">
|
||||
<path
|
||||
d="m385.6,511.3c0-1.3.2-2.6.6-3.7s1-2.1,1.7-3c.7-.9,1.7-1.5,2.8-2,1.1-.5,2.3-.7,3.7-.7s2.6.2,3.7.7c1.1.5,2,1.2,2.8,2s1.3,1.8,1.7,3,.6,2.4.6,3.7-.2,2.5-.6,3.6-1,2.1-1.7,2.9c-.7.8-1.7,1.5-2.8,2-1.1.5-2.3.7-3.7.7s-2.6-.2-3.7-.7c-1.1-.5-2-1.1-2.8-2-.7-.8-1.3-1.8-1.7-2.9-.4-1.1-.6-2.3-.6-3.6Zm3.9,0c0,.7.1,1.5.3,2.2.2.7.4,1.3.9,1.9.4.6.9,1,1.5,1.4s1.4.5,2.2.5,1.6-.2,2.2-.5,1.1-.8,1.5-1.4c.4-.6.6-1.2.9-1.9.2-.7.3-1.4.3-2.2s-.1-1.5-.3-2.3c-.2-.7-.4-1.4-.9-1.9-.4-.6-.9-1-1.5-1.4-.6-.3-1.4-.5-2.2-.5s-1.6.2-2.2.5c-.6.3-1.1.8-1.5,1.4s-.6,1.2-.9,1.9c-.2.7-.3,1.5-.3,2.3Z"
|
||||
style="fill: #959998;"
|
||||
id="path6" />
|
||||
<path
|
||||
d="m404.8,502.3h9.6c.8,0,1.5.1,2.1.4.6.3,1.2.6,1.6,1.1.4.4.8,1,1.1,1.6.2.6.4,1.2.4,1.9,0,1.1-.2,1.9-.6,2.7-.4.7-1.2,1.4-2.1,1.7h0c.5.2.9.4,1.2.6s.6.6.8,1c.2.4.3.8.4,1.2s.2.9.2,1.3,0,.6,0,1,0,.8.1,1.2c0,.4.1.8.2,1.1.1.4.2.7.4.9h-3.9c-.1-.3-.2-.6-.3-.9,0-.3-.1-.7-.1-1.1s0-.7-.1-1.2c0-.4,0-.7-.1-1.1-.1-.9-.4-1.6-.9-2s-1.1-.6-2.1-.6h-3.9v6.9h-3.9v-17.7Zm3.9,8.1h4.3c.9,0,1.6-.2,2-.6.4-.4.7-1.1.7-1.9s-.2-1.5-.7-1.9-1.1-.6-2-.6h-4.3v5Z"
|
||||
style="fill: #959998;"
|
||||
id="path7" />
|
||||
<path
|
||||
d="m421.8,502.3h3.9v17.8h-3.9v-17.8Z"
|
||||
style="fill: #959998;"
|
||||
id="path8" />
|
||||
<path
|
||||
d="m441,518.1c-.7.9-1.5,1.5-2.3,1.9s-1.7.5-2.6.5c-1.4,0-2.6-.2-3.7-.7-1.1-.5-2-1.1-2.8-2-.7-.8-1.3-1.8-1.7-2.9-.4-1.1-.6-2.3-.6-3.6s.2-2.6.6-3.7,1-2.1,1.7-3c.7-.9,1.7-1.5,2.8-2,1.1-.5,2.3-.7,3.7-.7s1.8.1,2.7.4c.9.3,1.6.7,2.3,1.2s1.3,1.2,1.7,2,.7,1.7.9,2.7h-3.7c-.2-1-.7-1.7-1.4-2.2s-1.5-.7-2.4-.7-1.6.2-2.2.5-1.1.8-1.5,1.4c-.4.6-.6,1.2-.9,1.9-.2.7-.3,1.5-.3,2.3s.1,1.5.3,2.2c.2.7.4,1.3.9,1.9.4.6.9,1,1.5,1.4.6.3,1.4.5,2.2.5,1.3,0,2.3-.3,3-1s1.1-1.6,1.3-2.9h-3.9v-2.9h7.5v9.6h-2.5l-.4-2Z"
|
||||
style="fill: #959998;"
|
||||
id="path9" />
|
||||
<path
|
||||
d="m446.3,502.3h3.9v17.8h-3.9v-17.8Z"
|
||||
style="fill: #959998;"
|
||||
id="path10" />
|
||||
<path
|
||||
d="m452.7,502.3h3.9l7.4,11.9h0v-11.9h3.7v17.8h-3.9l-7.4-11.9h0v11.9h-3.7v-17.8Z"
|
||||
style="fill: #959998;"
|
||||
id="path11" />
|
||||
<path
|
||||
d="m475.1,502.3h4l6.6,17.8h-4l-1.4-3.9h-6.6l-1.4,3.9h-3.9l6.7-17.8Zm-.4,10.9h4.6l-2.2-6.5h0l-2.3,6.5Z"
|
||||
style="fill: #959998;"
|
||||
id="path12" />
|
||||
<path
|
||||
d="m486.5,502.3h3.9v14.5h8.6v3.3h-12.6v-17.8h0Z"
|
||||
style="fill: #959998;"
|
||||
id="path13" />
|
||||
</g>
|
||||
<g
|
||||
id="g18">
|
||||
<path
|
||||
d="m508.9,502.3h8c1.1,0,2.1.2,2.8.5s1.4.7,1.9,1.3c.5.5.9,1.2,1.1,1.8.2.7.3,1.4.3,2.1s-.1,1.4-.3,2.1-.6,1.3-1.1,1.8c-.5.5-1.1,1-1.9,1.3s-1.7.5-2.8.5h-4.1v6.4h-3.9v-17.8Zm3.9,8.4h3c.4,0,.9,0,1.3-.1.4,0,.8-.2,1.1-.4.3-.2.6-.5.7-.8.2-.3.3-.8.3-1.4s-.1-1-.3-1.4c-.2-.3-.4-.6-.7-.8-.3-.2-.7-.3-1.1-.4s-.9-.1-1.3-.1h-3v5.3Z"
|
||||
style="fill: #fff;"
|
||||
id="path14" />
|
||||
<path
|
||||
d="m524.2,502.3h9.6c.8,0,1.5.1,2.1.4.6.3,1.2.6,1.6,1.1.4.4.8,1,1.1,1.6.2.6.4,1.2.4,1.9,0,1.1-.2,1.9-.6,2.7-.4.7-1.2,1.4-2.1,1.7h0c.5.2.9.4,1.2.6s.6.6.8,1c.2.4.3.8.4,1.2s.2.9.2,1.3,0,.6,0,1,0,.8.1,1.2c0,.4.1.8.2,1.1.1.4.2.7.4.9h-3.9c-.1-.3-.2-.6-.3-.9,0-.3-.1-.7-.1-1.1s0-.7-.1-1.2c0-.4,0-.7-.1-1.1-.1-.9-.4-1.6-.9-2s-1.1-.6-2.1-.6h-3.9v6.9h-3.9v-17.7Zm3.9,8.1h4.3c.9,0,1.5-.2,2-.6.4-.4.7-1.1.7-1.9s-.2-1.5-.7-1.9-1.1-.6-2-.6h-4.3v5Z"
|
||||
style="fill: #fff;"
|
||||
id="path15" />
|
||||
<path
|
||||
d="m556.1,513.3c0,2.4-.7,4.2-2,5.4-1.4,1.2-3.2,1.8-5.6,1.8s-4.3-.6-5.6-1.8c-1.3-1.2-2-3-2-5.4v-11.1h3.9v11.1c0,.5,0,1,.1,1.4,0,.5.3.9.5,1.3.3.4.6.6,1.1.9.5.2,1.1.3,1.9.3,1.4,0,2.3-.3,2.9-.9s.8-1.6.8-2.9v-11.1h3.9v11h0Z"
|
||||
style="fill: #fff;"
|
||||
id="path16" />
|
||||
<path
|
||||
d="m561.1,514.2c0,.6.1,1.1.3,1.5.2.4.5.7.9,1s.8.4,1.3.6,1,.2,1.5.2.7,0,1.1-.1.8-.2,1.1-.3.6-.4.9-.7c.2-.3.3-.6.3-1.1s-.2-.9-.5-1.2-.7-.5-1.2-.7c-.5-.2-1.1-.4-1.7-.5s-1.3-.3-1.9-.5c-.7-.2-1.3-.4-1.9-.6-.6-.2-1.2-.5-1.7-.9s-.9-.9-1.2-1.4c-.3-.6-.5-1.3-.5-2.1s.2-1.7.6-2.4.9-1.2,1.5-1.7,1.4-.8,2.1-1,1.6-.3,2.4-.3,1.8.1,2.7.3,1.6.5,2.3,1,1.2,1.1,1.6,1.8.6,1.6.6,2.6h-3.8c0-.5-.1-1-.3-1.3s-.4-.6-.7-.8-.7-.3-1.1-.4-.9-.1-1.3-.1-.6,0-1,.1c-.3,0-.6.2-.9.3-.3.2-.5.4-.6.6s-.2.6-.2.9,0,.6.2.9c.1.2.4.4.8.6s.9.4,1.6.5c.7.2,1.6.4,2.7.7.2,0,.5.1.9.2.3.1.7.2,1.1.4.4.2.8.4,1.2.6.4.2.7.5,1.1.9.3.4.6.8.8,1.3.2.5.3,1.1.3,1.8s-.2,1.6-.5,2.3c-.3.7-.8,1.3-1.4,1.8s-1.4.9-2.3,1.2c-.9.3-2,.4-3.2.4s-1.9-.1-2.9-.4-1.7-.6-2.4-1.2-1.3-1.2-1.7-2c-.4-.8-.6-1.7-.6-2.8h3.8Z"
|
||||
style="fill: #fff;"
|
||||
id="path17" />
|
||||
<path
|
||||
d="m578.5,502.3h4l6.6,17.8h-4l-1.4-3.9h-6.6l-1.4,3.9h-3.9l6.7-17.8Zm-.4,10.9h4.6l-2.2-6.5h0l-2.3,6.5Z"
|
||||
style="fill: #fff;"
|
||||
id="path18" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 28 KiB |
@ -10,8 +10,6 @@
|
||||
#include <functional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "utils/VoronoiUtils.hpp"
|
||||
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "SVG.hpp"
|
||||
@ -19,27 +17,10 @@
|
||||
#include "Geometry/VoronoiUtilsCgal.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
|
||||
#include "Geometry/VoronoiUtils.hpp"
|
||||
|
||||
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
|
||||
|
||||
namespace boost::polygon {
|
||||
|
||||
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
|
||||
{
|
||||
return dir.to_int() ? CSegment.p() : CSegment.next().p();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost::polygon
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
@ -108,8 +89,7 @@ static void export_graph_to_svg(const std::string
|
||||
}
|
||||
#endif
|
||||
|
||||
SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p)
|
||||
{
|
||||
SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::vertex_type &vd_node, Point p) {
|
||||
auto he_node_it = vd_node_to_he_node.find(&vd_node);
|
||||
if (he_node_it == vd_node_to_he_node.end())
|
||||
{
|
||||
@ -124,8 +104,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments)
|
||||
{
|
||||
void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments) {
|
||||
auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin());
|
||||
if (he_edge_it != vd_edge_to_he_edge.end())
|
||||
{ // Twin segment(s) have already been made
|
||||
@ -235,23 +214,20 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
|
||||
}
|
||||
}
|
||||
|
||||
Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector<Segment>& segments)
|
||||
Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(vd_edge));
|
||||
|
||||
/*Terminology in this function assumes that the edge moves horizontally from
|
||||
left to right. This is not necessarily the case; the edge can go in any
|
||||
direction, but it helps to picture it in a certain direction in your head.*/
|
||||
|
||||
const vd_t::cell_type* left_cell = vd_edge.cell();
|
||||
const vd_t::cell_type* right_cell = vd_edge.twin()->cell();
|
||||
const VD::cell_type *left_cell = vd_edge.cell();
|
||||
const VD::cell_type *right_cell = vd_edge.twin()->cell();
|
||||
|
||||
assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
|
||||
Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
|
||||
|
||||
Point start = VoronoiUtils::p(vd_edge.vertex0()).cast<coord_t>();
|
||||
Point end = VoronoiUtils::p(vd_edge.vertex1()).cast<coord_t>();
|
||||
|
||||
bool point_left = left_cell->contains_point();
|
||||
bool point_right = right_cell->contains_point();
|
||||
if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment
|
||||
@ -260,20 +236,20 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const
|
||||
}
|
||||
else if (point_left != point_right) //This is a parabolic edge between a point and a line.
|
||||
{
|
||||
Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments);
|
||||
const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments);
|
||||
return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle);
|
||||
Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
|
||||
const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
|
||||
return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
|
||||
}
|
||||
else //This is a straight edge between two points.
|
||||
{
|
||||
/*While the edge is straight, it is still discretized since the part
|
||||
becomes narrower between the two points. As such it may need different
|
||||
beadings along the way.*/
|
||||
Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments);
|
||||
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments);
|
||||
coord_t d = (right_point - left_point).cast<int64_t>().norm();
|
||||
Point middle = (left_point + right_point) / 2;
|
||||
Point x_axis_dir = perp(Point(right_point - left_point));
|
||||
Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
|
||||
Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end());
|
||||
coord_t d = (right_point - left_point).cast<int64_t>().norm();
|
||||
Point middle = (left_point + right_point) / 2;
|
||||
Point x_axis_dir = perp(Point(right_point - left_point));
|
||||
coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm();
|
||||
|
||||
const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge.
|
||||
@ -350,8 +326,7 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const
|
||||
}
|
||||
}
|
||||
|
||||
bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments) {
|
||||
if (cell.incident_edge()->is_infinite())
|
||||
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell.
|
||||
|
||||
@ -359,16 +334,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
|
||||
// Copy whole cell into graph or not at all
|
||||
|
||||
// If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon.
|
||||
if (const vd_t::vertex_type &vert = *cell.incident_edge()->vertex0();
|
||||
if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0();
|
||||
vert.x() >= double(std::numeric_limits<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) ||
|
||||
vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest()))
|
||||
return false; // Don't copy any part of this cell
|
||||
|
||||
const Point source_point = VoronoiUtils::getSourcePoint(cell, segments);
|
||||
const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments);
|
||||
Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0());
|
||||
const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
|
||||
const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
|
||||
Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
|
||||
if (some_point == source_point.cast<int64_t>())
|
||||
some_point = VoronoiUtils::p(cell.incident_edge()->vertex1());
|
||||
some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1());
|
||||
|
||||
//Test if the some_point is even inside the polygon.
|
||||
//The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex.
|
||||
@ -377,16 +352,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
|
||||
if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point))
|
||||
return false; // Don't copy any part of this cell
|
||||
|
||||
vd_t::edge_type* vd_edge = cell.incident_edge();
|
||||
const VD::edge_type* vd_edge = cell.incident_edge();
|
||||
do {
|
||||
assert(vd_edge->is_finite());
|
||||
if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
|
||||
if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
|
||||
start_source_point = source_point;
|
||||
end_source_point = source_point;
|
||||
starting_vd_edge = vd_edge->next();
|
||||
ending_vd_edge = vd_edge;
|
||||
} else {
|
||||
assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input.");
|
||||
assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input.");
|
||||
}
|
||||
}
|
||||
while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge());
|
||||
@ -395,47 +370,6 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
|
||||
const Point from = source_segment.from();
|
||||
const Point to = source_segment.to();
|
||||
|
||||
// Find starting edge
|
||||
// Find end edge
|
||||
bool seen_possible_start = false;
|
||||
bool after_start = false;
|
||||
bool ending_edge_is_set_before_start = false;
|
||||
vd_t::edge_type* edge = cell.incident_edge();
|
||||
do {
|
||||
if (edge->is_infinite())
|
||||
continue;
|
||||
|
||||
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
|
||||
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
|
||||
|
||||
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>() ));
|
||||
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
|
||||
starting_vd_edge = edge;
|
||||
seen_possible_start = true;
|
||||
}
|
||||
else if (seen_possible_start) {
|
||||
after_start = true;
|
||||
}
|
||||
|
||||
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
|
||||
ending_edge_is_set_before_start = !after_start;
|
||||
ending_vd_edge = edge;
|
||||
}
|
||||
} while (edge = edge->next(), edge != cell.incident_edge());
|
||||
|
||||
assert(starting_vd_edge && ending_vd_edge);
|
||||
assert(starting_vd_edge != ending_vd_edge);
|
||||
|
||||
start_source_point = source_segment.to();
|
||||
end_source_point = source_segment.from();
|
||||
}
|
||||
|
||||
SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy,
|
||||
double transitioning_angle, coord_t discretization_step_size,
|
||||
coord_t transition_filter_dist, coord_t allowed_filter_deviation,
|
||||
@ -450,195 +384,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
|
||||
constructFromPolygons(polys);
|
||||
}
|
||||
|
||||
static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.is_finite()) {
|
||||
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) ||
|
||||
!VoronoiUtils::is_finite(*edge.vertex1()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
|
||||
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram))
|
||||
return true;
|
||||
|
||||
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // There is no spoon
|
||||
|
||||
if (cell.contains_segment()) {
|
||||
const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
|
||||
const Point from = source_segment.from();
|
||||
const Point to = source_segment.to();
|
||||
|
||||
// Find starting edge
|
||||
// Find end edge
|
||||
bool seen_possible_start = false;
|
||||
bool after_start = false;
|
||||
bool ending_edge_is_set_before_start = false;
|
||||
VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr;
|
||||
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr;
|
||||
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge();
|
||||
do {
|
||||
if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
continue;
|
||||
|
||||
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
|
||||
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
|
||||
|
||||
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>()));
|
||||
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
|
||||
starting_vd_edge = edge;
|
||||
seen_possible_start = true;
|
||||
} else if (seen_possible_start) {
|
||||
after_start = true;
|
||||
}
|
||||
|
||||
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
|
||||
ending_edge_is_set_before_start = !after_start;
|
||||
ending_vd_edge = edge;
|
||||
}
|
||||
} while (edge = edge->next(), edge != cell.incident_edge());
|
||||
|
||||
if (!starting_vd_edge || !ending_vd_edge || starting_vd_edge == ending_vd_edge)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph)
|
||||
{
|
||||
for (const auto &edge : graph.edges)
|
||||
if (edge.twin == nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
using PointMap = SkeletalTrapezoidation::PointMap;
|
||||
|
||||
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
|
||||
const double fix_angle,
|
||||
const PointMap &vertex_mapping)
|
||||
{
|
||||
for (STHalfEdgeNode &node : graph.nodes) {
|
||||
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
|
||||
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
|
||||
node.p = node_it->second;
|
||||
else
|
||||
node.p.rotate(-fix_angle);
|
||||
}
|
||||
}
|
||||
|
||||
bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
|
||||
{
|
||||
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // Degenerated cell, there is no spoon
|
||||
|
||||
if (!cell.contains_segment())
|
||||
continue; // Skip cells that don't contain segments.
|
||||
|
||||
const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
|
||||
const Vec2d source_segment_from = source_segment.from().cast<double>();
|
||||
const Vec2d source_segment_vec = source_segment.to().cast<double>() - source_segment_from;
|
||||
|
||||
Point start_source_point, end_source_point;
|
||||
VoronoiUtils::vd_t::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr;
|
||||
SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments);
|
||||
// All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid.
|
||||
// FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases.
|
||||
// It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf).
|
||||
if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr)
|
||||
for (VoronoiUtils::vd_t::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next())
|
||||
if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
enum class VoronoiDiagramStatus {
|
||||
NO_ISSUE_DETECTED,
|
||||
MISSING_VORONOI_VERTEX,
|
||||
NON_PLANAR_VORONOI_DIAGRAM,
|
||||
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
|
||||
OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION
|
||||
};
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram
|
||||
// is not planar or some Voronoi edge is intersecting input segment.
|
||||
VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::VoronoiDiagram &voronoi_diagram,
|
||||
const std::vector<SkeletalTrapezoidation::Segment> &segments)
|
||||
{
|
||||
if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) {
|
||||
return VoronoiDiagramStatus::MISSING_VORONOI_VERTEX;
|
||||
} else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) {
|
||||
// Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446.
|
||||
return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
|
||||
} else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); !is_voronoi_diagram_planar) {
|
||||
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
|
||||
return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM;
|
||||
}
|
||||
return VoronoiDiagramStatus::NO_ISSUE_DETECTED;
|
||||
}
|
||||
|
||||
inline static std::pair<PointMap, double> try_to_fix_degenerated_voronoi_diagram_by_rotation(
|
||||
Geometry::VoronoiDiagram &voronoi_diagram,
|
||||
const Polygons &polys,
|
||||
Polygons &polys_rotated,
|
||||
std::vector<SkeletalTrapezoidation::Segment> &segments,
|
||||
const std::vector<double> &fix_angles)
|
||||
{
|
||||
const Polygons polys_rotated_original = polys_rotated;
|
||||
double fixed_by_angle = fix_angles.front();
|
||||
PointMap vertex_mapping;
|
||||
|
||||
for (const double &fix_angle : fix_angles) {
|
||||
vertex_mapping.clear();
|
||||
polys_rotated = polys_rotated_original;
|
||||
fixed_by_angle = fix_angle;
|
||||
|
||||
for (Polygon &poly : polys_rotated)
|
||||
poly.rotate(fix_angle);
|
||||
|
||||
assert(polys_rotated.size() == polys.size());
|
||||
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
|
||||
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
|
||||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
|
||||
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
|
||||
}
|
||||
|
||||
segments.clear();
|
||||
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
|
||||
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
|
||||
|
||||
voronoi_diagram.clear();
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (detect_voronoi_diagram_known_issues(voronoi_diagram, segments) == VoronoiDiagramStatus::NO_ISSUE_DETECTED)
|
||||
break;
|
||||
}
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
return {vertex_mapping, fixed_by_angle};
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
{
|
||||
#ifdef ARACHNE_DEBUG
|
||||
@ -670,8 +415,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
}
|
||||
#endif
|
||||
|
||||
Geometry::VoronoiDiagram voronoi_diagram;
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
VD voronoi_diagram;
|
||||
voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
|
||||
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
@ -680,45 +425,15 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
}
|
||||
#endif
|
||||
|
||||
// When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is
|
||||
// intersecting input segment, rotate the input polygon and try again.
|
||||
VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
|
||||
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
|
||||
double fixed_by_angle = fix_angles.front();
|
||||
|
||||
PointMap vertex_mapping;
|
||||
// polys_copy is referenced through items stored in the std::vector segments.
|
||||
Polygons polys_copy = polys;
|
||||
if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
|
||||
if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
else if (status == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
else if (status == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
|
||||
|
||||
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
|
||||
|
||||
VoronoiDiagramStatus status_after_fix = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
|
||||
assert(status_after_fix == VoronoiDiagramStatus::NO_ISSUE_DETECTED);
|
||||
if (status_after_fix == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
|
||||
else if (status_after_fix == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM)
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
|
||||
else if (status_after_fix == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
|
||||
}
|
||||
|
||||
process_voronoi_diagram:
|
||||
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
|
||||
for (vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
for (const VD::cell_type &cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // There is no spoon
|
||||
|
||||
Point start_source_point;
|
||||
Point end_source_point;
|
||||
vd_t::edge_type* starting_voronoi_edge = nullptr;
|
||||
vd_t::edge_type* ending_voronoi_edge = nullptr;
|
||||
Point start_source_point;
|
||||
Point end_source_point;
|
||||
const VD::edge_type *starting_voronoi_edge = nullptr;
|
||||
const VD::edge_type *ending_voronoi_edge = nullptr;
|
||||
// Compute and store result in above variables
|
||||
|
||||
if (cell.contains_point()) {
|
||||
@ -727,7 +442,12 @@ process_voronoi_diagram:
|
||||
continue;
|
||||
} else {
|
||||
assert(cell.contains_segment());
|
||||
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments);
|
||||
Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend());
|
||||
assert(cell_range.is_valid());
|
||||
start_source_point = cell_range.segment_start_point;
|
||||
end_source_point = cell_range.segment_end_point;
|
||||
starting_voronoi_edge = cell_range.edge_begin;
|
||||
ending_voronoi_edge = cell_range.edge_end;
|
||||
}
|
||||
|
||||
if (!starting_voronoi_edge || !ending_voronoi_edge) {
|
||||
@ -736,69 +456,28 @@ process_voronoi_diagram:
|
||||
}
|
||||
|
||||
// Copy start to end edge to graph
|
||||
edge_t* prev_edge = nullptr;
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
|
||||
edge_t *prev_edge = nullptr;
|
||||
transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
|
||||
starting_node->data.distance_to_boundary = 0;
|
||||
|
||||
constexpr bool is_next_to_start_or_end = true;
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
|
||||
for (vd_t::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
|
||||
for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
|
||||
assert(vd_edge->is_finite());
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
|
||||
|
||||
assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
|
||||
Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast<coord_t>();
|
||||
Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast<coord_t>();
|
||||
Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
|
||||
Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
|
||||
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
|
||||
}
|
||||
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
prev_edge->to->data.distance_to_boundary = 0;
|
||||
}
|
||||
|
||||
// For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar.
|
||||
// When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have
|
||||
// a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi
|
||||
// diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram.
|
||||
if (status == VoronoiDiagramStatus::NO_ISSUE_DETECTED && has_missing_twin_edge(this->graph)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION;
|
||||
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
|
||||
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input.";
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
this->graph.edges.clear();
|
||||
this->graph.nodes.clear();
|
||||
this->vd_edge_to_he_edge.clear();
|
||||
this->vd_node_to_he_node.clear();
|
||||
|
||||
goto process_voronoi_diagram;
|
||||
}
|
||||
|
||||
if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
|
||||
assert(!has_missing_twin_edge(this->graph));
|
||||
|
||||
if (has_missing_twin_edge(this->graph))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED)
|
||||
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
#endif
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <Arachne/utils/VoronoiUtils.hpp>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "utils/PolygonsSegmentIndex.hpp"
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
@ -26,8 +24,9 @@
|
||||
//#define ARACHNE_DEBUG
|
||||
//#define ARACHNE_DEBUG_VORONOI
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
/*!
|
||||
* Main class of the dynamic beading strategies.
|
||||
@ -50,8 +49,6 @@ deposition modeling" by Kuipers et al.
|
||||
*/
|
||||
class SkeletalTrapezoidation
|
||||
{
|
||||
using pos_t = double;
|
||||
using vd_t = boost::polygon::voronoi_diagram<pos_t>;
|
||||
using graph_t = SkeletalTrapezoidationGraph;
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
@ -83,7 +80,6 @@ class SkeletalTrapezoidation
|
||||
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using PointMap = ankerl::unordered_dense::map<Point, Point, PointHash>;
|
||||
using NodeSet = ankerl::unordered_dense::set<node_t*>;
|
||||
|
||||
/*!
|
||||
@ -168,9 +164,9 @@ protected:
|
||||
* mapping each voronoi VD edge to the corresponding halfedge HE edge
|
||||
* In case the result segment is discretized, we map the VD edge to the *last* HE edge
|
||||
*/
|
||||
ankerl::unordered_dense::map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge;
|
||||
ankerl::unordered_dense::map<vd_t::vertex_type*, node_t*> vd_node_to_he_node;
|
||||
node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
|
||||
ankerl::unordered_dense::map<const VD::edge_type *, edge_t *> vd_edge_to_he_edge;
|
||||
ankerl::unordered_dense::map<const VD::vertex_type *, node_t *> vd_node_to_he_node;
|
||||
node_t &makeNode(const VD::vertex_type &vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
|
||||
|
||||
/*!
|
||||
* (Eventual) returned 'polylines per index' result (from generateToolpaths):
|
||||
@ -181,7 +177,7 @@ protected:
|
||||
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
|
||||
* \p prev_edge serves as input and output. May be null as input.
|
||||
*/
|
||||
void transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments);
|
||||
void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments);
|
||||
|
||||
/*!
|
||||
* Discretize a Voronoi edge that represents the medial axis of a vertex-
|
||||
@ -208,7 +204,7 @@ protected:
|
||||
* \return A number of coordinates along the edge where the edge is broken
|
||||
* up into discrete pieces.
|
||||
*/
|
||||
Points discretize(const vd_t::edge_type& segment, const std::vector<Segment>& segments);
|
||||
Points discretize(const VD::edge_type& segment, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
@ -234,33 +230,7 @@ protected:
|
||||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
static bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
* graph that belongs to a line segment of the medial axis.
|
||||
*
|
||||
* This should only be used on cells that belong to a central line segment
|
||||
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
|
||||
*
|
||||
* The resulting line segments is just the first and the last segment. They
|
||||
* are linked to the neighboring segments, so you can iterate over the
|
||||
* segments until you reach the last segment.
|
||||
* \param cell The cell to compute the range of line segments for.
|
||||
* \param[out] start_source_point The start point of the source segment of
|
||||
* this cell.
|
||||
* \param[out] end_source_point The end point of the source segment of this
|
||||
* cell.
|
||||
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
|
||||
* loop around the cell starts.
|
||||
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
|
||||
* around the cell ends.
|
||||
* \param points All vertices of the input Polygons.
|
||||
* \param segments All edges of the input Polygons.
|
||||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
static void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
|
||||
static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments);
|
||||
|
||||
/*!
|
||||
* For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two
|
||||
@ -603,7 +573,7 @@ protected:
|
||||
*/
|
||||
void generateLocalMaximaSingleBeads();
|
||||
|
||||
friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments);
|
||||
friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
@ -156,8 +156,6 @@ struct PathsPointIndexLocator
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
|
||||
namespace std
|
||||
|
@ -27,5 +27,24 @@ public:
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace boost::polygon {
|
||||
|
||||
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
|
||||
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
|
||||
{
|
||||
return dir.to_int() ? CSegment.to() : CSegment.from();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost::polygon
|
||||
|
||||
#endif//UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
|
@ -1,251 +0,0 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <stack>
|
||||
#include <optional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "linearAlg2D.hpp"
|
||||
#include "VoronoiUtils.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
|
||||
{
|
||||
const double x = node->x();
|
||||
const double y = node->y();
|
||||
assert(std::isfinite(x) && std::isfinite(y));
|
||||
assert(x <= double(std::numeric_limits<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
|
||||
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::lowest());
|
||||
return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates.
|
||||
}
|
||||
|
||||
Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(cell.contains_point());
|
||||
if(!cell.contains_point())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
|
||||
|
||||
switch (cell.source_category()) {
|
||||
case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT:
|
||||
assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!";
|
||||
break;
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT:
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()].to();
|
||||
break;
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT:
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()].from();
|
||||
break;
|
||||
default:
|
||||
assert(false && "getSourcePoint should only be called on point cells!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
assert(false && "cell.source_category() is equal to an invalid value!\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!";
|
||||
return {};
|
||||
}
|
||||
|
||||
PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(cell.contains_point());
|
||||
if(!cell.contains_point())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
|
||||
|
||||
assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
|
||||
switch (cell.source_category()) {
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: {
|
||||
assert(cell.source_index() < segments.size());
|
||||
PolygonsPointIndex ret = segments[cell.source_index()];
|
||||
++ret;
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: {
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "getSourcePoint should only be called on point cells!\n");
|
||||
break;
|
||||
}
|
||||
PolygonsPointIndex ret = segments[cell.source_index()];
|
||||
return ++ret;
|
||||
}
|
||||
|
||||
const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments)
|
||||
{
|
||||
assert(cell.contains_segment());
|
||||
if (!cell.contains_segment())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!";
|
||||
|
||||
return segments[cell.source_index()];
|
||||
}
|
||||
|
||||
class PointMatrix
|
||||
{
|
||||
public:
|
||||
double matrix[4];
|
||||
|
||||
PointMatrix()
|
||||
{
|
||||
matrix[0] = 1;
|
||||
matrix[1] = 0;
|
||||
matrix[2] = 0;
|
||||
matrix[3] = 1;
|
||||
}
|
||||
|
||||
PointMatrix(double rotation)
|
||||
{
|
||||
rotation = rotation / 180 * M_PI;
|
||||
matrix[0] = cos(rotation);
|
||||
matrix[1] = -sin(rotation);
|
||||
matrix[2] = -matrix[1];
|
||||
matrix[3] = matrix[0];
|
||||
}
|
||||
|
||||
PointMatrix(const Point p)
|
||||
{
|
||||
matrix[0] = p.x();
|
||||
matrix[1] = p.y();
|
||||
double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1]));
|
||||
matrix[0] /= f;
|
||||
matrix[1] /= f;
|
||||
matrix[2] = -matrix[1];
|
||||
matrix[3] = matrix[0];
|
||||
}
|
||||
|
||||
static PointMatrix scale(double s)
|
||||
{
|
||||
PointMatrix ret;
|
||||
ret.matrix[0] = s;
|
||||
ret.matrix[3] = s;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Point apply(const Point p) const
|
||||
{
|
||||
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3]));
|
||||
}
|
||||
|
||||
Point unapply(const Point p) const
|
||||
{
|
||||
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3]));
|
||||
}
|
||||
};
|
||||
Points VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle)
|
||||
{
|
||||
Points discretized;
|
||||
// x is distance of point projected on the segment ab
|
||||
// xx is point projected on the segment ab
|
||||
const Point a = segment.from();
|
||||
const Point b = segment.to();
|
||||
const Point ab = b - a;
|
||||
const Point as = s - a;
|
||||
const Point ae = e - a;
|
||||
const coord_t ab_size = ab.cast<int64_t>().norm();
|
||||
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t sxex = ex - sx;
|
||||
|
||||
assert((as.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
assert((ae.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const Point ap = p - a;
|
||||
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
|
||||
assert((ap.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
Point pxx;
|
||||
Line(a, b).distance_to_infinite_squared(p, &pxx);
|
||||
const Point ppxx = pxx - p;
|
||||
const coord_t d = ppxx.cast<int64_t>().norm();
|
||||
const PointMatrix rot = PointMatrix(perp(ppxx));
|
||||
|
||||
if (d == 0)
|
||||
{
|
||||
discretized.emplace_back(s);
|
||||
discretized.emplace_back(e);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
const float marking_bound = atan(transitioning_angle * 0.5);
|
||||
int64_t msx = - marking_bound * int64_t(d); // projected marking_start
|
||||
int64_t mex = marking_bound * int64_t(d); // projected marking_end
|
||||
|
||||
assert(msx <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(mex <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
|
||||
Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx;
|
||||
Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx;
|
||||
const int dir = (sx > ex) ? -1 : 1;
|
||||
if (dir < 0)
|
||||
{
|
||||
std::swap(marking_start, marking_end);
|
||||
std::swap(msx, mex);
|
||||
}
|
||||
|
||||
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
|
||||
const Point apex = rot.unapply(Point(0, d / 2)) + pxx;
|
||||
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
|
||||
|
||||
assert(!(add_marking_start && add_marking_end) || add_apex);
|
||||
if(add_marking_start && add_marking_end && !add_apex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
|
||||
}
|
||||
|
||||
const coord_t step_count = static_cast<coord_t>(static_cast<float>(std::abs(ex - sx)) / approximate_step_size + 0.5);
|
||||
|
||||
discretized.emplace_back(s);
|
||||
for (coord_t step = 1; step < step_count; step++)
|
||||
{
|
||||
assert(double(sxex) * double(step) <= double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
|
||||
assert(double(x) * double(x) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
|
||||
|
||||
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir))
|
||||
{
|
||||
discretized.emplace_back(marking_start);
|
||||
add_marking_start = false;
|
||||
}
|
||||
if (add_apex && int64_t(x) * int64_t(dir) > 0)
|
||||
{
|
||||
discretized.emplace_back(apex);
|
||||
add_apex = false; // only add the apex just before the
|
||||
}
|
||||
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir))
|
||||
{
|
||||
discretized.emplace_back(marking_end);
|
||||
add_marking_end = false;
|
||||
}
|
||||
assert(x <= std::numeric_limits<coord_t>::max() && x >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(y <= std::numeric_limits<coord_t>::max() && y >= std::numeric_limits<coord_t>::lowest());
|
||||
const Point result = rot.unapply(Point(x, y)) + pxx;
|
||||
discretized.emplace_back(result);
|
||||
}
|
||||
if (add_apex)
|
||||
{
|
||||
discretized.emplace_back(apex);
|
||||
}
|
||||
if (add_marking_end)
|
||||
{
|
||||
discretized.emplace_back(marking_end);
|
||||
}
|
||||
discretized.emplace_back(e);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
}//namespace Slic3r::Arachne
|
@ -1,47 +0,0 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_VORONOI_UTILS_H
|
||||
#define UTILS_VORONOI_UTILS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include "PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
*/
|
||||
class VoronoiUtils
|
||||
{
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using voronoi_data_t = double;
|
||||
using vd_t = boost::polygon::voronoi_diagram<voronoi_data_t>;
|
||||
|
||||
static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
|
||||
static Vec2i64 p(const vd_t::vertex_type *node);
|
||||
|
||||
/*!
|
||||
* Discretize a parabola based on (approximate) step size.
|
||||
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
|
||||
*/
|
||||
static Points discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle);
|
||||
|
||||
static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex)
|
||||
{
|
||||
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_VORONOI_UTILS_H
|
@ -292,7 +292,7 @@ class DefaultArranger: public Arranger<ArrItem> {
|
||||
|
||||
firstfit::SelectionStrategy sel{cmpfn, on_arranged, stop_cond};
|
||||
|
||||
constexpr auto ep = ex_seq;
|
||||
constexpr auto ep = ex_tbb;
|
||||
|
||||
VariantKernel basekernel;
|
||||
switch (m_settings.get_arrange_strategy()) {
|
||||
|
@ -214,6 +214,8 @@ set(SLIC3R_SOURCES
|
||||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
JumpPointSearch.cpp
|
||||
@ -464,7 +466,6 @@ set(SLIC3R_SOURCES
|
||||
BranchingTree/BranchingTree.hpp
|
||||
BranchingTree/PointCloud.cpp
|
||||
BranchingTree/PointCloud.hpp
|
||||
|
||||
Arachne/BeadingStrategy/BeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
|
||||
@ -495,8 +496,9 @@ set(SLIC3R_SOURCES
|
||||
Arachne/utils/PolygonsSegmentIndex.hpp
|
||||
Arachne/utils/PolylineStitcher.hpp
|
||||
Arachne/utils/PolylineStitcher.cpp
|
||||
Arachne/utils/VoronoiUtils.hpp
|
||||
Arachne/utils/VoronoiUtils.cpp
|
||||
Geometry/Voronoi.cpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Arachne/SkeletalTrapezoidation.hpp
|
||||
Arachne/SkeletalTrapezoidation.cpp
|
||||
Arachne/SkeletalTrapezoidationEdge.hpp
|
||||
|
@ -265,6 +265,8 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
||||
{
|
||||
if (this->nullable) {
|
||||
switch (this->type) {
|
||||
case coFloat: return new ConfigOptionFloatNullable();
|
||||
case coInt: return new ConfigOptionIntNullable();
|
||||
case coFloats: return new ConfigOptionFloatsNullable();
|
||||
case coInts: return new ConfigOptionIntsNullable();
|
||||
case coPercents: return new ConfigOptionPercentsNullable();
|
||||
@ -1432,6 +1434,9 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<std::string>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle<bool>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable<double>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable<int>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingleNullable<bool>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<double>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<int>)
|
||||
@ -1439,9 +1444,11 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<std::string>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatNullable)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntNullable)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString)
|
||||
@ -1468,6 +1475,9 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionS
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<Slic3r::Vec3d>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle<bool>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable<double>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable<int>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingleNullable<bool>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<double>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<int>)
|
||||
@ -1475,9 +1485,11 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::Con
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<double>, Slic3r::ConfigOptionFloat)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingleNullable<double>, Slic3r::ConfigOptionFloatNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloats)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloatsNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<int>, Slic3r::ConfigOptionInt)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingleNullable<int>, Slic3r::ConfigOptionIntNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionInts)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionIntsNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString)
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
#include <climits>
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
@ -60,9 +61,9 @@ namespace Slic3r {
|
||||
template<class Archive> void serialize(Archive& ar) { ar(this->value); ar(this->percent); }
|
||||
};
|
||||
|
||||
inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value == r.value && l.percent == r.percent; }
|
||||
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) throw() { return !(l == r); }
|
||||
inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) throw() { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); }
|
||||
inline bool operator==(const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return l.value == r.value && l.percent == r.percent; }
|
||||
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return !(l == r); }
|
||||
inline bool operator< (const FloatOrPercent& l, const FloatOrPercent& r) noexcept { return l.value < r.value || (l.value == r.value && int(l.percent) < int(r.percent)); }
|
||||
}
|
||||
|
||||
namespace std {
|
||||
@ -319,8 +320,56 @@ public:
|
||||
typedef ConfigOption* ConfigOptionPtr;
|
||||
typedef const ConfigOption* ConfigOptionConstPtr;
|
||||
|
||||
// Nill value will be defined in specializations
|
||||
template<class T, class En = void> struct NilValueTempl
|
||||
{
|
||||
using NilType = T;
|
||||
static_assert(always_false<T>::value, "Type has no well defined nil value");
|
||||
};
|
||||
|
||||
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_integral_v<T>, void>> {
|
||||
using NilType = T;
|
||||
static constexpr auto value = std::numeric_limits<T>::max();
|
||||
};
|
||||
|
||||
template<> struct NilValueTempl<bool> : public NilValueTempl<int>{};
|
||||
|
||||
// For enums the nil is the max value of the underlying type.
|
||||
template<class T>
|
||||
struct NilValueTempl<T, std::enable_if_t<std::is_enum_v<T>, void>>
|
||||
{
|
||||
using NilType = T;
|
||||
static constexpr auto value = static_cast<T>(std::numeric_limits<std::underlying_type_t<T>>::max());
|
||||
};
|
||||
|
||||
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_floating_point_v<T>, void>> {
|
||||
using NilType = T;
|
||||
static constexpr auto value = std::numeric_limits<T>::quiet_NaN();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct NilValueTempl<FloatOrPercent> : public NilValueTempl<double> {};
|
||||
|
||||
template<> struct NilValueTempl<std::string> {
|
||||
using NilType = const char *;
|
||||
|
||||
static constexpr const char* value = "";
|
||||
};
|
||||
|
||||
template<int N, class T> struct NilValueTempl<Vec<N, T>> {
|
||||
using NilType = Vec<N, T>;
|
||||
// No constexpr for Vec<N, T>
|
||||
static inline const Vec<N, T> value = Vec<N, T>::Ones() * NilValueTempl<remove_cvref_t<T>>::value;
|
||||
};
|
||||
|
||||
template<class T> using NilType = typename NilValueTempl<remove_cvref_t<T>>::NilType;
|
||||
|
||||
// Define shortcut as a function instead of a static const var so that it can be constexpr
|
||||
// even if the NilValueTempl::value is not constexpr.
|
||||
template<class T> static constexpr NilType<T> NilValue() noexcept { return NilValueTempl<remove_cvref_t<T>>::value; }
|
||||
|
||||
// Value of a single valued option (bool, int, float, string, point, enum)
|
||||
template <class T>
|
||||
template <class T, bool NULLABLE = false>
|
||||
class ConfigOptionSingle : public ConfigOption {
|
||||
public:
|
||||
T value;
|
||||
@ -331,16 +380,18 @@ public:
|
||||
{
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
|
||||
assert(dynamic_cast<const ConfigOptionSingle*>(rhs));
|
||||
this->value = static_cast<const ConfigOptionSingle*>(rhs)->value;
|
||||
}
|
||||
|
||||
bool operator==(const ConfigOption &rhs) const override
|
||||
{
|
||||
if (rhs.type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
|
||||
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
|
||||
assert(dynamic_cast<const ConfigOptionSingle*>(&rhs));
|
||||
if (this->is_nil() && rhs.is_nil())
|
||||
return true;
|
||||
return this->value == static_cast<const ConfigOptionSingle*>(&rhs)->value;
|
||||
}
|
||||
|
||||
bool operator==(const T &rhs) const throw() { return this->value == rhs; }
|
||||
@ -349,11 +400,67 @@ public:
|
||||
|
||||
size_t hash() const throw() override { return std::hash<T>{}(this->value); }
|
||||
|
||||
// Is this option overridden by another option?
|
||||
// An option overrides another option if it is not nil and not equal.
|
||||
bool overriden_by(const ConfigOption *rhs) const override {
|
||||
if (this->nullable())
|
||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
|
||||
auto rhs_co = static_cast<const ConfigOptionSingle*>(rhs);
|
||||
if (! rhs->nullable())
|
||||
// Overridding a non-nullable object with another non-nullable object.
|
||||
return this->value != rhs_co->value;
|
||||
|
||||
return !rhs_co->is_nil() && rhs_co->value != this->value;
|
||||
}
|
||||
// Apply an override option, possibly a nullable one.
|
||||
bool apply_override(const ConfigOption *rhs) override {
|
||||
if (this->nullable())
|
||||
throw ConfigurationError("Cannot override a nullable ConfigOption.");
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
|
||||
auto rhs_co = static_cast<const ConfigOptionSingle*>(rhs);
|
||||
if (! rhs->nullable()) {
|
||||
// Overridding a non-nullable object with another non-nullable object.
|
||||
if (this->value != rhs_co->value) {
|
||||
this->value = rhs_co->value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rhs_co->is_nil() && rhs_co->value != this->value) {
|
||||
this->value = rhs_co->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool nullable() const override { return NULLABLE; }
|
||||
|
||||
static constexpr NilType<T> nil_value() { return NilValue<T>(); }
|
||||
|
||||
// A scalar is nil, or all values of a vector are nil.
|
||||
bool is_nil() const override
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if constexpr (NULLABLE)
|
||||
ret = this->value == nil_value();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive & ar) { ar(this->value); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
using ConfigOptionSingleNullable = ConfigOptionSingle<T, true>;
|
||||
|
||||
// Value of a vector valued option (bools, ints, floats, strings, points)
|
||||
class ConfigOptionVectorBase : public ConfigOption {
|
||||
public:
|
||||
@ -572,23 +679,35 @@ private:
|
||||
template<class Archive> void serialize(Archive & ar) { ar(this->values); }
|
||||
};
|
||||
|
||||
class ConfigOptionFloat : public ConfigOptionSingle<double>
|
||||
template<bool NULLABLE = false>
|
||||
class ConfigOptionFloatTempl : public ConfigOptionSingle<double, NULLABLE>
|
||||
{
|
||||
public:
|
||||
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}
|
||||
explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}
|
||||
ConfigOptionFloatTempl() : ConfigOptionSingle<double, NULLABLE>(0) {}
|
||||
explicit ConfigOptionFloatTempl(double _value) : ConfigOptionSingle<double, NULLABLE>(_value) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coFloat; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
double getFloat() const override { return this->value; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloat(*this); }
|
||||
bool operator==(const ConfigOptionFloat &rhs) const throw() { return this->value == rhs.value; }
|
||||
bool operator< (const ConfigOptionFloat &rhs) const throw() { return this->value < rhs.value; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloatTempl(*this); }
|
||||
bool operator==(const ConfigOptionFloatTempl &rhs) const throw() { return this->value == rhs.value; }
|
||||
bool operator< (const ConfigOptionFloatTempl &rhs) const throw() { return this->value < rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << this->value;
|
||||
double v = this->value;
|
||||
|
||||
if (std::isfinite(v))
|
||||
ss << v;
|
||||
else if (std::isnan(v)) {
|
||||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
} else
|
||||
throw ConfigurationError("Serializing invalid number");
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
@ -596,19 +715,33 @@ public:
|
||||
{
|
||||
UNUSED(append);
|
||||
std::istringstream iss(str);
|
||||
iss >> this->value;
|
||||
|
||||
if (str == "nil") {
|
||||
if (NULLABLE)
|
||||
this->value = this->nil_value();
|
||||
else
|
||||
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
iss >> this->value;
|
||||
}
|
||||
|
||||
return !iss.fail();
|
||||
}
|
||||
|
||||
ConfigOptionFloat& operator=(const ConfigOption *opt)
|
||||
ConfigOptionFloatTempl& operator=(const ConfigOption *opt)
|
||||
{
|
||||
this->set(opt);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_nil() const override
|
||||
{
|
||||
return std::isnan(this->value);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double>>(this)); }
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double, NULLABLE>>(this)); }
|
||||
};
|
||||
|
||||
template<bool NULLABLE>
|
||||
@ -734,27 +867,37 @@ private:
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<double>>(this)); }
|
||||
};
|
||||
|
||||
using ConfigOptionFloat = ConfigOptionFloatTempl<false>;
|
||||
using ConfigOptionFloatNullable = ConfigOptionFloatTempl<true>;
|
||||
using ConfigOptionFloats = ConfigOptionFloatsTempl<false>;
|
||||
using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl<true>;
|
||||
|
||||
class ConfigOptionInt : public ConfigOptionSingle<int>
|
||||
template<bool NULLABLE = false>
|
||||
class ConfigOptionIntTempl : public ConfigOptionSingle<int, NULLABLE>
|
||||
{
|
||||
public:
|
||||
ConfigOptionInt() : ConfigOptionSingle<int>(0) {}
|
||||
explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}
|
||||
explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}
|
||||
ConfigOptionIntTempl() : ConfigOptionSingle<int, NULLABLE>(0) {}
|
||||
explicit ConfigOptionIntTempl(int value) : ConfigOptionSingle<int, NULLABLE>(value) {}
|
||||
explicit ConfigOptionIntTempl(double _value) : ConfigOptionSingle<int, NULLABLE>(int(floor(_value + 0.5))) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coInt; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
int getInt() const override { return this->value; }
|
||||
void setInt(int val) override { this->value = val; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionInt(*this); }
|
||||
bool operator==(const ConfigOptionInt &rhs) const throw() { return this->value == rhs.value; }
|
||||
ConfigOption* clone() const override { return new ConfigOptionIntTempl(*this); }
|
||||
bool operator==(const ConfigOptionIntTempl &rhs) const throw() { return this->value == rhs.value; }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << this->value;
|
||||
if (this->value == this->nil_value()) {
|
||||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
} else
|
||||
ss << this->value;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
@ -762,11 +905,20 @@ public:
|
||||
{
|
||||
UNUSED(append);
|
||||
std::istringstream iss(str);
|
||||
iss >> this->value;
|
||||
|
||||
if (str == "nil") {
|
||||
if (NULLABLE)
|
||||
this->value = this->nil_value();
|
||||
else
|
||||
throw ConfigurationError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
iss >> this->value;
|
||||
}
|
||||
|
||||
return !iss.fail();
|
||||
}
|
||||
|
||||
ConfigOptionInt& operator=(const ConfigOption *opt)
|
||||
ConfigOptionIntTempl& operator=(const ConfigOption *opt)
|
||||
{
|
||||
this->set(opt);
|
||||
return *this;
|
||||
@ -774,9 +926,12 @@ public:
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<int>>(this)); }
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<int, NULLABLE>>(this)); }
|
||||
};
|
||||
|
||||
using ConfigOptionInt = ConfigOptionIntTempl<false>;
|
||||
using ConfigOptionIntNullable = ConfigOptionIntTempl<true>;
|
||||
|
||||
template<bool NULLABLE>
|
||||
class ConfigOptionIntsTempl : public ConfigOptionVector<int>
|
||||
{
|
||||
@ -1860,6 +2015,8 @@ public:
|
||||
template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
|
||||
if (this->nullable) {
|
||||
switch (this->type) {
|
||||
case coFloat: { auto opt = new ConfigOptionFloatNullable(); archive(*opt); return opt; }
|
||||
case coInt: { auto opt = new ConfigOptionIntNullable(); archive(*opt); return opt; }
|
||||
case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; }
|
||||
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
|
||||
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
|
||||
@ -1892,6 +2049,8 @@ public:
|
||||
template<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
|
||||
if (this->nullable) {
|
||||
switch (this->type) {
|
||||
case coFloat: archive(*static_cast<const ConfigOptionFloatNullable*>(opt)); break;
|
||||
case coInt: archive(*static_cast<const ConfigOptionIntNullable*>(opt)); break;
|
||||
case coFloats: archive(*static_cast<const ConfigOptionFloatsNullable*>(opt)); break;
|
||||
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
|
||||
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
|
||||
|
@ -72,6 +72,22 @@ std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& cus
|
||||
return custom_tool_changes;
|
||||
}
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// Where print_z corresponds to the layer on which we perform a color change for the specified extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders)
|
||||
{
|
||||
std::vector<std::pair<double, unsigned int>> custom_color_changes;
|
||||
for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes)
|
||||
if (custom_gcode.type == ColorChange) {
|
||||
// If extruder count in PrinterSettings was changed, ignore custom g-codes for extruder ids bigger than num_extruders.
|
||||
assert(custom_gcode.extruder >= 0);
|
||||
if (size_t(custom_gcode.extruder) <= num_extruders) {
|
||||
custom_color_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(custom_gcode.extruder));
|
||||
}
|
||||
}
|
||||
return custom_color_changes;
|
||||
}
|
||||
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -91,6 +91,10 @@ extern void check_mode_for_custom_gcode_per_print_z(Info& info);
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// Where print_z corresponds to the layer on which we perform a color change for the specified extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
|
||||
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -124,7 +124,7 @@ static constexpr const char* PRINTABLE_ATTR = "printable";
|
||||
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
|
||||
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
|
||||
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
|
||||
static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
|
||||
static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
|
||||
|
||||
static constexpr const char* KEY_ATTR = "key";
|
||||
static constexpr const char* VALUE_ATTR = "value";
|
||||
@ -362,7 +362,7 @@ namespace Slic3r {
|
||||
std::vector<Vec3i> triangles;
|
||||
std::vector<std::string> custom_supports;
|
||||
std::vector<std::string> custom_seam;
|
||||
std::vector<std::string> mmu_segmentation;
|
||||
std::vector<std::string> mm_segmentation;
|
||||
|
||||
bool empty() { return vertices.empty() || triangles.empty(); }
|
||||
|
||||
@ -371,7 +371,7 @@ namespace Slic3r {
|
||||
triangles.clear();
|
||||
custom_supports.clear();
|
||||
custom_seam.clear();
|
||||
mmu_segmentation.clear();
|
||||
mm_segmentation.clear();
|
||||
}
|
||||
};
|
||||
|
||||
@ -706,12 +706,20 @@ namespace Slic3r {
|
||||
m_name = boost::filesystem::path(filename).stem().string();
|
||||
|
||||
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
|
||||
bool found_model = false;
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
std::string name(stat.m_filename);
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
|
||||
if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) {
|
||||
if (boost::algorithm::iends_with(name, MODEL_EXTENSION)) {
|
||||
if(found_model){
|
||||
close_zip_reader(&archive);
|
||||
add_error("3mf contain multiple .model files and it is not supported yet.");
|
||||
return false;
|
||||
}
|
||||
found_model = true;
|
||||
|
||||
try
|
||||
{
|
||||
// valid model name -> extract model
|
||||
@ -730,6 +738,11 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found_model) {
|
||||
close_zip_reader(&archive);
|
||||
add_error("Not valid 3mf. There is missing .model file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// we then loop again the entries to read other files stored in the archive
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
@ -1830,7 +1843,7 @@ namespace Slic3r {
|
||||
|
||||
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
|
||||
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
||||
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
||||
m_curr_object.geometry.mm_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MM_SEGMENTATION_ATTR));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2320,25 +2333,25 @@ namespace Slic3r {
|
||||
if (has_transform)
|
||||
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
|
||||
|
||||
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
|
||||
// recreate custom supports, seam and mm segmentation from previously loaded attribute
|
||||
volume->supported_facets.reserve(triangles_count);
|
||||
volume->seam_facets.reserve(triangles_count);
|
||||
volume->mmu_segmentation_facets.reserve(triangles_count);
|
||||
volume->mm_segmentation_facets.reserve(triangles_count);
|
||||
for (size_t i=0; i<triangles_count; ++i) {
|
||||
size_t index = volume_data.first_triangle_id + i;
|
||||
assert(index < geometry.custom_supports.size());
|
||||
assert(index < geometry.custom_seam.size());
|
||||
assert(index < geometry.mmu_segmentation.size());
|
||||
assert(index < geometry.mm_segmentation.size());
|
||||
if (! geometry.custom_supports[index].empty())
|
||||
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
|
||||
if (! geometry.custom_seam[index].empty())
|
||||
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
|
||||
if (! geometry.mmu_segmentation[index].empty())
|
||||
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]);
|
||||
if (! geometry.mm_segmentation[index].empty())
|
||||
volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]);
|
||||
}
|
||||
volume->supported_facets.shrink_to_fit();
|
||||
volume->seam_facets.shrink_to_fit();
|
||||
volume->mmu_segmentation_facets.shrink_to_fit();
|
||||
volume->mm_segmentation_facets.shrink_to_fit();
|
||||
|
||||
if (auto &es = volume_data.shape_configuration; es.has_value())
|
||||
volume->emboss_shape = std::move(es);
|
||||
@ -3002,12 +3015,12 @@ namespace Slic3r {
|
||||
output_buffer += "\"";
|
||||
}
|
||||
|
||||
std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i);
|
||||
if (! mmu_painting_data_string.empty()) {
|
||||
std::string mm_painting_data_string = volume->mm_segmentation_facets.get_triangle_as_string(i);
|
||||
if (! mm_painting_data_string.empty()) {
|
||||
output_buffer += " ";
|
||||
output_buffer += MMU_SEGMENTATION_ATTR;
|
||||
output_buffer += MM_SEGMENTATION_ATTR;
|
||||
output_buffer += "=\"";
|
||||
output_buffer += mmu_painting_data_string;
|
||||
output_buffer += mm_painting_data_string;
|
||||
output_buffer += "\"";
|
||||
}
|
||||
|
||||
@ -3574,11 +3587,11 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo
|
||||
// All import should use "C" locales for number formatting.
|
||||
CNumericLocalesSetter locales_setter;
|
||||
_3MF_Importer importer;
|
||||
importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
|
||||
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
|
||||
importer.log_errors();
|
||||
handle_legacy_project_loaded(importer.version(), config, importer.prusaslicer_generator_version());
|
||||
|
||||
return !model->objects.empty() || !config.empty();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64)
|
||||
|
@ -45,8 +45,9 @@ namespace {
|
||||
std::string to_ini(const ConfMap &m)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto ¶m : m) ret += param.first + " = " + param.second + "\n";
|
||||
|
||||
for (auto ¶m : m)
|
||||
ret += param.first + " = " + param.second + "\n";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ std::pair<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
|
||||
// as output argument then replace it with the readed profile to report
|
||||
// that it was empty.
|
||||
profile_use = profile_in.empty() ? profile_out : profile_in;
|
||||
profile_out = profile_in;
|
||||
profile_out += std::move(profile_in);
|
||||
|
||||
return {profile_use, std::move(config_substitutions)};
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &write
|
||||
this->parser.set("zhop", this->opt_zhop);
|
||||
}
|
||||
|
||||
void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer)
|
||||
void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer, const WipeTowerData& wipe_tower_data)
|
||||
{
|
||||
memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3);
|
||||
this->opt_position->values = this->position;
|
||||
@ -253,7 +253,19 @@ void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const
|
||||
for (const Extruder &e : extruders) {
|
||||
this->e_retracted[e.id()] = e.retracted();
|
||||
this->e_restart_extra[e.id()] = e.restart_extra();
|
||||
double v = e.extruded_volume();
|
||||
|
||||
// Wipe tower filament consumption has to be added separately, because that gcode is not generated by GCodeWriter.
|
||||
double wt_vol = 0.;
|
||||
const std::vector<std::pair<float, std::vector<float>>>& wtuf = wipe_tower_data.used_filament_until_layer;
|
||||
if (!wtuf.empty()) {
|
||||
auto it = std::lower_bound(wtuf.begin(), wtuf.end(), writer.get_position().z(),
|
||||
[](const auto& a, const float& val) { return a.first < val; });
|
||||
if (it == wtuf.end())
|
||||
it = wtuf.end() - 1;
|
||||
wt_vol = it->second[e.id()] * e.filament_crossection();
|
||||
}
|
||||
|
||||
double v = e.extruded_volume() + wt_vol;
|
||||
double w = v * e.filament_density() * 0.001;
|
||||
this->opt_extruded_volume->values[e.id()] = v;
|
||||
this->opt_extruded_weight->values[e.id()] = w;
|
||||
@ -545,6 +557,29 @@ namespace DoExport {
|
||||
}
|
||||
} // namespace DoExport
|
||||
|
||||
GCodeGenerator::GCodeGenerator(const Print* print) :
|
||||
m_origin(Vec2d::Zero()),
|
||||
m_enable_loop_clipping(true),
|
||||
m_enable_cooling_markers(false),
|
||||
m_enable_extrusion_role_markers(false),
|
||||
m_last_processor_extrusion_role(GCodeExtrusionRole::None),
|
||||
m_layer_count(0),
|
||||
m_layer_index(-1),
|
||||
m_layer(nullptr),
|
||||
m_object_layer_over_raft(false),
|
||||
m_volumetric_speed(0),
|
||||
m_last_extrusion_role(GCodeExtrusionRole::None),
|
||||
m_last_width(0.0f),
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_last_mm3_per_mm(0.0),
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_brim_done(false),
|
||||
m_second_layer_things_done(false),
|
||||
m_silent_time_estimator_enabled(false),
|
||||
m_current_instance({nullptr, -1}),
|
||||
m_print(print)
|
||||
{}
|
||||
|
||||
void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter;
|
||||
@ -742,8 +777,8 @@ namespace DoExport {
|
||||
print_statistics.printing_extruders.emplace_back(extruder.id());
|
||||
filament_types.emplace_back(config.filament_type.get_at(extruder.id()));
|
||||
|
||||
double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f);
|
||||
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
|
||||
double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament_until_layer.back().second[extruder.id()] : 0.f);
|
||||
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament_until_layer.back().second[extruder.id()] * extruder.filament_crossection() : 0.f); // assumes 1.75mm filament diameter
|
||||
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
|
||||
double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
|
||||
auto append = [&extruder](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
|
||||
@ -896,7 +931,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
binary_data.file_metadata.raw_data.emplace_back("Producer", std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION));
|
||||
|
||||
// config data
|
||||
encode_full_config(print, binary_data.slicer_metadata.raw_data);
|
||||
encode_full_config(*m_print, binary_data.slicer_metadata.raw_data);
|
||||
|
||||
// printer data - this section contains duplicates from the slicer metadata
|
||||
// that we just created. Find and copy the entries that we want to duplicate.
|
||||
@ -930,7 +965,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
m_last_mm3_per_mm = 0.;
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
// How many times will be change_layer() called?
|
||||
// How many times will be change_layer() called?gcode.cpp
|
||||
// change_layer() in turn increments the progress bar status.
|
||||
m_layer_count = 0;
|
||||
if (print.config().complete_objects.value) {
|
||||
@ -1384,7 +1419,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
{
|
||||
file.write("\n; prusaslicer_config = begin\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
append_full_config(*m_print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
file.write("; prusaslicer_config = end\n");
|
||||
@ -1634,7 +1669,7 @@ std::string GCodeGenerator::placeholder_parser_process(
|
||||
|
||||
PlaceholderParserIntegration &ppi = m_placeholder_parser_integration;
|
||||
try {
|
||||
ppi.update_from_gcodewriter(m_writer);
|
||||
ppi.update_from_gcodewriter(m_writer, m_print->wipe_tower_data());
|
||||
std::string output = ppi.parser.process(templ, current_extruder_id, config_override, &ppi.output_config, &ppi.context);
|
||||
ppi.validate_output_vector_variables();
|
||||
|
||||
@ -1740,7 +1775,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
|
||||
|
||||
// Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters.
|
||||
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
|
||||
void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, Print &print)
|
||||
void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, const Print &print)
|
||||
{
|
||||
const GCodeFlavor flavor = print.config().gcode_flavor.value;
|
||||
if ( (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware)
|
||||
@ -1801,7 +1836,7 @@ void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, Print &prin
|
||||
// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
|
||||
// M140 - Set Extruder Temperature
|
||||
// M190 - Set Extruder Temperature and Wait
|
||||
void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
{
|
||||
bool autoemit = print.config().autoemit_temperature_commands;
|
||||
// Initial bed temperature based on the first extruder.
|
||||
@ -1823,7 +1858,7 @@ void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file,
|
||||
// M104 - Set Extruder Temperature
|
||||
// M109 - Set Extruder Temperature and Wait
|
||||
// RepRapFirmware: G10 Sxx
|
||||
void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
|
||||
{
|
||||
bool autoemit = print.config().autoemit_temperature_commands;
|
||||
// Is the bed temperature set by the provided custom G-code?
|
||||
@ -1901,84 +1936,92 @@ std::vector<GCodeGenerator::InstanceToPrint> GCodeGenerator::sort_print_object_i
|
||||
namespace ProcessLayer
|
||||
{
|
||||
|
||||
static std::string emit_custom_color_change_gcode_per_print_z(
|
||||
GCodeGenerator &gcodegen,
|
||||
const CustomGCode::Item &custom_gcode,
|
||||
unsigned int current_extruder_id,
|
||||
unsigned int first_extruder_id, // ID of the first extruder printing this layer.
|
||||
const PrintConfig &config
|
||||
) {
|
||||
const bool single_extruder_multi_material = config.single_extruder_multi_material;
|
||||
const bool single_extruder_printer = config.nozzle_diameter.size() == 1;
|
||||
const bool color_change = custom_gcode.type == CustomGCode::ColorChange;
|
||||
|
||||
std::string gcode;
|
||||
|
||||
int color_change_extruder = -1;
|
||||
if (color_change && custom_gcode.extruder > 0)
|
||||
color_change_extruder = custom_gcode.extruder - 1;
|
||||
|
||||
assert(color_change_extruder >= 0);
|
||||
// Color Change or Tool Change as Color Change.
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(color_change_extruder) + "," + custom_gcode.color + "\n";
|
||||
|
||||
DynamicConfig cfg;
|
||||
cfg.set_key_value("color_change_extruder", new ConfigOptionInt(color_change_extruder));
|
||||
if (single_extruder_multi_material && !single_extruder_printer && color_change_extruder >= 0 && first_extruder_id != unsigned(color_change_extruder)) {
|
||||
//! FIXME_in_fw show message during print pause
|
||||
// FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"?
|
||||
gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg);
|
||||
gcode += "\n";
|
||||
gcode += "M117 Change filament for Extruder " + std::to_string(color_change_extruder) + "\n";
|
||||
} else {
|
||||
gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id, &cfg);
|
||||
gcode += "\n";
|
||||
//FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after
|
||||
// return from M600. Thus the G-code generated by the following line is ignored.
|
||||
// see GH issue #6362
|
||||
gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
static std::string emit_custom_gcode_per_print_z(
|
||||
GCodeGenerator &gcodegen,
|
||||
const CustomGCode::Item *custom_gcode,
|
||||
const CustomGCode::Item &custom_gcode,
|
||||
unsigned int current_extruder_id,
|
||||
// ID of the first extruder printing this layer.
|
||||
unsigned int first_extruder_id,
|
||||
const PrintConfig &config)
|
||||
{
|
||||
std::string gcode;
|
||||
bool single_extruder_printer = config.nozzle_diameter.size() == 1;
|
||||
|
||||
if (custom_gcode != nullptr) {
|
||||
// Extruder switches are processed by LayerTools, they should be filtered out.
|
||||
assert(custom_gcode->type != CustomGCode::ToolChange);
|
||||
// Extruder switches are processed by LayerTools, they should be filtered out.
|
||||
assert(custom_gcode.type != CustomGCode::ToolChange);
|
||||
|
||||
CustomGCode::Type gcode_type = custom_gcode->type;
|
||||
bool color_change = gcode_type == CustomGCode::ColorChange;
|
||||
bool tool_change = gcode_type == CustomGCode::ToolChange;
|
||||
// Tool Change is applied as Color Change for a single extruder printer only.
|
||||
assert(! tool_change || single_extruder_printer);
|
||||
CustomGCode::Type gcode_type = custom_gcode.type;
|
||||
const bool color_change = gcode_type == CustomGCode::ColorChange;
|
||||
const bool tool_change = gcode_type == CustomGCode::ToolChange;
|
||||
// Tool Change is applied as Color Change for a single extruder printer only.
|
||||
assert(!tool_change || config.nozzle_diameter.size() == 1);
|
||||
|
||||
std::string pause_print_msg;
|
||||
int m600_extruder_before_layer = -1;
|
||||
if (color_change && custom_gcode->extruder > 0)
|
||||
m600_extruder_before_layer = custom_gcode->extruder - 1;
|
||||
else if (gcode_type == CustomGCode::PausePrint)
|
||||
pause_print_msg = custom_gcode->extra;
|
||||
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
|
||||
if (color_change || tool_change) {
|
||||
gcode += emit_custom_color_change_gcode_per_print_z(gcodegen, custom_gcode, current_extruder_id, first_extruder_id, config);
|
||||
} else {
|
||||
if (gcode_type == CustomGCode::PausePrint) { // Pause print
|
||||
const std::string pause_print_msg = custom_gcode.extra;
|
||||
|
||||
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
|
||||
if (color_change || tool_change)
|
||||
{
|
||||
assert(m600_extruder_before_layer >= 0);
|
||||
// Color Change or Tool Change as Color Change.
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(m600_extruder_before_layer) + "," + custom_gcode->color + "\n";
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n";
|
||||
//! FIXME_in_fw show message during print pause
|
||||
if (!pause_print_msg.empty())
|
||||
gcode += "M117 " + pause_print_msg + "\n";
|
||||
|
||||
if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer
|
||||
// && !MMU1
|
||||
) {
|
||||
//! FIXME_in_fw show message during print pause
|
||||
// FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"? Why is that not
|
||||
// passed to color_change_gcode below?
|
||||
DynamicConfig cfg;
|
||||
cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer));
|
||||
gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg);
|
||||
gcode += "\n";
|
||||
gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
|
||||
}
|
||||
else {
|
||||
gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id);
|
||||
gcode += "\n";
|
||||
//FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after
|
||||
// return from M600. Thus the G-code generated by the following line is ignored.
|
||||
// see GH issue #6362
|
||||
gcodegen.writer().unretract();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (gcode_type == CustomGCode::PausePrint) // Pause print
|
||||
{
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n";
|
||||
//! FIXME_in_fw show message during print pause
|
||||
if (!pause_print_msg.empty())
|
||||
gcode += "M117 " + pause_print_msg + "\n";
|
||||
gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id);
|
||||
}
|
||||
else {
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n";
|
||||
if (gcode_type == CustomGCode::Template) // Template Custom Gcode
|
||||
gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id);
|
||||
else // custom Gcode
|
||||
gcode += custom_gcode->extra;
|
||||
|
||||
}
|
||||
gcode += "\n";
|
||||
DynamicConfig cfg;
|
||||
cfg.set_key_value("color_change_extruder", new ConfigOptionInt(int(current_extruder_id)));
|
||||
gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg);
|
||||
} else {
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n";
|
||||
if (gcode_type == CustomGCode::Template) // Template Custom Gcode
|
||||
gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id);
|
||||
else // custom Gcode
|
||||
gcode += custom_gcode.extra;
|
||||
}
|
||||
gcode += "\n";
|
||||
}
|
||||
|
||||
return gcode;
|
||||
@ -2081,8 +2124,10 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3
|
||||
elevation_params.lift_height = std::max(z_change, elevation_params.lift_height);
|
||||
|
||||
const double path_length = unscaled(xy_path.length());
|
||||
const double lift_at_travel_end =
|
||||
(elevation_params.lift_height / elevation_params.slope_end * path_length);
|
||||
const double lift_at_travel_end = std::min(
|
||||
elevation_params.lift_height,
|
||||
elevation_params.lift_height / elevation_params.slope_end * path_length
|
||||
);
|
||||
if (lift_at_travel_end < z_change) {
|
||||
elevation_params.lift_height = z_change;
|
||||
elevation_params.slope_end = path_length;
|
||||
@ -2101,7 +2146,7 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3
|
||||
|
||||
std::string travel_gcode;
|
||||
Vec3d previous_point{this->point_to_gcode(travel.front())};
|
||||
for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
|
||||
for (const Vec3crd& point : travel) {
|
||||
const Vec3d gcode_point{this->point_to_gcode(point)};
|
||||
travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change");
|
||||
previous_point = gcode_point;
|
||||
@ -2198,6 +2243,7 @@ LayerResult GCodeGenerator::process_layer(
|
||||
m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z);
|
||||
m_last_height = height;
|
||||
m_current_layer_first_position = std::nullopt;
|
||||
m_already_unretracted = false;
|
||||
|
||||
// Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
|
||||
if (!first_layer && ! print.config().before_layer_gcode.value.empty()) {
|
||||
@ -2247,10 +2293,6 @@ LayerResult GCodeGenerator::process_layer(
|
||||
// Map from extruder ID to <begin, end> index of skirt loops to be extruded with that extruder.
|
||||
std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder;
|
||||
|
||||
if (single_object_instance_idx == size_t(-1)) {
|
||||
// Normal (non-sequential) print.
|
||||
gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config());
|
||||
}
|
||||
// Extrude skirt at the print_z of the raft layers and normal object layers
|
||||
// not at the print_z of the interlaced support material layers.
|
||||
skirt_loops_per_extruder = first_layer ?
|
||||
@ -2269,6 +2311,23 @@ LayerResult GCodeGenerator::process_layer(
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_custom_gcode_to_emit = single_object_instance_idx == size_t(-1) && layer_tools.custom_gcode != nullptr;
|
||||
const int extruder_id_for_custom_gcode = int(layer_tools.extruder_needed_for_color_changer) - 1;
|
||||
|
||||
if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == -1) {
|
||||
// Normal (non-sequential) print with some custom code without picking a specific extruder before it.
|
||||
// If we don't need to pick a specific extruder before the color change, we can just emit a custom g-code.
|
||||
// Otherwise, we will emit the g-code after picking the specific extruder.
|
||||
|
||||
std::string custom_gcode = ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config());
|
||||
if (layer_tools.custom_gcode->type == CustomGCode::ColorChange) {
|
||||
// We have a color change to do on this layer, but we want to do it immediately before the first extrusion instead of now, in order to fix GH #2672.
|
||||
m_pending_pre_extrusion_gcode = custom_gcode;
|
||||
} else {
|
||||
gcode += custom_gcode;
|
||||
}
|
||||
}
|
||||
|
||||
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
|
||||
for (unsigned int extruder_id : layer_tools.extruders)
|
||||
{
|
||||
@ -2280,6 +2339,13 @@ LayerResult GCodeGenerator::process_layer(
|
||||
if (layer_tools.has_wipe_tower && m_wipe_tower)
|
||||
m_last_processor_extrusion_role = GCodeExtrusionRole::WipeTower;
|
||||
|
||||
if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == int(extruder_id)) {
|
||||
assert(m_writer.extruder()->id() == extruder_id_for_custom_gcode);
|
||||
assert(m_pending_pre_extrusion_gcode.empty());
|
||||
// Now we have picked the right extruder, so we can emit the custom g-code.
|
||||
gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config());
|
||||
}
|
||||
|
||||
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
|
||||
const std::pair<size_t, size_t> loops = loops_it->second;
|
||||
this->set_origin(0., 0.);
|
||||
@ -2348,8 +2414,9 @@ LayerResult GCodeGenerator::process_layer(
|
||||
&& m_layer_change_extruder_id
|
||||
&& !result.spiral_vase_enable
|
||||
&& print_z > previous_layer_z
|
||||
&& EXTRUDER_CONFIG(travel_ramping_lift)
|
||||
&& EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
|
||||
&& this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id)
|
||||
&& this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) > 0
|
||||
&& this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) < 90
|
||||
);
|
||||
if (first_layer) {
|
||||
layer_change_gcode = ""; // Explicit for readability.
|
||||
@ -2359,6 +2426,45 @@ LayerResult GCodeGenerator::process_layer(
|
||||
layer_change_gcode = this->writer().get_travel_to_z_gcode(print_z, "simple layer change");
|
||||
}
|
||||
|
||||
const auto keep_retraciton{[&](){
|
||||
if (!do_ramping_layer_change) {
|
||||
return true;
|
||||
}
|
||||
const double travel_length{(*m_current_layer_first_position - *m_previous_layer_last_position_before_wipe).norm()};
|
||||
if (this->m_config.retract_before_travel.get_at(*m_layer_change_extruder_id) < travel_length) {
|
||||
// Travel is long, keep retraction.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}};
|
||||
|
||||
bool removed_retraction{false};
|
||||
if (this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id)) {
|
||||
const std::string retraction_start_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start);
|
||||
const std::string retraction_end_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End);
|
||||
|
||||
if (keep_retraciton()) {
|
||||
boost::algorithm::replace_first(gcode, retraction_start_tag, "");
|
||||
boost::algorithm::replace_first(gcode, retraction_end_tag, "");
|
||||
} else {
|
||||
const std::size_t start{gcode.find(retraction_start_tag)};
|
||||
const std::size_t end_tag_start{gcode.find(retraction_end_tag)};
|
||||
const std::size_t end{end_tag_start + retraction_end_tag.size()};
|
||||
gcode.replace(start, end - start, "");
|
||||
|
||||
layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id);
|
||||
removed_retraction = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed_retraction) {
|
||||
const std::size_t start{gcode.find("FIRST_UNRETRACT")};
|
||||
const std::size_t end{gcode.find("\n", start)};
|
||||
gcode.replace(start, end - start, "");
|
||||
} else {
|
||||
boost::algorithm::replace_first(gcode,"FIRST_UNRETRACT", "");
|
||||
}
|
||||
|
||||
boost::algorithm::replace_first(gcode, tag, layer_change_gcode);
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
|
||||
@ -2602,7 +2708,7 @@ void GCodeGenerator::apply_print_config(const PrintConfig &print_config)
|
||||
m_scaled_resolution = scaled<double>(print_config.gcode_resolution.value);
|
||||
}
|
||||
|
||||
void GCodeGenerator::append_full_config(const Print &print, std::string &str)
|
||||
void GCodeGenerator::append_full_config(const Print& print, std::string &str)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> config;
|
||||
encode_full_config(print, config);
|
||||
@ -2675,8 +2781,16 @@ std::string GCodeGenerator::change_layer(
|
||||
// Increment a progress bar indicator.
|
||||
gcode += m_writer.update_progress(++ m_layer_index, m_layer_count);
|
||||
|
||||
if (EXTRUDER_CONFIG(retract_layer_change))
|
||||
if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) {
|
||||
gcode += this->retract_and_wipe();
|
||||
} else if (EXTRUDER_CONFIG(travel_ramping_lift)){
|
||||
m_previous_layer_last_position_before_wipe = this->last_position ?
|
||||
std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} :
|
||||
std::nullopt;
|
||||
gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start);
|
||||
gcode += this->retract_and_wipe();
|
||||
gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End);
|
||||
}
|
||||
|
||||
Vec3d new_position = this->writer().get_position();
|
||||
new_position.z() = print_z;
|
||||
@ -2953,19 +3067,22 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point) {
|
||||
|
||||
const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z()));
|
||||
|
||||
if (!this->last_position) {
|
||||
double lift{
|
||||
EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) :
|
||||
EXTRUDER_CONFIG(retract_lift)};
|
||||
const double upper_limit = EXTRUDER_CONFIG(retract_lift_below);
|
||||
const double lower_limit = EXTRUDER_CONFIG(retract_lift_above);
|
||||
if ((lower_limit > 0 && gcode_point.z() < lower_limit) ||
|
||||
(upper_limit > 0 && gcode_point.z() > upper_limit)) {
|
||||
lift = 0.0;
|
||||
}
|
||||
gcode += this->writer().get_travel_to_z_gcode(gcode_point.z() + lift, "lift");
|
||||
double lift{
|
||||
EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) :
|
||||
EXTRUDER_CONFIG(retract_lift)};
|
||||
const double upper_limit = EXTRUDER_CONFIG(retract_lift_below);
|
||||
const double lower_limit = EXTRUDER_CONFIG(retract_lift_above);
|
||||
if ((lower_limit > 0 && gcode_point.z() < lower_limit) ||
|
||||
(upper_limit > 0 && gcode_point.z() > upper_limit)) {
|
||||
lift = 0.0;
|
||||
}
|
||||
|
||||
if (EXTRUDER_CONFIG(retract_length) > 0 && (!this->last_position || (!EXTRUDER_CONFIG(travel_ramping_lift)))) {
|
||||
if (!this->last_position || EXTRUDER_CONFIG(retract_before_travel) < (this->point_to_gcode(*this->last_position) - gcode_point.head<2>()).norm()) {
|
||||
gcode += this->writer().retract();
|
||||
gcode += this->writer().get_travel_to_z_gcode(gcode_point.z() + lift, "lift");
|
||||
}
|
||||
}
|
||||
this->last_position = point.head<2>();
|
||||
this->writer().update_position(gcode_point);
|
||||
|
||||
@ -2977,6 +3094,20 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point) {
|
||||
return gcode;
|
||||
}
|
||||
|
||||
double cap_speed(
|
||||
double speed, const double mm3_per_mm, const FullPrintConfig &config, int extruder_id
|
||||
) {
|
||||
const double general_cap{config.max_volumetric_speed.value};
|
||||
if (general_cap > 0) {
|
||||
speed = std::min(speed, general_cap / mm3_per_mm);
|
||||
}
|
||||
const double filament_cap{config.filament_max_volumetric_speed.get_at(extruder_id)};
|
||||
if (filament_cap > 0) {
|
||||
speed = std::min(speed, filament_cap / mm3_per_mm);
|
||||
}
|
||||
return speed;
|
||||
}
|
||||
|
||||
std::string GCodeGenerator::_extrude(
|
||||
const ExtrusionAttributes &path_attr,
|
||||
const Geometry::ArcWelder::Path &path,
|
||||
@ -3008,7 +3139,18 @@ std::string GCodeGenerator::_extrude(
|
||||
}
|
||||
|
||||
// compensate retraction
|
||||
gcode += this->unretract();
|
||||
if (this->m_already_unretracted) {
|
||||
gcode += this->unretract();
|
||||
} else {
|
||||
this->m_already_unretracted = true;
|
||||
gcode += "FIRST_UNRETRACT" + this->unretract();
|
||||
}
|
||||
|
||||
if (!m_pending_pre_extrusion_gcode.empty()) {
|
||||
// There is G-Code that is due to be inserted before an extrusion starts. Insert it.
|
||||
gcode += m_pending_pre_extrusion_gcode;
|
||||
m_pending_pre_extrusion_gcode.clear();
|
||||
}
|
||||
|
||||
// adjust acceleration
|
||||
if (m_config.default_acceleration.value > 0) {
|
||||
@ -3070,33 +3212,15 @@ std::string GCodeGenerator::_extrude(
|
||||
speed = m_config.get_abs_value("first_layer_speed", speed);
|
||||
else if (this->object_layer_over_raft())
|
||||
speed = m_config.get_abs_value("first_layer_speed_over_raft", speed);
|
||||
if (m_config.max_volumetric_speed.value > 0) {
|
||||
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
|
||||
speed = std::min(
|
||||
speed,
|
||||
m_config.max_volumetric_speed.value / path_attr.mm3_per_mm
|
||||
);
|
||||
}
|
||||
if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) {
|
||||
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
|
||||
speed = std::min(
|
||||
speed,
|
||||
EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm
|
||||
);
|
||||
}
|
||||
|
||||
std::pair<float, float> dynamic_speed_and_fan_speed{-1, -1};
|
||||
if (path_attr.overhang_attributes.has_value()) {
|
||||
double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed");
|
||||
if (external_perim_reference_speed == 0)
|
||||
external_perim_reference_speed = m_volumetric_speed / path_attr.mm3_per_mm;
|
||||
if (m_config.max_volumetric_speed.value > 0)
|
||||
external_perim_reference_speed = std::min(external_perim_reference_speed,
|
||||
m_config.max_volumetric_speed.value / path_attr.mm3_per_mm);
|
||||
if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) {
|
||||
external_perim_reference_speed = std::min(external_perim_reference_speed,
|
||||
EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm);
|
||||
}
|
||||
external_perim_reference_speed = cap_speed(
|
||||
external_perim_reference_speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id()
|
||||
);
|
||||
|
||||
dynamic_speed_and_fan_speed = ExtrusionProcessor::calculate_overhang_speed(path_attr, this->m_config, m_writer.extruder()->id(),
|
||||
external_perim_reference_speed, speed);
|
||||
@ -3106,6 +3230,9 @@ std::string GCodeGenerator::_extrude(
|
||||
speed = dynamic_speed_and_fan_speed.first;
|
||||
}
|
||||
|
||||
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
|
||||
speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id());
|
||||
|
||||
double F = speed * 60; // convert mm/sec to mm/min
|
||||
|
||||
// extrude arc or line
|
||||
@ -3247,7 +3374,7 @@ std::string GCodeGenerator::generate_travel_gcode(
|
||||
gcode += this->m_writer.set_travel_acceleration(acceleration);
|
||||
|
||||
Vec3d previous_point{this->point_to_gcode(travel.front())};
|
||||
for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
|
||||
for (const Vec3crd& point : travel) {
|
||||
const Vec3d gcode_point{this->point_to_gcode(point)};
|
||||
|
||||
gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment);
|
||||
|
@ -53,6 +53,7 @@ namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class GCodeGenerator;
|
||||
struct WipeTowerData;
|
||||
|
||||
namespace { struct Item; }
|
||||
struct PrintInstance;
|
||||
@ -116,28 +117,8 @@ struct PrintObjectInstance
|
||||
|
||||
class GCodeGenerator {
|
||||
|
||||
public:
|
||||
GCodeGenerator() :
|
||||
m_origin(Vec2d::Zero()),
|
||||
m_enable_loop_clipping(true),
|
||||
m_enable_cooling_markers(false),
|
||||
m_enable_extrusion_role_markers(false),
|
||||
m_last_processor_extrusion_role(GCodeExtrusionRole::None),
|
||||
m_layer_count(0),
|
||||
m_layer_index(-1),
|
||||
m_layer(nullptr),
|
||||
m_object_layer_over_raft(false),
|
||||
m_volumetric_speed(0),
|
||||
m_last_extrusion_role(GCodeExtrusionRole::None),
|
||||
m_last_width(0.0f),
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_last_mm3_per_mm(0.0),
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_brim_done(false),
|
||||
m_second_layer_things_done(false),
|
||||
m_silent_time_estimator_enabled(false),
|
||||
m_current_instance({nullptr, -1})
|
||||
{}
|
||||
public:
|
||||
GCodeGenerator(const Print* print = nullptr); // The default value is only used in unit tests.
|
||||
~GCodeGenerator() = default;
|
||||
|
||||
// throws std::runtime_exception on error,
|
||||
@ -376,7 +357,7 @@ private:
|
||||
struct PlaceholderParserIntegration {
|
||||
void reset();
|
||||
void init(const GCodeWriter &config);
|
||||
void update_from_gcodewriter(const GCodeWriter &writer);
|
||||
void update_from_gcodewriter(const GCodeWriter &writer, const WipeTowerData& wipe_tower_data);
|
||||
void validate_output_vector_variables();
|
||||
|
||||
PlaceholderParser parser;
|
||||
@ -443,9 +424,11 @@ private:
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
std::optional<Vec3d> m_previous_layer_last_position;
|
||||
std::optional<Vec3d> m_previous_layer_last_position_before_wipe;
|
||||
// This needs to be populated during the layer processing!
|
||||
std::optional<Vec3d> m_current_layer_first_position;
|
||||
std::optional<unsigned> m_layer_change_extruder_id;
|
||||
bool m_already_unretracted{false};
|
||||
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
std::unique_ptr<GCodeFindReplace> m_find_replace;
|
||||
@ -458,6 +441,8 @@ private:
|
||||
bool m_brim_done;
|
||||
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
|
||||
bool m_second_layer_things_done;
|
||||
// G-code that is due to be written before the next extrusion
|
||||
std::string m_pending_pre_extrusion_gcode;
|
||||
// Pointer to currently exporting PrintObject and instance index.
|
||||
GCode::PrintObjectInstance m_current_instance;
|
||||
|
||||
@ -466,11 +451,14 @@ private:
|
||||
// Processor
|
||||
GCodeProcessor m_processor;
|
||||
|
||||
// Back-pointer to Print (const).
|
||||
const Print* m_print;
|
||||
|
||||
std::string _extrude(
|
||||
const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1);
|
||||
void print_machine_envelope(GCodeOutputStream &file, Print &print);
|
||||
void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
void print_machine_envelope(GCodeOutputStream &file, const Print &print);
|
||||
void _print_first_layer_bed_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
// On the first printing layer. This flag triggers first layer speeds.
|
||||
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }
|
||||
// To control print speed of 1st object layer over raft interface.
|
||||
|
@ -213,4 +213,4 @@ std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attri
|
||||
return {final_speed, fan_speed};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
||||
|
@ -58,6 +58,8 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
||||
"WIDTH:",
|
||||
"LAYER_CHANGE",
|
||||
"LAYER_CHANGE_TRAVEL",
|
||||
"LAYER_CHANGE_RETRACTION_START",
|
||||
"LAYER_CHANGE_RETRACTION_END",
|
||||
"COLOR_CHANGE",
|
||||
"PAUSE_PRINT",
|
||||
"CUSTOM_GCODE",
|
||||
|
@ -193,6 +193,8 @@ namespace Slic3r {
|
||||
Width,
|
||||
Layer_Change,
|
||||
Layer_Change_Travel,
|
||||
Layer_Change_Retraction_Start,
|
||||
Layer_Change_Retraction_End,
|
||||
Color_Change,
|
||||
Pause_Print,
|
||||
Custom_Code,
|
||||
|
@ -323,13 +323,17 @@ std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(to);
|
||||
|
||||
double speed_z = this->config.travel_speed_z.value;
|
||||
if (speed_z == 0.)
|
||||
speed_z = this->config.travel_speed.value;
|
||||
|
||||
const double distance_xy{(to.head<2>() - from.head<2>()).norm()};
|
||||
const double distnace_z{std::abs(to.z() - from.z())};
|
||||
const double time_z = distnace_z / this->config.travel_speed_z.value;
|
||||
const double time_z = distnace_z / speed_z;
|
||||
const double time_xy = distance_xy / this->config.travel_speed.value;
|
||||
const double factor = time_z > 0 ? time_xy / time_z : 1;
|
||||
if (factor < 1) {
|
||||
w.emit_f((this->config.travel_speed.value * factor + (1 - factor) * this->config.travel_speed_z.value) * 60.0);
|
||||
w.emit_f((this->config.travel_speed.value * factor + (1 - factor) * speed_z) * 60.0);
|
||||
} else {
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude
|
||||
double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height);
|
||||
|
||||
// Collect extruders required to print the layers.
|
||||
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>());
|
||||
this->collect_extruders(object, std::vector<std::pair<double, unsigned int>>(), std::vector<std::pair<double, unsigned int>>());
|
||||
|
||||
// Reorder the extruders to minimize tool switches.
|
||||
this->reorder_extruders(first_extruder);
|
||||
@ -156,17 +156,24 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
|
||||
// Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object.
|
||||
// Do it only if all the objects were configured to be printed with a single extruder.
|
||||
std::vector<std::pair<double, unsigned int>> per_layer_extruder_switches;
|
||||
if (auto num_extruders = unsigned(print.config().nozzle_diameter.size());
|
||||
num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
|
||||
auto num_extruders = unsigned(print.config().nozzle_diameter.size());
|
||||
if (num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle
|
||||
print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) {
|
||||
// Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material).
|
||||
// There may be custom per-layer tool changes available at the model.
|
||||
per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders);
|
||||
}
|
||||
|
||||
// Collect extruders reuqired to print the layers.
|
||||
// Color changes for each layer to determine which extruder needs to be picked before color change.
|
||||
// This is done just for multi-extruder printers without enabled Single Extruder Multi Material (tool changer printers).
|
||||
std::vector<std::pair<double, unsigned int>> per_layer_color_changes;
|
||||
if (num_extruders > 1 && print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) {
|
||||
per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z, num_extruders);
|
||||
}
|
||||
|
||||
// Collect extruders required to print the layers.
|
||||
for (auto object : print.objects())
|
||||
this->collect_extruders(*object, per_layer_extruder_switches);
|
||||
this->collect_extruders(*object, per_layer_extruder_switches, per_layer_color_changes);
|
||||
|
||||
// Reorder the extruders to minimize tool switches.
|
||||
this->reorder_extruders(first_extruder);
|
||||
@ -219,8 +226,11 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
|
||||
}
|
||||
|
||||
// Collect extruders reuqired to print layers.
|
||||
void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches)
|
||||
{
|
||||
void ToolOrdering::collect_extruders(
|
||||
const PrintObject &object,
|
||||
const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches,
|
||||
const std::vector<std::pair<double, unsigned int>> &per_layer_color_changes
|
||||
) {
|
||||
// Collect the support extruders.
|
||||
for (auto support_layer : object.support_layers()) {
|
||||
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
|
||||
@ -238,10 +248,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
}
|
||||
|
||||
// Extruder overrides are ordered by print_z.
|
||||
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override;
|
||||
it_per_layer_extruder_override = per_layer_extruder_switches.begin();
|
||||
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_extruder_override = per_layer_extruder_switches.begin();
|
||||
unsigned int extruder_override = 0;
|
||||
|
||||
std::vector<std::pair<double, unsigned int>>::const_iterator it_per_layer_color_changes = per_layer_color_changes.begin();
|
||||
|
||||
// Collect the object extruders.
|
||||
for (auto layer : object.layers()) {
|
||||
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
|
||||
@ -253,6 +264,15 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
// Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it.
|
||||
layer_tools.extruder_override = extruder_override;
|
||||
|
||||
// Append the extruder needed to be picked before performing the color change.
|
||||
for (; it_per_layer_color_changes != per_layer_color_changes.end() && it_per_layer_color_changes->first < layer->print_z + EPSILON; ++it_per_layer_color_changes) {
|
||||
if (std::abs(it_per_layer_color_changes->first - layer->print_z) < EPSILON) {
|
||||
assert(layer_tools.extruder_needed_for_color_changer == 0); // Just on color change per layer is allowed.
|
||||
layer_tools.extruder_needed_for_color_changer = it_per_layer_color_changes->second;
|
||||
layer_tools.extruders.emplace_back(it_per_layer_color_changes->second);
|
||||
}
|
||||
}
|
||||
|
||||
// What extruders are required to print this object layer?
|
||||
for (const LayerRegion *layerm : layer->regions()) {
|
||||
const PrintRegion ®ion = layerm->region();
|
||||
@ -370,6 +390,11 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
|
||||
std::swap(lt.extruders[i], lt.extruders.front());
|
||||
break;
|
||||
}
|
||||
} else if (lt.extruder_needed_for_color_changer != 0) {
|
||||
// Put the extruder needed for performing the color change at the beginning.
|
||||
auto it = std::find(lt.extruders.begin(), lt.extruders.end(), lt.extruder_needed_for_color_changer);
|
||||
assert(it != lt.extruders.end());
|
||||
std::rotate(lt.extruders.begin(), it, it + 1);
|
||||
}
|
||||
}
|
||||
last_extruder_id = lt.extruders.back();
|
||||
|
@ -98,6 +98,9 @@ public:
|
||||
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
||||
// If not overriden, it is set to 0.
|
||||
unsigned int extruder_override = 0;
|
||||
// For multi-extruder printers, when there is a color change, this contains an extruder (1 based) on which the color change will be performed.
|
||||
// Otherwise, it is set to 0.
|
||||
unsigned int extruder_needed_for_color_changer = 0;
|
||||
// Should a skirt be printed at this layer?
|
||||
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
||||
bool has_skirt = false;
|
||||
@ -169,7 +172,7 @@ public:
|
||||
|
||||
private:
|
||||
void initialize_layers(std::vector<coordf_t> &zs);
|
||||
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
|
||||
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches, const std::vector<std::pair<double, unsigned int>> &per_layer_color_changes);
|
||||
void reorder_extruders(unsigned int last_extruder_id);
|
||||
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
|
||||
bool insert_wipe_tower_extruder();
|
||||
|
@ -433,7 +433,6 @@ Points3 generate_travel_to_extrusion(
|
||||
elevation_params.slope_end + elevation_params.blend_width / 2.0,
|
||||
elevation_params.parabola_points_count
|
||||
);
|
||||
|
||||
Points3 result{generate_elevated_travel(
|
||||
xy_path.points, ensure_points_at_distances, initial_elevation,
|
||||
ElevatedTravelFormula{elevation_params}
|
||||
|
@ -537,7 +537,6 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed),
|
||||
m_travel_speed_z(config.travel_speed_z),
|
||||
m_infill_speed(default_region_config.infill_speed),
|
||||
m_perimeter_speed(default_region_config.perimeter_speed),
|
||||
m_current_tool(initial_tool),
|
||||
@ -1569,8 +1568,9 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& used : m_used_filament_length) // reset used filament stats
|
||||
used = 0.f;
|
||||
m_used_filament_length.assign(m_used_filament_length.size(), 0.f); // reset used filament stats
|
||||
assert(m_used_filament_length_until_layer.empty());
|
||||
m_used_filament_length_until_layer.emplace_back(0.f, m_used_filament_length);
|
||||
|
||||
m_old_temperature = -1; // reset last temperature written in the gcode
|
||||
|
||||
@ -1613,6 +1613,10 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(layer_result));
|
||||
|
||||
if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z)
|
||||
m_used_filament_length_until_layer.emplace_back();
|
||||
m_used_filament_length_until_layer.back() = std::make_pair(layer.z, m_used_filament_length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ public:
|
||||
return m_current_layer_finished;
|
||||
}
|
||||
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
std::vector<std::pair<float, std::vector<float>>> get_used_filament_until_layer() const { return m_used_filament_length_until_layer; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
struct FilamentParameters {
|
||||
@ -279,7 +279,6 @@ private:
|
||||
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
|
||||
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
|
||||
float m_travel_speed = 0.f;
|
||||
float m_travel_speed_z = 0.f;
|
||||
float m_infill_speed = 0.f;
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
@ -384,6 +383,7 @@ private:
|
||||
|
||||
// Stores information about used filament length per extruder:
|
||||
std::vector<float> m_used_filament_length;
|
||||
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
|
@ -93,7 +93,8 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower) {
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(tcr.print_z, "restore layer Z");
|
||||
const double retract_to_z = tcr.priming ? tcr.print_z + gcodegen.config().z_offset.value : z;
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(retract_to_z, "restore layer Z");
|
||||
deretraction_str += gcodegen.unretract();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "VoronoiOffset.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
namespace boost { namespace polygon {
|
||||
@ -467,7 +470,20 @@ void MedialAxis::build(ThickPolylines* polylines)
|
||||
test(l.b.y());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
|
||||
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
|
||||
|
||||
// For several ExPolygons in SPE-1729, an invalid Voronoi diagram was produced that wasn't fixable by rotating input data.
|
||||
// Those ExPolygons contain very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
|
||||
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
|
||||
// So we filter out such thin lines and holes and try to compute the Voronoi diagram again.
|
||||
if (!m_vd.is_valid()) {
|
||||
m_lines = to_lines(closing_ex({m_expolygon}, float(2. * SCALED_EPSILON)));
|
||||
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
|
||||
|
||||
if (!m_vd.is_valid())
|
||||
BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing.";
|
||||
}
|
||||
|
||||
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
|
||||
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
||||
|
354
src/libslic3r/Geometry/Voronoi.cpp
Normal file
@ -0,0 +1,354 @@
|
||||
#include "Voronoi.hpp"
|
||||
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
#include "libslic3r/Geometry/VoronoiUtils.hpp"
|
||||
#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp"
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_iterator;
|
||||
|
||||
// Explicit template instantiation.
|
||||
template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool);
|
||||
template void VoronoiDiagram::construct_voronoi(ColoredLinesConstIt, ColoredLinesConstIt, bool);
|
||||
template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
void>::type
|
||||
VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const SegmentIterator segment_end, const bool try_to_repair_if_needed) {
|
||||
boost::polygon::construct_voronoi(segment_begin, segment_end, &m_voronoi_diagram);
|
||||
if (try_to_repair_if_needed) {
|
||||
if (m_issue_type = detect_known_issues(*this, segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
|
||||
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
|
||||
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth.";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth.";
|
||||
}
|
||||
|
||||
if (m_issue_type = try_to_repair_degenerated_voronoi_diagram(segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
|
||||
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
|
||||
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
|
||||
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
|
||||
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input.";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input.";
|
||||
}
|
||||
|
||||
m_state = State::REPAIR_UNSUCCESSFUL;
|
||||
} else {
|
||||
m_state = State::REPAIR_SUCCESSFUL;
|
||||
}
|
||||
} else {
|
||||
m_state = State::REPAIR_NOT_NEEDED;
|
||||
m_issue_type = IssueType::NO_ISSUE_DETECTED;
|
||||
}
|
||||
} else {
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void VoronoiDiagram::clear()
|
||||
{
|
||||
if (m_is_modified) {
|
||||
m_vertices.clear();
|
||||
m_edges.clear();
|
||||
m_cells.clear();
|
||||
m_is_modified = false;
|
||||
} else {
|
||||
m_voronoi_diagram.clear();
|
||||
}
|
||||
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
|
||||
void VoronoiDiagram::copy_to_local(voronoi_diagram_type &voronoi_diagram) {
|
||||
m_edges.clear();
|
||||
m_cells.clear();
|
||||
m_vertices.clear();
|
||||
|
||||
// Copy Voronoi edges.
|
||||
m_edges.reserve(voronoi_diagram.num_edges());
|
||||
for (const edge_type &edge : voronoi_diagram.edges()) {
|
||||
m_edges.emplace_back(edge.is_linear(), edge.is_primary());
|
||||
m_edges.back().color(edge.color());
|
||||
}
|
||||
|
||||
// Copy Voronoi cells.
|
||||
m_cells.reserve(voronoi_diagram.num_cells());
|
||||
for (const cell_type &cell : voronoi_diagram.cells()) {
|
||||
m_cells.emplace_back(cell.source_index(), cell.source_category());
|
||||
m_cells.back().color(cell.color());
|
||||
|
||||
if (cell.incident_edge()) {
|
||||
size_t incident_edge_idx = cell.incident_edge() - voronoi_diagram.edges().data();
|
||||
m_cells.back().incident_edge(&m_edges[incident_edge_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy Voronoi vertices.
|
||||
m_vertices.reserve(voronoi_diagram.num_vertices());
|
||||
for (const vertex_type &vertex : voronoi_diagram.vertices()) {
|
||||
m_vertices.emplace_back(vertex.x(), vertex.y());
|
||||
m_vertices.back().color(vertex.color());
|
||||
|
||||
if (vertex.incident_edge()) {
|
||||
size_t incident_edge_idx = vertex.incident_edge() - voronoi_diagram.edges().data();
|
||||
m_vertices.back().incident_edge(&m_edges[incident_edge_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign all pointers for each Voronoi edge.
|
||||
for (const edge_type &old_edge : voronoi_diagram.edges()) {
|
||||
size_t edge_idx = &old_edge - voronoi_diagram.edges().data();
|
||||
edge_type &new_edge = m_edges[edge_idx];
|
||||
|
||||
if (old_edge.cell()) {
|
||||
size_t cell_idx = old_edge.cell() - voronoi_diagram.cells().data();
|
||||
new_edge.cell(&m_cells[cell_idx]);
|
||||
}
|
||||
|
||||
if (old_edge.vertex0()) {
|
||||
size_t vertex0_idx = old_edge.vertex0() - voronoi_diagram.vertices().data();
|
||||
new_edge.vertex0(&m_vertices[vertex0_idx]);
|
||||
}
|
||||
|
||||
if (old_edge.twin()) {
|
||||
size_t twin_edge_idx = old_edge.twin() - voronoi_diagram.edges().data();
|
||||
new_edge.twin(&m_edges[twin_edge_idx]);
|
||||
}
|
||||
|
||||
if (old_edge.next()) {
|
||||
size_t next_edge_idx = old_edge.next() - voronoi_diagram.edges().data();
|
||||
new_edge.next(&m_edges[next_edge_idx]);
|
||||
}
|
||||
|
||||
if (old_edge.prev()) {
|
||||
size_t prev_edge_idx = old_edge.prev() - voronoi_diagram.edges().data();
|
||||
new_edge.prev(&m_edges[prev_edge_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end)
|
||||
{
|
||||
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) {
|
||||
return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX;
|
||||
} else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) {
|
||||
return cell_issue_type;
|
||||
} else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) {
|
||||
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
|
||||
return IssueType::NON_PLANAR_VORONOI_DIAGRAM;
|
||||
}
|
||||
|
||||
return IssueType::NO_ISSUE_DETECTED;
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram,
|
||||
const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
using Point = typename boost::polygon::segment_point_type<Segment>::type;
|
||||
using SegmentCellRange = SegmentCellRange<Point>;
|
||||
|
||||
for (VD::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (cell.is_degenerate() || !cell.contains_segment())
|
||||
continue; // Skip degenerated cell that has no spoon. Also, skip a cell that doesn't contain a segment.
|
||||
|
||||
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segment_begin, segment_end); cell_range.is_valid()) {
|
||||
// Detection if Voronoi edge is intersecting input segment.
|
||||
// It detects this type of issue at least in GH issues #8446, #8474 and #8514.
|
||||
|
||||
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
|
||||
const Vec2d source_segment_from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW).template cast<double>();
|
||||
const Vec2d source_segment_to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH).template cast<double>();
|
||||
const Vec2d source_segment_vec = source_segment_to - source_segment_from;
|
||||
|
||||
// All Voronoi vertices must be on the left side of the source segment, otherwise the Voronoi diagram is invalid.
|
||||
for (const VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) {
|
||||
if (edge->is_infinite()) {
|
||||
// When there is a missing Voronoi vertex, we may encounter an infinite Voronoi edge.
|
||||
// This happens, for example, in GH issue #8846.
|
||||
return IssueType::MISSING_VORONOI_VERTEX;
|
||||
} else if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) {
|
||||
return IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When there is a missing Voronoi vertex (especially at one of the endpoints of the input segment),
|
||||
// the returned cell_range is marked as invalid.
|
||||
// It detects this type of issue at least in GH issue #8846.
|
||||
return IssueType::MISSING_VORONOI_VERTEX;
|
||||
}
|
||||
}
|
||||
|
||||
return IssueType::NO_ISSUE_DETECTED;
|
||||
}
|
||||
|
||||
bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const voronoi_diagram_type::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.is_finite()) {
|
||||
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || !VoronoiUtils::is_finite(*edge.vertex1()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
IssueType issue_type = m_issue_type;
|
||||
|
||||
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
|
||||
for (const double fix_angle : fix_angles) {
|
||||
issue_type = try_to_repair_degenerated_voronoi_diagram_by_rotation(segment_begin, segment_end, fix_angle);
|
||||
if (issue_type == IssueType::NO_ISSUE_DETECTED) {
|
||||
return issue_type;
|
||||
}
|
||||
}
|
||||
|
||||
return issue_type;
|
||||
}
|
||||
|
||||
inline VD::vertex_type::color_type encode_input_segment_endpoint(const VD::cell_type::source_index_type cell_source_index, const boost::polygon::direction_1d dir)
|
||||
{
|
||||
return (cell_source_index + 1) << 1 | (dir.to_int() ? 1 : 0);
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
inline typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
|
||||
decode_input_segment_endpoint(const VD::vertex_type::color_type color, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
|
||||
|
||||
const size_t segment_idx = (color >> 1) - 1;
|
||||
const SegmentIterator segment_it = segment_begin + segment_idx;
|
||||
const PointType source_point = boost::polygon::segment_traits<SegmentType>::get(*segment_it, ((color & 1) ? boost::polygon::HIGH :
|
||||
boost::polygon::LOW));
|
||||
return source_point;
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end,
|
||||
const double fix_angle)
|
||||
{
|
||||
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
|
||||
|
||||
// Copy all segments and rotate their vertices.
|
||||
std::vector<VoronoiDiagram::Segment> segments_rotated;
|
||||
segments_rotated.reserve(std::distance(segment_begin, segment_end));
|
||||
for (auto segment_it = segment_begin; segment_it != segment_end; ++segment_it) {
|
||||
PointType from = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::LOW);
|
||||
PointType to = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::HIGH);
|
||||
segments_rotated.emplace_back(from.rotated(fix_angle), to.rotated(fix_angle));
|
||||
}
|
||||
|
||||
VoronoiDiagram::voronoi_diagram_type voronoi_diagram_rotated;
|
||||
boost::polygon::construct_voronoi(segments_rotated.begin(), segments_rotated.end(), &voronoi_diagram_rotated);
|
||||
|
||||
this->copy_to_local(voronoi_diagram_rotated);
|
||||
const IssueType issue_type = detect_known_issues(*this, segments_rotated.begin(), segments_rotated.end());
|
||||
|
||||
// We want to remap all Voronoi vertices at the endpoints of input segments
|
||||
// to ensure that Voronoi vertices at endpoints will be preserved after rotation.
|
||||
// So we assign every Voronoi vertices color to map this Vertex into input segments.
|
||||
for (cell_type cell : m_cells) {
|
||||
if (cell.is_degenerate())
|
||||
continue;
|
||||
|
||||
if (cell.contains_segment()) {
|
||||
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segments_rotated.begin(), segments_rotated.end()); cell_range.is_valid()) {
|
||||
if (cell_range.edge_end->vertex1()->color() == 0) {
|
||||
// Vertex 1 of edge_end points to the starting endpoint of the input segment (from() or line.a).
|
||||
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::LOW);
|
||||
cell_range.edge_end->vertex1()->color(color);
|
||||
}
|
||||
|
||||
if (cell_range.edge_begin->vertex0()->color() == 0) {
|
||||
// Vertex 0 of edge_end points to the ending endpoint of the input segment (to() or line.b).
|
||||
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::HIGH);
|
||||
cell_range.edge_begin->vertex0()->color(color);
|
||||
}
|
||||
} else {
|
||||
// This could happen when there is a missing Voronoi vertex even after rotation.
|
||||
assert(cell_range.is_valid());
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME @hejllukas: Implement mapping also for source points and not just for source segments.
|
||||
}
|
||||
|
||||
// Rotate all Voronoi vertices back.
|
||||
// When a Voronoi vertex can be mapped to the input segment endpoint, then we don't need to do rotation back.
|
||||
for (vertex_type &vertex : m_vertices) {
|
||||
if (vertex.color() == 0) {
|
||||
// This vertex isn't mapped to any vertex, so we rotate it back.
|
||||
vertex = VoronoiUtils::make_rotated_vertex(vertex, -fix_angle);
|
||||
} else {
|
||||
// This vertex can be mapped to the input segment endpoint.
|
||||
PointType endpoint = decode_input_segment_endpoint(vertex.color(), segment_begin, segment_end);
|
||||
vertex_type endpoint_vertex{double(endpoint.x()), double(endpoint.y())};
|
||||
endpoint_vertex.incident_edge(vertex.incident_edge());
|
||||
endpoint_vertex.color(vertex.color());
|
||||
vertex = endpoint_vertex;
|
||||
}
|
||||
}
|
||||
|
||||
// We have to clear all marked vertices because some algorithms expect that all vertices have a color equal to 0.
|
||||
for (vertex_type &vertex : m_vertices)
|
||||
vertex.color(0);
|
||||
|
||||
m_voronoi_diagram.clear();
|
||||
m_is_modified = true;
|
||||
|
||||
return issue_type;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Geometry
|
@ -8,10 +8,8 @@
|
||||
#include "../Line.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
#define BOOST_VORONOI_USE_GMP 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
|
||||
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4146)
|
||||
#endif // _MSC_VER
|
||||
@ -20,18 +18,182 @@
|
||||
#pragma warning(pop)
|
||||
#endif // _MSC_VER
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
namespace Geometry {
|
||||
|
||||
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
|
||||
class VoronoiDiagram
|
||||
{
|
||||
public:
|
||||
typedef double coord_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
using coord_type = double;
|
||||
using voronoi_diagram_type = boost::polygon::voronoi_diagram<coord_type>;
|
||||
using point_type = boost::polygon::point_data<voronoi_diagram_type::coordinate_type>;
|
||||
using segment_type = boost::polygon::segment_data<voronoi_diagram_type::coordinate_type>;
|
||||
using rect_type = boost::polygon::rectangle_data<voronoi_diagram_type::coordinate_type>;
|
||||
|
||||
using coordinate_type = voronoi_diagram_type::coordinate_type;
|
||||
using vertex_type = voronoi_diagram_type::vertex_type;
|
||||
using edge_type = voronoi_diagram_type::edge_type;
|
||||
using cell_type = voronoi_diagram_type::cell_type;
|
||||
|
||||
using const_vertex_iterator = voronoi_diagram_type::const_vertex_iterator;
|
||||
using const_edge_iterator = voronoi_diagram_type::const_edge_iterator;
|
||||
using const_cell_iterator = voronoi_diagram_type::const_cell_iterator;
|
||||
|
||||
using vertex_container_type = voronoi_diagram_type::vertex_container_type;
|
||||
using edge_container_type = voronoi_diagram_type::edge_container_type;
|
||||
using cell_container_type = voronoi_diagram_type::cell_container_type;
|
||||
|
||||
enum class IssueType {
|
||||
NO_ISSUE_DETECTED,
|
||||
FINITE_EDGE_WITH_NON_FINITE_VERTEX,
|
||||
MISSING_VORONOI_VERTEX,
|
||||
NON_PLANAR_VORONOI_DIAGRAM,
|
||||
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
|
||||
UNKNOWN // Repairs are disabled in the constructor.
|
||||
};
|
||||
|
||||
enum class State {
|
||||
REPAIR_NOT_NEEDED, // The original Voronoi diagram doesn't have any issue.
|
||||
REPAIR_SUCCESSFUL, // The original Voronoi diagram has some issues, but it was repaired.
|
||||
REPAIR_UNSUCCESSFUL, // The original Voronoi diagram has some issues, but it wasn't repaired.
|
||||
UNKNOWN // Repairs are disabled in the constructor.
|
||||
};
|
||||
|
||||
VoronoiDiagram() = default;
|
||||
|
||||
virtual ~VoronoiDiagram() = default;
|
||||
|
||||
IssueType get_issue_type() const { return m_issue_type; }
|
||||
|
||||
State get_state() const { return m_state; }
|
||||
|
||||
bool is_valid() const { return m_state != State::REPAIR_UNSUCCESSFUL; }
|
||||
|
||||
void clear();
|
||||
|
||||
const vertex_container_type &vertices() const { return m_is_modified ? m_vertices : m_voronoi_diagram.vertices(); }
|
||||
|
||||
const edge_container_type &edges() const { return m_is_modified ? m_edges : m_voronoi_diagram.edges(); }
|
||||
|
||||
const cell_container_type &cells() const { return m_is_modified ? m_cells : m_voronoi_diagram.cells(); }
|
||||
|
||||
std::size_t num_vertices() const { return m_is_modified ? m_vertices.size() : m_voronoi_diagram.num_vertices(); }
|
||||
|
||||
std::size_t num_edges() const { return m_is_modified ? m_edges.size() : m_voronoi_diagram.num_edges(); }
|
||||
|
||||
std::size_t num_cells() const { return m_is_modified ? m_cells.size() : m_voronoi_diagram.num_cells(); }
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
void>::type
|
||||
construct_voronoi(SegmentIterator segment_begin, SegmentIterator segment_end, bool try_to_repair_if_needed = true);
|
||||
|
||||
template<typename PointIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
|
||||
void>::type
|
||||
construct_voronoi(const PointIterator first, const PointIterator last)
|
||||
{
|
||||
boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram);
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
|
||||
template<typename PointIterator, typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_and<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<typename boost::polygon::geometry_concept<
|
||||
typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type>::type,
|
||||
void>::type
|
||||
construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last)
|
||||
{
|
||||
boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram);
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram
|
||||
// is not planar or some Voronoi edge is intersecting input segment.
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
IssueType>::type
|
||||
detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
try_to_repair_degenerated_voronoi_diagram_by_rotation(SegmentIterator segment_begin, SegmentIterator segment_end, double fix_angle);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
try_to_repair_degenerated_voronoi_diagram(SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
private:
|
||||
struct Segment
|
||||
{
|
||||
Point from;
|
||||
Point to;
|
||||
|
||||
Segment() = delete;
|
||||
explicit Segment(const Point &from, const Point &to) : from(from), to(to) {}
|
||||
};
|
||||
|
||||
void copy_to_local(voronoi_diagram_type &voronoi_diagram);
|
||||
|
||||
// Detect issues related to Voronoi cells, or that can be detected by iterating over Voronoi cells.
|
||||
// The first type of issue that can be detected is a missing Voronoi vertex, especially when it is
|
||||
// missing at one of the endpoints of the input segment.
|
||||
// The second type of issue that can be detected is a Voronoi edge that intersects the input segment.
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
IssueType>::type
|
||||
detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
voronoi_diagram_type m_voronoi_diagram;
|
||||
vertex_container_type m_vertices;
|
||||
edge_container_type m_edges;
|
||||
cell_container_type m_cells;
|
||||
bool m_is_modified = false;
|
||||
State m_state = State::UNKNOWN;
|
||||
IssueType m_issue_type = IssueType::UNKNOWN;
|
||||
|
||||
public:
|
||||
using SegmentIt = std::vector<Slic3r::Geometry::VoronoiDiagram::Segment>::iterator;
|
||||
|
||||
friend struct boost::polygon::segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>;
|
||||
};
|
||||
|
||||
} } // namespace Slicer::Geometry
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
namespace boost::polygon {
|
||||
template<> struct geometry_concept<Slic3r::Geometry::VoronoiDiagram::Segment>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>
|
||||
{
|
||||
using coordinate_type = coord_t;
|
||||
using point_type = Slic3r::Point;
|
||||
using segment_type = Slic3r::Geometry::VoronoiDiagram::Segment;
|
||||
|
||||
static inline point_type get(const segment_type &segment, direction_1d dir) { return dir.to_int() ? segment.to : segment.from; }
|
||||
};
|
||||
} // namespace boost::polygon
|
||||
|
||||
#endif // slic3r_Geometry_Voronoi_hpp_
|
||||
|
283
src/libslic3r/Geometry/VoronoiUtils.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <Arachne/utils/PolygonsSegmentIndex.hpp>
|
||||
#include <MultiMaterialSegmentation.hpp>
|
||||
|
||||
#include "VoronoiUtils.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesIt = ColoredLines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_iterator;
|
||||
|
||||
// Explicit template instantiation.
|
||||
template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
|
||||
template ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float);
|
||||
template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename std::iterator_traits<SegmentIterator>::reference>::type
|
||||
VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
if (!cell.contains_segment())
|
||||
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source segment!");
|
||||
|
||||
if (cell.source_index() >= size_t(std::distance(segment_begin, segment_end)))
|
||||
throw Slic3r::OutOfRange("Voronoi cell source index is out of range!");
|
||||
|
||||
return *(segment_begin + cell.source_index());
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
|
||||
VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
|
||||
if (!cell.contains_point())
|
||||
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
|
||||
|
||||
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::LOW);
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::HIGH);
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
|
||||
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Function get_source_point() should only be called on point cells!");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
Arachne::PolygonsPointIndex>::type
|
||||
VoronoiUtils::get_source_point_index(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
if (!cell.contains_point())
|
||||
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
|
||||
|
||||
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return (*segment_it);
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return (*segment_it).next();
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
|
||||
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Function get_source_point_index() should only be called on point cells!");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Segment>
|
||||
typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
|
||||
Points>::type
|
||||
VoronoiUtils::discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle)
|
||||
{
|
||||
Points discretized;
|
||||
// x is distance of point projected on the segment ab
|
||||
// xx is point projected on the segment ab
|
||||
const Point a = source_segment.from();
|
||||
const Point b = source_segment.to();
|
||||
const Point ab = b - a;
|
||||
const Point as = start - a;
|
||||
const Point ae = end - a;
|
||||
const coord_t ab_size = ab.cast<int64_t>().norm();
|
||||
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t sxex = ex - sx;
|
||||
|
||||
const Point ap = source_point - a;
|
||||
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
|
||||
Point pxx;
|
||||
Line(a, b).distance_to_infinite_squared(source_point, &pxx);
|
||||
const Point ppxx = pxx - source_point;
|
||||
const coord_t d = ppxx.cast<int64_t>().norm();
|
||||
|
||||
const Vec2d rot = perp(ppxx).cast<double>().normalized();
|
||||
const double rot_cos_theta = rot.x();
|
||||
const double rot_sin_theta = rot.y();
|
||||
|
||||
if (d == 0) {
|
||||
discretized.emplace_back(start);
|
||||
discretized.emplace_back(end);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
const double marking_bound = atan(transitioning_angle * 0.5);
|
||||
int64_t msx = -marking_bound * int64_t(d); // projected marking_start
|
||||
int64_t mex = marking_bound * int64_t(d); // projected marking_end
|
||||
|
||||
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
|
||||
Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
const int dir = (sx > ex) ? -1 : 1;
|
||||
if (dir < 0) {
|
||||
std::swap(marking_start, marking_end);
|
||||
std::swap(msx, mex);
|
||||
}
|
||||
|
||||
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
|
||||
const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
|
||||
|
||||
assert(!add_marking_start || !add_marking_end || add_apex);
|
||||
if (add_marking_start && add_marking_end && !add_apex)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
|
||||
|
||||
const coord_t step_count = lround(static_cast<double>(std::abs(ex - sx)) / approximate_step_size);
|
||||
discretized.emplace_back(start);
|
||||
for (coord_t step = 1; step < step_count; ++step) {
|
||||
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
|
||||
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
|
||||
|
||||
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) {
|
||||
discretized.emplace_back(marking_start);
|
||||
add_marking_start = false;
|
||||
}
|
||||
|
||||
if (add_apex && int64_t(x) * int64_t(dir) > 0) {
|
||||
discretized.emplace_back(apex);
|
||||
add_apex = false; // only add the apex just before the
|
||||
}
|
||||
|
||||
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) {
|
||||
discretized.emplace_back(marking_end);
|
||||
add_marking_end = false;
|
||||
}
|
||||
|
||||
assert(is_in_range<coord_t>(x) && is_in_range<coord_t>(y));
|
||||
const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
discretized.emplace_back(result);
|
||||
}
|
||||
|
||||
if (add_apex)
|
||||
discretized.emplace_back(apex);
|
||||
|
||||
if (add_marking_end)
|
||||
discretized.emplace_back(marking_end);
|
||||
|
||||
discretized.emplace_back(end);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
Geometry::SegmentCellRange<
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
|
||||
VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
using Point = typename boost::polygon::segment_point_type<Segment>::type;
|
||||
using SegmentCellRange = SegmentCellRange<Point>;
|
||||
|
||||
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
|
||||
const Point from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW);
|
||||
const Point to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH);
|
||||
const Vec2i64 from_i64 = from.template cast<int64_t>();
|
||||
const Vec2i64 to_i64 = to.template cast<int64_t>();
|
||||
|
||||
// FIXME @hejllukas: Ensure that there is no infinite edge during iteration between edge_begin and edge_end.
|
||||
SegmentCellRange cell_range(to, from);
|
||||
|
||||
// Find starting edge and end edge
|
||||
bool seen_possible_start = false;
|
||||
bool after_start = false;
|
||||
bool ending_edge_is_set_before_start = false;
|
||||
const VD::edge_type *edge = cell.incident_edge();
|
||||
do {
|
||||
if (edge->is_infinite())
|
||||
continue;
|
||||
|
||||
Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0());
|
||||
Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1());
|
||||
assert(v0 != to_i64 || v1 != from_i64);
|
||||
|
||||
if (v0 == to_i64 && !after_start) { // Use the last edge which starts in source_segment.to
|
||||
cell_range.edge_begin = edge;
|
||||
seen_possible_start = true;
|
||||
} else if (seen_possible_start) {
|
||||
after_start = true;
|
||||
}
|
||||
|
||||
if (v1 == from_i64 && (!cell_range.edge_end || ending_edge_is_set_before_start)) {
|
||||
ending_edge_is_set_before_start = !after_start;
|
||||
cell_range.edge_end = edge;
|
||||
}
|
||||
} while (edge = edge->next(), edge != cell.incident_edge());
|
||||
|
||||
return cell_range;
|
||||
}
|
||||
|
||||
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex)
|
||||
{
|
||||
assert(vertex != nullptr);
|
||||
return VoronoiUtils::to_point(*vertex);
|
||||
}
|
||||
|
||||
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type &vertex)
|
||||
{
|
||||
const double x = vertex.x(), y = vertex.y();
|
||||
|
||||
assert(std::isfinite(x) && std::isfinite(y));
|
||||
assert(is_in_range<int64_t>(x) && is_in_range<int64_t>(y));
|
||||
|
||||
return {std::llround(x), std::llround(y)};
|
||||
}
|
||||
|
||||
bool VoronoiUtils::is_finite(const VD::vertex_type &vertex)
|
||||
{
|
||||
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
|
||||
}
|
||||
|
||||
VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle)
|
||||
{
|
||||
const double cos_a = std::cos(angle);
|
||||
const double sin_a = std::sin(angle);
|
||||
|
||||
const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y());
|
||||
const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x());
|
||||
|
||||
VD::vertex_type rotated_vertex{rotated_x, rotated_y};
|
||||
rotated_vertex.incident_edge(vertex.incident_edge());
|
||||
rotated_vertex.color(vertex.color());
|
||||
|
||||
return rotated_vertex;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Geometry
|
120
src/libslic3r/Geometry/VoronoiUtils.hpp
Normal file
@ -0,0 +1,120 @@
|
||||
#ifndef slic3r_VoronoiUtils_hpp_
|
||||
#define slic3r_VoronoiUtils_hpp_
|
||||
|
||||
#include "libslic3r/Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
// Represent trapezoid Voronoi cell around segment.
|
||||
template<typename PT> struct SegmentCellRange
|
||||
{
|
||||
const PT segment_start_point; // The start point of the source segment of this cell.
|
||||
const PT segment_end_point; // The end point of the source segment of this cell.
|
||||
const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
|
||||
const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
|
||||
|
||||
SegmentCellRange() = delete;
|
||||
explicit SegmentCellRange(const PT &segment_start_point, const PT &segment_end_point)
|
||||
: segment_start_point(segment_start_point), segment_end_point(segment_end_point)
|
||||
{}
|
||||
|
||||
bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; }
|
||||
};
|
||||
|
||||
class VoronoiUtils
|
||||
{
|
||||
public:
|
||||
static Vec2i64 to_point(const VD::vertex_type *vertex);
|
||||
|
||||
static Vec2i64 to_point(const VD::vertex_type &vertex);
|
||||
|
||||
static bool is_finite(const VD::vertex_type &vertex);
|
||||
|
||||
static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename std::iterator_traits<SegmentIterator>::reference>::type
|
||||
get_source_segment(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
|
||||
get_source_point(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
Arachne::PolygonsPointIndex>::type
|
||||
get_source_point_index(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
/**
|
||||
* Discretize a parabola based on (approximate) step size.
|
||||
*
|
||||
* Adapted from CuraEngine VoronoiUtils::discretizeParabola by Tim Kuipers @BagelOrb and @Ghostkeeper.
|
||||
*
|
||||
* @param approximate_step_size is measured parallel to the source_segment, not along the parabola.
|
||||
*/
|
||||
template<typename Segment>
|
||||
static typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
|
||||
Points>::type
|
||||
discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, coord_t approximate_step_size, float transitioning_angle);
|
||||
|
||||
/**
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
* graph that belongs to a line segment of the medial axis.
|
||||
*
|
||||
* This should only be used on cells that belong to a central line segment
|
||||
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
|
||||
*
|
||||
* The resulting line segments is just the first and the last segment. They
|
||||
* are linked to the neighboring segments, so you can iterate over the
|
||||
* segments until you reach the last segment.
|
||||
*
|
||||
* Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb,
|
||||
* Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper.
|
||||
*
|
||||
* @param cell The cell to compute the range of line segments for.
|
||||
* @param segment_begin Begin iterator for all edges of the input Polygons.
|
||||
* @param segment_end End iterator for all edges of the input Polygons.
|
||||
* @return Range of line segments that surround the cell.
|
||||
*/
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
Geometry::SegmentCellRange<
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
|
||||
compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
template<typename T> static bool is_in_range(double value)
|
||||
{
|
||||
return double(std::numeric_limits<T>::lowest()) <= value && value <= double(std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
template<typename T> static bool is_in_range(const VD::vertex_type &vertex)
|
||||
{
|
||||
return VoronoiUtils::is_finite(vertex) && is_in_range<T>(vertex.x()) && is_in_range<T>(vertex.y());
|
||||
}
|
||||
|
||||
template<typename T> static bool is_in_range(const VD::edge_type &edge)
|
||||
{
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr)
|
||||
return false;
|
||||
|
||||
return is_in_range<T>(*edge.vertex0()) && is_in_range<T>(*edge.vertex1());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
#endif // slic3r_VoronoiUtils_hpp_
|
@ -7,15 +7,26 @@
|
||||
#include <CGAL/Surface_sweep_2_algorithms.h>
|
||||
|
||||
#include "libslic3r/Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp"
|
||||
#include "libslic3r/Geometry/VoronoiUtils.hpp"
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
|
||||
#include "VoronoiUtilsCgal.hpp"
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
using namespace Slic3r::Arachne;
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_iterator;
|
||||
|
||||
// Explicit template instantiation.
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
|
||||
// The tangent vector of the parabola is computed based on the Proof of the reflective property.
|
||||
// https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property
|
||||
// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663
|
||||
@ -121,30 +132,30 @@ using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrie
|
||||
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
|
||||
using CGAL_Point = impl::K::Point_2;
|
||||
|
||||
inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
|
||||
inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
|
||||
inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
|
||||
inline CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
|
||||
inline CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
|
||||
inline CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
|
||||
|
||||
inline static Linef make_linef(const VD::edge_type &edge)
|
||||
inline Linef make_linef(const VD::edge_type &edge)
|
||||
{
|
||||
const VD::vertex_type *v0 = edge.vertex0();
|
||||
const VD::vertex_type *v1 = edge.vertex1();
|
||||
return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())};
|
||||
}
|
||||
|
||||
[[maybe_unused]] inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); }
|
||||
[[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_second.y(); }
|
||||
|
||||
// FIXME Lukas H.: Also includes parabolic segments.
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
|
||||
{
|
||||
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
|
||||
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
|
||||
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; };
|
||||
using CGAL_E_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
|
||||
using CGAL_E_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
|
||||
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_Point { return {pt.x(), pt.y()}; };
|
||||
|
||||
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
|
||||
[](const VD::edge_type &edge) { return edge.color() == 0; }));
|
||||
|
||||
std::vector<CGAL_Segment> segments;
|
||||
std::vector<CGAL_E_Segment> segments;
|
||||
segments.reserve(voronoi_diagram.num_edges());
|
||||
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges()) {
|
||||
@ -163,7 +174,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges())
|
||||
edge.color(0);
|
||||
|
||||
std::vector<CGAL_Point> intersections_pt;
|
||||
std::vector<CGAL_E_Point> intersections_pt;
|
||||
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
|
||||
return intersections_pt.empty();
|
||||
}
|
||||
@ -178,29 +189,44 @@ struct ParabolicSegment
|
||||
const CGAL::Orientation is_focus_on_left;
|
||||
};
|
||||
|
||||
inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<VoronoiUtils::Segment> &segments)
|
||||
template<typename SegmentIterator>
|
||||
inline static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
ParabolicSegment>::type
|
||||
get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
assert(edge.is_curved());
|
||||
|
||||
const VD::cell_type *left_cell = edge.cell();
|
||||
const VD::cell_type *right_cell = edge.twin()->cell();
|
||||
|
||||
const Point focus_pt = VoronoiUtils::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments);
|
||||
const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments);
|
||||
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt)));
|
||||
const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end);
|
||||
const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end);
|
||||
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt)));
|
||||
|
||||
assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN);
|
||||
return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side};
|
||||
|
||||
const Point directrix_from = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::LOW);
|
||||
const Point directrix_to = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::HIGH);
|
||||
return {focus_pt, Line(directrix_from, directrix_to), make_linef(edge), focus_side};
|
||||
}
|
||||
|
||||
inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector<VoronoiUtils::Segment> &segments) {
|
||||
template<typename SegmentIterator>
|
||||
inline static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
CGAL::Orientation>::type
|
||||
orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
|
||||
CGAL::Orientation orientation;
|
||||
if (edge_a.is_linear() && edge_b.is_linear()) {
|
||||
orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1()));
|
||||
} else if (edge_a.is_curved() && edge_b.is_curved()) {
|
||||
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments);
|
||||
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments);
|
||||
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end);
|
||||
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end);
|
||||
orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a),
|
||||
to_cgal_point(parabolic_a.focus),
|
||||
to_cgal_point(parabolic_a.directrix.a),
|
||||
@ -216,7 +242,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
|
||||
|
||||
const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a;
|
||||
const VD::edge_type ¶bolic_edge = edge_a.is_curved() ? edge_a : edge_b;
|
||||
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments);
|
||||
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end);
|
||||
orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()),
|
||||
to_cgal_point(parabolic.focus),
|
||||
to_cgal_point(parabolic.directrix.a),
|
||||
@ -230,39 +256,54 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
|
||||
return orientation;
|
||||
}
|
||||
|
||||
static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector<VoronoiUtils::Segment> &segments)
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
bool>::type
|
||||
check_if_three_edges_are_ccw(const VD::edge_type &edge_first,
|
||||
const VD::edge_type &edge_second,
|
||||
const VD::edge_type &edge_third,
|
||||
const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end)
|
||||
{
|
||||
assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0()));
|
||||
assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0()));
|
||||
|
||||
CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments);
|
||||
CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end);
|
||||
if (orientation == CGAL::Orientation::COLLINEAR) {
|
||||
// The first two edges are collinear, so the third edge must be on the right side on the first of them.
|
||||
return orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN;
|
||||
return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == CGAL::Orientation::RIGHT_TURN;
|
||||
} else if (orientation == CGAL::Orientation::LEFT_TURN) {
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI.
|
||||
// So we need to check if test_pt isn't between them.
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
|
||||
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
|
||||
} else {
|
||||
assert(orientation == CGAL::Orientation::RIGHT_TURN);
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI.
|
||||
// So we need to check if test_pt is between them.
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
|
||||
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
bool>::type
|
||||
VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram,
|
||||
const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end)
|
||||
{
|
||||
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
|
||||
std::vector<const VD::edge_type *> edges;
|
||||
const VD::edge_type *edge = vertex.incident_edge();
|
||||
|
||||
do {
|
||||
if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
|
||||
VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
edges.emplace_back(edge);
|
||||
|
||||
edge = edge->rot_next();
|
||||
@ -271,11 +312,11 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
|
||||
// Checking for CCW make sense for three and more edges.
|
||||
if (edges.size() > 2) {
|
||||
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) {
|
||||
const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
|
||||
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it;
|
||||
const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
|
||||
const VD::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
|
||||
const VD::edge_type *curr_edge = *edge_it;
|
||||
const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
|
||||
|
||||
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments))
|
||||
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
#define slic3r_VoronoiUtilsCgal_hpp_
|
||||
|
||||
#include "Voronoi.hpp"
|
||||
#include "../Arachne/utils/VoronoiUtils.hpp"
|
||||
#include "../Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
class VoronoiDiagram;
|
||||
@ -18,8 +18,12 @@ public:
|
||||
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
// Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex.
|
||||
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<Arachne::VoronoiUtils::Segment> &segments);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
bool>::type
|
||||
is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
};
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
|
@ -1237,7 +1237,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
|
||||
vol->supported_facets.assign(volume->supported_facets);
|
||||
vol->seam_facets.assign(volume->seam_facets);
|
||||
vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets);
|
||||
vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets);
|
||||
|
||||
// Perform conversion only if the target "imperial" state is different from the current one.
|
||||
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
|
||||
@ -1349,7 +1349,7 @@ void ModelVolume::reset_extra_facets()
|
||||
{
|
||||
this->supported_facets.reset();
|
||||
this->seam_facets.reset();
|
||||
this->mmu_segmentation_facets.reset();
|
||||
this->mm_segmentation_facets.reset();
|
||||
}
|
||||
|
||||
|
||||
@ -1915,7 +1915,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
|
||||
config.set_new_unique_id();
|
||||
supported_facets.set_new_unique_id();
|
||||
seam_facets.set_new_unique_id();
|
||||
mmu_segmentation_facets.set_new_unique_id();
|
||||
mm_segmentation_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
void ModelVolume::rotate(double angle, Axis axis)
|
||||
@ -2224,7 +2224,7 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
|
||||
{
|
||||
return model_property_changed(mo, mo_new,
|
||||
[](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; },
|
||||
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); });
|
||||
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); });
|
||||
}
|
||||
|
||||
bool model_has_parameter_modifiers_in_objects(const Model &model)
|
||||
|
@ -823,8 +823,8 @@ public:
|
||||
// List of seam enforcers/blockers.
|
||||
FacetsAnnotation seam_facets;
|
||||
|
||||
// List of mesh facets painted for MMU segmentation.
|
||||
FacetsAnnotation mmu_segmentation_facets;
|
||||
// List of mesh facets painted for MM segmentation.
|
||||
FacetsAnnotation mm_segmentation_facets;
|
||||
|
||||
// Is set only when volume is Embossed Text type
|
||||
// Contain information how to re-create volume
|
||||
@ -929,12 +929,12 @@ public:
|
||||
this->config.set_new_unique_id();
|
||||
this->supported_facets.set_new_unique_id();
|
||||
this->seam_facets.set_new_unique_id();
|
||||
this->mmu_segmentation_facets.set_new_unique_id();
|
||||
this->mm_segmentation_facets.set_new_unique_id();
|
||||
}
|
||||
|
||||
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
|
||||
bool is_seam_painted() const { return !this->seam_facets.empty(); }
|
||||
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
|
||||
bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); }
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
@ -973,11 +973,11 @@ private:
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->mm_segmentation_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
assert(this->id() != this->mmu_segmentation_facets.id());
|
||||
assert(this->id() != this->mm_segmentation_facets.id());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1003,23 +1003,23 @@ private:
|
||||
ObjectBase(other),
|
||||
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
|
||||
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
|
||||
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
|
||||
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets),
|
||||
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->mm_segmentation_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
assert(this->id() != this->mmu_segmentation_facets.id());
|
||||
assert(this->id() != this->mm_segmentation_facets.id());
|
||||
assert(this->id() == other.id());
|
||||
assert(this->config.id() == other.config.id());
|
||||
assert(this->supported_facets.id() == other.supported_facets.id());
|
||||
assert(this->seam_facets.id() == other.seam_facets.id());
|
||||
assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id());
|
||||
assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id());
|
||||
this->set_material_id(other.material_id());
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
@ -1031,11 +1031,11 @@ private:
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->mm_segmentation_facets.id().valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
assert(this->id() != this->mmu_segmentation_facets.id());
|
||||
assert(this->id() != this->mm_segmentation_facets.id());
|
||||
assert(this->id() != other.id());
|
||||
assert(this->config.id() == other.config.id());
|
||||
this->set_material_id(other.material_id());
|
||||
@ -1046,11 +1046,11 @@ private:
|
||||
assert(this->config.id() != other.config.id());
|
||||
assert(this->supported_facets.id() != other.supported_facets.id());
|
||||
assert(this->seam_facets.id() != other.seam_facets.id());
|
||||
assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
|
||||
assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->supported_facets.empty());
|
||||
assert(this->seam_facets.empty());
|
||||
assert(this->mmu_segmentation_facets.empty());
|
||||
assert(this->mm_segmentation_facets.empty());
|
||||
}
|
||||
|
||||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||
@ -1058,19 +1058,19 @@ private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization, therefore no IDs are allocated.
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) {
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->supported_facets.id().invalid());
|
||||
assert(this->seam_facets.id().invalid());
|
||||
assert(this->mmu_segmentation_facets.id().invalid());
|
||||
assert(this->mm_segmentation_facets.id().invalid());
|
||||
}
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
bool has_convex_hull;
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
|
||||
cereal::load_by_value(ar, supported_facets);
|
||||
cereal::load_by_value(ar, seam_facets);
|
||||
cereal::load_by_value(ar, mmu_segmentation_facets);
|
||||
cereal::load_by_value(ar, mm_segmentation_facets);
|
||||
cereal::load_by_value(ar, config);
|
||||
cereal::load(ar, text_configuration);
|
||||
cereal::load(ar, emboss_shape);
|
||||
@ -1088,7 +1088,7 @@ private:
|
||||
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
|
||||
cereal::save_by_value(ar, supported_facets);
|
||||
cereal::save_by_value(ar, seam_facets);
|
||||
cereal::save_by_value(ar, mmu_segmentation_facets);
|
||||
cereal::save_by_value(ar, mm_segmentation_facets);
|
||||
cereal::save_by_value(ar, config);
|
||||
cereal::save(ar, text_configuration);
|
||||
cereal::save(ar, emboss_shape);
|
||||
|