diff --git a/.clang-format b/.clang-format index 440c89ec57..6ec205af84 100644 --- a/.clang-format +++ b/.clang-format @@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 78 +ColumnLimit: 140 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: true ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/resources/profiles/Creality/ENDER5PRO_thumbnail.png b/resources/profiles/Creality/ENDER5PRO_thumbnail.png new file mode 100644 index 0000000000..ce4e5b0442 Binary files /dev/null and b/resources/profiles/Creality/ENDER5PRO_thumbnail.png differ diff --git a/resources/profiles/Rigid3D.idx b/resources/profiles/Rigid3D.idx new file mode 100644 index 0000000000..21c2f4b0c2 --- /dev/null +++ b/resources/profiles/Rigid3D.idx @@ -0,0 +1,3 @@ +min_slic3r_version = 2.6.0-alpha0 +1.0.0 Initial Rigid3D bundle + diff --git a/resources/profiles/Rigid3D.ini b/resources/profiles/Rigid3D.ini new file mode 100644 index 0000000000..0583862939 --- /dev/null +++ b/resources/profiles/Rigid3D.ini @@ -0,0 +1,469 @@ +# Print profiles for the Rigid3D printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Rigid3D +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 1.0.0 +# Where to get the updates from? +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Rigid3D/ +# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +# Printer model name will be shown by the installation wizard. + +[printer_model:Zero2] +name = Rigid3D Zero2 +variants = 0.4 +technology = FFF +family = Zero +bed_model = zero_bed.stl +bed_texture = zero2_bed.png +default_materials = Generic PLA @Rigid3D; Generic PETG @Rigid3D; Generic ABS @Rigid3D; Rigid3D PLA @Rigid3D; Generic Nylon @Rigid3D; Generic FLEX @Rigid3D + +[printer_model:Zero3] +name = Rigid3D Zero3 +variants = 0.4 +technology = FFF +family = Zero +bed_model = zero_bed.stl +bed_texture = zero3_bed.png +default_materials =Generic PLA @Rigid3D; Generic PETG @Rigid3D; Generic ABS @Rigid3D; Rigid3D PLA @Rigid3D; Generic Nylon @Rigid3D; Generic FLEX @Rigid3D + +[printer_model:Mucit] +name = Rigid3D Mucit +variants = 0.4 +technology = FFF +family = Mucit +bed_model = mucit_bed.stl +bed_texture = mucit_bed.png +default_materials = Generic PLA @Rigid3D; Rigid3D PLA @Rigid3D + +[printer_model:Mucit2] +name = Rigid3D Mucit2 +variants = 0.4 +technology = FFF +family = Mucit +bed_model = mucit2_bed.stl +bed_texture = mucit2_bed.png +default_materials = Generic PLA @Rigid3D; Generic PETG @Rigid3D; Generic ABS @Rigid3D; Rigid3D PLA @Rigid3D; Generic Nylon @Rigid3D; Generic FLEX @Rigid3D + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +# Common print preset +[print:*common*] +avoid_crossing_perimeters = 0 +avoid_crossing_perimeters_max_detour = 0 +bottom_fill_pattern = monotonic +bottom_solid_min_thickness = 0.8 +bridge_acceleration = 0 +bridge_angle = 0 +bridge_flow_ratio = 1 +bridge_speed = 60 +brim_separation = 0 +brim_type = no_brim +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_RIGID3D.*/ +complete_objects = 0 +default_acceleration = 0 +dont_support_bridges = 1 +draft_shield = disabled +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 0 +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 50% +external_perimeters_first = 1 +extra_perimeters = 1 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 10% +fill_pattern = line +first_layer_acceleration = 0 +first_layer_acceleration_over_raft = 0 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 50% +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 = 2.5 +infill_anchor_max = 12 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 1 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +inherits = +interface_shells = 0 +ironing = 0 +ironing_flowrate = 15% +ironing_spacing = 0.1 +ironing_speed = 15 +ironing_type = top +max_print_speed = 80 +max_volumetric_speed = 0 +min_skirt_length = 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_{initial_filament_type}_{printer_model}_{print_time}.gcode +overhangs = 1 +perimeter_acceleration = 0 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_contact_distance = 0.1 +raft_expansion = 1.5 +raft_first_layer_density = 90% +raft_first_layer_expansion = 3 +raft_layers = 0 +resolution = 0 +seam_position = rear +single_extruder_multi_material_priming = 1 +skirt_distance = 6 +skirt_height = 1 +skirts = 0 +slice_closing_radius = 0.049 +slicing_mode = regular +small_perimeter_speed = 15 +solid_infill_below_area = 70 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 100% +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_auto = 1 +support_material_bottom_contact_distance = 0 +support_material_bottom_interface_layers = -1 +support_material_buildplate_only = 0 +support_material_closing_radius = 2 +support_material_contact_distance = 0.2 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 3 +support_material_interface_pattern = rectilinear +support_material_interface_spacing = 0 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2.5 +support_material_speed = 60 +support_material_style = grid +support_material_synchronize_layers = 0 +support_material_threshold = 0 +support_material_with_sheath = 1 +support_material_xy_spacing = 50% +thick_bridges = 0 +thin_walls = 1 +top_fill_pattern = monotonic +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 100% +top_solid_min_thickness = 0.8 +travel_speed = 80 +travel_speed_z = 0 +wipe_tower = 0 +wipe_tower_bridging = 10 +wipe_tower_brim_width = 2 +wipe_tower_no_sparse_layers = 0 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.06mm - Ultra @Rigid3D] +inherits = *common* +layer_height = 0.06 +bottom_solid_layers = 10 +top_solid_layers = 14 + +[print:0.12mm - Super @Rigid3D] +inherits = *common* +layer_height = 0.12 +bottom_solid_layers = 7 +top_solid_layers = 7 + +[print:0.16mm - Good @Rigid3D] +inherits = *common* +layer_height = 0.16 +bottom_solid_layers = 5 +top_solid_layers = 5 + +[print:0.20mm - Standard @Rigid3D] +inherits = *common* +layer_height = 0.20 +bottom_solid_layers = 4 +top_solid_layers = 4 + +[print:0.24mm - Draft @Rigid3D] +inherits = *common* +layer_height = 0.24 +bottom_solid_layers = 3 +top_solid_layers = 4 + +[print:0.28mm - Low @Rigid3D] +inherits = *common* +layer_height = 0.28 +bottom_solid_layers = 3 +top_solid_layers = 4 + +[filament:*common*] +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +extrusion_multiplier = 1 +fan_below_layer_time = 60 +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_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_retract_before_travel = nil +filament_retract_before_wipe = nil +filament_retract_layer_change = nil +filament_retract_length = nil +filament_retract_lift = nil +filament_retract_lift_above = nil +filament_retract_lift_below = nil +filament_retract_restart_extra = nil +filament_retract_speed = nil +filament_settings_id = "" +filament_soluble = 0 +filament_spool_weight = 0 +filament_wipe = nil +min_print_speed = 10 +slowdown_below_layer_time = 10 +start_filament_gcode = "; Filament gcode\n" + +[filament:*PLA*] +inherits = *common* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_RIGID3D.*/ +bridge_fan_speed = 100 +cooling = 1 +fan_always_on = 1 +filament_type = PLA +full_fan_speed_layer = 4 +max_fan_speed = 100 +min_fan_speed = 100 + +[filament:*ABS*] +inherits = *common* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_RIGID3D.*/ and printer_notes=~/.*PRINTER_HAS_HEATEDBED.*/ +cooling = 1 +fan_always_on = 0 +filament_type = ABS +max_fan_speed = 0 +min_fan_speed = 0 + +[filament:*PETG*] +inherits = *common* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_RIGID3D.*/ and printer_notes=~/.*PRINTER_HAS_HEATEDBED.*/ +cooling = 1 +fan_always_on = 0 +filament_type = PETG +max_fan_speed = 50 +min_fan_speed = 30 + +[filament:*FLEX*] +inherits = *common* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_RIGID3D.*/ and printer_notes=~/.*PRINTER_HAS_HEATEDBED.*/ +bridge_fan_speed = 100 +cooling = 1 +fan_always_on = 1 +filament_type = FLEX +full_fan_speed_layer = 4 +max_fan_speed = 100 +min_fan_speed = 100 + +[filament:*NYLON*] +inherits = *common* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_RIGID3D.*/ and printer_notes=~/.*PRINTER_HAS_HEATEDBED.*/ +cooling = 1 +fan_always_on = 0 +filament_type = NYLON +max_fan_speed = 0 +min_fan_speed = 0 + +[filament:Generic PLA @Rigid3D] +inherits = *PLA* +bed_temperature = 55 +filament_colour = #FFFF00 +filament_density = 1.24 +filament_max_volumetric_speed = 12 +first_layer_temperature = 215 +temperature = 215 +first_layer_bed_temperature = 60 + +[filament:Rigid3D PLA @Rigid3D] +inherits = *PLA* +filament_colour = #FFFF00 +filament_density = 1.24 +first_layer_temperature = 210 +temperature = 205 +first_layer_bed_temperature = 60 +bed_temperature = 55 +filament_max_volumetric_speed = 12 + +[filament:Generic ABS @Rigid3D] +inherits = *ABS* +filament_colour = #49B928 +filament_density = 1.04 +first_layer_temperature = 235 +temperature = 235 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_max_volumetric_speed = 8 + +[filament:Generic PETG @Rigid3D] +inherits = *PETG* +filament_colour = #FF8000 +filament_density = 1.27 +first_layer_temperature = 240 +temperature = 240 +first_layer_bed_temperature = 70 +bed_temperature = 70 +filament_max_volumetric_speed = 8 + +[filament:Generic FLEX @Rigid3D] +inherits = *FLEX* +filament_colour = #C80000 +filament_density = 1.21 +first_layer_temperature = 225 +temperature = 225 +first_layer_bed_temperature = 70 +bed_temperature = 70 +filament_max_volumetric_speed = 2.5 + +[filament:Generic Nylon @Rigid3D] +inherits = *NYLON* +filament_colour = #0AF1CE +filament_density = 1.12 +first_layer_temperature = 250 +temperature = 250 +first_layer_bed_temperature = 100 +bed_temperature = 100 +filament_max_volumetric_speed = 8 + +# Common printer preset +[printer:*common*] +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +color_change_gcode = M600 +default_filament_profile = "Rigid3D PLA @Rigid3D" +default_print_profile = "0.20mm - Standard @Rigid3D" +deretract_speed = 0 +extruder_offset = 0x0 +gcode_flavor = marlin2 +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z]\n\n +machine_limits_usage = time_estimate_only +machine_max_acceleration_e = 1000 +machine_max_acceleration_extruding = 500 +machine_max_acceleration_retracting = 1000 +machine_max_acceleration_travel = 1000 +machine_max_acceleration_x = 500 +machine_max_acceleration_y = 500 +machine_max_acceleration_z = 100 +machine_max_feedrate_e = 25 +machine_max_feedrate_x = 120 +machine_max_feedrate_y = 120 +machine_max_feedrate_z = 20 +machine_max_jerk_e = 5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.3 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +max_layer_height = 0.32 +min_layer_height = 0.05 +nozzle_diameter = 0.4 +pause_print_gcode = M0 +printer_settings_id = +printer_technology = FFF +printer_variant = 0.4 +remaining_times = 1 +retract_before_travel = 2 +retract_before_wipe = 0% +retract_layer_change = 0 +retract_length = 1 +retract_length_toolchange = 10 +retract_lift = 0 +retract_lift_above = 0 +retract_lift_below = 0 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 25 +silent_mode = 0 +single_extruder_multi_material = 0 +template_custom_gcode = +thumbnails = +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 0 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 0 +z_offset = 0 + +[printer:Rigid3D Zero2] +inherits = *common* +bed_shape = 0x0,200x0,200x200,0x200 +max_print_height = 190 +printer_model = Zero2 +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_RIGID3D\nPRINTER_MODEL_ZERO2\nPRINTER_HAS_HEATEDBED\n +start_gcode = G21\nG92 E0\nG28\nM420 S1\nM107\nG90\nG1 X10.0 Y0.1 Z0.3 F3000.0\nG1 X190.0 Y0.1 Z0.3 F1500.0 E15\nG1 X190 Y0.4 Z0.3 F3000.0\nG1 X10.0 Y0.4 Z0.3 F1500.0 E30\nG1 Z2.0 F1500.0\nG92 E0\n +end_gcode = G1 X0 Y180\nM107\nG91\nG0 Z20\nT0\nG1 E-1\nM104 T0 S0\nG90\nG92 E0\nM140 S0\nM84\nM300 S2093 P150\nM300 S2637 P150\nM300 S3135 P150\nM300 S4186 P150\nM300 S3135 P150\nM300 S2637 P150\nM300 S2793 P150\nM300 S2349 P150\nM300 S1975 P150\nM300 S2093 P450\n + +[printer:Rigid3D Zero3] +inherits = *common* +bed_shape = 0x0,200x0,200x200,0x200 +max_print_height = 200 +printer_model = Zero3 +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_RIGID3D\nPRINTER_MODEL_ZERO3\nPRINTER_HAS_HEATEDBED\n +start_gcode = G21\nG92 E0\nG28\nM420 S1\nM107\nG90\nG1 X10.0 Y0.1 Z0.3 F3000.0\nG1 X190.0 Y0.1 Z0.3 F1500.0 E15\nG1 X190 Y0.4 Z0.3 F3000.0\nG1 X10.0 Y0.4 Z0.3 F1500.0 E30\nG1 Z2.0 F1500.0\nG92 E0\n +end_gcode = G92 E0\nT0\nG1 F1800 E-2\nG27 P2\nM107\nM104 T0 S0\nM140 S0\nG90\nG92 E0\nM18\n + +[printer:Rigid3D Mucit] +inherits = *common* +bed_shape = 0x0,150x0,150x150,0x150 +max_print_height = 150 +printer_model = Mucit +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_RIGID3D\nPRINTER_MODEL_MUCIT\n +start_gcode = G21\nG92 E0\nG28\nM420 S1\nM107\nG90\nG1 X10.0 Y0.1 Z0.3 F3000.0\nG1 X140.0 Y0.1 Z0.3 F1500.0 E10\nG1 X140 Y0.4 Z0.3 F3000.0\nG1 X10.0 Y0.4 Z0.3 F1500.0 E20\nG1 Z2.0 F1500.0\nG92 E0\n +end_gcode = G1 X0 Y140\nM107\nG91\nG0 Z20\nT0\nG1 E-2\nM104 T0 S0\nG90\nG92 E0\nM140 S0\nM84\nM300 S2093 P150\nM300 S2637 P150\nM300 S3135 P150\nM300 S4186 P150\nM300 S3135 P150\nM300 S2637 P150\nM300 S2793 P150\nM300 S2349 P150\nM300 S1975 P150\nM300 S2093 P450\n + +[printer:Rigid3D Mucit2] +inherits = *common* +bed_shape = 0x0,150x0,150x150,0x150 +max_print_height = 150 +printer_model = Mucit2 +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_RIGID3D\nPRINTER_MODEL_MUCIT2\nPRINTER_HAS_HEATEDBED\n +start_gcode = G21\nG92 E0\nG28\nM420 S1\nM107\nG90\nG1 X10.0 Y0.1 Z0.3 F3000.0\nG1 X140.0 Y0.1 Z0.3 F1500.0 E10\nG1 X140 Y0.4 Z0.3 F3000.0\nG1 X10.0 Y0.4 Z0.3 F1500.0 E20\nG1 Z2.0 F1500.0\nG92 E0\n +end_gcode = G92 E0\nT0\nG1 F1800 E-2\nG27 P2\nM107\nM104 T0 S0\nM140 S0\nG90\nG92 E0\nM18\n diff --git a/resources/profiles/Rigid3D/Mucit2_thumbnail.png b/resources/profiles/Rigid3D/Mucit2_thumbnail.png new file mode 100644 index 0000000000..c4eb8d5260 Binary files /dev/null and b/resources/profiles/Rigid3D/Mucit2_thumbnail.png differ diff --git a/resources/profiles/Rigid3D/Mucit_thumbnail.png b/resources/profiles/Rigid3D/Mucit_thumbnail.png new file mode 100644 index 0000000000..0796709d11 Binary files /dev/null and b/resources/profiles/Rigid3D/Mucit_thumbnail.png differ diff --git a/resources/profiles/Rigid3D/Zero2_thumbnail.png b/resources/profiles/Rigid3D/Zero2_thumbnail.png new file mode 100644 index 0000000000..af2606198d Binary files /dev/null and b/resources/profiles/Rigid3D/Zero2_thumbnail.png differ diff --git a/resources/profiles/Rigid3D/Zero3_thumbnail.png b/resources/profiles/Rigid3D/Zero3_thumbnail.png new file mode 100644 index 0000000000..7f65192f2c Binary files /dev/null and b/resources/profiles/Rigid3D/Zero3_thumbnail.png differ diff --git a/resources/profiles/Rigid3D/mucit2_bed.png b/resources/profiles/Rigid3D/mucit2_bed.png new file mode 100644 index 0000000000..d01c0b82d7 Binary files /dev/null and b/resources/profiles/Rigid3D/mucit2_bed.png differ diff --git a/resources/profiles/Rigid3D/mucit2_bed.stl b/resources/profiles/Rigid3D/mucit2_bed.stl new file mode 100644 index 0000000000..be5f5b21f0 Binary files /dev/null and b/resources/profiles/Rigid3D/mucit2_bed.stl differ diff --git a/resources/profiles/Rigid3D/mucit_bed.png b/resources/profiles/Rigid3D/mucit_bed.png new file mode 100644 index 0000000000..2f94cb8f26 Binary files /dev/null and b/resources/profiles/Rigid3D/mucit_bed.png differ diff --git a/resources/profiles/Rigid3D/mucit_bed.stl b/resources/profiles/Rigid3D/mucit_bed.stl new file mode 100644 index 0000000000..6d1d981d2b Binary files /dev/null and b/resources/profiles/Rigid3D/mucit_bed.stl differ diff --git a/resources/profiles/Rigid3D/zero2_bed.png b/resources/profiles/Rigid3D/zero2_bed.png new file mode 100644 index 0000000000..4e6e04e652 Binary files /dev/null and b/resources/profiles/Rigid3D/zero2_bed.png differ diff --git a/resources/profiles/Rigid3D/zero3_bed.png b/resources/profiles/Rigid3D/zero3_bed.png new file mode 100644 index 0000000000..3c5dd5f193 Binary files /dev/null and b/resources/profiles/Rigid3D/zero3_bed.png differ diff --git a/resources/profiles/Rigid3D/zero_bed.stl b/resources/profiles/Rigid3D/zero_bed.stl new file mode 100644 index 0000000000..027e280891 Binary files /dev/null and b/resources/profiles/Rigid3D/zero_bed.stl differ diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 7b9595419a..39f828b558 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -1,8 +1,6 @@ #ifndef SRC_LIBSLIC3R_AABBTREELINES_HPP_ #define SRC_LIBSLIC3R_AABBTREELINES_HPP_ -#include "libslic3r/Point.hpp" -#include "libslic3r/EdgeGrid.hpp" #include "libslic3r/AABBTreeIndirect.hpp" #include "libslic3r/Line.hpp" diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3ec4502a2d..f77bbaee83 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -245,6 +245,8 @@ set(SLIC3R_SOURCES SlicingAdaptive.hpp Subdivide.cpp Subdivide.hpp + SupportSpotsGenerator.cpp + SupportSpotsGenerator.hpp SupportMaterial.cpp SupportMaterial.hpp Surface.cpp @@ -277,6 +279,8 @@ set(SLIC3R_SOURCES TriangleSelector.hpp TriangleSetSampling.cpp TriangleSetSampling.hpp + TriangleSelectorWrapper.cpp + TriangleSelectorWrapper.hpp MTUtils.hpp Zipper.hpp Zipper.cpp diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3868c87dfa..3c773c89a3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5,6 +5,7 @@ #include "libslic3r/format.hpp" #include "GCodeProcessor.hpp" +#include #include #include #include @@ -777,8 +778,7 @@ void GCodeProcessorResult::reset() { max_print_height = 0.0f; settings_ids.reset(); extruders_count = 0; - extruder_colors = DEFAULT_EXTRUDER_COLORS; - assert(extruder_colors.size() == MIN_EXTRUDERS_COUNT); + extruder_colors = std::vector(); filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); #if ENABLE_USED_FILAMENT_POST_PROCESS @@ -870,6 +870,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); + m_producer = EProducer::PrusaSlicer; m_flavor = config.gcode_flavor; size_t extruders_count = config.nozzle_diameter.values.size(); @@ -1093,6 +1094,20 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } + // With MM setups like Prusa MMU2, the filaments may be expected to be parked at the beginning. + // Remember the parking position so the initial load is not included in filament estimate. + const ConfigOptionBool* single_extruder_multi_material = config.option("single_extruder_multi_material"); + const ConfigOptionBool* wipe_tower = config.option("wipe_tower"); + const ConfigOptionFloat* parking_pos_retraction = config.option("parking_pos_retraction"); + const ConfigOptionFloat* extra_loading_move = config.option("extra_loading_move"); + + if (single_extruder_multi_material != nullptr && wipe_tower != nullptr && parking_pos_retraction != nullptr && extra_loading_move != nullptr) { + if (single_extruder_multi_material->value && m_result.extruders_count > 1 && wipe_tower->value) { + m_parking_position = float(parking_pos_retraction->value); + m_extra_loading_move = float(extra_loading_move->value); + } + } + bool use_machine_limits = false; const ConfigOptionEnum* machine_limits_usage = config.option>("machine_limits_usage"); if (machine_limits_usage != nullptr) @@ -1289,6 +1304,7 @@ void GCodeProcessor::reset() m_options_z_corrector.reset(); m_spiral_vase_active = false; + m_kissslicer_toolchange_time_correction = 0.0f; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_mm3_per_mm_compare.reset(); @@ -1343,10 +1359,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function elements; + boost::split(elements, comment, boost::is_any_of("=")); + if (elements.size() == 2) { + try + { + switch (std::stoi(elements[1])) + { + default: { break; } + case 1: + case 2: + case 3: { m_flavor = gcfMarlinLegacy; break; } + } + return true; + } + catch (...) + { + // invalid data, do nothing + } + } + } + return false; + }; + + auto detect_printer = [this](const std::string_view comment) { + static const std::string search_str = "printer_name"; + const size_t pos = comment.find(search_str); + if (pos != comment.npos) { + std::vector elements; + boost::split(elements, comment, boost::is_any_of("=")); + if (elements.size() == 2) { + elements[1] = boost::to_upper_copy(elements[1]); + if (boost::contains(elements[1], "MK2.5") || boost::contains(elements[1], "MK3")) + m_kissslicer_toolchange_time_correction = 18.0f; // MMU2 + else if (boost::contains(elements[1], "MK2")) + m_kissslicer_toolchange_time_correction = 5.0f; // MMU + } + return true; + } + + return false; + }; + + begin = skip_whitespaces(begin, end); + if (begin != end) { + if (*begin == ';') { + // Comment. + begin = skip_whitespaces(++begin, end); + end = remove_eols(begin, end); + if (begin != end) { + const std::string_view comment(begin, end - begin); + if (detect_flavor(comment) || detect_printer(comment)) + ++found_counter; + } + + // we got the data, + // force early exit to avoid parsing the entire file + if (found_counter == 2) + m_parser.quit_parsing(); + } + else if (*begin == 'M' || *begin == 'G') + // the header has been fully parsed, quit search + m_parser.quit_parsing(); + } + } + ); + m_parser.reset(); +} + std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const { return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? @@ -2559,7 +2657,7 @@ bool GCodeProcessor::process_bambustudio_tags(const std::string_view comment) bool GCodeProcessor::detect_producer(const std::string_view comment) { for (const auto& [id, search_string] : Producers) { - size_t pos = comment.find(search_string); + const size_t pos = comment.find(search_string); if (pos != comment.npos) { m_producer = id; BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string; @@ -3615,10 +3713,12 @@ void GCodeProcessor::process_T(const std::string_view command) // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ")."; - } else { + } + else { unsigned char id = static_cast(eid); if (m_extruder_id != id) { - if (id >= m_result.extruder_colors.size()) + if (((m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) && id >= m_result.extruders_count) || + ((m_producer != EProducer::PrusaSlicer && m_producer != EProducer::Slic3rPE && m_producer != EProducer::Slic3r) && id >= m_result.extruder_colors.size())) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { unsigned char old_extruder_id = m_extruder_id; @@ -3631,6 +3731,8 @@ void GCodeProcessor::process_T(const std::string_view command) float extra_time = get_filament_unload_time(static_cast(old_extruder_id)); m_time_processor.extruder_unloaded = false; extra_time += get_filament_load_time(static_cast(m_extruder_id)); + if (m_producer == EProducer::KissSlicer && m_flavor == gcfMarlinLegacy) + extra_time += m_kissslicer_toolchange_time_correction; simulate_st_synchronize(extra_time); m_result.extruders_count = std::max(m_result.extruders_count, m_extruder_id + 1); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 3edea61f42..484f416163 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -575,6 +575,7 @@ namespace Slic3r { OptionsZCorrector m_options_z_corrector; size_t m_last_default_color_id; bool m_spiral_vase_active; + float m_kissslicer_toolchange_time_correction; #if ENABLE_GCODE_VIEWER_STATISTICS std::chrono::time_point m_start_time; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -648,6 +649,7 @@ namespace Slic3r { void apply_config(const DynamicPrintConfig& config); void apply_config_simplify3d(const std::string& filename); void apply_config_superslicer(const std::string& filename); + void apply_config_kissslicer(const std::string& filename); void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); // Process tags embedded into comments diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 4df95d7552..0f4e43a076 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1,5 +1,7 @@ #include "SeamPlacer.hpp" +#include "Point.hpp" +#include "libslic3r.h" #include "tbb/parallel_for.h" #include "tbb/blocked_range.h" #include "tbb/parallel_reduce.h" @@ -642,8 +644,8 @@ void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po, BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: decimate: start"; - its_short_edge_collpase(triangle_set, 25000); - its_short_edge_collpase(negative_volumes_set, 25000); + its_short_edge_collpase(triangle_set, SeamPlacer::fast_decimation_triangle_count_target); + its_short_edge_collpase(negative_volumes_set, SeamPlacer::fast_decimation_triangle_count_target); size_t negative_volumes_start_index = triangle_set.indices.size(); its_merge(triangle_set, negative_volumes_set); @@ -729,8 +731,9 @@ void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { struct SeamComparator { SeamPosition setup; float angle_importance; - explicit SeamComparator(SeamPosition setup) : - setup(setup) { + Vec2f rear_attractor; + explicit SeamComparator(SeamPosition setup, const Vec2f& rear_attractor = Vec2f::Zero()) : + setup(setup), rear_attractor(rear_attractor) { angle_importance = setup == spNearest ? SeamPlacer::angle_importance_nearest : SeamPlacer::angle_importance_aligned; } @@ -761,8 +764,9 @@ struct SeamComparator { return false; } - if (setup == SeamPosition::spRear && a.position.y() != b.position.y()) { - return a.position.y() > b.position.y(); + if (setup == SeamPosition::spRear) { + return (a.position.head<2>() - rear_attractor).squaredNorm() < + (b.position.head<2>() - rear_attractor).squaredNorm(); } float distance_penalty_a = 0.0f; @@ -824,7 +828,8 @@ struct SeamComparator { } if (setup == SeamPosition::spRear) { - return a.position.y() + SeamPlacer::seam_align_score_tolerance * 5.0f > b.position.y(); + return (a.position.head<2>() - rear_attractor).squaredNorm() - a.perimeter.flow_width < + (b.position.head<2>() - rear_attractor).squaredNorm(); } float penalty_a = a.overhang + a.visibility @@ -1452,7 +1457,9 @@ void SeamPlacer::init(const Print &print, std::function throw_if_can for (const PrintObject *po : print.objects()) { throw_if_canceled_func(); SeamPosition configured_seam_preference = po->config().seam_position.value; - SeamComparator comparator { configured_seam_preference }; + Vec2f rear_attractor = unscaled(po->bounding_box().center()).cast() + + 1.5f * Vec2f(0.0f, unscale(po->bounding_box().max.y())); + SeamComparator comparator{configured_seam_preference, rear_attractor}; { GlobalModelInfo global_model_info { }; diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 12db7c72bc..671f6bcce8 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -110,6 +110,7 @@ class SeamPlacer { public: // Number of samples generated on the mesh. There are sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples static constexpr size_t raycasting_visibility_samples_count = 30000; + static constexpr size_t fast_decimation_triangle_count_target = 16000; //square of number of rays per sample point static constexpr size_t sqr_rays_per_sample_point = 5; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 2741e9146c..aba428f8b0 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -924,6 +924,66 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det this->get_selected_preset().save(); } +Preset& PresetCollection::get_preset_with_name(const std::string& new_name, const Preset* initial_preset) +{ + // 1) Find the preset with a new_name or create a new one, + // initialize it with the preset_to config. + auto it = this->find_preset_internal(new_name); + if (it != m_presets.end() && it->name == new_name) { + // Preset with the same name found. + Preset& preset = *it; + if (!preset.is_default && !preset.is_external && !preset.is_system && initial_preset->name != new_name) { + // Overwriting an existing preset if it isn't default/external/system or isn't an initial_preset + preset.config = initial_preset->config; + // The newly saved preset can be activated -> make it visible. + preset.is_visible = true; + } + return preset; + } + + const std::string selected_preset_name = this->get_selected_preset_name(); + + // Creating a new preset. + Preset& preset = *m_presets.insert(it, *initial_preset); + std::string& inherits = preset.inherits(); + std::string old_name = preset.name; + preset.name = new_name; + preset.file = this->path_from_name(new_name); + preset.vendor = nullptr; + preset.alias.clear(); + preset.renamed_from.clear(); + if (preset.is_system) { + // Inheriting from a system preset. + inherits = old_name; + } + else if (inherits.empty()) { + // Inheriting from a user preset. Link the new preset to the old preset. + // inherits = old_name; + } + else { + // Inherited from a user preset. Just maintain the "inherited" flag, + // meaning it will inherit from either the system preset, or the inherited user preset. + } + preset.is_default = false; + preset.is_system = false; + preset.is_external = false; + // The newly saved preset can be activated -> make it visible. + preset.is_visible = true; + // Just system presets have aliases + preset.alias.clear(); + + // sort printers and get new it + std::sort(m_presets.begin(), m_presets.end()); + + // set initial preset selection + this->select_preset_by_name(selected_preset_name, true); + + it = this->find_preset_internal(new_name); + assert(it != m_presets.end()); + + return *it; +} + bool PresetCollection::delete_current_preset() { const Preset &selected = this->get_selected_preset(); @@ -947,6 +1007,11 @@ bool PresetCollection::delete_current_preset() bool PresetCollection::delete_preset(const std::string& name) { + if (name == this->get_selected_preset().name) + return delete_current_preset(); + + const std::string selected_preset_name = this->get_selected_preset_name(); + auto it = this->find_preset_internal(name); const Preset& preset = *it; @@ -957,6 +1022,10 @@ bool PresetCollection::delete_preset(const std::string& name) boost::nowide::remove(preset.file.c_str()); } m_presets.erase(it); + + // update selected preset + this->select_preset_by_name(selected_preset_name, true); + return true; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 839ebca460..f0a81a71ad 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -341,6 +341,10 @@ public: // All presets are marked as not modified and the new preset is activated. void save_current_preset(const std::string &new_name, bool detach = false); + // Find the preset with a new_name or create a new one, + // initialize it with the initial_preset config. + Preset& get_preset_with_name(const std::string& new_name, const Preset* initial_preset); + // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. bool delete_current_preset(); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d98998cc40..b783c8aebc 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -425,16 +425,24 @@ void PresetBundle::load_installed_printers(const AppConfig &config) preset.set_visible_from_appconfig(config); } -const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) const +PresetCollection& PresetBundle::get_presets(Preset::Type type) +{ + assert(type >= Preset::TYPE_PRINT && type <= Preset::TYPE_PRINTER); + + return type == Preset::TYPE_PRINT ? prints : + type == Preset::TYPE_SLA_PRINT ? sla_prints : + type == Preset::TYPE_FILAMENT ? filaments : + type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers; +} + + +const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias) { // there are not aliases for Printers profiles if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID) return alias; - const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints : - preset_type == Preset::TYPE_SLA_PRINT ? sla_prints : - preset_type == Preset::TYPE_FILAMENT ? filaments : - sla_materials; + const PresetCollection& presets = get_presets(preset_type); return presets.get_preset_name_by_alias(alias); } @@ -442,10 +450,7 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options) { - PresetCollection& presets = type == Preset::TYPE_PRINT ? prints : - type == Preset::TYPE_SLA_PRINT ? sla_prints : - type == Preset::TYPE_FILAMENT ? filaments : - type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers; + PresetCollection& presets = get_presets(type); // if we want to save just some from selected options if (!unselected_options.empty()) { @@ -468,6 +473,49 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: } } +bool PresetBundle::transfer_and_save(Preset::Type type, const std::string& preset_from_name, const std::string& preset_to_name, + const std::string& preset_new_name, const std::vector& options) +{ + if (options.empty()) + return false; + + PresetCollection& presets = get_presets(type); + + const Preset* preset_to = presets.find_preset(preset_to_name, false, false); + if (!preset_to) + return false; + + // Find the preset with a new_name or create a new one, + // initialize it with the preset_to config. + Preset& preset = presets.get_preset_with_name(preset_new_name, preset_to); + if (preset.is_default || preset.is_external || preset.is_system) + // Cannot overwrite the default preset. + return false; + + // Apply options from the preset_from_name. + const Preset* preset_from = presets.find_preset(preset_from_name, false, false); + if (!preset_from) + return false; + preset.config.apply_only(preset_from->config, options); + + // Store new_name preset to disk. + preset.save(); + + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. + update_compatible(PresetSelectCompatibleType::Never); + + if (type == Preset::TYPE_PRINTER) + copy_bed_model_and_texture_if_needed(preset.config); + + if (type == Preset::TYPE_FILAMENT) { + // synchronize the first filament presets. + set_filament_preset(0, filaments.get_selected_preset_name()); + } + + return true; +} + void PresetBundle::load_installed_filaments(AppConfig &config) { if (! config.has_section(AppConfig::SECTION_FILAMENTS)) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 0213069679..549a132866 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -54,6 +54,8 @@ public: // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; + PresetCollection& get_presets(Preset::Type preset_type); + // The project configuration values are kept separated from the print/filament/printer preset, // they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode, // and they are being used by slicing core. @@ -142,11 +144,15 @@ public: // If the "vendor" section is missing, enable all models and variants of the particular vendor. void load_installed_printers(const AppConfig &config); - const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const; + const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias); // Save current preset of a provided type under a new name. If the name is different from the old one, // Unselected option would be reverted to the beginning values void save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options); + // Transfer options form preset_from_name preset to preset_to_name preset and save preset_to_name preset as new new_name preset + // Return false, if new preset wasn't saved + bool transfer_and_save(Preset::Type type, const std::string& preset_from_name, const std::string& preset_to_name, + const std::string& new_name, const std::vector& options); static const char *PRUSA_BUNDLE; private: diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9792a6968b..2979b3557b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -825,6 +825,8 @@ void Print::process() obj->infill(); for (PrintObject *obj : m_objects) obj->ironing(); + for (PrintObject *obj : m_objects) + obj->generate_support_spots(); for (PrintObject *obj : m_objects) obj->generate_support_material(); if (this->set_started(psWipeTower)) { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index cb01600fa5..c2777083d8 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -61,7 +61,7 @@ enum PrintStep : unsigned int { enum PrintObjectStep : unsigned int { posSlice, posPerimeters, posPrepareInfill, - posInfill, posIroning, posSupportMaterial, posCount, + posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posCount, }; // A PrintRegion object represents a group of volumes to print @@ -381,6 +381,7 @@ private: void prepare_infill(); void infill(); void ironing(); + void generate_support_spots(); void generate_support_material(); void slice_volumes(); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 1cae900e79..c3792779c3 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1195,8 +1195,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ update_apply_status(false); } // Invalidate just the supports step. - for (const PrintObjectStatus &print_object_status : print_objects_range) + for (const PrintObjectStatus &print_object_status : print_objects_range) { + update_apply_status(print_object_status.print_object->invalidate_step(posSupportSpotsSearch)); update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); + } if (supports_differ) { // Copy just the support volumes. model_volume_list_update_supports(model_object, model_object_new); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e0806818ce..f4b69bb812 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -17,6 +17,9 @@ #include "Fill/FillAdaptive.hpp" #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" +#include "SupportSpotsGenerator.hpp" +#include "TriangleSelectorWrapper.hpp" +#include "format.hpp" #include #include @@ -394,6 +397,65 @@ void PrintObject::ironing() } } + +/* +std::vector problematic_layers = SupportSpotsGenerator::quick_search(this); + if (!problematic_layers.empty()) { + std::cout << "Object needs supports" << std::endl; + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("Supportable issues found. Consider enabling supports for this object")); + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + L("Supportable issues found. Consider enabling supports for this object")); + for (size_t index = 0; index < std::min(problematic_layers.size(), size_t(4)); ++index) { + this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + format(L("Layer with issues: %1%"), problematic_layers[index] + 1)); + } + } + */ +void PrintObject::generate_support_spots() +{ + if (this->set_started(posSupportSpotsSearch)) { + BOOST_LOG_TRIVIAL(debug) + << "Searching support spots - start"; + m_print->set_status(75, L("Searching support spots")); + if (this->m_config.support_material && !this->m_config.support_material_auto && + std::all_of(this->model_object()->volumes.begin(), this->model_object()->volumes.end(), + [](const ModelVolume* mv){return mv->supported_facets.empty();}) + ) { + SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; + SupportSpotsGenerator::Issues issues = SupportSpotsGenerator::full_search(this, params); + auto obj_transform = this->trafo_centered(); + for (ModelVolume *model_volume : this->model_object()->volumes) { + if (model_volume->is_model_part()) { + Transform3d mesh_transformation = obj_transform * model_volume->get_matrix(); + Transform3d inv_transform = mesh_transformation.inverse(); + TriangleSelectorWrapper selector { model_volume->mesh(), mesh_transformation}; + + for (const SupportSpotsGenerator::SupportPoint &support_point : issues.support_points) { + Vec3f point = Vec3f(inv_transform.cast() * support_point.position); + Vec3f origin = Vec3f( + inv_transform.cast() * Vec3f(support_point.position.x(), support_point.position.y(), 0.0f)); + selector.enforce_spot(point, origin, support_point.spot_radius); + } + + model_volume->supported_facets.set(selector.selector); +#if 0 //DEBUG export + indexed_triangle_set copy = model_volume->mesh().its; + its_transform(copy, obj_transform * model_transformation); + its_write_obj(copy, + debug_out_path(("model"+std::to_string(model_volume->id().id)+".obj").c_str()).c_str()); +#endif + } + } + } + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) + << "Searching support spots - end"; + this->set_done(posSupportSpotsSearch); + } +} + void PrintObject::generate_support_material() { if (this->set_started(posSupportMaterial)) { diff --git a/src/libslic3r/ShortEdgeCollapse.cpp b/src/libslic3r/ShortEdgeCollapse.cpp index b36278c37f..0c940cb475 100644 --- a/src/libslic3r/ShortEdgeCollapse.cpp +++ b/src/libslic3r/ShortEdgeCollapse.cpp @@ -97,7 +97,8 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_ //shuffle the faces and traverse in random order, this MASSIVELY improves the quality of the result std::shuffle(face_indices.begin(), face_indices.end(), generator); - + + int allowed_face_removals = int(face_indices.size()) - int(target_triangle_count); for (const size_t &face_idx : face_indices) { if (face_removal_flags[face_idx]) { // if face already removed from previous collapses, skip (each collapse removes two triangles [at least] ) @@ -130,10 +131,13 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_ // remove faces remove_face(face_idx, neighbor_to_remove_face_idx); remove_face(neighbor_to_remove_face_idx, face_idx); + allowed_face_removals-=2; // break. this triangle is done break; } + + if (allowed_face_removals <= 0) { break; } } // filter face_indices, remove those that have been collapsed diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp new file mode 100644 index 0000000000..697518bd08 --- /dev/null +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -0,0 +1,1257 @@ +#include "SupportSpotsGenerator.hpp" + +#include "ExtrusionEntity.hpp" +#include "tbb/parallel_for.h" +#include "tbb/blocked_range.h" +#include "tbb/blocked_range2d.h" +#include "tbb/parallel_reduce.h" +#include +#include +#include +#include + +#include "AABBTreeLines.hpp" +#include "KDTreeIndirect.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "Geometry/ConvexHull.hpp" + +// #define DETAILED_DEBUG_LOGS +// #define DEBUG_FILES + +#ifdef DEBUG_FILES +#include +#include "libslic3r/Color.hpp" +#endif + +namespace Slic3r { + +class ExtrusionLine +{ +public: + ExtrusionLine() : + a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0f), origin_entity(nullptr) { + } + ExtrusionLine(const Vec2f &a, const Vec2f &b, const ExtrusionEntity *origin_entity) : + a(a), b(b), len((a - b).norm()), origin_entity(origin_entity) { + } + + float length() { + return (a - b).norm(); + } + + bool is_external_perimeter() const { + assert(origin_entity != nullptr); + return origin_entity->role() == erExternalPerimeter || origin_entity->role() == erOverhangPerimeter; + } + + Vec2f a; + Vec2f b; + float len; + const ExtrusionEntity *origin_entity; + + bool support_point_generated = false; + float malformation = 0.0f; + + static const constexpr int Dim = 2; + using Scalar = Vec2f::Scalar; +}; + +auto get_a(ExtrusionLine &&l) { + return l.a; +} +auto get_b(ExtrusionLine &&l) { + return l.b; +} + +namespace SupportSpotsGenerator { + +SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction) : + position(position), force(force), spot_radius(spot_radius), direction(direction) { +} + +class LinesDistancer { +private: + std::vector lines; + AABBTreeIndirect::Tree<2, float> tree; + +public: + explicit LinesDistancer(const std::vector &lines) : + lines(lines) { + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines); + } + + // negative sign means inside + float signed_distance_from_lines(const Vec2f &point, size_t &nearest_line_index_out, + Vec2f &nearest_point_out) const { + auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, point, nearest_line_index_out, + nearest_point_out); + if (distance < 0) + return std::numeric_limits::infinity(); + + distance = sqrt(distance); + const ExtrusionLine &line = lines[nearest_line_index_out]; + Vec2f v1 = line.b - line.a; + Vec2f v2 = point - line.a; + if ((v1.x() * v2.y()) - (v1.y() * v2.x()) > 0.0) { + distance *= -1; + } + return distance; + } + + const ExtrusionLine& get_line(size_t line_idx) const { + return lines[line_idx]; + } + + const std::vector& get_lines() const { + return lines; + } +}; + +static const size_t NULL_ISLAND = std::numeric_limits::max(); + +class PixelGrid { + Vec2f pixel_size; + Vec2f origin; + Vec2f size; + Vec2i pixel_count; + + std::vector pixels { }; + +public: + PixelGrid(const PrintObject *po, float resolution) { + pixel_size = Vec2f(resolution, resolution); + + Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); + Vec2f min = unscale(Vec2crd(-size_half.x(), -size_half.y())).cast(); + Vec2f max = unscale(Vec2crd(size_half.x(), size_half.y())).cast(); + + origin = min; + size = max - min; + pixel_count = size.cwiseQuotient(pixel_size).cast() + Vec2i::Ones(); + + pixels.resize(pixel_count.y() * pixel_count.x()); + clear(); + } + + void distribute_edge(const Vec2f &p1, const Vec2f &p2, size_t value) { + Vec2f dir = (p2 - p1); + float length = dir.norm(); + if (length < 0.1) { + return; + } + float step_size = this->pixel_size.x() / 2.0; + + float distributed_length = 0; + while (distributed_length < length) { + float next_len = std::min(length, distributed_length + step_size); + Vec2f location = p1 + ((next_len / length) * dir); + this->access_pixel(location) = value; + + distributed_length = next_len; + } + } + + void clear() { + for (size_t &val : pixels) { + val = NULL_ISLAND; + } + } + + float pixel_area() const { + return this->pixel_size.x() * this->pixel_size.y(); + } + + size_t get_pixel(const Vec2i &coords) const { + return pixels[this->to_pixel_index(coords)]; + } + + Vec2i get_pixel_count() { + return pixel_count; + } + + Vec2f get_pixel_center(const Vec2i &coords) const { + return origin + coords.cast().cwiseProduct(this->pixel_size) + + this->pixel_size.cwiseQuotient(Vec2f(2.0f, 2.0f)); + } + +private: + Vec2i to_pixel_coords(const Vec2f &position) const { + Vec2i pixel_coords = (position - this->origin).cwiseQuotient(this->pixel_size).cast(); + return pixel_coords; + } + + size_t to_pixel_index(const Vec2i &pixel_coords) const { + assert(pixel_coords.x() >= 0); + assert(pixel_coords.x() < pixel_count.x()); + assert(pixel_coords.y() >= 0); + assert(pixel_coords.y() < pixel_count.y()); + + return pixel_coords.y() * pixel_count.x() + pixel_coords.x(); + } + + size_t& access_pixel(const Vec2f &position) { + return pixels[this->to_pixel_index(this->to_pixel_coords(position))]; + } +}; + +struct SupportGridFilter { +private: + Vec3f cell_size; + Vec3f origin; + Vec3f size; + Vec3i cell_count; + + std::unordered_set taken_cells { }; + +public: + SupportGridFilter(const PrintObject *po, float voxel_size) { + cell_size = Vec3f(voxel_size, voxel_size, voxel_size); + + Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); + Vec3f min = unscale(Vec3crd(-size_half.x(), -size_half.y(), 0)).cast() - cell_size; + Vec3f max = unscale(Vec3crd(size_half.x(), size_half.y(), po->height())).cast() + cell_size; + + origin = min; + size = max - min; + cell_count = size.cwiseQuotient(cell_size).cast() + Vec3i::Ones(); + } + + Vec3i to_cell_coords(const Vec3f &position) const { + Vec3i cell_coords = (position - this->origin).cwiseQuotient(this->cell_size).cast(); + return cell_coords; + } + + size_t to_cell_index(const Vec3i &cell_coords) const { + assert(cell_coords.x() >= 0); + assert(cell_coords.x() < cell_count.x()); + assert(cell_coords.y() >= 0); + assert(cell_coords.y() < cell_count.y()); + assert(cell_coords.z() >= 0); + assert(cell_coords.z() < cell_count.z()); + + return cell_coords.z() * cell_count.x() * cell_count.y() + + cell_coords.y() * cell_count.x() + + cell_coords.x(); + } + + Vec3f get_cell_center(const Vec3i &cell_coords) const { + return origin + cell_coords.cast().cwiseProduct(this->cell_size) + + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); + } + + void take_position(const Vec3f &position) { + taken_cells.insert(to_cell_index(to_cell_coords(position))); + } + + bool position_taken(const Vec3f &position) const { + return taken_cells.find(to_cell_index(to_cell_coords(position))) != taken_cells.end(); + } + +}; + +struct IslandConnection { + float area { }; + Vec3f centroid_accumulator = Vec3f::Zero(); + Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); + float second_moment_of_area_covariance_accumulator { }; + + void add(const IslandConnection &other) { + this->area += other.area; + this->centroid_accumulator += other.centroid_accumulator; + this->second_moment_of_area_accumulator += other.second_moment_of_area_accumulator; + this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; + } + + void print_info(const std::string &tag) { + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = + (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); + float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); + std::cout << tag << std::endl; + std::cout << "area: " << area << std::endl; + std::cout << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z() << std::endl; + std::cout << "variance: " << variance.x() << " " << variance.y() << std::endl; + std::cout << "covariance: " << covariance << std::endl; + } +}; + +struct Island { + std::unordered_map connected_islands { }; + float volume { }; + Vec3f volume_centroid_accumulator = Vec3f::Zero(); + float sticking_area { }; // for support points present on this layer (or bed extrusions) + Vec3f sticking_centroid_accumulator = Vec3f::Zero(); + Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); + float sticking_second_moment_of_area_covariance_accumulator { }; + + std::vector external_lines; +}; + +struct LayerIslands { + std::vector islands; + float layer_z; +}; + +float get_flow_width(const LayerRegion *region, ExtrusionRole role) { + switch (role) { + case ExtrusionRole::erBridgeInfill: + return region->flow(FlowRole::frExternalPerimeter).width(); + case ExtrusionRole::erExternalPerimeter: + return region->flow(FlowRole::frExternalPerimeter).width(); + case ExtrusionRole::erGapFill: + return region->flow(FlowRole::frInfill).width(); + case ExtrusionRole::erPerimeter: + return region->flow(FlowRole::frPerimeter).width(); + case ExtrusionRole::erSolidInfill: + return region->flow(FlowRole::frSolidInfill).width(); + case ExtrusionRole::erInternalInfill: + return region->flow(FlowRole::frInfill).width(); + case ExtrusionRole::erTopSolidInfill: + return region->flow(FlowRole::frTopSolidInfill).width(); + default: + return region->flow(FlowRole::frPerimeter).width(); + } +} + +// Accumulator of current extrusion path properties +// It remembers unsuported distance and maximum accumulated curvature over that distance. +// Used to determine local stability issues (too long bridges, extrusion curves into air) +struct ExtrusionPropertiesAccumulator { + float distance = 0; //accumulated distance + float curvature = 0; //accumulated signed ccw angles + float max_curvature = 0; //max absolute accumulated value + + void add_distance(float dist) { + distance += dist; + } + + void add_angle(float ccw_angle) { + curvature += ccw_angle; + max_curvature = std::max(max_curvature, std::abs(curvature)); + } + + void reset() { + distance = 0; + curvature = 0; + max_curvature = 0; + } +}; + +// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) +// checkout e.g. here: https://www.geogebra.org/calculator +float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { + float shifted = value - mean_x_coord; + float denominator = falloff_speed * shifted * shifted + 1.0f; + float exponent = 1.0f / denominator; + return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); +} + +void push_lines(const ExtrusionEntity *e, std::vector& destination) +{ + assert(!e->is_collection()); + Polyline pl = e->as_polyline(); + for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { + Vec2f start = unscaled(pl.points[point_idx]).cast(); + Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); + ExtrusionLine line{start, next, e}; + destination.push_back(line); + } +} + +std::vector to_short_lines(const ExtrusionEntity *e, float length_limit) +{ + assert(!e->is_collection()); + Polyline pl = e->as_polyline(); + std::vector lines; + lines.reserve(pl.points.size() * 1.5f); + lines.emplace_back(unscaled(pl.points[0]).cast(), unscaled(pl.points[0]).cast(), e); + for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { + Vec2f start = unscaled(pl.points[point_idx]).cast(); + Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); + Vec2f v = next - start; // vector from next to current + float dist_to_next = v.norm(); + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / length_limit)); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Vec2f a(start + v * (i * step_size)); + Vec2f b(start + v * ((i + 1) * step_size)); + lines.emplace_back(a, b, e); + } + } + return lines; +} + +void check_extrusion_entity_stability(const ExtrusionEntity *entity, + std::vector &checked_lines_out, + float layer_z, + const LayerRegion *layer_region, + const LinesDistancer &prev_layer_lines, + Issues &issues, + const Params ¶ms) { + + if (entity->is_collection()) { + for (const auto *e : static_cast(entity)->entities) { + check_extrusion_entity_stability(e, checked_lines_out, layer_z, layer_region, prev_layer_lines, + issues, params); + } + } else { //single extrusion path, with possible varying parameters + const auto to_vec3f = [layer_z](const Vec2f &point) { + return Vec3f(point.x(), point.y(), layer_z); + }; + float overhang_dist = tan(params.overhang_angle_deg * PI / 180.0f) * layer_region->layer()->height; + float min_malformation_dist = tan(params.malformation_angle_span_deg.first * PI / 180.0f) + * layer_region->layer()->height; + float max_malformation_dist = tan(params.malformation_angle_span_deg.second * PI / 180.0f) + * layer_region->layer()->height; + + std::vector lines = to_short_lines(entity, params.bridge_distance); + if (lines.empty()) return; + + ExtrusionPropertiesAccumulator bridging_acc { }; + ExtrusionPropertiesAccumulator malformation_acc { }; + bridging_acc.add_distance(params.bridge_distance + 1.0f); + const float flow_width = get_flow_width(layer_region, entity->role()); + + for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { + ExtrusionLine ¤t_line = lines[line_idx]; + if (line_idx + 1 == lines.size() && current_line.b != lines.begin()->a) { + bridging_acc.add_distance(params.bridge_distance + 1.0f); + } + float curr_angle = 0; + if (line_idx + 1 < lines.size()) { + const Vec2f v1 = current_line.b - current_line.a; + const Vec2f v2 = lines[line_idx + 1].b - lines[line_idx + 1].a; + curr_angle = angle(v1, v2); + } + bridging_acc.add_angle(curr_angle); + // malformation in concave angles does not happen + malformation_acc.add_angle(std::max(0.0f, curr_angle)); + + size_t nearest_line_idx; + Vec2f nearest_point; + float dist_from_prev_layer = prev_layer_lines.signed_distance_from_lines(current_line.b, nearest_line_idx, + nearest_point); + + if (fabs(dist_from_prev_layer) < overhang_dist) { + bridging_acc.reset(); + } else { + bridging_acc.add_distance(current_line.len); + // if unsupported distance is larger than bridge distance linearly decreased by curvature, enforce supports. + bool in_layer_dist_condition = bridging_acc.distance + > params.bridge_distance / (1.0f + (bridging_acc.max_curvature + * params.bridge_distance_decrease_by_curvature_factor / PI)); + bool between_layers_condition = fabs(dist_from_prev_layer) > flow_width || + prev_layer_lines.get_line(nearest_line_idx).malformation > 3.0f * layer_region->layer()->height; + + if (in_layer_dist_condition && between_layers_condition) { + issues.support_points.emplace_back(to_vec3f(current_line.b), 0.0f, params.support_points_interface_radius, Vec3f(0.f, 0.0f, -1.0f)); + current_line.support_point_generated = true; + bridging_acc.reset(); + } + } + + //malformation + if (fabs(dist_from_prev_layer) < 3.0f * flow_width) { + const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); + current_line.malformation += 0.9 * nearest_line.malformation; + } + if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { + malformation_acc.add_distance(current_line.len); + current_line.malformation += layer_region->layer()->height * + (0.5f + 1.5f * (malformation_acc.max_curvature / PI) * + gauss(malformation_acc.distance, 5.0f, 1.0f, 0.2f)); + } else { + malformation_acc.reset(); + } + } + checked_lines_out.insert(checked_lines_out.end(), lines.begin(), lines.end()); + } +} + +std::tuple reckon_islands( + const Layer *layer, bool first_layer, + size_t prev_layer_islands_count, + const PixelGrid &prev_layer_grid, + const std::vector &layer_lines, + const Params ¶ms) { + + //extract extrusions (connected paths from multiple lines) from the layer_lines. Grouping by the same polyline is determined by common origin_entity ptr. + // result is a vector of [start, end) index pairs into the layer_lines vector + std::vector> extrusions; //start and end idx (one beyond last extrusion) [start,end) + const ExtrusionEntity *current_ex = nullptr; + for (size_t lidx = 0; lidx < layer_lines.size(); ++lidx) { + const ExtrusionLine &line = layer_lines[lidx]; + if (line.origin_entity == current_ex) { + extrusions.back().second = lidx + 1; + } else { + extrusions.emplace_back(lidx, lidx + 1); + current_ex = line.origin_entity; + } + } + + std::vector islands; // these search trees will be used to determine to which island does the extrusion belong. + std::vector> island_extrusions; //final assigment of each extrusion to an island. + // initliaze the search from external perimeters - at the beginning, there is island candidate for each external perimeter. + // some of them will disappear (e.g. holes) + for (size_t e = 0; e < extrusions.size(); ++e) { + if (layer_lines[extrusions[e].first].origin_entity->is_loop() && + layer_lines[extrusions[e].first].is_external_perimeter()) { + std::vector copy(extrusions[e].second - extrusions[e].first); + for (size_t ex_line_idx = extrusions[e].first; ex_line_idx < extrusions[e].second; ++ex_line_idx) { + copy[ex_line_idx - extrusions[e].first] = layer_lines[ex_line_idx]; + } + islands.emplace_back(copy); + island_extrusions.push_back( { e }); + } + } + + // backup code if islands not found + // If that happens, just make the first extrusion into island - it may be wrong, but it won't crash. + if (islands.empty() && !extrusions.empty()) { + std::vector copy(extrusions[0].second - extrusions[0].first); + for (size_t ex_line_idx = extrusions[0].first; ex_line_idx < extrusions[0].second; ++ex_line_idx) { + copy[ex_line_idx - extrusions[0].first] = layer_lines[ex_line_idx]; + } + islands.emplace_back(copy); + island_extrusions.push_back( { 0 }); + } + + // assign non external extrusions to islands + for (size_t e = 0; e < extrusions.size(); ++e) { + if (!layer_lines[extrusions[e].first].origin_entity->is_loop() || + !layer_lines[extrusions[e].first].is_external_perimeter()) { + bool island_assigned = false; + for (size_t i = 0; i < islands.size(); ++i) { + if (island_extrusions[i].empty()) { + continue; + } + size_t idx = 0; + Vec2f pt = Vec2f::Zero(); + if (islands[i].signed_distance_from_lines(layer_lines[extrusions[e].first].a, idx, pt) < 0) { + island_extrusions[i].push_back(e); + island_assigned = true; + break; + } + } + if (!island_assigned) { // If extrusion is not assigned for some reason, push it into the first island. As with the previous backup code, + // it may be wrong, but it won't crash + island_extrusions[0].push_back(e); + } + } + } + + float flow_width = get_flow_width(layer->regions()[0], erExternalPerimeter); + // after filtering the layer lines into islands, build the result LayerIslands structure. + LayerIslands result { }; + result.layer_z = layer->slice_z; + std::vector line_to_island_mapping(layer_lines.size(), NULL_ISLAND); + for (const std::vector &island_ex : island_extrusions) { + if (island_ex.empty()) { + continue; + } + + Island island { }; + island.external_lines.insert(island.external_lines.end(), + layer_lines.begin() + extrusions[island_ex[0]].first, + layer_lines.begin() + extrusions[island_ex[0]].second); + for (size_t extrusion_idx : island_ex) { + for (size_t lidx = extrusions[extrusion_idx].first; lidx < extrusions[extrusion_idx].second; ++lidx) { + line_to_island_mapping[lidx] = result.islands.size(); + const ExtrusionLine &line = layer_lines[lidx]; + float volume = line.len * layer->height * flow_width * PI / 4.0f; + island.volume += volume; + island.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), float(layer->slice_z)) + * volume; + + if (first_layer) { + float sticking_area = line.len * flow_width; + island.sticking_area += sticking_area; + Vec2f middle = Vec2f((line.a + line.b) / 2.0f); + island.sticking_centroid_accumulator += sticking_area * to_3d(middle, float(layer->slice_z)); + // Bottom infill lines can be quite long, and algined, so the middle approximaton used above does not work + Vec2f dir = (line.b - line.a).normalized(); + float segment_length = flow_width; // segments of size flow_width + for (float segment_middle_dist = std::min(line.len, segment_length * 0.5f); + segment_middle_dist < line.len; + segment_middle_dist += segment_length) { + Vec2f segment_middle = line.a + segment_middle_dist * dir; + island.sticking_second_moment_of_area_accumulator += segment_length * flow_width + * segment_middle.cwiseProduct(segment_middle); + island.sticking_second_moment_of_area_covariance_accumulator += segment_length * flow_width + * segment_middle.x() + * segment_middle.y(); + } + } else if (layer_lines[lidx].support_point_generated) { + float sticking_area = line.len * flow_width; + island.sticking_area += sticking_area; + island.sticking_centroid_accumulator += sticking_area * to_3d(line.b, float(layer->slice_z)); + island.sticking_second_moment_of_area_accumulator += sticking_area * line.b.cwiseProduct(line.b); + island.sticking_second_moment_of_area_covariance_accumulator += sticking_area * line.b.x() + * line.b.y(); + } + } + } + result.islands.push_back(island); + } + + //LayerIslands structure built. Now determine connections and their areas to the previous layer using rasterization. + PixelGrid current_layer_grid = prev_layer_grid; + current_layer_grid.clear(); + // build index image of current layer + tbb::parallel_for(tbb::blocked_range(0, layer_lines.size()), + [&layer_lines, ¤t_layer_grid, &line_to_island_mapping]( + tbb::blocked_range r) { + for (size_t i = r.begin(); i < r.end(); ++i) { + size_t island = line_to_island_mapping[i]; + const ExtrusionLine &line = layer_lines[i]; + current_layer_grid.distribute_edge(line.a, line.b, island); + } + }); + + //compare the image of previous layer with the current layer. For each pair of overlapping valid pixels, add pixel area to the respective island connection + for (size_t x = 0; x < size_t(current_layer_grid.get_pixel_count().x()); ++x) { + for (size_t y = 0; y < size_t(current_layer_grid.get_pixel_count().y()); ++y) { + Vec2i coords = Vec2i(x, y); + if (current_layer_grid.get_pixel(coords) != NULL_ISLAND + && prev_layer_grid.get_pixel(coords) != NULL_ISLAND) { + IslandConnection &connection = result.islands[current_layer_grid.get_pixel(coords)] + .connected_islands[prev_layer_grid.get_pixel(coords)]; + Vec2f current_coords = current_layer_grid.get_pixel_center(coords); + connection.area += current_layer_grid.pixel_area(); + connection.centroid_accumulator += to_3d(current_coords, result.layer_z) + * current_layer_grid.pixel_area(); + connection.second_moment_of_area_accumulator += current_coords.cwiseProduct(current_coords) + * current_layer_grid.pixel_area(); + connection.second_moment_of_area_covariance_accumulator += current_coords.x() * current_coords.y() + * current_layer_grid.pixel_area(); + } + } + } + + // filter out very small connection areas, they brake the graph building + for (Island &island : result.islands) { + std::vector conns_to_remove; + for (const auto &conn : island.connected_islands) { + if (conn.second.area < params.connections_min_considerable_area) { conns_to_remove.push_back(conn.first); } + } + for (size_t conn : conns_to_remove) { island.connected_islands.erase(conn); } + } + + return {result, current_layer_grid}; +} + +struct CoordinateFunctor { + const std::vector *coordinates; + CoordinateFunctor(const std::vector *coords) : + coordinates(coords) { + } + CoordinateFunctor() : + coordinates(nullptr) { + } + + const float& operator()(size_t idx, size_t dim) const { + return coordinates->operator [](idx)[dim]; + } +}; + +class ObjectPart { + float volume { }; + Vec3f volume_centroid_accumulator = Vec3f::Zero(); + float sticking_area { }; + Vec3f sticking_centroid_accumulator = Vec3f::Zero(); + Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); + float sticking_second_moment_of_area_covariance_accumulator { }; + +public: + ObjectPart() = default; + + ObjectPart(const Island &island) { + this->volume = island.volume; + this->volume_centroid_accumulator = island.volume_centroid_accumulator; + this->sticking_area = island.sticking_area; + this->sticking_centroid_accumulator = island.sticking_centroid_accumulator; + this->sticking_second_moment_of_area_accumulator = island.sticking_second_moment_of_area_accumulator; + this->sticking_second_moment_of_area_covariance_accumulator = + island.sticking_second_moment_of_area_covariance_accumulator; + } + + float get_volume() const { + return volume; + } + + void add(const ObjectPart &other) { + this->volume_centroid_accumulator += other.volume_centroid_accumulator; + this->volume += other.volume; + this->sticking_area += other.sticking_area; + this->sticking_centroid_accumulator += other.sticking_centroid_accumulator; + this->sticking_second_moment_of_area_accumulator += other.sticking_second_moment_of_area_accumulator; + this->sticking_second_moment_of_area_covariance_accumulator += + other.sticking_second_moment_of_area_covariance_accumulator; + } + + void add_support_point(const Vec3f &position, float sticking_area) { + this->sticking_area += sticking_area; + this->sticking_centroid_accumulator += sticking_area * position; + this->sticking_second_moment_of_area_accumulator += sticking_area + * position.head<2>().cwiseProduct(position.head<2>()); + this->sticking_second_moment_of_area_covariance_accumulator += sticking_area + * position.x() * position.y(); + } + + float compute_directional_xy_variance( + const Vec2f &line_dir, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const { + assert(area > 0); + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area + - centroid.head<2>().cwiseProduct(centroid.head<2>())); + float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); + // Var(aX+bY)=a^2*Var(X)+b^2*Var(Y)+2*a*b*Cov(X,Y) + float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() + + line_dir.y() * line_dir.y() * variance.y() + + 2.0f * line_dir.x() * line_dir.y() * covariance; +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) + << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); + BOOST_LOG_TRIVIAL(debug) + << "variance: " << variance.x() << " " << variance.y(); + BOOST_LOG_TRIVIAL(debug) + << "covariance: " << covariance; + BOOST_LOG_TRIVIAL(debug) + << "directional_xy_variance: " << directional_xy_variance; +#endif + return directional_xy_variance; + } + + float compute_elastic_section_modulus( + const Vec2f &line_dir, + const Vec3f &extreme_point, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const { + + float directional_xy_variance = compute_directional_xy_variance( + line_dir, + centroid_accumulator, + second_moment_of_area_accumulator, + second_moment_of_area_covariance_accumulator, + area); + if (directional_xy_variance < EPSILON) { + return 0.0f; + } + Vec3f centroid = centroid_accumulator / area; + float extreme_fiber_dist = line_alg::distance_to( + Linef(centroid.head<2>().cast(), + (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), + extreme_point.head<2>().cast()); + float elastic_section_modulus = area * directional_xy_variance / extreme_fiber_dist; + +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) + << "extreme_fiber_dist: " << extreme_fiber_dist; + BOOST_LOG_TRIVIAL(debug) + << "elastic_section_modulus: " << elastic_section_modulus; +#endif + + return elastic_section_modulus; + } + + float is_stable_while_extruding( + const IslandConnection &connection, + const ExtrusionLine &extruded_line, + const Vec3f &extreme_point, + float layer_z, + const Params ¶ms) const { + + Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); + const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; + float mass = this->volume * params.filament_density; + float weight = mass * params.gravity_constant; + + float movement_force = params.max_acceleration * mass; + + float extruder_conflict_force = params.standard_extruder_conflict_force + + std::min(extruded_line.malformation, 1.0f) * params.malformations_additive_conflict_extruder_force; + + // section for bed calculations + { + if (this->sticking_area < EPSILON) + return 1.0f; + + Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; + float bed_yield_torque = -compute_elastic_section_modulus( + line_dir, + extreme_point, + this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area) + * params.get_bed_adhesion_yield_strength(); + + Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); + float bed_weight_arm_len = bed_weight_arm.norm(); + float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, + this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area); + float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; + float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; + + float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); + float bed_movement_torque = movement_force * bed_movement_arm; + + float bed_conflict_torque_arm = layer_z - bed_centroid.z(); + float bed_extruder_conflict_torque = extruder_conflict_force * bed_conflict_torque_arm; + + float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque + + bed_yield_torque; + +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) + << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_yield_torque: " << bed_yield_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_weight_arm: " << bed_weight_arm; + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_weight_torque: " << bed_weight_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_movement_arm: " << bed_movement_arm; + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_movement_torque: " << bed_movement_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) + << "SSG: extruded_line.malformation: " << extruded_line.malformation; + BOOST_LOG_TRIVIAL(debug) + << "SSG: extruder_conflict_force: " << extruder_conflict_force; + BOOST_LOG_TRIVIAL(debug) + << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; +#endif + + if (bed_total_torque > 0) + return bed_total_torque / bed_conflict_torque_arm; + } + + //section for weak connection calculations + { + if (connection.area < EPSILON) + return 1.0f; + + Vec3f conn_centroid = connection.centroid_accumulator / connection.area; + + if (layer_z - conn_centroid.z() < 3.0f) { + return -1.0f; + } + float conn_yield_torque = compute_elastic_section_modulus( + line_dir, + extreme_point, + connection.centroid_accumulator, + connection.second_moment_of_area_accumulator, + connection.second_moment_of_area_covariance_accumulator, + connection.area) * params.material_yield_strength; + + float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); + float conn_weight_torque = conn_weight_arm * weight * (conn_centroid.z() / layer_z); + + float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); + float conn_movement_torque = movement_force * conn_movement_arm; + + float conn_conflict_torque_arm = layer_z - conn_centroid.z(); + float conn_extruder_conflict_torque = extruder_conflict_force * conn_conflict_torque_arm; + + float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque + - conn_yield_torque; + +#ifdef DETAILED_DEBUG_LOGS + BOOST_LOG_TRIVIAL(debug) + << "bed_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_yield_torque: " << conn_yield_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_weight_arm: " << conn_weight_arm; + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_weight_torque: " << conn_weight_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_movement_arm: " << conn_movement_arm; + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_movement_torque: " << conn_movement_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) + << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) + << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; +#endif + + return conn_total_torque / conn_conflict_torque_arm; + } + } +}; + +#ifdef DETAILED_DEBUG_LOGS +void debug_print_graph(const std::vector &islands_graph) { + std::cout << "BUILT ISLANDS GRAPH:" << std::endl; + for (size_t layer_idx = 0; layer_idx < islands_graph.size(); ++layer_idx) { + std::cout << "ISLANDS AT LAYER: " << layer_idx << " AT HEIGHT: " << islands_graph[layer_idx].layer_z + << std::endl; + for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { + const Island &island = islands_graph[layer_idx].islands[island_idx]; + std::cout << " ISLAND " << island_idx << std::endl; + std::cout << " volume: " << island.volume << std::endl; + std::cout << " sticking_area: " << island.sticking_area << std::endl; + std::cout << " connected_islands count: " << island.connected_islands.size() << std::endl; + std::cout << " lines count: " << island.external_lines.size() << std::endl; + } + } + std::cout << "END OF GRAPH" << std::endl; +} +#endif + +class ActiveObjectParts { + size_t next_part_idx = 0; + std::unordered_map active_object_parts; + std::unordered_map active_object_parts_id_mapping; + +public: + size_t get_flat_id(size_t id) { + size_t index = active_object_parts_id_mapping.at(id); + while (index != active_object_parts_id_mapping.at(index)) { + index = active_object_parts_id_mapping.at(index); + } + size_t i = id; + while (index != active_object_parts_id_mapping.at(i)) { + size_t next = active_object_parts_id_mapping[i]; + active_object_parts_id_mapping[i] = index; + i = next; + } + return index; + } + + ObjectPart& access(size_t id) { + return this->active_object_parts.at(this->get_flat_id(id)); + } + + size_t insert(const Island &island) { + this->active_object_parts.emplace(next_part_idx, ObjectPart(island)); + this->active_object_parts_id_mapping.emplace(next_part_idx, next_part_idx); + return next_part_idx++; + } + + void merge(size_t from, size_t to) { + size_t to_flat = this->get_flat_id(to); + size_t from_flat = this->get_flat_id(from); + active_object_parts.at(to_flat).add(active_object_parts.at(from_flat)); + active_object_parts.erase(from_flat); + active_object_parts_id_mapping[from] = to_flat; + } +}; + +Issues check_global_stability(SupportGridFilter supports_presence_grid, + const std::vector &islands_graph, const Params ¶ms) { +#ifdef DETAILED_DEBUG_LOGS + debug_print_graph(islands_graph); +#endif + + Issues issues { }; + ActiveObjectParts active_object_parts { }; + std::unordered_map prev_island_to_object_part_mapping; + std::unordered_map next_island_to_object_part_mapping; + + std::unordered_map prev_island_weakest_connection; + std::unordered_map next_island_weakest_connection; + + for (size_t layer_idx = 0; layer_idx < islands_graph.size(); ++layer_idx) { + float layer_z = islands_graph[layer_idx].layer_z; + +#ifdef DETAILED_DEBUG_LOGS + for (const auto &m : prev_island_to_object_part_mapping) { + std::cout << "island " << m.first << " maps to part " << m.second << std::endl; + prev_island_weakest_connection[m.first].print_info("connection info:"); + } +#endif + + for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { + const Island &island = islands_graph[layer_idx].islands[island_idx]; + if (island.connected_islands.empty()) { //new object part emerging + size_t part_id = active_object_parts.insert(island); + next_island_to_object_part_mapping.emplace(island_idx, part_id); + next_island_weakest_connection.emplace(island_idx, + IslandConnection { 1.0f, Vec3f::Zero(), Vec2f { INFINITY, INFINITY } }); + } else { + size_t final_part_id { }; + IslandConnection transfered_weakest_connection { }; + IslandConnection new_weakest_connection { }; + // MERGE parts + { + std::unordered_set parts_ids; + for (const auto &connection : island.connected_islands) { + size_t part_id = active_object_parts.get_flat_id( + prev_island_to_object_part_mapping.at(connection.first)); + parts_ids.insert(part_id); + transfered_weakest_connection.add(prev_island_weakest_connection.at(connection.first)); + new_weakest_connection.add(connection.second); + } + final_part_id = *parts_ids.begin(); + for (size_t part_id : parts_ids) { + if (final_part_id != part_id) { + active_object_parts.merge(part_id, final_part_id); + } + } + } + auto estimate_conn_strength = [layer_z](const IslandConnection &conn) { + Vec3f centroid = conn.centroid_accumulator / conn.area; + Vec2f variance = (conn.second_moment_of_area_accumulator / conn.area + - centroid.head<2>().cwiseProduct(centroid.head<2>())); + float xy_variance = variance.x() + variance.y(); + float arm_len_estimate = std::max(1.0f, layer_z - (conn.centroid_accumulator.z() / conn.area)); + return conn.area * sqrt(xy_variance) / arm_len_estimate; + }; + +#ifdef DETAILED_DEBUG_LOGS + new_weakest_connection.print_info("new_weakest_connection"); + transfered_weakest_connection.print_info("transfered_weakest_connection"); +#endif + + if (estimate_conn_strength(transfered_weakest_connection) + > estimate_conn_strength(new_weakest_connection)) { + transfered_weakest_connection = new_weakest_connection; + } + next_island_weakest_connection.emplace(island_idx, transfered_weakest_connection); + next_island_to_object_part_mapping.emplace(island_idx, final_part_id); + ObjectPart &part = active_object_parts.access(final_part_id); + part.add(ObjectPart(island)); + } + } + + prev_island_to_object_part_mapping = next_island_to_object_part_mapping; + next_island_to_object_part_mapping.clear(); + prev_island_weakest_connection = next_island_weakest_connection; + next_island_weakest_connection.clear(); + + // All object parts updated, inactive parts removed and weakest point of each island updated as well. + // Now compute the stability of each active object part, adding supports where necessary, and also + // check each island whether the weakest point is strong enough. If not, add supports as well. + + for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { + const Island &island = islands_graph[layer_idx].islands[island_idx]; + ObjectPart &part = active_object_parts.access(prev_island_to_object_part_mapping[island_idx]); + + IslandConnection &weakest_conn = prev_island_weakest_connection[island_idx]; +#ifdef DETAILED_DEBUG_LOGS + weakest_conn.print_info("weakest connection info: "); +#endif + LinesDistancer island_lines_dist(island.external_lines); + float unchecked_dist = params.min_distance_between_support_points + 1.0f; + + for (const ExtrusionLine &line : island.external_lines) { + if ((unchecked_dist + line.len < params.min_distance_between_support_points + && line.malformation < 0.3f) || line.len == 0) { + unchecked_dist += line.len; + } else { + unchecked_dist = line.len; + Vec2f target_point; + size_t idx; + Vec3f pivot_site_search_point = to_3d(Vec2f(line.b + (line.b - line.a).normalized() * 300.0f), + layer_z); + island_lines_dist.signed_distance_from_lines(pivot_site_search_point.head<2>(), idx, + target_point); + Vec3f support_point = to_3d(target_point, layer_z); + auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, layer_z, params); + if (force > 0) { + if (!supports_presence_grid.position_taken(support_point)) { + float orig_area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); + // artifically lower the area for materials that have strong bed adhesion, as this adhesion does not apply for support points + float altered_area = orig_area * params.get_support_spots_adhesion_strength() / params.get_bed_adhesion_yield_strength(); + part.add_support_point(support_point, altered_area); + + float radius = part.get_volume() < params.small_parts_threshold ? params.small_parts_support_points_interface_radius : params.support_points_interface_radius; + issues.support_points.emplace_back(support_point, force, radius, to_3d(Vec2f(line.b - line.a).normalized(), 0.0f)); + supports_presence_grid.take_position(support_point); + + weakest_conn.area += altered_area; + weakest_conn.centroid_accumulator += support_point * altered_area; + weakest_conn.second_moment_of_area_accumulator += altered_area * + support_point.head<2>().cwiseProduct(support_point.head<2>()); + weakest_conn.second_moment_of_area_covariance_accumulator += altered_area * support_point.x() * + support_point.y(); + } + } + } + } + } + //end of iteration over layer + } + return issues; +} + +std::tuple> check_extrusions_and_build_graph(const PrintObject *po, + const Params ¶ms) { +#ifdef DEBUG_FILES + FILE *segmentation_f = boost::nowide::fopen(debug_out_path("segmentation.obj").c_str(), "w"); + FILE *malform_f = boost::nowide::fopen(debug_out_path("malformations.obj").c_str(), "w"); +#endif + + Issues issues { }; + std::vector islands_graph; + std::vector layer_lines; + float flow_width = get_flow_width(po->layers()[po->layer_count() - 1]->regions()[0], erExternalPerimeter); + PixelGrid prev_layer_grid(po, flow_width*2.0f); + +// PREPARE BASE LAYER + const Layer *layer = po->layers()[0]; + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { + push_lines(perimeter, layer_lines); + } // perimeter + } // ex_entity + for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { + for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { + push_lines(fill, layer_lines); + } // fill + } // ex_entity + } // region + + auto [layer_islands, layer_grid] = reckon_islands(layer, true, 0, prev_layer_grid, + layer_lines, params); + islands_graph.push_back(std::move(layer_islands)); +#ifdef DEBUG_FILES + for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { + for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { + Vec2i coords = Vec2i(x, y); + size_t island_idx = layer_grid.get_pixel(coords); + if (layer_grid.get_pixel(coords) != NULL_ISLAND) { + Vec2f pos = layer_grid.get_pixel_center(coords); + size_t pseudornd = ((island_idx + 127) * 33331 + 6907) % 23; + Vec3f color = value_to_rgbf(0.0f, float(23), float(pseudornd)); + fprintf(segmentation_f, "v %f %f %f %f %f %f\n", pos[0], + pos[1], layer->slice_z, color[0], color[1], color[2]); + } + } + } + for (const auto &line : layer_lines) { + if (line.malformation > 0.0f) { + Vec3f color = value_to_rgbf(-EPSILON, 1.0f, line.malformation); + fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], + line.b[1], layer->slice_z, color[0], color[1], color[2]); + } + } +#endif + LinesDistancer external_lines(layer_lines); + layer_lines.clear(); + prev_layer_grid = layer_grid; + + for (size_t layer_idx = 1; layer_idx < po->layer_count(); ++layer_idx) { + const Layer *layer = po->layers()[layer_idx]; + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { + check_extrusion_entity_stability(perimeter, layer_lines, layer->slice_z, layer_region, + external_lines, issues, params); + } // perimeter + } // ex_entity + for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { + for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { + if (fill->role() == ExtrusionRole::erGapFill + || fill->role() == ExtrusionRole::erBridgeInfill) { + check_extrusion_entity_stability(fill, layer_lines, layer->slice_z, layer_region, + external_lines, issues, params); + } else { + push_lines(fill, layer_lines); + } + } // fill + } // ex_entity + } // region + + auto [layer_islands, layer_grid] = reckon_islands(layer, false, 0, prev_layer_grid, + layer_lines, params); + islands_graph.push_back(std::move(layer_islands)); + +#ifdef DEBUG_FILES + for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { + for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { + Vec2i coords = Vec2i(x, y); + size_t island_idx = layer_grid.get_pixel(coords); + if (layer_grid.get_pixel(coords) != NULL_ISLAND) { + Vec2f pos = layer_grid.get_pixel_center(coords); + size_t pseudornd = ((island_idx + 127) * 33331 + 6907) % 23; + Vec3f color = value_to_rgbf(0.0f, float(23), float(pseudornd)); + fprintf(segmentation_f, "v %f %f %f %f %f %f\n", pos[0], + pos[1], layer->slice_z, color[0], color[1], color[2]); + } + } + } + for (const auto &line : layer_lines) { + if (line.malformation > 0.0f) { + Vec3f color = value_to_rgbf(0, 1.0f, line.malformation); + fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], + line.b[1], layer->slice_z, color[0], color[1], color[2]); + } + } +#endif + external_lines = LinesDistancer(layer_lines); + layer_lines.clear(); + prev_layer_grid = layer_grid; + } + +#ifdef DEBUG_FILES + fclose(segmentation_f); + fclose(malform_f); +#endif + + return {issues, islands_graph}; +} + +#ifdef DEBUG_FILES +void debug_export(Issues issues, std::string file_name) { + Slic3r::CNumericLocalesSetter locales_setter; + { + FILE *fp = boost::nowide::fopen(debug_out_path((file_name + "_supports.obj").c_str()).c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Debug files: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < issues.support_points.size(); ++i) { + fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), + issues.support_points[i].position(1), + issues.support_points[i].position(2), 1.0, 0.0, 1.0); + } + + fclose(fp); + } +} +#endif + +// std::vector quick_search(const PrintObject *po, const Params ¶ms) { +// return {}; +// } + +Issues full_search(const PrintObject *po, const Params ¶ms) { + auto [local_issues, graph] = check_extrusions_and_build_graph(po, params); + Issues global_issues = check_global_stability( { po, params.min_distance_between_support_points }, graph, params); +#ifdef DEBUG_FILES + debug_export(local_issues, "local_issues"); + debug_export(global_issues, "global_issues"); +#endif + + global_issues.support_points.insert(global_issues.support_points.end(), + local_issues.support_points.begin(), local_issues.support_points.end()); + + return global_issues; +} + +} //SupportableIssues End +} + diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp new file mode 100644 index 0000000000..61a1bc5385 --- /dev/null +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -0,0 +1,82 @@ +#ifndef SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ +#define SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ + +#include "libslic3r/Print.hpp" +#include + +namespace Slic3r { + +namespace SupportSpotsGenerator { + +struct Params { + Params(const std::vector &filament_types) { + if (filament_types.size() > 1) { + BOOST_LOG_TRIVIAL(warning) + << "SupportSpotsGenerator does not currently handle different materials properly, only first will be used"; + } + if (filament_types.empty() || filament_types[0].empty()) { + BOOST_LOG_TRIVIAL(error) + << "SupportSpotsGenerator error: empty filament_type"; + filament_type = std::string("PLA"); + } else { + filament_type = filament_types[0]; + } + } + + // the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2] + const float bridge_distance = 12.0f; //mm + const float bridge_distance_decrease_by_curvature_factor = 5.0f; // allowed bridge distance = bridge_distance / (1 + this factor * (curvature / PI) ) + const float overhang_angle_deg = 80.0f; + const std::pair malformation_angle_span_deg = std::pair { 45.0f, 80.0f }; + + const float min_distance_between_support_points = 3.0f; //mm + const float support_points_interface_radius = 1.5f; // mm + const float connections_min_considerable_area = 1.5f; //mm^2 + const float small_parts_threshold = 5.0f; //mm^3 + const float small_parts_support_points_interface_radius = 3.0f; // mm + + std::string filament_type; + const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. + const float max_acceleration = 9 * 1000.0f; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the jerk phase, so the usual machine limits are too low) + const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important + const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials. + const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... ); + const float malformations_additive_conflict_extruder_force = 300.0f * gravity_constant; // for areas with possible high layered curled filaments + + // MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface + double get_bed_adhesion_yield_strength() const { + if (filament_type == "PLA") { + return 0.018 * 1e6; + } else if (filament_type == "PET" || filament_type == "PETG") { + return 0.3 * 1e6; + } else { //PLA default value - defensive approach, PLA has quite low adhesion + return 0.018 * 1e6; + } + } + + //just return PLA adhesion value as value for supports + double get_support_spots_adhesion_strength() const { + return 0.018f * 1e6; + } +}; + +struct SupportPoint { + SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction); + Vec3f position; + float force; + float spot_radius; + Vec3f direction; +}; + +struct Issues { + std::vector support_points; +}; + +// std::vector quick_search(const PrintObject *po, const Params ¶ms); +Issues full_search(const PrintObject *po, const Params ¶ms); + +} + +} + +#endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */ diff --git a/src/libslic3r/TriangleSelectorWrapper.cpp b/src/libslic3r/TriangleSelectorWrapper.cpp new file mode 100644 index 0000000000..3a5f44d843 --- /dev/null +++ b/src/libslic3r/TriangleSelectorWrapper.cpp @@ -0,0 +1,46 @@ +#include "TriangleSelectorWrapper.hpp" +#include + +namespace Slic3r { + +TriangleSelectorWrapper::TriangleSelectorWrapper(const TriangleMesh &mesh, const Transform3d& mesh_transform) : + mesh(mesh), mesh_transform(mesh_transform), selector(mesh), triangles_tree( + AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices)) { + +} + +void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &origin, float radius) { + std::vector hits; + Vec3f dir = (point - origin).normalized(); + if (AABBTreeIndirect::intersect_ray_all_hits(mesh.its.vertices, mesh.its.indices, triangles_tree, + Vec3d(origin.cast()), + Vec3d(dir.cast()), + hits)) { + for (int hit_idx = hits.size() - 1; hit_idx >= 0; --hit_idx) { + const igl::Hit &hit = hits[hit_idx]; + Vec3f pos = origin + dir * hit.t; + Vec3f face_normal = its_face_normal(mesh.its, hit.id); + if ((point - pos).norm() < radius && face_normal.dot(dir) < 0) { + std::unique_ptr cursor = std::make_unique( + pos, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { }); + selector.select_patch(hit.id, std::move(cursor), EnforcerBlockerType::ENFORCER, Transform3d::Identity(), + true, 0.0f); + break; + } + } + } else { + size_t hit_idx_out; + Vec3f hit_point_out; + float dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(mesh.its.vertices, mesh.its.indices, + triangles_tree, point, hit_idx_out, hit_point_out); + if (dist < radius) { + std::unique_ptr cursor = std::make_unique( + point, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { }); + selector.select_patch(hit_idx_out, std::move(cursor), EnforcerBlockerType::ENFORCER, + Transform3d::Identity(), + true, 0.0f); + } + } +} + +} diff --git a/src/libslic3r/TriangleSelectorWrapper.hpp b/src/libslic3r/TriangleSelectorWrapper.hpp new file mode 100644 index 0000000000..22c61d6279 --- /dev/null +++ b/src/libslic3r/TriangleSelectorWrapper.hpp @@ -0,0 +1,31 @@ +#ifndef SRC_LIBSLIC3R_TRIANGLESELECTORWRAPPER_HPP_ +#define SRC_LIBSLIC3R_TRIANGLESELECTORWRAPPER_HPP_ + +#include "TriangleSelector.hpp" +#include "Model.hpp" +#include "AABBTreeIndirect.hpp" + +namespace Slic3r { + +//NOTE: We need to replace the FacetsAnnotation struct for support storage (or extend/add another) +// Problems: Does not support negative volumes, strange usage for supports computed from extrusion - +// expensively converted back to triangles and then sliced again. +// Another problem is weird and very limited interface when painting supports via algorithms + + +class TriangleSelectorWrapper { +public: + const TriangleMesh &mesh; + const Transform3d& mesh_transform; + TriangleSelector selector; + AABBTreeIndirect::Tree<3, float> triangles_tree; + + TriangleSelectorWrapper(const TriangleMesh &mesh, const Transform3d& mesh_transform); + + void enforce_spot(const Vec3f &point, const Vec3f& origin, float radius); + +}; + +} + +#endif /* SRC_LIBSLIC3R_TRIANGLESELECTORWRAPPER_HPP_ */ diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 53790e076b..173d345704 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -738,7 +738,7 @@ void BackgroundSlicingProcess::prepare_upload() ThumbnailsList thumbnails = this->render_thumbnails( ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); - m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.string()); + m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.filename().string()); } m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str()); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index d95848300e..490e8e54ab 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2564,9 +2564,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); if (check_unsaved_preset_changes) header = _L("All user presets will be deleted."); - int act_btns = UnsavedChangesDialog::ActionButtons::KEEP; + int act_btns = ActionButtons::KEEP; if (!check_unsaved_preset_changes) - act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; + act_btns |= ActionButtons::SAVE; // Install bundles from resources if needed: std::vector install_bundles; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 54b4eb1eca..b2b4b25eae 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3860,14 +3860,16 @@ void GCodeViewer::render_legend(float& legend_height) if (m_view_type == EViewType::Tool) { // calculate used filaments data + used_filaments_m = std::vector(m_extruders_count, 0.0); + used_filaments_g = std::vector(m_extruders_count, 0.0); for (size_t extruder_id : m_extruder_ids) { if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) continue; double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); - used_filaments_m.push_back(used_filament_m); - used_filaments_g.push_back(used_filament_g); + used_filaments_m[extruder_id] = used_filament_m; + used_filaments_g[extruder_id] = used_filament_g; } std::string longest_used_filament_string; @@ -4000,11 +4002,10 @@ void GCodeViewer::render_legend(float& legend_height) #endif // ENABLE_PREVIEW_LAYER_TIME case EViewType::Tool: { // shows only extruders actually used - size_t i = 0; for (unsigned char extruder_id : m_extruder_ids) { - append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), - true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); - ++i; + if (used_filaments_m[extruder_id] > 0.0 && used_filaments_g[extruder_id] > 0.0) + append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), + true, "", 0.0f, 0.0f, offsets, used_filaments_m[extruder_id], used_filaments_g[extruder_id]); } break; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 31b5a7613e..ff13637df0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -834,14 +834,14 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_ imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); float win_w = ImGui::GetWindowWidth(); - float label_len = imgui.calc_text_size(owner.label).x; + float label_len = ImGui::CalcTextSize(owner.label.c_str()).x; ImGui::SetCursorPosX(0.5f * (win_w - label_len)); ImGui::AlignTextToFramePadding(); imgui.text(owner.label); if (!owner.print_order.empty()) { ImGui::Separator(); - float po_len = imgui.calc_text_size(owner.print_order).x; + float po_len = ImGui::CalcTextSize(owner.print_order.c_str()).x; ImGui::SetCursorPosX(0.5f * (win_w - po_len)); ImGui::AlignTextToFramePadding(); imgui.text(owner.print_order); @@ -4185,7 +4185,7 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) { - std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second); + std::string field = "layer_" + std::to_string(type) + "_" + float_to_string_decimal_point(range.first) + "_" + float_to_string_decimal_point(range.second); handle_sidebar_focus_event(field, true); } diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 32dc9ad18f..5959c19510 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -171,7 +171,7 @@ namespace GUI { GLModel::Geometry init_data; #if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P4 }; - init_data.reserve_vertices(8); + init_data.reserve_vertices(5); init_data.reserve_indices(8); #else init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; @@ -187,25 +187,19 @@ namespace GUI { init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter)); perimeter += width; - init_data.add_vertex(Vec4f(right, bottom, 0.0f, perimeter)); - init_data.add_vertex(Vec4f(right, bottom, 0.0f, perimeter)); perimeter += height; - init_data.add_vertex(Vec4f(right, top, 0.0f, perimeter)); - init_data.add_vertex(Vec4f(right, top, 0.0f, perimeter)); perimeter += width; - init_data.add_vertex(Vec4f(left, top, 0.0f, perimeter)); - init_data.add_vertex(Vec4f(left, top, 0.0f, perimeter)); perimeter += height; init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter)); // indices init_data.add_line(0, 1); + init_data.add_line(1, 2); init_data.add_line(2, 3); - init_data.add_line(4, 5); - init_data.add_line(6, 7); + init_data.add_line(3, 4); #else init_data.add_vertex(Vec2f(left, bottom)); init_data.add_vertex(Vec2f(right, bottom)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3ad592c27f..795a2a9a32 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2521,9 +2521,9 @@ bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, con { if (has_current_preset_changes()) { const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; - int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE; + int act_buttons = ActionButtons::SAVE; if (dont_save_insted_of_discard) - act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE; + act_buttons |= ActionButtons::DONT_SAVE; UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); if (act == "none" && dlg.ShowModal() == wxID_CANCEL) @@ -2540,8 +2540,7 @@ bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, con // synchronize config.ini with the current selections. preset_bundle->export_selections(*app_config); - MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal(); + MessageDialog(nullptr, dlg.msg_success_saved_modifications(dlg.get_names_and_types().size())).ShowModal(); } } @@ -2601,8 +2600,7 @@ bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, con // synchronize config.ini with the current selections. preset_bundle->export_selections(*app_config); - wxString text = _L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", preset_names_and_types.size()); + wxString text = dlg.msg_success_saved_modifications(preset_names_and_types.size()); if (!is_called_from_configwizard) text += "\n\n" + _L("For new project all modifications will be reseted"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index aee2785dbc..5f0a110ce9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -10,6 +10,8 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "libslic3r/Print.hpp" +#include "slic3r/GUI/MsgDialog.hpp" #include @@ -39,6 +41,8 @@ bool GLGizmoFdmSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; + m_desc["auto_generate"] = _L("Auto-generate supports"); + m_desc["generating"] = _L("Generating supports..."); m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Brush size") + ": "; @@ -91,7 +95,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(23.f); + const float approx_height = m_imgui->scaled(25.f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -153,6 +157,15 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); + if (waiting_for_autogenerated_supports) { + m_imgui->text(m_desc.at("generating")); + } else { + bool generate = m_imgui->button(m_desc.at("auto_generate")); + if (generate) + auto_generate(); + } + ImGui::Separator(); + float position_before_text_y = ImGui::GetCursorPos().y; ImGui::AlignTextToFramePadding(); m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); @@ -319,6 +332,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } update_model_object(); + this->waiting_for_autogenerated_supports = false; m_parent.set_as_dirty(); } @@ -366,9 +380,54 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") : _L("Add supports by angle")); update_model_object(); + this->waiting_for_autogenerated_supports = false; m_parent.set_as_dirty(); } +void GLGizmoFdmSupports::data_changed() +{ + GLGizmoPainterBase::data_changed(); + if (! m_c->selection_info()) + return; + + ModelObject* mo = m_c->selection_info()->model_object(); + if (mo && this->waiting_for_autogenerated_supports) { + get_data_from_backend(); + } else { + this->waiting_for_autogenerated_supports = false; + } +} + +void GLGizmoFdmSupports::get_data_from_backend() +{ + if (! has_backend_supports()) + return; + ModelObject* mo = m_c->selection_info()->model_object(); + + // find the respective PrintObject, we need a pointer to it + for (const PrintObject* po : m_parent.fff_print()->objects()) { + if (po->model_object()->id() == mo->id()) { + std::unordered_map mvs; + for (const ModelVolume* mv : po->model_object()->volumes) { + if (mv->is_model_part()) { + mvs.emplace(mv->id().id, mv); + } + } + int mesh_id = -1.0f; + for (ModelVolume* mv : mo->volumes){ + if (mv->is_model_part()){ + mesh_id++; + mv->supported_facets.assign(mvs[mv->id().id]->supported_facets); + m_triangle_selectors[mesh_id]->deserialize(mv->supported_facets.get_data(), true); + m_triangle_selectors[mesh_id]->request_update_render_data(); + } + } + this->waiting_for_autogenerated_supports = false; + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + m_parent.set_as_dirty(); + } + } +} void GLGizmoFdmSupports::update_model_object() const @@ -391,8 +450,6 @@ void GLGizmoFdmSupports::update_model_object() const } } - - void GLGizmoFdmSupports::update_from_model_object() { wxBusyCursor wait; @@ -417,6 +474,58 @@ void GLGizmoFdmSupports::update_from_model_object() } } +bool GLGizmoFdmSupports::has_backend_supports() const +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + if (! mo) + return false; + + // find PrintObject with this ID + for (const PrintObject* po : m_parent.fff_print()->objects()) { + if (po->model_object()->id() == mo->id()) + return po->is_step_done(posSupportSpotsSearch); + } + return false; +} + +void GLGizmoFdmSupports::reslice_FDM_supports(bool postpone_error_messages) const { + wxGetApp().CallAfter( + [this, postpone_error_messages]() { + wxGetApp().plater()->reslice_FFF_until_step(posSupportSpotsSearch, + *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + +void GLGizmoFdmSupports::auto_generate() +{ + ModelObject *mo = m_c->selection_info()->model_object(); + bool not_painted = std::all_of(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume* vol){ + return vol->type() != ModelVolumeType::MODEL_PART || vol->supported_facets.empty(); + }); + + MessageDialog dlg(GUI::wxGetApp().plater(), + _L("Autogeneration will erase all currently painted areas.") + "\n\n" + + _L("Are you sure you want to do it?") + "\n", + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (not_painted || dlg.ShowModal() == wxID_YES) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); + int mesh_id = -1.0f; + for (ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + mesh_id++; + mv->supported_facets.reset(); + m_triangle_selectors[mesh_id]->reset(); + m_triangle_selectors[mesh_id]->request_update_render_data(); + } + } + + mo->config.set("support_material", true); + mo->config.set("support_material_auto", false); + this->waiting_for_autogenerated_supports = true; + wxGetApp().CallAfter([this]() { reslice_FDM_supports(); }); + } +} PainterGizmoType GLGizmoFdmSupports::get_painter_type() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index df9cdce56f..2820298b6f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -26,6 +26,7 @@ protected: private: bool on_init() override; + void data_changed() override; void update_model_object() const override; void update_from_model_object() override; @@ -39,6 +40,13 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + + + bool waiting_for_autogenerated_supports = false; + bool has_backend_supports() const; + void reslice_FDM_supports(bool postpone_error_messages = false) const; + void auto_generate(); + void get_data_from_backend(); }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d4198b4c54..df531c5629 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -277,8 +277,51 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S preferences_dialog = new PreferencesDialog(this); } + + // bind events from DiffDlg + + bind_diff_dialog(); } +void MainFrame::bind_diff_dialog() +{ + auto get_tab = [](Preset::Type type) { + Tab* null_tab = nullptr; + for (Tab* tab : wxGetApp().tabs_list) + if (tab->type() == type) + return tab; + return null_tab; + }; + + auto transfer = [this, get_tab](Preset::Type type) { + get_tab(type)->transfer_options(diff_dialog.get_left_preset_name(type), + diff_dialog.get_right_preset_name(type), + diff_dialog.get_selected_options(type)); + }; + + auto update_presets = [this, get_tab](Preset::Type type) { + get_tab(type)->update_preset_choice(); + m_plater->sidebar().update_presets(type); + }; + + auto process_options = [this](std::function process) { + const Preset::Type diff_dlg_type = diff_dialog.view_type(); + if (diff_dlg_type == Preset::TYPE_INVALID) { + for (const Preset::Type& type : diff_dialog.printer_technology() == ptFFF ? + std::initializer_list{Preset::TYPE_PRINTER, Preset::TYPE_PRINT, Preset::TYPE_FILAMENT} : + std::initializer_list{ Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL } ) + process(type); + } + else + process(diff_dlg_type); + }; + + diff_dialog.Bind(EVT_DIFF_DIALOG_TRANSFER, [this, process_options, transfer](SimpleEvent&) { process_options(transfer); }); + + diff_dialog.Bind(EVT_DIFF_DIALOG_UPDATE_PRESETS,[this, process_options, update_presets](SimpleEvent&) { process_options(update_presets); }); +} + + #ifdef _MSW_DARK_MODE static wxString pref() { return " [ "; } static wxString suff() { return " ] "; } @@ -2130,9 +2173,6 @@ void MainFrame::add_to_recent_projects(const wxString& filename) void MainFrame::technology_changed() { - // upadte DiffDlg - diff_dialog.update_presets(); - // update menu titles PrinterTechnology pt = plater()->printer_technology(); if (int id = m_menubar->FindMenu(pt == ptFFF ? _L("Material Settings") : _L("Filament Settings")); id != wxNOT_FOUND) diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 893febf4bd..66adf806aa 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -107,6 +107,7 @@ class MainFrame : public DPIFrame bool can_delete() const; bool can_delete_all() const; bool can_reslice() const; + void bind_diff_dialog(); // MenuBar items changeable in respect to printer technology enum MenuItems diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5608c11a66..7f351e0b9a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1609,7 +1609,8 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi m_mainframe.Raise(); m_mainframe.select_tab(size_t(0)); - m_plater.select_view_3D("3D"); + if (wxGetApp().is_editor()) + m_plater.select_view_3D("3D"); bool res = m_plater.load_files(filenames); m_mainframe.update_title(); return res; @@ -5347,10 +5348,9 @@ void Plater::new_project() (saved_project == wxID_YES ? _L("You can keep presets modifications to the new project or discard them") : _L("You can keep presets modifications to the new project, discard them or save changes as new presets.\n" "Note, if changes will be saved then new project wouldn't keep them")); - using ab = UnsavedChangesDialog::ActionButtons; - int act_buttons = ab::KEEP; + int act_buttons = ActionButtons::KEEP; if (saved_project == wxID_NO) - act_buttons |= ab::SAVE; + act_buttons |= ActionButtons::SAVE; if (!wxGetApp().check_and_keep_current_preset_changes(_L("Creating a new project"), header, act_buttons)) return; } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 7f781e243e..65469e6a03 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -38,7 +38,6 @@ #include "../Utils/UndoRedo.hpp" #include "BitmapCache.hpp" #include "PhysicalPrinterDialog.hpp" -#include "SavePresetDialog.hpp" #include "MsgDialog.hpp" // A workaround for a set of issues related to text fitting into gtk widgets: diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 974d771b95..57aa5da983 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "libslic3r/PresetBundle.hpp" @@ -22,25 +21,23 @@ using Slic3r::GUI::format_wxstr; namespace Slic3r { namespace GUI { -#define BORDER_W 10 - +constexpr auto BORDER_W = 10; //----------------------------------------------- // SavePresetDialog::Item //----------------------------------------------- -SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent): - m_type(type), - m_parent(parent) +std::string SavePresetDialog::Item::get_init_preset_name(const std::string &suffix) { - Tab* tab = wxGetApp().get_tab(m_type); - assert(tab); - m_presets = tab->get_presets(); + PresetBundle* preset_bundle = m_parent->get_preset_bundle(); + if (!preset_bundle) + preset_bundle = wxGetApp().preset_bundle; + m_presets = &preset_bundle->get_presets(m_type); const Preset& sel_preset = m_presets->get_selected_preset(); - std::string preset_name = sel_preset.is_default ? "Untitled" : - sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : - sel_preset.name; + std::string preset_name = sel_preset.is_default ? "Untitled" : + sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : + sel_preset.name; // if name contains extension if (boost::iends_with(preset_name, ".ini")) { @@ -48,18 +45,11 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox preset_name.resize(len); } - std::vector values; - for (const Preset& preset : *m_presets) { - if (preset.is_default || preset.is_system || preset.is_external) - continue; - values.push_back(preset.name); - } - - std::string label_str = m_parent->is_for_rename() ?_utf8(L("Rename %s to:")) : _utf8(L("Save %s as:")); - wxStaticText* label_top = new wxStaticText(m_parent, wxID_ANY, from_u8((boost::format(label_str) % into_u8(tab->title())).str())); - - m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark")); + return preset_name; +} +void SavePresetDialog::Item::init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string preset_name) +{ if (m_parent->is_for_rename()) { #ifdef _WIN32 long style = wxBORDER_SIMPLE; @@ -68,10 +58,19 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox #endif m_text_ctrl = new wxTextCtrl(m_parent, wxID_ANY, from_u8(preset_name), wxDefaultPosition, wxSize(35 * wxGetApp().em_unit(), -1), style); m_text_ctrl->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); + + input_name_sizer->Add(m_text_ctrl,1, wxEXPAND, BORDER_W); } else { + std::vector values; + for (const Preset&preset : *m_presets) { + if (preset.is_default || preset.is_system || preset.is_external) + continue; + values.push_back(preset.name); + } + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name), wxDefaultPosition, wxSize(35 * wxGetApp().em_unit(), -1)); - for (const std::string& value : values) + for (const std::string&value : values) m_combo->Append(from_u8(value)); m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); @@ -80,20 +79,34 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox // So process wxEVT_COMBOBOX too m_combo->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { update(); }); #endif //__WXOSX__ - } - m_valid_label = new wxStaticText(m_parent, wxID_ANY, ""); + input_name_sizer->Add(m_combo, 1, wxEXPAND, BORDER_W); + } +} + +wxString SavePresetDialog::Item::get_top_label_text() const +{ + const std::string label_str = m_parent->is_for_rename() ?_u8L("Rename %s to:") : _u8L("Save %s as:"); + Tab* tab = wxGetApp().get_tab(m_type); + return from_u8((boost::format(label_str) % into_u8(tab->title())).str()); +} + +SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent): + m_type(type), + m_parent(parent), + m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), + m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")) +{ m_valid_label->SetFont(wxGetApp().bold_font()); - wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL); - combo_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); - if (m_parent->is_for_rename()) - combo_sizer->Add(m_text_ctrl,1, wxEXPAND, BORDER_W); - else - combo_sizer->Add(m_combo, 1, wxEXPAND, BORDER_W); + wxStaticText* label_top = new wxStaticText(m_parent, wxID_ANY, get_top_label_text()); + + wxBoxSizer* input_name_sizer = new wxBoxSizer(wxHORIZONTAL); + input_name_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); + init_input_name_ctrl(input_name_sizer, get_init_preset_name(suffix)); sizer->Add(label_top, 0, wxEXPAND | wxTOP| wxBOTTOM, BORDER_W); - sizer->Add(combo_sizer, 0, wxEXPAND | wxBOTTOM, BORDER_W); + sizer->Add(input_name_sizer,0, wxEXPAND | wxBOTTOM, BORDER_W); sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); if (m_type == Preset::TYPE_PRINTER) @@ -107,7 +120,7 @@ void SavePresetDialog::Item::update() bool rename = m_parent->is_for_rename(); m_preset_name = into_u8(rename ? m_text_ctrl->GetValue() : m_combo->GetValue()); - m_valid_type = Valid; + m_valid_type = ValidationType::Valid; wxString info_line; const char* unusable_symbols = "<>[]:/\\|?*\""; @@ -117,44 +130,45 @@ void SavePresetDialog::Item::update() if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { info_line = _L("The supplied name is not valid;") + "\n" + _L("the following characters are not allowed:") + " " + unusable_symbols; - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; break; } } - if (m_valid_type == Valid && m_preset_name.find(unusable_suffix) != std::string::npos) { + if (m_valid_type == ValidationType::Valid && m_preset_name.find(unusable_suffix) != std::string::npos) { info_line = _L("The supplied name is not valid;") + "\n" + _L("the following suffix is not allowed:") + "\n\t" + from_u8(PresetCollection::get_suffix_modified()); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (m_valid_type == Valid && m_preset_name == "- default -") { + if (m_valid_type == ValidationType::Valid && m_preset_name == "- default -") { info_line = _L("The supplied name is not available."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } const Preset* existing = m_presets->find_preset(m_preset_name, false); - if (m_valid_type == Valid && existing && (existing->is_default || existing->is_system)) { + if (m_valid_type == ValidationType::Valid && existing && (existing->is_default || existing->is_system)) { info_line = rename ? _L("The supplied name is used for a system profile.") : _L("Cannot overwrite a system profile."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (m_valid_type == Valid && existing && (existing->is_external)) { + if (m_valid_type == ValidationType::Valid && existing && (existing->is_external)) { info_line = rename ? _L("The supplied name is used for a external profile.") : _L("Cannot overwrite an external profile."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (m_valid_type == Valid && existing) + if (m_valid_type == ValidationType::Valid && existing) { if (m_preset_name == m_presets->get_selected_preset_name()) { - if (!rename && m_presets->get_edited_preset().is_dirty) - info_line = _L("Just save preset modifications"); + if (!rename && m_presets->get_edited_preset().is_dirty || + m_parent->get_preset_bundle()) // means that we save modifications from the DiffDialog + info_line = _L("Save preset modifications to existing user profile"); else info_line = _L("Nothing changed"); - m_valid_type = Valid; + m_valid_type = ValidationType::Valid; } else { if (existing->is_compatible) @@ -162,31 +176,31 @@ void SavePresetDialog::Item::update() else info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists and is incompatible with selected printer.")) % m_preset_name).str()); info_line += "\n" + _L("Note: This preset will be replaced after saving"); - m_valid_type = Warning; + m_valid_type = ValidationType::Warning; } } - if (m_valid_type == Valid && m_preset_name.empty()) { + if (m_valid_type == ValidationType::Valid && m_preset_name.empty()) { info_line = _L("The name cannot be empty."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (m_valid_type == Valid && m_preset_name.find_first_of(' ') == 0) { + if (m_valid_type == ValidationType::Valid && m_preset_name.find_first_of(' ') == 0) { info_line = _L("The name cannot start with space character."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (m_valid_type == Valid && m_preset_name.find_last_of(' ') == m_preset_name.length()-1) { + if (m_valid_type == ValidationType::Valid && m_preset_name.find_last_of(' ') == m_preset_name.length()-1) { info_line = _L("The name cannot end with space character."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (m_valid_type == Valid && m_presets->get_preset_name_by_alias(m_preset_name) != m_preset_name) { + if (m_valid_type == ValidationType::Valid && m_presets->get_preset_name_by_alias(m_preset_name) != m_preset_name) { info_line = _L("The name cannot be the same as a preset alias name."); - m_valid_type = NoValid; + m_valid_type = ValidationType::NoValid; } - if (!m_parent->get_info_line_extention().IsEmpty() && m_valid_type != NoValid) + if (!m_parent->get_info_line_extention().IsEmpty() && m_valid_type != ValidationType::NoValid) info_line += "\n\n" + m_parent->get_info_line_extention(); m_valid_label->SetLabel(info_line); @@ -202,14 +216,14 @@ void SavePresetDialog::Item::update() void SavePresetDialog::Item::update_valid_bmp() { - std::string bmp_name = m_valid_type == Warning ? "exclamation" : - m_valid_type == NoValid ? "cross" : "tick_mark" ; + std::string bmp_name = m_valid_type == ValidationType::Warning ? "exclamation" : + m_valid_type == ValidationType::NoValid ? "cross" : "tick_mark" ; m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name)); } void SavePresetDialog::Item::accept() { - if (m_valid_type == Warning) + if (m_valid_type == ValidationType::Warning) m_presets->delete_preset(m_preset_name); } @@ -224,8 +238,9 @@ SavePresetDialog::SavePresetDialog(wxWindow* parent, Preset::Type type, std::str build(std::vector{type}, suffix); } -SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix) - : DPIDialog(parent, wxID_ANY, _L("Save presets"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +SavePresetDialog::SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix, PresetBundle* preset_bundle/* = nullptr*/) + : DPIDialog(parent, wxID_ANY, _L("Save presets"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), + m_preset_bundle(preset_bundle) { build(types, suffix); } diff --git a/src/slic3r/GUI/SavePresetDialog.hpp b/src/slic3r/GUI/SavePresetDialog.hpp index 1ecda7b7fd..c40a5896f2 100644 --- a/src/slic3r/GUI/SavePresetDialog.hpp +++ b/src/slic3r/GUI/SavePresetDialog.hpp @@ -29,7 +29,7 @@ class SavePresetDialog : public DPIDialog struct Item { - enum ValidationType + enum class ValidationType { Valid, NoValid, @@ -41,15 +41,15 @@ class SavePresetDialog : public DPIDialog void update_valid_bmp(); void accept(); - bool is_valid() const { return m_valid_type != NoValid; } + bool is_valid() const { return m_valid_type != ValidationType::NoValid; } Preset::Type type() const { return m_type; } std::string preset_name() const { return m_preset_name; } private: Preset::Type m_type; - ValidationType m_valid_type; std::string m_preset_name; + ValidationType m_valid_type {ValidationType::NoValid}; SavePresetDialog* m_parent {nullptr}; wxStaticBitmap* m_valid_bmp {nullptr}; wxComboBox* m_combo {nullptr}; @@ -58,7 +58,11 @@ class SavePresetDialog : public DPIDialog PresetCollection* m_presets {nullptr}; - void update(); + std::string get_init_preset_name(const std::string &suffix); + void init_input_name_ctrl(wxBoxSizer *input_name_sizer, std::string preset_name); + wxString get_top_label_text() const ; + + void update(); }; std::vector m_items; @@ -73,19 +77,22 @@ class SavePresetDialog : public DPIDialog bool m_use_for_rename{false}; wxString m_info_line_extention{wxEmptyString}; + PresetBundle* m_preset_bundle{ nullptr }; + public: const wxString& get_info_line_extention() { return m_info_line_extention; } SavePresetDialog(wxWindow* parent, Preset::Type type, std::string suffix = ""); - SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix = ""); + SavePresetDialog(wxWindow* parent, std::vector types, std::string suffix = "", PresetBundle* preset_bundle = nullptr); SavePresetDialog(wxWindow* parent, Preset::Type type, bool rename, const wxString& info_line_extention); - ~SavePresetDialog(); + ~SavePresetDialog() override; void AddItem(Preset::Type type, const std::string& suffix); - std::string get_name(); - std::string get_name(Preset::Type type); + PresetBundle* get_preset_bundle() const { return m_preset_bundle; } + std::string get_name(); + std::string get_name(Preset::Type type); bool enable_ok_btn() const; void add_info_for_edit_ph_printer(wxBoxSizer *sizer); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5f92f98a37..d27bfc9315 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -30,6 +30,8 @@ #include #include #include +#include + #include "wxExtensions.hpp" #include "PresetComboBoxes.hpp" #include @@ -39,21 +41,20 @@ #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" -#include "PhysicalPrinterDialog.hpp" #include "UnsavedChangesDialog.hpp" #include "SavePresetDialog.hpp" #include "MsgDialog.hpp" #include "Notebook.hpp" #ifdef WIN32 - #include + #include #endif // WIN32 namespace Slic3r { namespace GUI { Tab::Tab(wxBookCtrlBase* parent, const wxString& title, Preset::Type type) : - m_parent(parent), m_title(title), m_type(type) + m_parent(parent), m_type(type), m_title(title) { Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL/*, name*/); this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -1183,9 +1184,9 @@ void Tab::activate_option(const std::string& opt_key, const wxString& category) m_highlighter.init(get_custom_ctrl_with_blinking_ptr(opt_key)); } -void Tab::cache_config_diff(const std::vector& selected_options) +void Tab::cache_config_diff(const std::vector& selected_options, const DynamicPrintConfig* config/* = nullptr*/) { - m_cache_config.apply_only(m_presets->get_edited_preset().config, selected_options); + m_cache_config.apply_only(config ? *config : m_presets->get_edited_preset().config, selected_options); } void Tab::apply_config_from_cache() @@ -3185,7 +3186,8 @@ void Tab::update_btns_enabling() const Preset& preset = m_presets->get_edited_preset(); m_btn_delete_preset->Show((m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) || (!preset.is_default && !preset.is_system)); - m_btn_rename_preset->Show(!preset.is_default && !preset.is_system && !m_presets_choice->is_selected_physical_printer()); + m_btn_rename_preset->Show(!preset.is_default && !preset.is_system && !preset.is_external && + !wxGetApp().preset_bundle->physical_printers.has_selection()); if (m_btn_edit_ph_printer) m_btn_edit_ph_printer->SetToolTip( m_preset_bundle->physical_printers.has_selection() ? @@ -3583,6 +3585,32 @@ void Tab::compare_preset() wxGetApp().mainframe->diff_dialog.show(m_type); } +void Tab::transfer_options(const std::string &name_from, const std::string &name_to, std::vector options) +{ + if (options.empty()) + return; + + Preset* preset_from = m_presets->find_preset(name_from); + Preset* preset_to = m_presets->find_preset(name_to); + + if (m_type == Preset::TYPE_PRINTER) { + auto it = std::find(options.begin(), options.end(), "extruders_count"); + if (it != options.end()) { + // erase "extruders_count" option from the list + options.erase(it); + // cache the extruders count + static_cast(this)->cache_extruder_cnt(&preset_from->config); + } + } + cache_config_diff(options, &preset_from->config); + + if (name_to != m_presets->get_edited_preset().name ) + select_preset(preset_to->name); + + apply_config_from_cache(); + load_current_preset(); +} + // Save the current preset into file. // This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, // and activates the new preset. @@ -3674,12 +3702,11 @@ void Tab::rename_preset() if (m_presets_choice->is_selected_physical_printer()) return; - Preset& selected_preset = m_presets->get_selected_preset(); wxString msg; if (m_type == Preset::TYPE_PRINTER && !m_preset_bundle->physical_printers.empty()) { // Check preset for rename in physical printers - std::vector ph_printers = m_preset_bundle->physical_printers.get_printers_with_preset(selected_preset.name); + std::vector ph_printers = m_preset_bundle->physical_printers.get_printers_with_preset(m_presets->get_selected_preset().name); if (!ph_printers.empty()) { msg += _L_PLURAL("The physical printer below is based on the preset, you are going to rename.", "The physical printers below are based on the preset, you are going to rename.", ph_printers.size()); @@ -3696,31 +3723,51 @@ void Tab::rename_preset() SavePresetDialog dlg(m_parent, m_type, true, msg); if (dlg.ShowModal() != wxID_OK) return; - std::string new_name = into_u8(dlg.get_name()); + const std::string new_name = into_u8(dlg.get_name()); if (new_name.empty() || new_name == m_presets->get_selected_preset().name) return; - // rename selected and edited presets + // Note: selected preset can be changed, if in SavePresetDialog was selected name of existing preset + Preset& selected_preset = m_presets->get_selected_preset(); + Preset& edited_preset = m_presets->get_edited_preset(); - std::string old_name = selected_preset.name; - std::string old_file_name = selected_preset.file; + const std::string old_name = selected_preset.name; + const std::string old_file_name = selected_preset.file; - selected_preset.name = new_name; - boost::replace_last(selected_preset.file, old_name, new_name); + assert(old_name == edited_preset.name); - Preset& edited_preset = m_presets->get_edited_preset(); - edited_preset.name = new_name; - boost::replace_last(edited_preset.file, old_name, new_name); + using namespace boost; + try { + // rename selected and edited presets - // rename file with renamed preset configuration - boost::filesystem::rename(old_file_name, selected_preset.file); + selected_preset.name = new_name; + replace_last(selected_preset.file, old_name, new_name); - // rename selected preset in printers, if it's needed - if (!msg.IsEmpty()) - m_preset_bundle->physical_printers.rename_preset_in_printers(old_name, new_name); + edited_preset.name = new_name; + replace_last(edited_preset.file, old_name, new_name); + + // rename file with renamed preset configuration + + filesystem::rename(old_file_name, selected_preset.file); + + // rename selected preset in printers, if it's needed + + if (!msg.IsEmpty()) + m_preset_bundle->physical_printers.rename_preset_in_printers(old_name, new_name); + } + catch (const exception& ex) { + const std::string exception = diagnostic_information(ex); + printf("Can't rename a preset : %s", exception.c_str()); + } + + // sort presets after renaming + std::sort(m_presets->begin(), m_presets->end()); + // update selection + m_presets->select_preset_by_name(new_name, true); m_presets_choice->update(); + on_presets_changed(); } // Called for a currently selected preset. @@ -4267,12 +4314,15 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) return sizer; } -void TabPrinter::cache_extruder_cnt() +void TabPrinter::cache_extruder_cnt(const DynamicPrintConfig* config/* = nullptr*/) { - if (m_presets->get_edited_preset().printer_technology() == ptSLA) + const DynamicPrintConfig& cached_config = config ? *config : m_presets->get_edited_preset().config; + if (Preset::printer_technology(cached_config) == ptSLA) return; - m_cache_extruder_count = m_extruders_count; + // get extruders count + auto* nozzle_diameter = dynamic_cast(cached_config.option("nozzle_diameter")); + m_cache_extruder_count = nozzle_diameter->values.size(); //m_extruders_count; } bool TabPrinter::apply_extruder_cnt_from_cache() @@ -4282,6 +4332,7 @@ bool TabPrinter::apply_extruder_cnt_from_cache() if (m_cache_extruder_count > 0) { m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count); +// extruders_count_changed(m_cache_extruder_count); m_cache_extruder_count = 0; return true; } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 8ed11bf9d0..448e4be050 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -322,6 +322,7 @@ public: void OnKeyDown(wxKeyEvent& event); void compare_preset(); + void transfer_options(const std::string&name_from, const std::string&name_to, std::vector options); void save_preset(std::string name = std::string(), bool detach = false); void rename_preset(); void delete_preset(); @@ -374,7 +375,7 @@ public: void update_wiping_button_visibility(); void activate_option(const std::string& opt_key, const wxString& category); - void cache_config_diff(const std::vector& selected_options); + void cache_config_diff(const std::vector& selected_options, const DynamicPrintConfig* config = nullptr); void apply_config_from_cache(); const std::map& get_category_icon_map() { return m_category_icon; } @@ -503,7 +504,7 @@ public: bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); - void cache_extruder_cnt(); + void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr); bool apply_extruder_cnt_from_cache(); }; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 225d92ec64..c04a536efb 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include @@ -22,10 +21,6 @@ #include "MainFrame.hpp" #include "MsgDialog.hpp" -//#define FTS_FUZZY_MATCH_IMPLEMENTATION -//#include "fts_fuzzy_match.h" - -#include "BitmapCache.hpp" #include "PresetComboBoxes.hpp" using boost::optional; @@ -40,6 +35,10 @@ namespace Slic3r { namespace GUI { +wxDEFINE_EVENT(EVT_DIFF_DIALOG_TRANSFER, SimpleEvent); +wxDEFINE_EVENT(EVT_DIFF_DIALOG_UPDATE_PRESETS, SimpleEvent); + + // ---------------------------------------------------------------------------- // ModelNode: a node inside DiffModel // ---------------------------------------------------------------------------- @@ -104,8 +103,8 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& ModelNode::ModelNode(ModelNode* parent, const wxString& text) : m_parent_win(parent->m_parent_win), m_parent(parent), - m_text(text), - m_icon_name("dot_small") + m_icon_name("dot_small"), + m_text(text) { UpdateIcons(); } @@ -135,12 +134,12 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& ol m_old_color(old_value.StartsWith("#") ? old_value : ""), m_mod_color(mod_value.StartsWith("#") ? mod_value : ""), m_new_color(new_value.StartsWith("#") ? new_value : ""), - m_container(false), - m_text(text), m_icon_name("empty"), + m_text(text), m_old_value(old_value), m_mod_value(mod_value), - m_new_value(new_value) + m_new_value(new_value), + m_container(false) { // check if old/new_value is color if (m_old_color.IsEmpty()) { @@ -505,7 +504,7 @@ unsigned int DiffModel::GetChildren(const wxDataViewItem& parent, wxDataViewItem for (const std::unique_ptr& child : children) array.Add(wxDataViewItem((void*)child.get())); - return array.size(); + return array.Count(); } @@ -591,7 +590,7 @@ void DiffModel::Clear() static std::string get_pure_opt_key(std::string opt_key) { - int pos = opt_key.find("#"); + const int pos = opt_key.find("#"); if (pos > 0) boost::erase_tail(opt_key, opt_key.size() - pos); return opt_key; @@ -1337,6 +1336,12 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres searcher.sort_options_by_label(); } +wxString UnsavedChangesDialog::msg_success_saved_modifications(size_t saved_presets_cnt) +{ + return _L_PLURAL("The preset modifications are successfully saved", + "The presets modifications are successfully saved", static_cast(saved_presets_cnt)); +} + void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) { int em = em_unit(); @@ -1380,7 +1385,7 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, has_new_value_column ? 3 : 2, 1, 0); grid_sizer->SetFlexibleDirection(wxBOTH); - for (size_t col = 0 ; col < grid_sizer->GetCols(); col++) + for (int col = 0 ; col < grid_sizer->GetCols(); col++) grid_sizer->AddGrowableCol(col, 1); grid_sizer->AddGrowableRow(1,1); @@ -1472,34 +1477,11 @@ static std::string get_selection(PresetComboBox* preset_combo) return into_u8(preset_combo->GetString(preset_combo->GetSelection())); } -DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) - : DPIDialog(mainframe, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), - m_pr_technology(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology()) -{ -#if defined(__WXMSW__) - // ys_FIXME! temporary workaround for correct font scaling - // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, - // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT - this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); -#endif // __WXMSW__ +void DiffPresetDialog::create_presets_sizer() +{ + m_presets_sizer = new wxBoxSizer(wxVERTICAL); - int border = 10; - int em = em_unit(); - - assert(wxGetApp().preset_bundle); - - m_preset_bundle_left = std::make_unique(*wxGetApp().preset_bundle); - m_preset_bundle_right = std::make_unique(*wxGetApp().preset_bundle); - - m_top_info_line = new wxStaticText(this, wxID_ANY, _L("Select presets to compare")); - m_top_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); - - m_bottom_info_line = new wxStaticText(this, wxID_ANY, ""); - m_bottom_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); - - wxBoxSizer* presets_sizer = new wxBoxSizer(wxVERTICAL); - - for (auto new_type : { Preset::TYPE_PRINT, Preset::TYPE_FILAMENT, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL, Preset::TYPE_PRINTER }) + for (auto new_type : { Preset::TYPE_PRINT, Preset::TYPE_SLA_PRINT, Preset::TYPE_FILAMENT, Preset::TYPE_SLA_MATERIAL, Preset::TYPE_PRINTER }) { const PresetCollection* collection = get_preset_collection(new_type); wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); @@ -1507,15 +1489,16 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) PresetComboBox* presets_right; ScalableButton* equal_bmp = new ScalableButton(this, wxID_ANY, "equal"); - auto add_preset_combobox = [collection, sizer, new_type, em, this](PresetComboBox** cb_, PresetBundle* preset_bundle) { - *cb_ = new PresetComboBox(this, new_type, wxSize(em * 35, -1), preset_bundle); - PresetComboBox* cb = (*cb_); + auto add_preset_combobox = [collection, sizer, new_type, this](PresetComboBox** cb_, PresetBundle* preset_bundle) { + *cb_ = new PresetComboBox(this, new_type, wxSize(em_unit() * 35, -1), preset_bundle); + PresetComboBox*cb = (*cb_); cb->show_modif_preset_separately(); cb->set_selection_changed_function([this, new_type, preset_bundle, cb](int selection) { - if (m_view_type == Preset::TYPE_INVALID) { - std::string preset_name = cb->GetString(selection).ToUTF8().data(); + std::string preset_name = Preset::remove_suffix_modified(cb->GetString(selection).ToUTF8().data()); + if (m_view_type == Preset::TYPE_INVALID) update_compatibility(preset_name, new_type, preset_bundle); - } + // update selection inside of related presets + preset_bundle->get_presets(new_type).select_preset_by_name(preset_name, true); update_tree(); }); if (collection->get_selected_idx() != (size_t)-1) @@ -1527,7 +1510,7 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) add_preset_combobox(&presets_left, m_preset_bundle_left.get()); sizer->Add(equal_bmp, 0, wxRIGHT | wxLEFT | wxALIGN_CENTER_VERTICAL, 5); add_preset_combobox(&presets_right, m_preset_bundle_right.get()); - presets_sizer->Add(sizer, 1, wxTOP, 5); + m_presets_sizer->Add(sizer, 1, wxTOP, 5); equal_bmp->Show(new_type == Preset::TYPE_PRINTER); m_preset_combos.push_back({ presets_left, equal_bmp, presets_right }); @@ -1540,7 +1523,10 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) update_tree(); }); } +} +void DiffPresetDialog::create_show_all_presets_chb() +{ m_show_all_presets = new wxCheckBox(this, wxID_ANY, _L("Show all presets (including incompatible)")); m_show_all_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { bool show_all = m_show_all_presets->GetValue(); @@ -1553,26 +1539,171 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) if (m_view_type == Preset::TYPE_INVALID) update_tree(); }); +} - m_tree = new DiffViewCtrl(this, wxSize(em * 65, em * 40)); +void DiffPresetDialog::create_info_lines() +{ + const wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + + m_top_info_line = new wxStaticText(this, wxID_ANY, _L("Select presets to compare")); + m_top_info_line->SetFont(font); + + m_bottom_info_line = new wxStaticText(this, wxID_ANY, ""); + m_bottom_info_line->SetFont(font); +} + +void DiffPresetDialog::create_tree() +{ + m_tree = new DiffViewCtrl(this, wxSize(em_unit() * 65, em_unit() * 40)); + m_tree->AppendToggleColumn_(L"\u2714", DiffModel::colToggle, wxLinux ? 9 : 6); m_tree->AppendBmpTextColumn("", DiffModel::colIconText, 35); m_tree->AppendBmpTextColumn(_L("Left Preset Value"), DiffModel::colOldValue, 15); m_tree->AppendBmpTextColumn(_L("Right Preset Value"),DiffModel::colModValue, 15); m_tree->Hide(); + m_tree->GetColumn(DiffModel::colToggle)->SetHidden(true); +} - wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); +void DiffPresetDialog::create_buttons() +{ + wxFont font = this->GetFont().Scaled(1.4f); + m_buttons = new wxBoxSizer(wxHORIZONTAL); - topSizer->Add(m_top_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2 * border); - topSizer->Add(presets_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_show_all_presets, 0, wxEXPAND | wxALL, border); - topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border); - topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border); + auto show_in_bottom_info = [this](const wxString& ext_line, wxMouseEvent& e) { + m_bottom_info_line->SetLabel(ext_line); + m_bottom_info_line->Show(true); + Layout(); + e.Skip(); + }; - this->SetMinSize(wxSize(80 * em, 30 * em)); + // Transfer + m_transfer_btn = new ScalableButton(this, wxID_ANY, "paste_menu", _L("Transfer"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); + m_transfer_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { button_event(Action::Transfer);}); + + + auto enable_transfer = [this](const Preset::Type& type) { + const Preset& main_edited_preset = get_preset_collection(type, wxGetApp().preset_bundle)->get_edited_preset(); + if (main_edited_preset.is_dirty) + return main_edited_preset.name == get_right_preset_name(type); + return true; + }; + m_transfer_btn->Bind(wxEVT_UPDATE_UI, [this, enable_transfer](wxUpdateUIEvent& evt) { + bool enable = m_tree->has_selection(); + if (enable) { + if (m_view_type == Preset::TYPE_INVALID) { + for (const Preset::Type& type : (m_pr_technology == ptFFF ? std::initializer_list{Preset::TYPE_PRINTER, Preset::TYPE_PRINT, Preset::TYPE_FILAMENT} : + std::initializer_list{ Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL })) + if (!enable_transfer(type)) { + enable = false; + break; + } + } + else + enable = enable_transfer(m_view_type); + } + evt.Enable(enable); + }); + m_transfer_btn->Bind(wxEVT_ENTER_WINDOW, [this, show_in_bottom_info](wxMouseEvent& e) { + show_in_bottom_info(_L("Transfer the selected options from left preset to the right.\n" + "Note: New modified presets will be selected in setting stabs after close this dialog."), e); }); + + // Save + m_save_btn = new ScalableButton(this, wxID_ANY, "save", _L("Save"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); + m_save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { button_event(Action::Save); }); + m_save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_tree->has_selection()); }); + m_save_btn->Bind(wxEVT_ENTER_WINDOW, [this, show_in_bottom_info](wxMouseEvent& e) { + show_in_bottom_info(_L("Save the selected options from left preset to the right."), e); }); + + // Cancel + m_cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); + m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { button_event(Action::Discard);}); + + for (ScalableButton* btn : { m_transfer_btn, m_save_btn, m_cancel_btn }) { + btn->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { update_bottom_info(); Layout(); e.Skip(); }); + m_buttons->Add(btn, 1, wxLEFT, 5); + btn->SetFont(font); + } + + m_buttons->Show(false); +} + +void DiffPresetDialog::create_edit_sizer() +{ + // Add check box for the edit mode + m_use_for_transfer = new wxCheckBox(this, wxID_ANY, _L("Transfer values from left to right")); + m_use_for_transfer->SetToolTip(_L("If enabled, this dialog can be used for transver selected values from left to right preset.")); + m_use_for_transfer->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + bool use = m_use_for_transfer->GetValue(); + m_tree->GetColumn(DiffModel::colToggle)->SetHidden(!use); + if (m_tree->IsShown()) { + m_buttons->Show(use); + Fit(); + Refresh(); + } + else + this->Layout(); + }); + + // Add Buttons + create_buttons(); + + // Create and fill edit sizer + m_edit_sizer = new wxBoxSizer(wxHORIZONTAL); + m_edit_sizer->Add(m_use_for_transfer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); + m_edit_sizer->AddSpacer(em_unit() * 10); + m_edit_sizer->Add(m_buttons, 1, wxLEFT, 5); + m_edit_sizer->Show(false); +} + +void DiffPresetDialog::complete_dialog_creation() +{ + wxBoxSizer*topSizer = new wxBoxSizer(wxVERTICAL); + + int border = 10; + topSizer->Add(m_top_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2 * border); + topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_show_all_presets, 0, wxEXPAND | wxALL, border); + topSizer->Add(m_tree, 1, wxEXPAND | wxALL, border); + topSizer->Add(m_bottom_info_line, 0, wxEXPAND | wxALL, 2 * border); + topSizer->Add(m_edit_sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxRIGHT, 2 * border); + + this->SetMinSize(wxSize(80 * em_unit(), 30 * em_unit())); this->SetSizer(topSizer); topSizer->SetSizeHints(this); } +DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe) + : DPIDialog(mainframe, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_pr_technology(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology()) +{ +#if defined(__WXMSW__) + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#endif // __WXMSW__ + + // Init bundles + + assert(wxGetApp().preset_bundle); + + m_preset_bundle_left = std::make_unique(*wxGetApp().preset_bundle); + m_preset_bundle_right = std::make_unique(*wxGetApp().preset_bundle); + + // Create UI items + + create_info_lines(); + + create_presets_sizer(); + + create_show_all_presets_chb(); + + create_tree(); + + create_edit_sizer(); + + complete_dialog_creation(); +} + void DiffPresetDialog::update_controls_visibility(Preset::Type type /* = Preset::TYPE_INVALID*/) { for (auto preset_combos : m_preset_combos) { @@ -1598,6 +1729,8 @@ void DiffPresetDialog::update_bundles_from_app() { *m_preset_bundle_left = *wxGetApp().preset_bundle; *m_preset_bundle_right = *wxGetApp().preset_bundle; + + m_pr_technology = m_preset_bundle_left.get()->printers.get_edited_preset().printer_technology(); } void DiffPresetDialog::show(Preset::Type type /* = Preset::TYPE_INVALID*/) @@ -1618,11 +1751,10 @@ void DiffPresetDialog::show(Preset::Type type /* = Preset::TYPE_INVALID*/) Show(); } -void DiffPresetDialog::update_presets(Preset::Type type) +void DiffPresetDialog::update_presets(Preset::Type type, bool update_preset_bundles_from_app/* = true */) { - m_pr_technology = m_preset_bundle_left.get()->printers.get_edited_preset().printer_technology(); - - update_bundles_from_app(); + if (update_preset_bundles_from_app) + update_bundles_from_app(); update_controls_visibility(type); if (type == Preset::TYPE_INVALID) @@ -1645,9 +1777,20 @@ void DiffPresetDialog::update_presets(Preset::Type type) update_tree(); } +void DiffPresetDialog::update_bottom_info(wxString bottom_info) +{ + if (m_tree->has_long_strings()) + bottom_info = _L("Some fields are too long to fit. Right mouse click reveals the full text."); + + const bool show_bottom_info = !m_tree->IsShown() || m_tree->has_long_strings(); + if (show_bottom_info) + m_bottom_info_line->SetLabel(bottom_info); + m_bottom_info_line->Show(show_bottom_info); +} + void DiffPresetDialog::update_tree() { - // update searcher befofre update of tree + // update searcher before update of tree wxGetApp().sidebar().check_and_update_searcher(); Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); searcher.sort_options_by_key(); @@ -1739,17 +1882,15 @@ void DiffPresetDialog::update_tree() left_val, right_val, "", category_icon_map.at(option.category)); } } - - if (m_tree->has_long_strings()) - bottom_info = _L("Some fields are too long to fit. Right mouse click reveals the full text."); bool tree_was_shown = m_tree->IsShown(); m_tree->Show(show_tree); - bool show_bottom_info = !show_tree || m_tree->has_long_strings(); - if (show_bottom_info) - m_bottom_info_line->SetLabel(bottom_info); - m_bottom_info_line->Show(show_bottom_info); + bool can_transfer_options = m_view_type == Preset::TYPE_INVALID || get_left_preset_name(m_view_type) != get_right_preset_name(m_view_type); + m_edit_sizer->Show(show_tree && can_transfer_options); + m_buttons->Show(m_edit_sizer->IsShown(size_t(0)) && m_use_for_transfer->GetValue()); + + update_bottom_info(bottom_info); if (tree_was_shown == m_tree->IsShown()) Layout(); @@ -1802,6 +1943,10 @@ void DiffPresetDialog::on_sys_color_changed() preset_combos.equal_bmp->sys_color_changed(); preset_combos.presets_right->sys_color_changed(); } + + for (ScalableButton* btn : { m_transfer_btn, m_save_btn, m_cancel_btn }) + btn->sys_color_changed(); + // msw_rescale updates just icons, so use it m_tree->Rescale(); Refresh(); @@ -1865,6 +2010,90 @@ void DiffPresetDialog::update_compatibility(const std::string& preset_name, Pres } } +bool DiffPresetDialog::save() +{ + presets_to_save.clear(); + + std::vector types_for_save; + + for (const Preset::Type& type : m_pr_technology == ptFFF ? std::initializer_list{Preset::TYPE_PRINTER, Preset::TYPE_PRINT, Preset::TYPE_FILAMENT} : + std::initializer_list{Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL }) + if (!m_tree->options(type, true).empty()) { + types_for_save.emplace_back(type); + presets_to_save.emplace_back(PresetToSave{ type, get_left_preset_name(type), get_right_preset_name(type), get_right_preset_name(type) }); + } + + if (!types_for_save.empty()) { + SavePresetDialog save_dlg(this, types_for_save, _u8L("Modified"), m_preset_bundle_right.get()); + if (save_dlg.ShowModal() != wxID_OK) + return false; + + for (auto& preset : presets_to_save) { + const std::string& name = save_dlg.get_name(preset.type); + if (!name.empty()) + preset.new_name = name; + } + } + return true; +} + +std::vector DiffPresetDialog::get_options_to_save(Preset::Type type) +{ + auto options = m_tree->options(type, true); + + // erase "inherits" option from the list if it exists there + if (const auto it = std::find(options.begin(), options.end(), "inherits"); it != options.end()) + options.erase(it); + + if (type == Preset::TYPE_PRINTER) { + // erase "extruders_count" option from the list if it exists there + if (const auto it = std::find(options.begin(), options.end(), "extruders_count"); it != options.end()) + options.erase(it); + } + return options; +} + +void DiffPresetDialog::button_event(Action act) +{ + if (act == Action::Save) { + if (save()) { + size_t saved_cnt = 0; + for (const auto& preset : presets_to_save) + if (wxGetApp().preset_bundle->transfer_and_save(preset.type, preset.from_name, preset.to_name, preset.new_name, get_options_to_save(preset.type))) + saved_cnt++; + + if (saved_cnt > 0) { + MessageDialog(this, UnsavedChangesDialog::msg_success_saved_modifications(saved_cnt)).ShowModal(); + update_bundles_from_app(); + for (const auto& preset : presets_to_save) { + m_preset_bundle_left->get_presets(preset.type).select_preset_by_name(preset.from_name, true); + m_preset_bundle_right->get_presets(preset.type).select_preset_by_name(preset.new_name, true); + } + update_presets(m_view_type, false); + } + } + } + else { + Hide(); + if (act == Action::Transfer) + wxPostEvent(this, SimpleEvent(EVT_DIFF_DIALOG_TRANSFER)); + else if (!presets_to_save.empty()) + wxPostEvent(this, SimpleEvent(EVT_DIFF_DIALOG_UPDATE_PRESETS)); + } +} + +std::string DiffPresetDialog::get_left_preset_name(Preset::Type type) +{ + PresetComboBox* cb = m_preset_combos[int(type - Preset::TYPE_PRINT)].presets_left; + return Preset::remove_suffix_modified(get_selection(cb)); +} + +std::string DiffPresetDialog::get_right_preset_name(Preset::Type type) +{ + PresetComboBox* cb = m_preset_combos[int(type - Preset::TYPE_PRINT)].presets_right; + return Preset::remove_suffix_modified(get_selection(cb)); +} + } } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 0d98129326..1592914fce 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -15,6 +15,9 @@ class wxStaticText; namespace Slic3r { namespace GUI{ +wxDECLARE_EVENT(EVT_DIFF_DIALOG_TRANSFER, SimpleEvent); +wxDECLARE_EVENT(EVT_DIFF_DIALOG_UPDATE_PRESETS, SimpleEvent); + // ---------------------------------------------------------------------------- // ModelNode: a node inside DiffModel // ---------------------------------------------------------------------------- @@ -103,7 +106,7 @@ public: ModelNode* GetParent() { return m_parent; } ModelNodePtrArray& GetChildren() { return m_children; } ModelNode* GetNthChild(unsigned int n) { return m_children[n].get(); } - unsigned int GetChildCount() const { return m_children.size(); } + unsigned int GetChildCount() const { return (unsigned int)(m_children.size()); } void Append(std::unique_ptr child) { m_children.emplace_back(std::move(child)); } @@ -154,7 +157,7 @@ public: }; DiffModel(wxWindow* parent); - ~DiffModel() {} + ~DiffModel() override = default; void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } @@ -242,6 +245,20 @@ public: std::vector selected_options(); }; +// Discard and Cancel buttons are always but next buttons are optional +enum ActionButtons { + TRANSFER = 1, + KEEP = 2, + SAVE = 4, + DONT_SAVE = 8, +}; + +enum class Action { + Undef, + Transfer, + Discard, + Save +}; //------------------------------------------ // UnsavedChangesDialog @@ -262,13 +279,6 @@ class UnsavedChangesDialog : public DPIDialog std::string m_app_config_key; - enum class Action { - Undef, - Transfer, - Discard, - Save - }; - static constexpr char ActTransfer[] = "transfer"; static constexpr char ActDiscard[] = "discard"; static constexpr char ActSave[] = "save"; @@ -281,19 +291,12 @@ class UnsavedChangesDialog : public DPIDialog int m_buttons { ActionButtons::TRANSFER | ActionButtons::SAVE }; public: - // Discard and Cancel buttons are always but next buttons are optional - enum ActionButtons { - TRANSFER = 1, - KEEP = 2, - SAVE = 4, - DONT_SAVE = 8, - }; // show unsaved changes when preset is switching UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset = std::string()); // show unsaved changes for all another cases UnsavedChangesDialog(const wxString& caption, const wxString& header, const std::string& app_config_key, int act_buttons); - ~UnsavedChangesDialog() {} + ~UnsavedChangesDialog() override = default; void build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header = ""); void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header); @@ -318,6 +321,8 @@ public: std::vector get_selected_options() { return m_tree->selected_options(); } bool has_unselected_options() { return m_tree->has_unselected_options(); } + static wxString msg_success_saved_modifications(size_t saved_presets_cnt); + protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override; @@ -332,7 +337,7 @@ class FullCompareDialog : public wxDialog public: FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& mod_value, const wxString& new_value, const wxString& old_value_header, const wxString& mod_value_header, const wxString& new_value_header); - ~FullCompareDialog() {} + ~FullCompareDialog() override = default; }; @@ -342,19 +347,39 @@ public: class DiffPresetDialog : public DPIDialog { DiffViewCtrl* m_tree { nullptr }; + wxBoxSizer* m_presets_sizer { nullptr }; wxStaticText* m_top_info_line { nullptr }; wxStaticText* m_bottom_info_line { nullptr }; wxCheckBox* m_show_all_presets { nullptr }; + wxCheckBox* m_use_for_transfer { nullptr }; + ScalableButton* m_transfer_btn { nullptr }; + ScalableButton* m_save_btn { nullptr }; + ScalableButton* m_cancel_btn { nullptr }; + wxBoxSizer* m_buttons { nullptr }; + wxBoxSizer* m_edit_sizer { nullptr }; Preset::Type m_view_type { Preset::TYPE_INVALID }; PrinterTechnology m_pr_technology; std::unique_ptr m_preset_bundle_left; std::unique_ptr m_preset_bundle_right; - void update_tree(); - void update_bundles_from_app(); - void update_controls_visibility(Preset::Type type = Preset::TYPE_INVALID); - void update_compatibility(const std::string& preset_name, Preset::Type type, PresetBundle* preset_bundle); + void create_buttons(); + void create_edit_sizer(); + void complete_dialog_creation(); + void create_presets_sizer(); + void create_info_lines(); + void create_tree(); + void create_show_all_presets_chb(); + + void update_bottom_info(wxString bottom_info = ""); + void update_tree(); + void update_bundles_from_app(); + void update_controls_visibility(Preset::Type type = Preset::TYPE_INVALID); + void update_compatibility(const std::string& preset_name, Preset::Type type, PresetBundle* preset_bundle); + + std::vector get_options_to_save(Preset::Type type); + void button_event(Action act); + bool save(); struct DiffPresets { @@ -365,12 +390,31 @@ class DiffPresetDialog : public DPIDialog std::vector m_preset_combos; -public: - DiffPresetDialog(MainFrame* mainframe); - ~DiffPresetDialog() {} + // attributes witch are used for save preset + struct PresetToSave + { + Preset::Type type; + std::string from_name; + std::string to_name; + std::string new_name; + }; - void show(Preset::Type type = Preset::TYPE_INVALID); - void update_presets(Preset::Type type = Preset::TYPE_INVALID); + std::vector presets_to_save; + +public: + DiffPresetDialog(MainFrame*mainframe); + ~DiffPresetDialog() override = default; + + void show(Preset::Type type = Preset::TYPE_INVALID); + void update_presets(Preset::Type type = Preset::TYPE_INVALID, bool update_preset_bundles_from_app = true); + + Preset::Type view_type() const { return m_view_type; } + PrinterTechnology printer_technology() const { return m_pr_technology; } + + std::string get_left_preset_name(Preset::Type type); + std::string get_right_preset_name(Preset::Type type); + + std::vector get_selected_options(Preset::Type type) const { return std::move(m_tree->options(type, true)); } protected: void on_dpi_changed(const wxRect& suggested_rect) override;