diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index 5c2019454..113c5cec0 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -71,7 +71,7 @@ is currently unsupported because some of the dependencies don't support this, mo Please note that the `CMAKE_OSX_DEPLOYMENT_TARGET` and `CMAKE_OSX_SYSROOT` options need to be set the same on both the dependencies bundle as well as PrusaSlicer itself. -Official Mac PrusaSlicer builds are currently built against SDK 10.9 to ensure compatibility with older Macs. +Official macOS PrusaSlicer builds are currently (as of PrusaSlicer 2.5) built against SDK 10.12 to ensure compatibility with older Macs. _Warning:_ XCode may be set such that it rejects SDKs bellow some version (silently, more or less). This is set in the property list file diff --git a/resources/profiles/RatRig.idx b/resources/profiles/RatRig.idx index a86724f1d..f0c117464 100644 --- a/resources/profiles/RatRig.idx +++ b/resources/profiles/RatRig.idx @@ -1,2 +1,3 @@ min_slic3r_version = 2.4.1-alpha0 +1.0.1 Various fixes and improvements. Commented filament sensor initialisation for v-Minion (optional HW). 1.0.0 Initial RatRig bundle \ No newline at end of file diff --git a/resources/profiles/RatRig.ini b/resources/profiles/RatRig.ini index 69379e781..5558cf698 100644 --- a/resources/profiles/RatRig.ini +++ b/resources/profiles/RatRig.ini @@ -9,7 +9,7 @@ name = RatRig # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 1.0.0 +config_version = 1.0.1 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/RatRig/ # The printer models will be shown by the Configuration Wizard in this order, @@ -25,7 +25,7 @@ technology = FFF family = V-Core-3 bed_model = rr-vc-300.stl bed_texture = rr-vc-300.svg -default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; Esun ePA-CF @RatRig; Generic ABS @RatRig +default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; Esun ePA-CF @RatRig; Generic ABS @RatRig; Generic TPU @RatRig [printer_model:VC3_400] name = RatRig V-Core-3 400mm @@ -33,7 +33,7 @@ variants = 0.4 family = V-Core-3 bed_model = rr-vc-400.stl bed_texture = rr-vc-400.svg -default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; Esun ePA-CF @RatRig; Generic ABS @RatRig +default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; Esun ePA-CF @RatRig; Generic ABS @RatRig; Generic TPU @RatRig [printer_model:VC3_500] name = RatRig V-Core-3 500mm @@ -41,7 +41,7 @@ variants = 0.4 family = V-Core-3 bed_model = rr-vc-500.stl bed_texture = rr-vc-500.svg -default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; Esun ePA-CF @RatRig; Generic ABS @RatRig +default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; Esun ePA-CF @RatRig; Generic ABS @RatRig; Generic TPU @RatRig [printer_model:VMINION] name = RatRig V-Minion @@ -50,7 +50,7 @@ technology = FFF family = V-Minion bed_model = rr-vminion.stl bed_texture = rr-vminion.svg -default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; @Esun ePA-CF RatRig; Generic ABS @RatRig +default_materials = Generic PETG V6 @RatRig; Generic PETG HF @RatRig; Generic PLA @RatRig; @Esun ePA-CF RatRig; Generic ABS @RatRig; Generic TPU @RatRig [print:*common*] compatible_printers_condition = printer_model=~/.*VC3_.*/ and nozzle_diameter[0]==0.4 @@ -75,7 +75,6 @@ dont_support_bridges = 1 draft_shield = 0 ensure_vertical_shell_thickness = 1 exact_last_layer_height = 0 -external_infill_margin = 150% external_perimeter_extrusion_width = 0.4 external_perimeter_speed = 90% extruder_clearance_height = 25 @@ -116,7 +115,6 @@ notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{print_time}.gcode -over_bridge_flow_ratio = 100% perimeter_acceleration = 3000 perimeter_extruder = 1 perimeter_extrusion_width = 0.4 @@ -163,7 +161,6 @@ support_material_threshold = 65 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 -threads = 4 top_fill_pattern = monotonic top_infill_extrusion_width = 0.4 top_solid_infill_speed = 60% @@ -437,7 +434,7 @@ retract_speed = 40 silent_mode = 0 single_extruder_multi_material = 0 start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature]\n;enable this if you have a BTT Smart Filament Sensor\nSET_FILAMENT_SENSOR SENSOR=my_sensor ENABLE=0\n -thumbnails = 16x16,220x220 +thumbnails = 64x64,400x300 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 @@ -499,10 +496,10 @@ retract_restart_extra_toolchange = 0 retract_speed = 40 silent_mode = 0 single_extruder_multi_material = 0 -start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature]\n;enable this if you have a BTT Smart Filament Sensor\nSET_FILAMENT_SENSOR SENSOR=my_sensor ENABLE=0\n +start_gcode = M190 S0 ; Prevents prusaslicer from prepending m190 to the gcode interfering with the macro\nM109 S0 ; Prevents prusaslicer from prepending m109 to the gcode interfering with the macro\nSTART_PRINT EXTRUDER_TEMP=[first_layer_temperature] BED_TEMP=[first_layer_bed_temperature]\n;enable this if you have a BTT Smart Filament Sensor\n; SET_FILAMENT_SENSOR SENSOR=my_sensor ENABLE=0\n start_gcode_manual = 0 template_custom_gcode = -thumbnails = 16x16,220x220 +thumbnails = 64x64,400x300 thumbnails_color = #018aff thumbnails_custom_color = 0 thumbnails_with_bed = 1 @@ -744,4 +741,87 @@ min_fan_speed = 0 min_print_speed = 15 slowdown_below_layer_time = 10 start_filament_gcode = "; Filament gcode\nSET_GCODE_OFFSET Z=0.0\n\n{if nozzle_diameter[0]==0.4} SET_PRESSURE_ADVANCE ADVANCE=0.045{elsif nozzle_diameter[0]==0.6}SET_PRESSURE_ADVANCE ADVANCE=0.02{endif}\n\n" -temperature = 270 \ No newline at end of file +temperature = 270 + +[filament:Generic TPU @RatRig] +inherits = Generic PLA @RatRig +bed_temperature = 30 +bridge_fan_speed = 100 +bridge_internal_fan_speed = -1 +chamber_temperature = 0 +compatible_printers = +compatible_printers_condition = +compatible_prints = +compatible_prints_condition = +cooling = 1 +disable_fan_first_layers = 2 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +external_perimeter_fan_speed = -1 +extrusion_multiplier = 0.89 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF00FF +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cooling_zone_pause = 0 +filament_cost = 20 +filament_custom_variables = "" +filament_density = 1.24 +filament_deretract_speed = nil +filament_diameter = 1.75 +filament_dip_extraction_speed = 70 +filament_dip_insertion_speed = 33 +filament_enable_toolchange_part_fan = 0 +filament_enable_toolchange_temp = 0 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_overlap = 100% +filament_max_speed = 0 +filament_max_volumetric_speed = 5 +filament_max_wipe_tower_speed = 0 +filament_melt_zone_pause = 0 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "This is a profile for TPU95A. It was tested with Anycubic TPU95A, but most of the economy TPU filaments behave pretty similar.\n\nYou will want to optimize the temperature. Even different colors of the same brand can require 10° more or less.\n\nIf you experience clogs or grinding, reduce the maximum volumetric speed!" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_before_travel = nil +filament_retract_before_wipe = nil +filament_retract_layer_change = nil +filament_retract_length = 1 +filament_retract_lift = 0.1 +filament_retract_lift_above = nil +filament_retract_lift_below = nil +filament_retract_restart_extra = nil +filament_retract_speed = nil +filament_seam_gap = nil +filament_shrink = 100% +filament_skinnydip_distance = 31 +filament_soluble = 0 +filament_spool_weight = 0 +filament_toolchange_delay = 0 +filament_toolchange_part_fan_speed = 50 +filament_toolchange_temp = 200 +filament_type = FLEX +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_use_fast_skinnydip = 0 +filament_use_skinnydip = 0 +filament_vendor = Generic +filament_wipe = nil +filament_wipe_advanced_pigment = 0.5 +filament_wipe_extra_perimeter = nil +filament_wipe_only_crossing = nil +filament_wipe_speed = nil +first_layer_bed_temperature = 30 +first_layer_temperature = 220 +full_fan_speed_layer = 4 +max_fan_speed = 100 +max_speed_reduction = 95% +min_fan_speed = 100 +min_print_speed = 10 +slowdown_below_layer_time = 8 +start_filament_gcode = "; Filament gcode\nSET_GCODE_OFFSET Z=0\nSET_PRESSURE_ADVANCE ADVANCE=0.1" +temperature = 220 +top_fan_speed = -1 diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index f2847579c..3db74b8b9 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -83,6 +83,13 @@ public: // to split around. template void build(std::vector &&input) + { + this->build_modify_input(input); + input.clear(); + } + + template + void build_modify_input(std::vector &input) { if (input.empty()) clear(); @@ -91,7 +98,6 @@ public: m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node()); build_recursive(input, 0, 0, input.size() - 1); } - input.clear(); } const std::vector& nodes() const { return m_nodes; } diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index d03cb5a70..650cfca15 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -710,6 +710,8 @@ Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygon { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 0388af9a3..50d96142d 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -437,6 +437,7 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip); diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 49b02b542..183d4bf96 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -6,6 +6,66 @@ namespace Slic3r { +class InfillPolylineClipper : public FillPlanePath::InfillPolylineOutput { +public: + InfillPolylineClipper(const BoundingBox bbox, const double scale_out) : FillPlanePath::InfillPolylineOutput(scale_out), m_bbox(bbox) {} + + void add_point(const Vec2d &pt); + Points&& result() { return std::move(m_out); } + bool clips() const override { return true; } + +private: + enum class Side { + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + int sides(const Point &p) const { + return int(p.x() < m_bbox.min.x()) * int(Side::Left) + + int(p.x() > m_bbox.max.x()) * int(Side::Right) + + int(p.y() < m_bbox.min.y()) * int(Side::Bottom) + + int(p.y() > m_bbox.max.y()) * int(Side::Top); + }; + + // Bounding box to clip the polyline with. + BoundingBox m_bbox; + + // Classification of the two last points processed. + int m_sides_prev; + int m_sides_this; +}; + +void InfillPolylineClipper::add_point(const Vec2d &fpt) +{ + const Point pt{ this->scaled(fpt) }; + + if (m_out.size() < 2) { + // Collect the two first points and their status. + (m_out.empty() ? m_sides_prev : m_sides_this) = sides(pt); + m_out.emplace_back(pt); + } else { + // Classify the last inserted point, possibly remove it. + int sides_next = sides(pt); + if (// This point is inside. Take it. + m_sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (m_sides_prev & m_sides_this & sides_next) == 0) { + // Keep the last point. + m_sides_prev = m_sides_this; + } else { + // All the three points (this, prev, next) are outside at the same side. + // Ignore the last point. + m_out.pop_back(); + } + // And save the current point. + m_out.emplace_back(pt); + m_sides_this = sides_next; + } +} + void FillPlanePath::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, @@ -13,37 +73,52 @@ void FillPlanePath::_fill_surface_single( ExPolygon expolygon, Polylines &polylines_out) { - expolygon.rotate(- direction.first); + expolygon.rotate(-direction.first); - coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density); - - // align infill across layers using the object's bounding box - // Rotated bounding box of the whole object. - BoundingBox bounding_box = this->bounding_box.rotated(- direction.first); - - Point shift = this->_centered() ? + //FIXME Vojtech: We are not sure whether the user expects the fill patterns on visible surfaces to be aligned across all the islands of a single layer. + // One may align for this->centered() to align the patterns for Archimedean Chords and Octagram Spiral patterns. + const bool align = params.density < 0.995; + + BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON); + + // Rotated bounding box of the area to fill in with the pattern. + BoundingBox bounding_box = align ? + // Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box. + this->bounding_box.rotated(-direction.first) : + // Solid infill does not need to be aligned across layers, generate the infill pattern + // around the clipping expolygon only. + snug_bounding_box; + + Point shift = this->centered() ? bounding_box.center() : bounding_box.min; expolygon.translate(-shift.x(), -shift.y()); bounding_box.translate(-shift.x(), -shift.y()); - Pointfs pts = _generate( - coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)), - params.resolution); + Polyline polyline; + { + auto distance_between_lines = scaled(this->spacing) / params.density; + auto min_x = coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)); + auto min_y = coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)); + auto max_x = coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)); + auto max_y = coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)); + auto resolution = scaled(params.resolution) / distance_between_lines; + if (align) { + // Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box. + snug_bounding_box.translate(-shift.x(), -shift.y()); + InfillPolylineClipper output(snug_bounding_box, distance_between_lines); + this->generate(min_x, min_y, max_x, max_y, resolution, output); + polyline.points = std::move(output.result()); + } else { + // Filling in a snug bounding box, no need to clip. + InfillPolylineOutput output(distance_between_lines); + this->generate(min_x, min_y, max_x, max_y, resolution, output); + polyline.points = std::move(output.result()); + } + } - if (pts.size() >= 2) { - // Convert points to a polyline, upscale. - Polylines polylines(1, Polyline()); - Polyline &polyline = polylines.front(); - polyline.points.reserve(pts.size()); - for (const Vec2d &pt : pts) - polyline.points.emplace_back( - coord_t(floor(pt.x() * distance_between_lines + 0.5)), - coord_t(floor(pt.y() * distance_between_lines + 0.5))); - polylines = intersection_pl(polylines, expolygon); + if (polyline.size() >= 2) { + Polylines polylines = intersection_pl(polyline, expolygon); Polylines chained; if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1) chained = chain_polylines(std::move(polylines)); @@ -59,7 +134,8 @@ void FillPlanePath::_fill_surface_single( } // Follow an Archimedean spiral, in polar coordinates: r=a+b\theta -Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) +template +static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, Output &output) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; @@ -70,15 +146,22 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m coordf_t r = 1; Pointfs out; //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. - out.emplace_back(0, 0); - out.emplace_back(1, 0); + output.add_point({ 0, 0 }); + output.add_point({ 1, 0 }); while (r < rmax) { // Discretization angle to achieve a discretization error lower than resolution. theta += 2. * acos(1. - resolution / r); r = a + b * theta; - out.emplace_back(r * cos(theta), r * sin(theta)); + output.add_point({ r * cos(theta), r * sin(theta) }); } - return out; +} + +void FillArchimedeanChords::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) +{ + if (output.clips()) + generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, static_cast(output)); + else + generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, output); } // Adapted from @@ -126,7 +209,8 @@ static inline Point hilbert_n_to_xy(const size_t n) return Point(x, y); } -Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */) +template +static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output) { // Minimum power of two square to fit the domain. size_t sz = 2; @@ -140,46 +224,59 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, } size_t sz2 = sz * sz; - Pointfs line; - line.reserve(sz2); + output.reserve(sz2); for (size_t i = 0; i < sz2; ++ i) { Point p = hilbert_n_to_xy(i); - line.emplace_back(p.x() + min_x, p.y() + min_y); + output.add_point({ p.x() + min_x, p.y() + min_y }); } - return line; } -Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */) +void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output) +{ + if (output.clips()) + generate_hilbert_curve(min_x, min_y, max_x, max_y, static_cast(output)); + else + generate_hilbert_curve(min_x, min_y, max_x, max_y, output); +} + +template +static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; // Now unwind the spiral. coordf_t r = 0; coordf_t r_inc = sqrt(2.); - Pointfs out; - out.emplace_back(0., 0.); + output.add_point({ 0., 0. }); while (r < rmax) { r += r_inc; coordf_t rx = r / sqrt(2.); coordf_t r2 = r + rx; - out.emplace_back( r, 0.); - out.emplace_back( r2, rx); - out.emplace_back( rx, rx); - out.emplace_back( rx, r2); - out.emplace_back( 0., r); - out.emplace_back(-rx, r2); - out.emplace_back(-rx, rx); - out.emplace_back(-r2, rx); - out.emplace_back(- r, 0.); - out.emplace_back(-r2, -rx); - out.emplace_back(-rx, -rx); - out.emplace_back(-rx, -r2); - out.emplace_back( 0., -r); - out.emplace_back( rx, -r2); - out.emplace_back( rx, -rx); - out.emplace_back( r2+r_inc, -rx); + output.add_point({ r, 0. }); + output.add_point({ r2, rx }); + output.add_point({ rx, rx }); + output.add_point({ rx, r2 }); + output.add_point({ 0., r }); + output.add_point({-rx, r2 }); + output.add_point({-rx, rx }); + output.add_point({-r2, rx }); + output.add_point({- r, 0. }); + output.add_point({-r2, -rx }); + output.add_point({-rx, -rx }); + output.add_point({-rx, -r2 }); + output.add_point({ 0., -r }); + output.add_point({ rx, -r2 }); + output.add_point({ rx, -rx }); + output.add_point({ r2+r_inc, -rx }); } - return out; +} + +void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output) +{ + if (output.clips()) + generate_octagram_spiral(min_x, min_y, max_x, max_y, static_cast(output)); + else + generate_octagram_spiral(min_x, min_y, max_x, max_y, output); } } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index 075f17433..4c6539f96 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -27,8 +27,30 @@ protected: Polylines &polylines_out) override; float _layer_angle(size_t idx) const override { return 0.f; } - virtual bool _centered() const = 0; - virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0; + virtual bool centered() const = 0; + + friend class InfillPolylineClipper; + class InfillPolylineOutput { + public: + InfillPolylineOutput(const double scale_out) : m_scale_out(scale_out) {} + + void reserve(size_t n) { m_out.reserve(n); } + void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); } + Points&& result() { return std::move(m_out); } + virtual bool clips() const { return false; } + + protected: + const Point scaled(const Vec2d &fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; } + + // Output polyline. + Points m_out; + + private: + // Scaling coefficient of the generated points before tested against m_bbox and clipped by bbox. + double m_scale_out; + }; + + virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0; }; class FillArchimedeanChords : public FillPlanePath @@ -38,8 +60,8 @@ public: ~FillArchimedeanChords() override = default; protected: - bool _centered() const override { return true; } - Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override; + bool centered() const override { return true; } + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override; }; class FillHilbertCurve : public FillPlanePath @@ -49,8 +71,8 @@ public: ~FillHilbertCurve() override = default; protected: - bool _centered() const override { return false; } - Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override; + bool centered() const override { return false; } + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override; }; class FillOctagramSpiral : public FillPlanePath @@ -60,8 +82,8 @@ public: ~FillOctagramSpiral() override = default; protected: - bool _centered() const override { return true; } - Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override; + bool centered() const override { return true; } + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override; }; } // namespace Slic3r diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e44dfb742..e2bd6ef65 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -9,6 +9,7 @@ #include "GCode/WipeTower.hpp" #include "ShortestPath.hpp" #include "Print.hpp" +#include "Thread.hpp" #include "Utils.hpp" #include "ClipperUtils.hpp" #include "libslic3r.h" @@ -34,8 +35,6 @@ #include "SVG.hpp" #include -#include -#include // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. @@ -1469,32 +1468,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } -// For unknown reasons and in sporadic cases when GCode export is processing, some participating thread -// in tbb::parallel_pipeline has not set locales to "C", probably because this thread is newly spawned. -// So in this class method on_scheduler_entry is called for every thread before it starts participating -// in tbb::parallel_pipeline to ensure that locales are set correctly - -// For tbb::parallel_pipeline, it seems that on_scheduler_entry is called for every layer and every filter. -// We ensure using thread-local storage that locales will be set to "C" just once for any participating thread. -class TBBLocalesSetter : public tbb::task_scheduler_observer -{ -public: - TBBLocalesSetter() { this->observe(true); } - ~TBBLocalesSetter() override { this->observe(false); }; - - void on_scheduler_entry(bool is_worker) override - { - if (bool &is_locales_sets = m_is_locales_sets.local(); !is_locales_sets) { - // Set locales of the worker thread to "C". - set_c_locales(); - is_locales_sets = true; - } - } - -private: - tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets{false}; -}; - // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3c773c89a..c82fd38e5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -199,9 +199,7 @@ void GCodeProcessor::TimeMachine::reset() max_travel_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; -#if ENABLE_TRAVEL_TIME travel_time = 0.0f; -#endif // ENABLE_TRAVEL_TIME stop_times = std::vector(); curr.reset(); prev.reset(); @@ -317,17 +315,12 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa block_time += additional_time; time += block_time; -#if ENABLE_TRAVEL_TIME if (block.move_type == EMoveType::Travel) travel_time += block_time; else roles_time[static_cast(block.role)] += block_time; -#endif // ENABLE_TRAVEL_TIME gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; -#if !ENABLE_TRAVEL_TIME - roles_time[static_cast(block.role)] += block_time; -#endif // !ENABLE_TRAVEL_TIME if (block.layer_id >= layers_time.size()) { const size_t curr_size = layers_time.size(); layers_time.resize(block.layer_id); @@ -1465,7 +1458,6 @@ std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mod return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } -#if ENABLE_TRAVEL_TIME float GCodeProcessor::get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const { return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].travel_time : 0.0f; @@ -1475,7 +1467,6 @@ std::string GCodeProcessor::get_travel_time_dhm(PrintEstimatedStatistics::ETimeM { return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].travel_time)) : std::string("N/A"); } -#endif // ENABLE_TRAVEL_TIME std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const { @@ -1769,7 +1760,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool switch (cmd[1]) { case '1': switch (cmd[2]) { - case '0': { process_G10(line); break; } // Retract + case '0': { process_G10(line); break; } // Retract or Set tool temperature case '1': { process_G11(line); break; } // Unretract default: break; } @@ -3232,6 +3223,23 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) { + if (m_flavor == gcfRepRapFirmware) { + // similar to M104/M109 + float new_temp; + if (line.has_value('S', new_temp)) { + size_t id = m_extruder_id; + float val; + if (line.has_value('P', val)) { + const size_t eid = static_cast(val); + if (eid < m_extruder_temps.size()) + id = eid; + } + + m_extruder_temps[id] = new_temp; + return; + } + } + // stores retract move store_move_vertex(EMoveType::Retract); } @@ -3441,18 +3449,22 @@ void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) { float new_temp; + size_t id = (size_t)-1; if (line.has_value('R', new_temp)) { float val; if (line.has_value('T', val)) { const size_t eid = static_cast(val); if (eid < m_extruder_temps.size()) - m_extruder_temps[eid] = new_temp; + id = eid; } else - m_extruder_temps[m_extruder_id] = new_temp; + id = m_extruder_id; } else if (line.has_value('S', new_temp)) - m_extruder_temps[m_extruder_id] = new_temp; + id = m_extruder_id; + + if (id != (size_t)-1) + m_extruder_temps[id] = new_temp; } void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) @@ -4306,9 +4318,7 @@ void GCodeProcessor::update_estimated_times_stats() auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); -#if ENABLE_TRAVEL_TIME data.travel_time = get_travel_time(mode); -#endif // ENABLE_TRAVEL_TIME data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); data.roles_times = get_roles_time(mode); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 484f41616..37c0eda41 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -44,9 +44,7 @@ namespace Slic3r { struct Mode { float time; -#if ENABLE_TRAVEL_TIME float travel_time; -#endif // ENABLE_TRAVEL_TIME std::vector>> custom_gcode_times; std::vector> moves_times; std::vector> roles_times; @@ -54,9 +52,7 @@ namespace Slic3r { void reset() { time = 0.0f; -#if ENABLE_TRAVEL_TIME travel_time = 0.0f; -#endif // ENABLE_TRAVEL_TIME custom_gcode_times.clear(); moves_times.clear(); roles_times.clear(); @@ -307,9 +303,7 @@ namespace Slic3r { float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s -#if ENABLE_TRAVEL_TIME float travel_time; // s -#endif // ENABLE_TRAVEL_TIME struct StopTime { unsigned int g1_line_id; @@ -635,10 +629,8 @@ namespace Slic3r { float get_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; -#if ENABLE_TRAVEL_TIME float get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; -#endif // ENABLE_TRAVEL_TIME std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; @@ -674,7 +666,7 @@ namespace Slic3r { void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); #endif // ENABLE_PROCESS_G2_G3_LINES - // Retract + // Retract or Set tool temperature void process_G10(const GCodeReader::GCodeLine& line); // Unretract diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 79c54748a..270ccb1be 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1619,7 +1619,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) size_t counter = 1; for (TriangleMesh &mesh : meshes) { // FIXME: crashes if not satisfied - if (mesh.facets_count() < 3) + if (mesh.facets_count() < 3 || mesh.has_zero_volume()) continue; // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? @@ -2093,7 +2093,7 @@ size_t ModelVolume::split(unsigned int max_extruders) const Vec3d offset = this->get_offset(); for (TriangleMesh &mesh : meshes) { - if (mesh.empty()) + if (mesh.empty() || mesh.has_zero_volume()) // Repair may have removed unconnected triangles, thus emptying the mesh. continue; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f4b69bb81..7ea55a3a4 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2198,8 +2198,7 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { if (m_config.support_material_style == smsTree) { - TreeSupport tree_support; - tree_support.generateSupportAreas(*this); + fff_tree_support_generate(*this, std::function([this](){ this->throw_if_canceled(); })); } else { PrintObjectSupportMaterial support_material(this, m_slicing_params); support_material.generate(*this); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ff514cc02..ad23b4cbe 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -39,14 +39,6 @@ //==================== #define ENABLE_2_5_0_ALPHA1 1 -// Enable changes in preview layout -#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_5_0_ALPHA1) -// Enable drawing the items in legend toolbar using icons -#define ENABLE_LEGEND_TOOLBAR_ICONS (1 && ENABLE_PREVIEW_LAYOUT) -// Enable coloring of toolpaths in preview by layer time -#define ENABLE_PREVIEW_LAYER_TIME (1 && ENABLE_2_5_0_ALPHA1) -// Enable showing time estimate for travel moves in legend -#define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) // Enable removal of wipe tower magic object_id equal to 1000 #define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable removal of legacy OpenGL calls @@ -61,8 +53,6 @@ #define ENABLE_GLMODEL_STATISTICS (0 && ENABLE_LEGACY_OPENGL_REMOVAL) // Enable rework of Reload from disk command #define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1) -// Enable recalculating toolpaths when switching to/from volumetric rate visualization -#define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable modified camera control using mouse diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 4fe07fb59..22d4cb419 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -4,6 +4,9 @@ #else // any posix system #include + #ifdef __APPLE__ + #include + #endif // __APPLE__ #endif #include @@ -241,4 +244,26 @@ void name_tbb_thread_pool_threads_set_locale() }); } +void set_current_thread_qos() +{ +#ifdef __APPLE__ + // OSX specific: Set Quality of Service to "user initiated", so that the threads will be scheduled to high performance + // cores if available. + pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); +#endif // __APPLE__ +} + +void TBBLocalesSetter::on_scheduler_entry(bool is_worker) +{ +// static std::atomic cnt = 0; +// std::cout << "TBBLocalesSetter Entering " << cnt ++ << " ID " << std::this_thread::get_id() << "\n"; + if (bool& is_locales_sets = m_is_locales_sets.local(); !is_locales_sets) { + // Set locales of the worker thread to "C". + set_c_locales(); + // OSX specific: Elevate QOS on Apple Silicon. + set_current_thread_qos(); + is_locales_sets = true; + } +} + } diff --git a/src/libslic3r/Thread.hpp b/src/libslic3r/Thread.hpp index 9afe13b42..252116ea7 100644 --- a/src/libslic3r/Thread.hpp +++ b/src/libslic3r/Thread.hpp @@ -6,6 +6,9 @@ #include #include +#include +#include + namespace Slic3r { // Set / get thread name. @@ -26,6 +29,10 @@ inline bool set_thread_name(boost::thread &thread, const std::string &thread_nam bool set_current_thread_name(const char *thread_name); inline bool set_current_thread_name(const std::string &thread_name) { return set_current_thread_name(thread_name.c_str()); } +// OSX specific: Set Quality of Service to "user initiated", so that the threads will be scheduled to high performance +// cores if available. +void set_current_thread_qos(); + // Returns nullopt if not supported. // Not supported by OSX. // Naming threads is only supported on newer Windows 10. @@ -53,6 +60,25 @@ template inline boost::thread create_thread(Fn &&fn) return create_thread(attrs, std::forward(fn)); } +// For unknown reasons and in sporadic cases when GCode export is processing, some participating thread +// in tbb::parallel_pipeline has not set locales to "C", probably because this thread is newly spawned. +// So in this class method on_scheduler_entry is called for every thread before it starts participating +// in tbb::parallel_pipeline to ensure that locales are set correctly +// +// For tbb::parallel_pipeline, it seems that on_scheduler_entry is called for every layer and every filter. +// We ensure using thread-local storage that locales will be set to "C" just once for any participating thread. +class TBBLocalesSetter : public tbb::task_scheduler_observer +{ +public: + TBBLocalesSetter() { this->observe(true); } + ~TBBLocalesSetter() override { this->observe(false); }; + + void on_scheduler_entry(bool is_worker) override; + +private: + tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets{ false }; +}; + } #endif // GUI_THREAD_HPP diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index aa968d996..ad27e7871 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -23,7 +23,7 @@ #include #include -namespace Slic3r +namespace Slic3r::FFFTreeSupport { // or warning @@ -106,7 +106,10 @@ TreeModelVolumes::TreeModelVolumes( const PrintObject &print_object, const BuildVolume &build_volume, const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx, - double progress_multiplier, double progress_offset, const std::vector& additional_excluded_areas) : +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + double progress_multiplier, double progress_offset, +#endif // SLIC3R_TREESUPPORTS_PROGRESS + const std::vector& additional_excluded_areas) : // -2 to avoid rounding errors m_max_move{ std::max(max_move - 2, 0) }, m_max_move_slow{ std::max(max_move_slow - 2, 0) }, #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -161,7 +164,7 @@ TreeModelVolumes::TreeModelVolumes( m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution); } - const TreeSupport::TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first }; + const TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first }; m_current_min_xy_dist = config.xy_min_distance; m_current_min_xy_dist_delta = config.xy_distance - m_current_min_xy_dist; assert(m_current_min_xy_dist_delta >= 0); @@ -206,7 +209,7 @@ void TreeModelVolumes::precalculate(const coord_t max_layer) // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. // Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex // like inital layer diameter are only done in once. - TreeSupport::TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); + TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); { // calculate which radius each layer in the tip may have. @@ -297,7 +300,7 @@ const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerI return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Collision requested.", false); + tree_supports_show_error("Not precalculated Collision requested.", false); } const_cast(this)->calculateCollision(radius, layer_idx); return getCollision(orig_radius, layer_idx, min_xy_dist); @@ -312,7 +315,7 @@ const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerInde return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Holefree Collision requested.", false); + tree_supports_show_error("Not precalculated Holefree Collision requested.", false); } const_cast(this)->calculateCollisionHolefree({ radius, layer_idx }); return getCollisionHolefree(radius, layer_idx); @@ -336,10 +339,10 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI if (m_precalculated) { if (to_model) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to model) requested.", false); } else { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); + tree_supports_show_error("Not precalculated Avoidance(to buildplate) requested.", false); } } const_cast(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model); @@ -357,7 +360,7 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError("Not precalculated Placeable areas requested.", false); + tree_supports_show_error("Not precalculated Placeable areas requested.", false); } const_cast(this)->calculatePlaceables(radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx); @@ -380,7 +383,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, return (*result).get(); if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; - TreeSupport::showError( + tree_supports_show_error( min_xy_dist ? "Not precalculated Wall restriction of minimum xy distance requested )." : "Not precalculated Wall restriction requested )." @@ -774,4 +777,4 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const return out; } -} +} // namespace Slic3r::FFFTreeSupport diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index f87204cf5..eea271bd4 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -22,11 +22,14 @@ namespace Slic3r { -using LayerIndex = int; - class BuildVolume; class PrintObject; +namespace FFFTreeSupport +{ + +using LayerIndex = int; + struct TreeSupportMeshGroupSettings { TreeSupportMeshGroupSettings() = default; explicit TreeSupportMeshGroupSettings(const PrintObject &print_object); @@ -194,15 +197,19 @@ class TreeModelVolumes public: TreeModelVolumes() = default; explicit TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume, - coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, double progress_multiplier, - double progress_offset, const std::vector &additional_excluded_areas = {}); + coord_t max_move, coord_t max_move_slow, size_t current_mesh_idx, +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + double progress_multiplier, + double progress_offset, +#endif // SLIC3R_TREESUPPORTS_PROGRESS + const std::vector &additional_excluded_areas = {}); TreeModelVolumes(TreeModelVolumes&&) = default; TreeModelVolumes& operator=(TreeModelVolumes&&) = default; TreeModelVolumes(const TreeModelVolumes&) = delete; TreeModelVolumes& operator=(const TreeModelVolumes&) = delete; - enum class AvoidanceType + enum class AvoidanceType : int8_t { Slow, FastSafe, @@ -605,6 +612,7 @@ private: #endif // SLIC3R_TREESUPPORTS_PROGRESS }; -} +} // namespace FFFTreeSupport +} // namespace Slic3r #endif //slic3r_TreeModelVolumes_hpp diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 56ccfd7d1..c408b2f85 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -7,6 +7,7 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeSupport.hpp" +#include "AABBTreeIndirect.hpp" #include "BuildVolume.hpp" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" @@ -38,6 +39,9 @@ namespace Slic3r { +namespace FFFTreeSupport +{ + enum class LineStatus { INVALID, @@ -165,24 +169,24 @@ static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Po static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); -static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) +static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) { - std::vector>> grouped_meshes; + std::vector>> grouped_meshes; //FIXME this is ugly, it does not belong here. - for (size_t object_id = 0; object_id < print_object_ids.size(); ++ object_id) { + for (size_t object_id : print_object_ids) { const PrintObject &print_object = *print.get_object(object_id); const PrintObjectConfig &object_config = print_object.config(); if (object_config.support_material_contact_distance < EPSILON) // || min_feature_size < scaled(0.1) that is the minimum line width - TreeSupport::TreeSupportSettings::soluble = true; + TreeSupportSettings::soluble = true; } size_t largest_printed_mesh_idx = 0; // Group all meshes that can be processed together. NOTE this is different from mesh-groups! Only one setting object is needed per group, // as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. - for (size_t object_id = 0; object_id < print_object_ids.size(); ++ object_id) { + for (size_t object_id : print_object_ids) { const PrintObject &print_object = *print.get_object(object_id); #ifndef NDEBUG const PrintObjectConfig &object_config = print_object.config(); @@ -192,7 +196,7 @@ static std::vector &overhangs, const TreeSupport::TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) +[[nodiscard]] static LayerIndex precalculate(const Print &print, const std::vector &overhangs, const TreeSupportSettings &config, const std::vector &object_ids, TreeModelVolumes &volumes) { // calculate top most layer that is relevant for support LayerIndex max_layer = 0; @@ -349,179 +354,6 @@ void TreeSupport::showError(std::string message, bool critical) return max_layer; } -//FIXME this is an ugly wrapper interface for a single print object and a phony build volume. -void TreeSupport::generateSupportAreas(PrintObject& print_object) -{ - size_t idx = 0; - for (PrintObject *po : print_object.print()->objects()) { - if (po == &print_object) - break; - ++ idx; - } - this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }); -} - -void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids) -{ - g_showed_critical_error = false; - g_showed_performance_warning = false; - - std::vector>> grouped_meshes = group_meshes(print, print_object_ids); - if (grouped_meshes.empty()) - return; - - size_t counter = 0; - - // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. - for (std::pair> &processing : grouped_meshes) - { - // process each combination of meshes - // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generateInitialAreas have knowledge of the existence of multiple meshes being processed. - //FIXME this is a copy - m_config = processing.first; - BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; - auto t_start = std::chrono::high_resolution_clock::now(); -#if 0 - std::vector exclude(num_support_layers); - // get all already existing support areas and exclude them - tbb::parallel_for(tbb::blocked_range(0, num_support_layers), - [&](const tbb::blocked_range &range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - Polygons exlude_at_layer; - append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); - append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); - for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) - append(exlude_at_layer, part.outline); - exclude[layer_idx] = union_(exlude_at_layer); - } - }); -#endif -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - m_progress_multiplier = 1.0 / double(m_grouped_meshes.size()); - m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); -#endif // SLIC3R_TREESUPPORT_PROGRESS - PrintObject &print_object = *print.get_object(processing.second.front()); - m_volumes = TreeModelVolumes(print_object, build_volume, m_config.maximum_move_distance, m_config.maximum_move_distance_slow, processing.second.front(), m_progress_multiplier, m_progress_offset, /* additional_excluded_areas */{}); - - //FIXME generating overhangs just for the furst mesh of the group. - assert(processing.second.size() == 1); - std::vector overhangs = generate_overhangs(*print.get_object(processing.second.front())); - - // ### Precalculate avoidances, collision etc. - size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, m_volumes); - if (num_support_layers == 0) - continue; - - auto t_precalc = std::chrono::high_resolution_clock::now(); - - // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas - std::vector> move_bounds(num_support_layers); - - // ### Place tips of the support tree - SupportGeneratorLayersPtr bottom_contacts(num_support_layers, nullptr); - SupportGeneratorLayersPtr top_contacts(num_support_layers, nullptr); - SupportGeneratorLayersPtr top_interface_layers(num_support_layers, nullptr); - SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); - SupportGeneratorLayerStorage layer_storage; - - for (size_t mesh_idx : processing.second) - generateInitialAreas(*print.get_object(mesh_idx), overhangs, move_bounds, top_contacts, top_interface_layers, layer_storage); - auto t_gen = std::chrono::high_resolution_clock::now(); - - // ### Propagate the influence areas downwards. - createLayerPathing(move_bounds); - auto t_path = std::chrono::high_resolution_clock::now(); - - // ### Set a point in each influence area - createNodesFromArea(move_bounds); - auto t_place = std::chrono::high_resolution_clock::now(); - - // ### draw these points as circles - drawAreas(*print.get_object(processing.second.front()), overhangs, move_bounds, - bottom_contacts, top_contacts, intermediate_layers, layer_storage); - - auto t_draw = std::chrono::high_resolution_clock::now(); - auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); - auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); - auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); - auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); - auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); - auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); - BOOST_LOG_TRIVIAL(info) << - "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " - "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " - "Creating inital influence areas: " << dur_gen << " ms " - "Influence area creation: " << dur_path << "ms " - "Placement of Points in InfluenceAreas: " << dur_place << "ms " - "Drawing result as support " << dur_draw << " ms"; -// if (m_config.branch_radius==2121) -// BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; - - for (auto &layer : move_bounds) { - for (auto elem : layer) { - delete elem->area; - delete elem; - } - } - - auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { - layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); - }; - remove_undefined_layers(bottom_contacts); - remove_undefined_layers(top_contacts); - remove_undefined_layers(intermediate_layers); - - // Produce the support G-code. - // Used by both classic and tree supports. - SupportParameters support_params(print_object); - support_params.with_sheath = true; - support_params.support_density = 0; - SupportGeneratorLayersPtr interface_layers, base_interface_layers; - SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); -#if 1 //#ifdef SLIC3R_DEBUG - SupportGeneratorLayersPtr layers_sorted = -#endif // SLIC3R_DEBUG - generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - // Don't fill in the tree supports, make them hollow with just a single sheath line. - generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), - raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); - - #if 0 -//#ifdef SLIC3R_DEBUG - { - static int iRun = 0; - ++ iRun; - size_t layer_id = 0; - for (int i = 0; i < int(layers_sorted.size());) { - // Find the last layer with roughly the same print_z, find the minimum layer height of all. - // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; - coordf_t zmax = layers_sorted[i]->print_z + EPSILON; - bool empty = layers_sorted[i]->polygons.empty(); - for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) - if (!layers_sorted[j]->polygons.empty()) - empty = false; - if (!empty) { - export_print_z_polygons_to_svg( - debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i); - export_print_z_polygons_and_extrusions_to_svg( - debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), - layers_sorted.data() + i, j - i, - *print_object.support_layers()[layer_id]); - ++layer_id; - } - i = j; - } - } -#endif /* SLIC3R_DEBUG */ - - ++ counter; - } - -// storage.support.generated = true; -} - /*! * \brief Converts a Polygons object representing a line into the internal format. * @@ -529,9 +361,9 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \param layer_idx[in] The current layer. * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. */ -// Called by TreeSupport::generateInitialAreas() -[[nodiscard]] static LineInformations convertLinesToInternal( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, +// Called by generate_initial_areas() +[[nodiscard]] static LineInformations convert_lines_to_internal( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const Polylines &polylines, LayerIndex layer_idx) { const bool min_xy_dist = config.xy_distance > config.xy_min_distance; @@ -572,7 +404,7 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \param lines[in] The lines that will be converted. * \return All lines of the \p lines object as a Polygons object. */ -[[nodiscard]] static Polylines convertInternalToLines(LineInformations lines) +[[nodiscard]] static Polylines convert_internal_to_lines(LineInformations lines) { Polylines result; for (LineInformation line : lines) { @@ -586,16 +418,16 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo } /*! - * \brief Evaluates if a point has to be added now. Required for a splitLines call in generateInitialAreas. + * \brief Evaluates if a point has to be added now. Required for a split_lines call in generate_initial_areas(). * * \param current_layer[in] The layer on which the point lies, point and its status. * \return whether the point is valid. */ -[[nodiscard]] static bool evaluatePointForNextLayerFunction( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, - size_t current_layer, std::pair &p) +[[nodiscard]] static bool evaluate_point_for_next_layer_function( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, + size_t current_layer, const std::pair &p) { - using AvoidanceType = TreeSupport::AvoidanceType; + using AvoidanceType = TreeModelVolumes::AvoidanceType; const bool min_xy_dist = config.xy_distance > config.xy_min_distance; if (! contains(volumes.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FastSafe : AvoidanceType::Fast, false, min_xy_dist), p.first)) return true; @@ -616,47 +448,25 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo * \return A pair with which points are still valid in the first slot and which are not in the second slot. */ template -[[nodiscard]] static std::pair splitLines(LineInformations lines, EvaluatePointFn evaluatePoint) +[[nodiscard]] static std::pair split_lines(const LineInformations &lines, EvaluatePointFn evaluatePoint) { // assumes all Points on the current line are valid - LineInformations keep(1); - LineInformations set_free(1); - enum STATE - { - keeping, - freeing - }; - for (std::vector> line : lines) { - STATE current = keeping; + LineInformations keep; + LineInformations set_free; + for (const std::vector> &line : lines) { + bool current_keep = true; LineInformation resulting_line; - for (std::pair me : line) { - if (evaluatePoint(me)) { - if (keeping != current) { - if (!resulting_line.empty()) { - set_free.emplace_back(resulting_line); - resulting_line.clear(); - } - current = keeping; - } - resulting_line.emplace_back(me); - } else { - if (freeing != current) { - if (!resulting_line.empty()) { - keep.emplace_back(resulting_line); - resulting_line.clear(); - } - current = freeing; - } - resulting_line.emplace_back(me); + for (const std::pair &me : line) { + if (evaluatePoint(me) != current_keep) { + if (! resulting_line.empty()) + (current_keep ? &keep : &set_free)->emplace_back(std::move(resulting_line)); + current_keep = !current_keep; } + resulting_line.emplace_back(me); } - if (!resulting_line.empty()) { - if (current == keeping) - keep.emplace_back(resulting_line); - else - set_free.emplace_back(resulting_line); - } + if (! resulting_line.empty()) + (current_keep ? &keep : &set_free)->emplace_back(std::move(resulting_line)); } validate_range(keep); validate_range(set_free); @@ -720,7 +530,7 @@ static std::optional> polyline_sample_next_point_at_dis * \param min_points[in] The amount of points that have to be placed. If not enough can be placed the distance will be reduced to place this many points. * \return A Polygons object containing the evenly spaced points. Does not represent an area, more a collection of points on lines. */ -[[nodiscard]] static Polylines ensureMaximumDistancePolyline(const Polylines &input, double distance, size_t min_points) +[[nodiscard]] static Polylines ensure_maximum_distance_polyline(const Polylines &input, double distance, size_t min_points) { Polylines result; for (Polyline part : input) { @@ -792,7 +602,7 @@ static std::optional> polyline_sample_next_point_at_dis // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... BOOST_LOG_TRIVIAL(warning) << "Tree Support: Encountered a fixpoint in polyline_sample_next_point_at_distance. This is expected to happen if the distance (currently " << next_distance << ") is smaller than 100"; - TreeSupport::showError("Encountered issue while placing tips. Some tips may be missing.", true); + tree_supports_show_error("Encountered issue while placing tips. Some tips may be missing.", true); if (next_distance > 2 * current_distance) // This case should never happen, but better safe than sorry. break; @@ -824,7 +634,7 @@ static std::optional> polyline_sample_next_point_at_dis * * \return A Polygons object that represents the resulting infill lines. */ -[[nodiscard]] static Polylines generateSupportInfillLines( +[[nodiscard]] static Polylines generate_support_infill_lines( const Polygons &polygon, const SupportParameters &support_params, bool roof, LayerIndex layer_idx, coord_t support_infill_distance) { @@ -919,7 +729,7 @@ static std::optional> polyline_sample_next_point_at_dis * \param second[in] The second Polygon. * \return The union of both Polygons */ -[[nodiscard]] static Polygons safeUnion(const Polygons first, const Polygons second = Polygons()) +[[nodiscard]] static Polygons safe_union(const Polygons first, const Polygons second = {}) { // unionPolygons can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed @@ -962,10 +772,10 @@ static std::optional> polyline_sample_next_point_at_dis * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get very small. Required as arcTolerance is not exposed in offset, which should result with a similar result. * \return The resulting Polygons object. */ -[[nodiscard]] static Polygons safeOffsetInc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) +[[nodiscard]] static Polygons safe_offset_inc(const Polygons& me, coord_t distance, const Polygons& collision, coord_t safe_step_size, coord_t last_step_offset_without_check, size_t min_amount_offset) { bool do_final_difference = last_step_offset_without_check == 0; - Polygons ret = safeUnion(me); // ensure sane input + Polygons ret = safe_union(me); // ensure sane input // Trim the collision polygons with the region of interest for diff() efficiency. Polygons collision_trimmed_buffer; @@ -979,7 +789,7 @@ static std::optional> polyline_sample_next_point_at_dis return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); if (safe_step_size < 0 || last_step_offset_without_check < 0) { BOOST_LOG_TRIVIAL(error) << "Offset increase got invalid parameter!"; - TreeSupport::showError("Negative offset distance... How did you manage this ?", true); + tree_supports_show_error("Negative offset distance... How did you manage this ?", true); return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret); } @@ -1058,14 +868,27 @@ inline SupportGeneratorLayer& layer_allocate( return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx); } -void TreeSupport::generateInitialAreas( - const PrintObject &print_object, - const std::vector &overhangs, - std::vector> &move_bounds, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, - SupportGeneratorLayerStorage &layer_storage) +using SupportElements = std::deque; +/*! + * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. + * + * Generates Points where the Model should be supported and creates the areas where these points have to be placed. + * + * \param mesh[in] The mesh that is currently processed. + * \param move_bounds[out] Storage for the influence areas. + * \param storage[in] Background storage, required for adding roofs. + */ +static void generate_initial_areas( + const PrintObject &print_object, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + const std::vector &overhangs, + std::vector &move_bounds, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &top_interface_layers, + SupportGeneratorLayerStorage &layer_storage) { + using AvoidanceType = TreeModelVolumes::AvoidanceType; static constexpr const auto base_radius = scaled(0.01); const Polygon base_circle = make_circle(base_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); TreeSupportMeshGroupSettings mesh_group_settings(print_object); @@ -1088,7 +911,9 @@ void TreeSupport::generateInitialAreas( // This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. // As a circle is round this length is identical for every axis as long as the 90 degrees angle between both remains. - const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? mesh_config.min_radius / 2 : sqrt(sqr(mesh_config.min_radius) - sqr(mesh_config.min_radius - mesh_config.support_line_width / 2)); + const coord_t circle_length_to_half_linewidth_change = mesh_config.min_radius < mesh_config.support_line_width ? + mesh_config.min_radius / 2 : + sqrt(sqr(mesh_config.min_radius) - sqr(mesh_config.min_radius - mesh_config.support_line_width / 2)); // Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. //FIXME Vojtech: This is not sufficient for support enforcers to work. //FIXME There is no account for the support overhang angle. @@ -1097,12 +922,18 @@ void TreeSupport::generateInitialAreas( //FIXME this is a heuristic value for support enforcers to work. // + 10 * mesh_config.support_line_width; ; - const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; - const bool roof_enabled = support_roof_layers != 0; - const bool force_tip_to_roof = sqr(mesh_config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; + const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0; + const bool roof_enabled = support_roof_layers != 0; + const bool force_tip_to_roof = sqr(mesh_config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled; //FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width). - const coord_t max_overhang_speed = (mesh_group_settings.support_angle < 0.5 * M_PI) ? (coord_t)(tan(mesh_group_settings.support_angle) * mesh_config.layer_height) : std::numeric_limits::max(); - const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. + //used by max_overhang_insert_lag, only if not min_xy_dist. + const coord_t max_overhang_speed = mesh_group_settings.support_angle < 0.5 * M_PI ? coord_t(tan(mesh_group_settings.support_angle) * mesh_config.layer_height) : std::numeric_limits::max(); + // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point + // may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang + // does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. + // The 2*z_distance_delta is only a catch for when the support angle is very high. + // Used only if not min_xy_dist. + const coord_t max_overhang_insert_lag = std::max(round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); //FIXME size_t num_support_layers = print_object.layer_count(); @@ -1110,7 +941,7 @@ void TreeSupport::generateInitialAreas( std::mutex mutex_layer_storage, mutex_movebounds; tbb::parallel_for(tbb::blocked_range(1, num_support_layers - z_distance_delta), - [this, &print_object, &overhangs, &mesh_config, &mesh_group_settings, &support_params, + [&print_object, &volumes, &config, &overhangs, &mesh_config, &mesh_group_settings, &support_params, z_distance_delta, min_xy_dist, force_tip_to_roof, roof_enabled, support_roof_layers, extra_outset, circle_length_to_half_linewidth_change, connect_length, max_overhang_insert_lag, &base_circle, &mutex_layer_storage, &mutex_movebounds, &top_contacts, &layer_storage, &already_inserted, &move_bounds](const tbb::blocked_range &range) { @@ -1120,48 +951,66 @@ void TreeSupport::generateInitialAreas( // take the least restrictive avoidance possible Polygons relevant_forbidden; { - const Polygons &relevant_forbidden_raw = (mesh_config.support_rests_on_model ? - (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, true, min_xy_dist) : - m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, min_xy_dist)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist)); + const Polygons &relevant_forbidden_raw = mesh_config.support_rests_on_model ? + volumes.getCollision(mesh_config.getRadius(0), layer_idx, min_xy_dist) : + volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::Fast, false, min_xy_dist); // prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. relevant_forbidden = offset(union_ex(relevant_forbidden_raw), scaled(0.005), jtMiter, 1.2); } auto generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) -> Polylines { const coord_t support_infill_distance = roof ? mesh_group_settings.support_roof_line_distance : mesh_group_settings.support_tree_branch_distance; - return generateSupportInfillLines(area, support_params, roof, layer_idx, support_infill_distance); - }; - - auto addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) - { - bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; - bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (!mesh_config.support_rests_on_model && !to_bp) { - BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; - TreeSupport::showError("Unable to add tip. Some overhang may not be supported correctly.", true); - return; - } - Polygon circle; - for (Point corner : base_circle) - circle.points.emplace_back(p.first + corner); - { - std::lock_guard critical_section_movebounds(mutex_movebounds); - if (! already_inserted[insert_layer].count(p.first / ((mesh_config.min_radius + 1) / 10))) { - // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing - already_inserted[insert_layer].emplace(p.first / ((mesh_config.min_radius + 1) / 10)); - SupportElement* elem = new SupportElement(dtt, insert_layer, p.first, to_bp, gracious, min_xy_dist, dont_move_until, roof, safe_radius, force_tip_to_roof, skip_ovalisation); - elem->area = new Polygons(); - validate_range(circle); - elem->area->emplace_back(std::move(circle)); - move_bounds[insert_layer].emplace(elem); - } - } + return generate_support_infill_lines(area, support_params, roof, layer_idx, support_infill_distance); }; + // roof_tip_layers = force_tip_to_roof ? support_roof_layers - dtt_roof : 0 + // insert_layer_idx = layer_idx - dtt_roof + // supports_roof = dtt_roof > 0 + // dont_move_until = roof_enabled ? support_roof_layers - dtt_roof : 0 auto addLinesAsInfluenceAreas = [&](LineInformations lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until) { + auto addPointAsInfluenceArea = [&](std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) + { + bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; + bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; + if (!mesh_config.support_rests_on_model && !to_bp) { + BOOST_LOG_TRIVIAL(warning) << "Tried to add an invalid support point"; + tree_supports_show_error("Unable to add tip. Some overhang may not be supported correctly.", true); + return; + } + Polygons circle{ base_circle }; + circle.front().translate(p.first); + { + std::lock_guard critical_section_movebounds(mutex_movebounds); + Point hash_pos = p.first / ((mesh_config.min_radius + 1) / 10); + if (! already_inserted[insert_layer].count(hash_pos)) { + // normalize the point a bit to also catch points which are so close that inserting it would achieve nothing + already_inserted[insert_layer].emplace(hash_pos); + SupportElementState state; + state.target_height = insert_layer; + state.target_position = p.first; + state.next_position = p.first; + state.layer_idx = insert_layer; + state.effective_radius_height = dtt; + state.to_buildplate = to_bp; + state.distance_to_top = dtt; + state.result_on_layer = p.first; + assert(state.result_on_layer_is_set()); + state.increased_to_model_radius = 0; + state.to_model_gracious = gracious; + state.elephant_foot_increases = 0; + state.use_min_xy_dist = min_xy_dist; + state.supports_roof = roof; + state.dont_move_until = dont_move_until; + state.can_use_safe_radius = safe_radius; + state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0; + state.skip_ovalisation = skip_ovalisation; + move_bounds[insert_layer].emplace_back(state, std::move(circle)); + } + } + }; + validate_range(lines); // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible size_t dtt_roof_tip; @@ -1173,36 +1022,37 @@ void TreeSupport::generateInitialAreas( Polygon roof_circle; for (Point corner : base_circle) roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius); - return !generateSupportInfillLines({ roof_circle }, mesh_config, true, insert_layer_idx - dtt_roof_tip, mesh_config.support_roof_line_distance).empty(); + return !generate_support_infill_lines({ roof_circle }, mesh_config, true, insert_layer_idx - dtt_roof_tip, mesh_config.support_roof_line_distance).empty(); #else return true; #endif }; - std::pair split = - // keep all lines that are still valid on the next layer - splitLines(lines, [this, insert_layer_idx, dtt_roof_tip](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, insert_layer_idx - dtt_roof_tip, p); }); - - for (LineInformation line : split.second) // add all points that would not be valid - for (std::pair point_data : line) - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); - - // not all roofs are guaranteed to actually generate lines, so filter these out and add them as points - split = splitLines(split.first, evaluateRoofWillGenerate); - lines = split.first; - - for (LineInformation line : split.second) - for (std::pair point_data : line) - addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + { + std::pair split = + // keep all lines that are still valid on the next layer + split_lines(lines, [&volumes, &config, insert_layer_idx, dtt_roof_tip](const std::pair &p) + { return evaluate_point_for_next_layer_function(volumes, config, insert_layer_idx - dtt_roof_tip, p); }); + LineInformations points = std::move(split.second); + // Not all roofs are guaranteed to actually generate lines, so filter these out and add them as points. + split = split_lines(split.first, evaluateRoofWillGenerate); + lines = std::move(split.first); + append(points, split.second); + // add all points that would not be valid + for (const LineInformation &line : points) + for (const std::pair &point_data : line) + addPointAsInfluenceArea(point_data, 0, insert_layer_idx - dtt_roof_tip, roof_tip_layers - dtt_roof_tip, dtt_roof_tip != 0, false); + } // add all tips as roof to the roof storage Polygons added_roofs; - for (LineInformation line : lines) - for (std::pair p : line) { - Polygon roof_circle; - for (Point corner : base_circle) - roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius / base_radius); - added_roofs.emplace_back(roof_circle); + for (const LineInformation &line : lines) + //FIXME sweep the tip radius along the line? + for (const std::pair &p : line) { + Polygon roof_circle{ base_circle }; + roof_circle.scale(mesh_config.min_radius / base_radius); + roof_circle.translate(p.first); + added_roofs.emplace_back(std::move(roof_circle)); } if (! added_roofs.empty()) { added_roofs = union_(added_roofs); @@ -1231,7 +1081,7 @@ void TreeSupport::generateInitialAreas( const Polygons &overhang_raw = overhangs[layer_idx + z_distance_delta]; overhang_regular = mesh_group_settings.support_offset == 0 ? overhang_raw : - safeOffsetInc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); + safe_offset_inc(overhang_raw, mesh_group_settings.support_offset, relevant_forbidden, mesh_config.min_radius * 1.75 + mesh_config.xy_min_distance, 0, 1); // offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang Polygons remaining_overhang = intersection( diff(mesh_group_settings.support_offset == 0 ? @@ -1251,13 +1101,13 @@ void TreeSupport::generateInitialAreas( circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); extra_total_offset_acc += offset_current_step; - const Polygons &raw_collision = m_volumes.getCollision(0, layer_idx, true); + const Polygons &raw_collision = volumes.getCollision(0, layer_idx, true); const coord_t offset_step = mesh_config.xy_min_distance + mesh_config.support_line_width; // Reducing the remaining overhang by the areas already supported. //FIXME 1.5 * extra_total_offset_acc seems to be too much, it may remove some remaining overhang without being supported at all. - remaining_overhang = diff(remaining_overhang, safeOffsetInc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); + remaining_overhang = diff(remaining_overhang, safe_offset_inc(overhang_regular, 1.5 * extra_total_offset_acc, raw_collision, offset_step, 0, 1)); // Extending the overhangs by the inflated remaining overhangs. - overhang_regular = union_(overhang_regular, diff(safeOffsetInc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); + overhang_regular = union_(overhang_regular, diff(safe_offset_inc(remaining_overhang, extra_total_offset_acc, raw_collision, offset_step, 0, 1), relevant_forbidden)); } // If the xy distance overrides the z distance, some support needs to be inserted further down. //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. ) @@ -1270,10 +1120,10 @@ void TreeSupport::generateInitialAreas( // mbut not only is this the only reasonable choice, but it ensures consistent behavior as some infill patterns generate // each line segment as its own polyline part causing a similar line forming behavior. Also it is assumed that // the area that is valid a layer below is to small for support roof. - Polylines polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); + Polylines polylines = ensure_maximum_distance_polyline(generateLines(remaining_overhang, false, layer_idx), mesh_config.min_radius, 1); if (polylines.size() <= 3) // add the outer wall to ensure it is correct supported instead - polylines = ensureMaximumDistancePolyline(to_polylines(remaining_overhang), connect_length, 3); + polylines = ensure_maximum_distance_polyline(to_polylines(remaining_overhang), connect_length, 3); for (const auto &line : polylines) { LineInformation res_line; for (Point p : line) @@ -1284,13 +1134,16 @@ void TreeSupport::generateInitialAreas( } for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) { // get least restricted avoidance for layer_idx-lag_ctr - const Polygons &relevant_forbidden_below = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, true, min_xy_dist) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, min_xy_dist)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist)); + const Polygons &relevant_forbidden_below = mesh_config.support_rests_on_model ? + volumes.getCollision(mesh_config.getRadius(0), layer_idx - lag_ctr, min_xy_dist) : + volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - lag_ctr, AvoidanceType::Fast, false, min_xy_dist); // it is not required to offset the forbidden area here as the points wont change: If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. auto evaluatePoint = [&](std::pair p) { return contains(relevant_forbidden_below, p.first); }; - std::pair split = splitLines(overhang_lines, evaluatePoint); // keep all lines that are invalid + std::pair split = split_lines(overhang_lines, evaluatePoint); // keep all lines that are invalid overhang_lines = split.first; - LineInformations fresh_valid_points = convertLinesToInternal(m_volumes, m_config, convertInternalToLines(split.second), layer_idx - lag_ctr); // set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + // Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + LineInformations fresh_valid_points = convert_lines_to_internal(volumes, config, convert_internal_to_lines(split.second), layer_idx - lag_ctr); validate_range(fresh_valid_points); addLinesAsInfluenceAreas(fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, roof_enabled ? support_roof_layers : 0); @@ -1302,7 +1155,7 @@ void TreeSupport::generateInitialAreas( std::vector> overhang_processing; if (roof_enabled) { static constexpr const coord_t support_roof_offset = 0; - overhang_roofs = safeOffsetInc(overhangs[layer_idx + z_distance_delta], support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); + overhang_roofs = safe_offset_inc(overhangs[layer_idx + z_distance_delta], support_roof_offset, relevant_forbidden, mesh_config.min_radius * 2 + mesh_config.xy_min_distance, 0, 1); if (mesh_group_settings.minimum_support_area > 0) remove_small(overhang_roofs, mesh_group_settings.minimum_roof_area); overhang_regular = diff(overhang_regular, overhang_roofs, ApplySafetyOffset::Yes); @@ -1339,23 +1192,24 @@ void TreeSupport::generateInitialAreas( Polygons forbidden_next; { const Polygons &forbidden_next_raw = mesh_config.support_rests_on_model ? - (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, true, min_xy_dist) : - m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist)) : - m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, min_xy_dist); + volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), min_xy_dist) : + volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::Fast, false, min_xy_dist); // prevent rounding errors down the line + //FIXME maybe use SafetyOffset::Yes at the following diff() instead? forbidden_next = offset(union_ex(forbidden_next_raw), scaled(0.005), jtMiter, 1.2); } Polygons overhang_outset_next = diff(overhang_outset, forbidden_next); if (area(overhang_outset_next) < mesh_group_settings.minimum_roof_area) { // next layer down the roof area would be to small so we have to insert our roof support here. Also convert squaremicrons to squaremilimeter - size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; if (dtt_roof != 0) { + size_t dtt_before = dtt_roof > 0 ? dtt_roof - 1 : 0; // Produce support head points supporting an interface layer: First produce the interface lines, then sample them. - overhang_lines = convertLinesToInternal(m_volumes, m_config, - ensureMaximumDistancePolyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); - overhang_lines = splitLines(overhang_lines, - [this, layer_idx, dtt_before](std::pair &p){ return evaluatePointForNextLayerFunction(m_volumes, m_config, layer_idx - dtt_before, p); }).first; + overhang_lines = convert_lines_to_internal(volumes, config, + ensure_maximum_distance_polyline(generateLines(last_overhang, true, layer_idx - dtt_before), connect_length, 1), layer_idx - dtt_before); + overhang_lines = split_lines(overhang_lines, + [&volumes, &config, layer_idx, dtt_before](const std::pair &p) + { return evaluate_point_for_next_layer_function(volumes, config, layer_idx - dtt_before, p); }) + .first; } break; } @@ -1384,7 +1238,7 @@ void TreeSupport::generateInitialAreas( SupportGeneratorLayer *&l = top_contacts[layer_idx - idx]; if (l == nullptr) l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx - idx); - // will be unioned in finalizeInterfaceAndSupportAreas + // will be unioned in finalize_interface_and_support_areas() append(l->polygons, std::move(added_roofs[idx])); } } @@ -1393,7 +1247,8 @@ void TreeSupport::generateInitialAreas( // support_line_width to form a line here as otherwise most will be unsupported. Technically this violates branch distance, but not only is this the only reasonable choice, // but it ensures consistant behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. // This is not doen when a roof is above as the roof will support the model and the trees only need to support the roof - Polylines polylines = ensureMaximumDistancePolyline(generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); + Polylines polylines = ensure_maximum_distance_polyline( + generateLines(overhang_outset, dtt_roof != 0, layer_idx - layer_generation_dtt), dtt_roof == 0 ? mesh_config.min_radius / 2 : connect_length, 1); size_t point_count = 0; for (const Polyline &poly : polylines) point_count += poly.size(); @@ -1403,14 +1258,16 @@ void TreeSupport::generateInitialAreas( // will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, // as some support is better than none. Polygons reduced_overhang_outset = offset(union_ex(overhang_outset), -mesh_config.support_line_width / 2.2, jtMiter, 1.2); - polylines = ensureMaximumDistancePolyline( - to_polylines(!reduced_overhang_outset.empty() && area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? + polylines = ensure_maximum_distance_polyline( + to_polylines( + ! reduced_overhang_outset.empty() && + area(offset(diff_ex(overhang_outset, reduced_overhang_outset), std::max(mesh_config.support_line_width, connect_length), jtMiter, 1.2)) < sqr(scaled(0.001)) ? reduced_overhang_outset : overhang_outset), connect_length, min_support_points); } LayerIndex last_insert_layer = layer_idx - dtt_roof; - overhang_lines = convertLinesToInternal(m_volumes, m_config, polylines, last_insert_layer); + overhang_lines = convert_lines_to_internal(volumes, config, polylines, last_insert_layer); } if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part && ! overhang_outset.empty()) { @@ -1427,7 +1284,7 @@ void TreeSupport::generateInitialAreas( }); } -static unsigned int moveInside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) +static unsigned int move_inside(const Polygons &polygons, Point &from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) { Point ret = from; double bestDist2 = std::numeric_limits::max(); @@ -1530,321 +1387,90 @@ static unsigned int moveInside(const Polygons &polygons, Point &from, int distan return -1; } -/*! - * \brief Merges Influence Areas if possible. - * - * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. - * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. - * - * \param reduced_aabb[in,out] The already processed elements. - * \param input_aabb[in] Not yet processed elements - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The influence areas without avoidance removed. - * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. - * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. - * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) - * \param erase[out] Elements that should be deleted from the above dictionaries. - * \param layer_idx[in] The Index of the current Layer. - */ -static void mergeHelper( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, - std::map& reduced_aabb, std::map& input_aabb, - const std::unordered_map& to_bp_areas, const std::unordered_map& to_model_areas, - const std::map& influence_areas, - std::unordered_map& insert_bp_areas, std::unordered_map& insert_model_areas, - std::unordered_map& insert_influence, std::vector& erase, const LayerIndex layer_idx) +static Point move_inside_if_outside(const Polygons &polygons, Point from, int distance = 0, int64_t maxDist2 = std::numeric_limits::max()) { - using SupportElement = TreeSupport::SupportElement; - - const bool first_merge_iteration = reduced_aabb.empty(); // If this is the first iteration, all elements in input have to be merged with each other - for (std::map::iterator influence_iter = input_aabb.begin(); influence_iter != input_aabb.end(); influence_iter++) - { - bool merged = false; - BoundingBox influence_aabb = influence_iter->second; - for (std::map::iterator reduced_check_iter = reduced_aabb.begin(); reduced_check_iter != reduced_aabb.end(); reduced_check_iter++) - { - // As every area has to be checked for overlaps with other areas, some fast heuristic is needed to abort early if clearly possible - // This is so performance critical that using a map lookup instead of the direct access of the cached AABBs can have a surprisingly large performance impact - BoundingBox aabb = reduced_check_iter->second; - if (aabb.overlap(influence_aabb)) { - if (!first_merge_iteration && input_aabb.count(reduced_check_iter->first)) - break; // Do not try to merge elements that already should have been merged. Done for potential performance improvement. - - bool merging_gracious_and_non_gracious = reduced_check_iter->first.to_model_gracious != influence_iter->first.to_model_gracious; // we do not want to merge a gracious with a non gracious area as bad placement could negatively impact the dependability of the whole subtree - bool merging_to_bp = reduced_check_iter->first.to_buildplate && influence_iter->first.to_buildplate; - bool merging_min_and_regular_xy = reduced_check_iter->first.use_min_xy_dist != influence_iter->first.use_min_xy_dist; // could cause some issues with the increase of one area, as it is assumed that if the smaller is increased by the delta to the larger it is engulfed by it already. But because a different collision may be removed from the in drawArea generated circles, this assumption could be wrong. - coord_t increased_to_model_radius = 0; - size_t larger_to_model_dtt = 0; - - if (!merging_to_bp) { - coord_t infl_radius = config.getRadius(influence_iter->first); // get the real radius increase as the user does not care for the collision model. - coord_t redu_radius = config.getRadius(reduced_check_iter->first); - if (reduced_check_iter->first.to_buildplate != influence_iter->first.to_buildplate) { - if (reduced_check_iter->first.to_buildplate) { - if (infl_radius < redu_radius) - increased_to_model_radius = influence_iter->first.increased_to_model_radius + redu_radius - infl_radius; - } else { - if (infl_radius > redu_radius) - increased_to_model_radius = reduced_check_iter->first.increased_to_model_radius + infl_radius - redu_radius; - } - } - larger_to_model_dtt = std::max(influence_iter->first.distance_to_top, reduced_check_iter->first.distance_to_top); - } - - // if a merge could place a stable branch on unstable ground, would be increasing the radius further than allowed to when merging to model and to_bp trees or would merge to model before it is known they will even been drawn the merge is skipped - if (merging_min_and_regular_xy || merging_gracious_and_non_gracious || increased_to_model_radius > config.max_to_model_radius_increase || (!merging_to_bp && larger_to_model_dtt < config.min_dtt_to_model && !reduced_check_iter->first.supports_roof && !influence_iter->first.supports_roof)) - continue; - - Polygons relevant_infl; - Polygons relevant_redu; - if (merging_to_bp) { - relevant_infl = to_bp_areas.count(influence_iter->first) ? to_bp_areas.at(influence_iter->first) : Polygons(); // influence_iter->first is a new element => not required to check if it was changed - relevant_redu = insert_bp_areas.count(reduced_check_iter->first) ? insert_bp_areas[reduced_check_iter->first] : (to_bp_areas.count(reduced_check_iter->first) ? to_bp_areas.at(reduced_check_iter->first) : Polygons()); - } else { - relevant_infl = to_model_areas.count(influence_iter->first) ? to_model_areas.at(influence_iter->first) : Polygons(); - relevant_redu = insert_model_areas.count(reduced_check_iter->first) ? insert_model_areas[reduced_check_iter->first] : (to_model_areas.count(reduced_check_iter->first) ? to_model_areas.at(reduced_check_iter->first) : Polygons()); - } - - const bool red_bigger = config.getCollisionRadius(reduced_check_iter->first) > config.getCollisionRadius(influence_iter->first); - std::pair smaller_rad = red_bigger ? std::pair(influence_iter->first, relevant_infl) : std::pair(reduced_check_iter->first, relevant_redu); - std::pair bigger_rad = red_bigger ? std::pair(reduced_check_iter->first, relevant_redu) : std::pair(influence_iter->first, relevant_infl); - const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.first) - config.getRadius(smaller_rad.first)); - const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.first); - - // the area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, so if that would change an invalid area may be created - if (!bigger_rad.first.can_use_safe_radius && smaller_rad.first.can_use_safe_radius) - continue; - - // the bigger radius is used to verify that the area is still valid after the increase with the delta. If there were a point where the big influence area could be valid with can_use_safe_radius the element would already be can_use_safe_radius - // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. - bool use_min_radius = bigger_rad.first.use_min_xy_dist && smaller_rad.first.use_min_xy_dist; - - // The idea is that the influence area with the smaller collision radius is increased by the radius difference. - // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. - // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. - // Remember that collision radius <= real radius as otherwise this assumption would be false. - Polygons small_rad_increased_by_big_minus_small = safeOffsetInc(smaller_rad.second, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); // -3 avoids possible rounding errors - Polygons intersect = intersection(small_rad_increased_by_big_minus_small, bigger_rad.second); - - if (area(intersect) > tiny_area_threshold) { // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) - if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. - continue; - - // Do the actual merge now that the branches are confirmed to be able to intersect. - - // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) - // used at the end to estimate where to best place the branch on the bottom most layer - // could be replaced with a random point inside the new area - Point new_pos = reduced_check_iter->first.next_position; - if (! contains(intersect, new_pos)) - moveInside(intersect, new_pos); - - if (increased_to_model_radius == 0) - increased_to_model_radius = std::max(reduced_check_iter->first.increased_to_model_radius, influence_iter->first.increased_to_model_radius); - - SupportElement key(reduced_check_iter->first, influence_iter->first, layer_idx - 1, new_pos, increased_to_model_radius, config); - - Polygons intersect_influence; - Polygons infl_small = insert_influence.count(smaller_rad.first) ? insert_influence[smaller_rad.first] : (influence_areas.count(smaller_rad.first) ? influence_areas.at(smaller_rad.first) : Polygons()); - Polygons infl_big = insert_influence.count(bigger_rad.first) ? insert_influence[bigger_rad.first] : (influence_areas.count(bigger_rad.first) ? influence_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_infl = safeOffsetInc(infl_small, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); - intersect_influence = intersection(small_rad_increased_by_big_minus_small_infl, infl_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge - intersect_influence = safeUnion(intersect_influence, intersect); // Rounding errors again. Do not ask me where or why. - - Polygons intersect_sec; - if (merging_to_bp && config.support_rests_on_model) { - if (key.to_model_gracious) { - Polygons sec_small = insert_model_areas.count(smaller_rad.first) ? insert_model_areas[smaller_rad.first] : (to_model_areas.count(smaller_rad.first) ? to_model_areas.at(smaller_rad.first) : Polygons()); - Polygons sec_big = insert_model_areas.count(bigger_rad.first) ? insert_model_areas[bigger_rad.first] : (to_model_areas.count(bigger_rad.first) ? to_model_areas.at(bigger_rad.first) : Polygons()); - Polygons small_rad_increased_by_big_minus_small_sec = safeOffsetInc(sec_small, real_radius_delta, volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0); - intersect_sec = intersection(small_rad_increased_by_big_minus_small_sec, sec_big); // if the one with the bigger radius with the lower radius removed overlaps we can merge - intersect_influence = safeUnion(intersect_influence, intersect_sec); // still rounding errors - } else - intersect_sec = intersect_influence; - } - - // remove the now merged elements from all buckets, as they do not exist anymore in their old form - insert_bp_areas.erase(reduced_check_iter->first); - insert_bp_areas.erase(influence_iter->first); - insert_model_areas.erase(reduced_check_iter->first); - insert_model_areas.erase(influence_iter->first); - insert_influence.erase(reduced_check_iter->first); - insert_influence.erase(influence_iter->first); - - (merging_to_bp ? insert_bp_areas : insert_model_areas).emplace(key, intersect); - if (merging_to_bp && config.support_rests_on_model) - insert_model_areas.emplace(key, intersect_sec); - insert_influence.emplace(key, intersect_influence); - - erase.emplace_back(reduced_check_iter->first); - erase.emplace_back(influence_iter->first); - Polygons merge = diff_clipped(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled(0.01)), volumes.getCollision(0, layer_idx - 1, false)); // regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. - - reduced_aabb.erase(reduced_check_iter->first); // this invalidates reduced_check_iter - reduced_aabb.emplace(key, get_extents(merge)); - - merged = true; - break; - } - } - } - - if (!merged) - reduced_aabb[influence_iter->first] = influence_aabb; - } + if (! contains(polygons, from)) + move_inside(polygons, from); + return from; } /*! - * \brief Merges Influence Areas if possible. + * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. * - * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. + * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. + * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. * - * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. - * Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. - * Value is the influence area where the center of a circle of support may be placed. - * \param layer_idx[in] The current layer. + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * + * \param settings[in] Which settings have to be used to check validity. + * \param layer_idx[in] Number of the current layer. + * \param parent[in] The metadata of the parents influence area. + * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! + * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. + * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. + * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. + * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. */ -static void mergeInfluenceAreas( - const TreeModelVolumes &volumes, const TreeSupport::TreeSupportSettings &config, - std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, LayerIndex layer_idx) +[[nodiscard]] static std::optional increase_single_area( + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + const AreaIncreaseSettings &settings, + const LayerIndex layer_idx, + const SupportElement &parent, + const Polygons &relevant_offset, + Polygons &to_bp_data, + Polygons &to_model_data, + Polygons &increased, + const coord_t overspeed, + const bool mergelayer) { - using SupportElement = TreeSupport::SupportElement; - /* - * Idea behind this is that the calculation of merges can be accelerated a bit using divide and conquer: - * If two groups of areas are already merged, only all elements in group 2 have to be merged into group one. - * This can only accelerate by factor 2 (as half the work is merging the last two groups). - * The actual merge logic is found in mergeHelper. This function only manages parallelization of different mergeHelper calls. - */ - - const size_t input_size = influence_areas.size(); - if (input_size == 0) - return; - - size_t num_threads = std::max(size_t(1), size_t(std::thread::hardware_concurrency())); // For some reason hardware concurrency can return 0; - constexpr int min_elements_per_bucket = 2; - - // max_bucket_count is input_size/min_elements_per_bucket round down to the next 2^n. - // The rounding to 2^n is to ensure improved performance, as every iteration two buckets will be merged, halving the amount of buckets. - // If halving would cause an uneven count, e.g. 3 Then bucket 0 and 1 would have to be merged, and in the next iteration the last remaining buckets. This is assumed to not be optimal performance-wise. - const size_t max_bucket_count = std::pow(2, std::floor(std::log(round_up_divide(input_size, min_elements_per_bucket)))); - int bucket_count = std::min(max_bucket_count, num_threads); // do not use more buckets than available threads. - - // To achieve that every element in a bucket is already correctly merged with other elements in this bucket - // an extra empty bucket is created for each bucket, and the elements are merged into the empty one. - // Each thread will then process two buckets by merging all elements in the second bucket into the first one as mergeHelper will disable not trying to merge elements from the same bucket in this case. - std::vector> buckets_area(2 * bucket_count); - std::vector> buckets_aabb(2 * bucket_count); - - size_t position = 0, counter = 0; - const size_t over_elements = input_size % bucket_count; - const size_t elements_per_step = input_size / bucket_count; - - // split the data in x parts to be able to divide and conquer - // the first "over_elements" of buckets gets elements_per_step+1 elements - for (std::map::iterator iter = influence_areas.begin(); iter != influence_areas.end(); ++ iter) { - buckets_area[position * 2 + 1].emplace(iter->first, iter->second); // only use every second bucket beginning with 1 as this makes the parallel call later easier as we assume everything in a bucket i%2==0 is already processed - ++ counter; - if ((counter == elements_per_step && position >= over_elements) || counter > elements_per_step) { - position++; - counter = 0; - } - } - - // precalculate the AABBs from the influence areas. - tbb::parallel_for(tbb::blocked_range(0, bucket_count), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - // +=2 as in the beginning only uneven buckets will be filled - size_t bucket_idx = 2 * idx + 1; - for (const std::pair& input_pair : buckets_area[bucket_idx]) - buckets_aabb[bucket_idx].emplace(input_pair.first, get_extents(input_pair.second).inflated(config.getRadius(input_pair.first))); - } - }); - - while (buckets_area.size() > 1) { - // Some temporary storage, of elements that have to be inserted or removed from the background storage. Only one per two buckets required - std::vector> insert_main(buckets_area.size() / 2); - std::vector> insert_secondary(buckets_area.size() / 2); - std::vector> insert_influence(buckets_area.size() / 2); - std::vector> erase(buckets_area.size() / 2); - - tbb::parallel_for(tbb::blocked_range(0, buckets_area.size() / 2), - [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - const size_t bucket_pair_idx = idx * 2; - // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets - mergeHelper(volumes, config, buckets_aabb[bucket_pair_idx], buckets_aabb[bucket_pair_idx + 1], to_bp_areas, to_model_areas, influence_areas, insert_main[bucket_pair_idx / 2], insert_secondary[bucket_pair_idx / 2], insert_influence[bucket_pair_idx / 2], erase[bucket_pair_idx / 2], layer_idx); - // clear now irrelevant max_bucket_count, and delete them later - buckets_area[bucket_pair_idx + 1].clear(); - buckets_aabb[bucket_pair_idx + 1].clear(); - } - }); - - for (size_t i = 0; i + 1 < buckets_area.size(); i += 2) { - for (SupportElement &del : erase[i / 2]) { - to_bp_areas.erase(del); - to_model_areas.erase(del); - influence_areas.erase(del); - } - for (const std::pair &tup : insert_main[i / 2]) - to_bp_areas.emplace(std::move(tup)); - for (const std::pair &tup : insert_secondary[i / 2]) - to_model_areas.emplace(std::move(tup)); - for (const std::pair &tup : insert_influence[i / 2]) - influence_areas.emplace(std::move(tup)); - } - - buckets_area.erase(std::remove_if(buckets_area.begin(), buckets_area.end(), [&](const std::map &x) { return x.empty(); }), buckets_area.end()); - buckets_aabb.erase(std::remove_if(buckets_aabb.begin(), buckets_aabb.end(), [&](const std::map &x) { return x.empty(); }), buckets_aabb.end()); - } -} - - -std::optional TreeSupport::increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer) -{ - SupportElement current_elem(parent); // also increases DTT by one + SupportElementState current_elem{ SupportElementState::propagate_down(parent.state) }; Polygons check_layer_data; if (settings.increase_radius) current_elem.effective_radius_height += 1; - coord_t radius = m_config.getCollisionRadius(current_elem); + coord_t radius = config.getCollisionRadius(current_elem); if (settings.move) { increased = relevant_offset; if (overspeed > 0) { - const coord_t safe_movement_distance = (current_elem.use_min_xy_dist ? m_config.xy_min_distance : m_config.xy_distance) + (std::min(m_config.z_distance_top_layers, m_config.z_distance_bottom_layers) > 0 ? m_config.min_feature_size : 0); - // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = safeOffsetInc(increased, overspeed, m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), safe_movement_distance, safe_movement_distance + radius, 1); + const coord_t safe_movement_distance = + (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); + // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. + // The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. + increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(config.getCollisionRadius(parent.state), layer_idx, parent.state.use_min_xy_dist), + safe_movement_distance, safe_movement_distance + radius, 1); } if (settings.no_error && settings.move) // as ClipperLib::jtRound has to be used for offsets this simplify is VERY important for performance. polygons_simplify(increased, scaled(0.025)); } else // if no movement is done the areas keep parent area as no move == offset(0) - increased = *parent->area; + increased = parent.influence_area; if (mergelayer || current_elem.to_buildplate) { - to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); if (! current_elem.to_buildplate && area(to_bp_data) > tiny_area_threshold) { // mostly happening in the tip, but with merges one should check every time, just to be sure. current_elem.to_buildplate = true; // sometimes nodes that can reach the buildplate are marked as cant reach, tainting subtrees. This corrects it. - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong to model value on layer " << layer_idx - 1 << " targeting " << + current_elem.target_height << " with radius " << radius; } } - if (m_config.support_rests_on_model) { + if (config.support_rests_on_model) { if (mergelayer || current_elem.to_model_gracious) - to_model_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); + to_model_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance))); if (!current_elem.to_model_gracious) { if (mergelayer && area(to_model_data) >= tiny_area_threshold) { current_elem.to_model_gracious = true; - BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << current_elem.target_height << " with radius " << radius; + BOOST_LOG_TRIVIAL(debug) << "Corrected taint leading to a wrong non gracious value on layer " << layer_idx - 1 << " targeting " << + current_elem.target_height << " with radius " << radius; } else - to_model_data = safeUnion(diff_clipped(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); + to_model_data = safe_union(diff_clipped(increased, volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance))); } } @@ -1852,83 +1478,163 @@ std::optional TreeSupport::increaseSingleArea(AreaI if (settings.increase_radius && area(check_layer_data) > tiny_area_threshold) { auto validWithRadius = [&](coord_t next_radius) { - if (m_volumes.ceilRadius(next_radius, settings.use_min_distance) <= m_volumes.ceilRadius(radius, settings.use_min_distance)) + if (volumes.ceilRadius(next_radius, settings.use_min_distance) <= volumes.ceilRadius(radius, settings.use_min_distance)) return true; Polygons to_bp_data_2; if (current_elem.to_buildplate) - to_bp_data_2 = diff_clipped(increased, m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); // regular union as output will not be used later => this area should always be a subset of the safeUnion one (i think) + // regular union as output will not be used later => this area should always be a subset of the safe_union one (i think) + to_bp_data_2 = diff_clipped(increased, volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)); Polygons to_model_data_2; - if (m_config.support_rests_on_model && !current_elem.to_buildplate) + if (config.support_rests_on_model && !current_elem.to_buildplate) to_model_data_2 = diff_clipped(increased, current_elem.to_model_gracious ? - m_volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : - m_volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); + volumes.getAvoidance(next_radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : + volumes.getCollision(next_radius, layer_idx - 1, settings.use_min_distance)); Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; return area(check_layer_data_2) > tiny_area_threshold; }; - coord_t ceil_radius_before = m_volumes.ceilRadius(radius, settings.use_min_distance); + coord_t ceil_radius_before = volumes.ceilRadius(radius, settings.use_min_distance); - if (m_config.getCollisionRadius(current_elem) < m_config.increase_radius_until_radius && m_config.getCollisionRadius(current_elem) < m_config.getRadius(current_elem)) { - coord_t target_radius = std::min(m_config.getRadius(current_elem), m_config.increase_radius_until_radius); - coord_t current_ceil_radius = m_volumes.getRadiusNextCeil(radius, settings.use_min_distance); + if (config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) { + coord_t target_radius = std::min(config.getRadius(current_elem), config.increase_radius_until_radius); + coord_t current_ceil_radius = volumes.getRadiusNextCeil(radius, settings.use_min_distance); - while (current_ceil_radius < target_radius && validWithRadius(m_volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) - current_ceil_radius = m_volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); + while (current_ceil_radius < target_radius && validWithRadius(volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance))) + current_ceil_radius = volumes.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); size_t resulting_eff_dtt = current_elem.effective_radius_height; - while (resulting_eff_dtt + 1 < current_elem.distance_to_top && m_config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && m_config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= m_config.getRadius(current_elem)) - resulting_eff_dtt++; + while (resulting_eff_dtt + 1 < current_elem.distance_to_top && + config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= current_ceil_radius && + config.getRadius(resulting_eff_dtt + 1, current_elem.elephant_foot_increases) <= config.getRadius(current_elem)) + ++ resulting_eff_dtt; current_elem.effective_radius_height = resulting_eff_dtt; } - radius = m_config.getCollisionRadius(current_elem); + radius = config.getCollisionRadius(current_elem); - const coord_t foot_radius_increase = m_config.branch_radius * (std::max(m_config.diameter_scale_bp_radius - m_config.diameter_angle_scale_factor, 0.0)); - // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. - double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); + const coord_t foot_radius_increase = config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)); + // Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, + // which could cause the radius to become bigger than precalculated. + double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - config.getRadius(current_elem)) / foot_radius_increase); //FIXME bool increase_bp_foot = planned_foot_increase > 0 && current_elem.to_buildplate; // bool increase_bp_foot = false; - if (increase_bp_foot && m_config.getRadius(current_elem) >= m_config.branch_radius && m_config.getRadius(current_elem) >= m_config.increase_radius_until_radius) - if (validWithRadius(m_config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { + if (increase_bp_foot && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) + if (validWithRadius(config.getRadius(current_elem.effective_radius_height, current_elem.elephant_foot_increases + planned_foot_increase))) { current_elem.elephant_foot_increases += planned_foot_increase; - radius = m_config.getCollisionRadius(current_elem); + radius = config.getCollisionRadius(current_elem); } - if (ceil_radius_before != m_volumes.ceilRadius(radius, settings.use_min_distance)) { + if (ceil_radius_before != volumes.ceilRadius(radius, settings.use_min_distance)) { if (current_elem.to_buildplate) - to_bp_data = safeUnion(diff_clipped(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); - if (m_config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) - to_model_data = safeUnion(diff_clipped(increased, + to_bp_data = safe_union(diff_clipped(increased, volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); + if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) + to_model_data = safe_union(diff_clipped(increased, current_elem.to_model_gracious ? - m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : - m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) + volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance) : + volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance) )); check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; if (area(check_layer_data) < tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << m_volumes.ceilRadius(m_config.getCollisionRadius(current_elem), settings.use_min_distance); - TreeSupport::showError("Area lost catching up radius. May not cause visible malformation.", true); + BOOST_LOG_TRIVIAL(error) << "Lost area by doing catch up from " << ceil_radius_before << " to radius " << + volumes.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance); + tree_supports_show_error("Area lost catching up radius. May not cause visible malformation.", true); } } } - return area(check_layer_data) > tiny_area_threshold ? std::optional(current_elem) : std::optional(); + return area(check_layer_data) > tiny_area_threshold ? std::optional(current_elem) : std::optional(); } -void TreeSupport::increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer) +struct SupportElementInfluenceAreas { + // All influence areas: both to build plate and model. + Polygons influence_areas; + // Influence areas just to build plate. + Polygons to_bp_areas; + // Influence areas just to model. + Polygons to_model_areas; + + void clear() { + this->influence_areas.clear(); + this->to_bp_areas.clear(); + this->to_model_areas.clear(); + } +}; + +struct SupportElementMerging { + SupportElementState state; + /*! + * \brief All elements in the layer above the current one that are supported by this element + */ + SupportElement::ParentIndices parents; + + SupportElementInfluenceAreas areas; + // Bounding box of all influence areas. + Eigen::AlignedBox bbox_data; + + const Eigen::AlignedBox& bbox() const { return bbox_data;} + const Point centroid() const { return (bbox_data.min() + bbox_data.max()) / 2; } + void set_bbox(const BoundingBox& abbox) + { Point eps { coord_t(SCALED_EPSILON), coord_t(SCALED_EPSILON) }; bbox_data = { abbox.min - eps, abbox.max + eps }; } + + // Called by the AABBTree builder to get an index into the vector of source elements. + // Not needed, thus zero is returned. + static size_t idx() { return 0; } +}; + +// #define TREESUPPORT_DEBUG_SVG + +/*! + * \brief Increases influence areas as far as required. + * + * Calculates influence areas of the layer below, based on the influence areas of the current layer. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. + * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. + * + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * + * \param to_bp_areas[out] Influence areas that can reach the buildplate + * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. + * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. + * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. + * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. + * \param last_layer[in] Influence areas of the current layer. + * \param layer_idx[in] Number of the current layer. + * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + */ +static void increase_areas_one_layer( + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + // New areas at the layer below layer_idx + std::vector &merging_areas, + // Layer above merging_areas. + const LayerIndex layer_idx, + // Layer elements above merging_areas. + SupportElements &layer_elements, + // If false, the merging_areas will not be merged for performance reasons. + const bool mergelayer) { - std::mutex critical_sections; - tbb::parallel_for(tbb::blocked_range(0, last_layer.size()), + using AvoidanceType = TreeModelVolumes::AvoidanceType; + + tbb::parallel_for(tbb::blocked_range(0, merging_areas.size()), [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - SupportElement* parent = last_layer[idx]; + for (size_t merging_area_idx = range.begin(); merging_area_idx < range.end(); ++ merging_area_idx) { + SupportElementMerging &merging_area = merging_areas[merging_area_idx]; + assert(merging_area.parents.size() == 1); + SupportElement &parent = layer_elements[merging_area.parents.front()]; + SupportElementState elem = SupportElementState::propagate_down(parent.state); + const Polygons &wall_restriction = + // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. + volumes.getWallRestriction(config.getCollisionRadius(parent.state), layer_idx, parent.state.use_min_xy_dist); - SupportElement elem(parent); // also increases dtt - - const Polygons &wall_restriction = m_volumes.getWallRestriction(m_config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist); // Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall. +#ifdef TREESUPPORT_DEBUG_SVG + SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-%d-%ld.svg", layer_idx, int(merging_area_idx)), + { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(parent.influence_area) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif // TREESUPPORT_DEBUG_SVG Polygons to_bp_data, to_model_data; - coord_t radius = m_config.getCollisionRadius(elem); + coord_t radius = config.getCollisionRadius(elem); // When the radius increases, the outer "support wall" of the branch will have been moved farther away from the center (as this is the definition of radius). // As it is not specified that the support_tree_angle has to be one of the center of the branch, it is here seen as the smaller angle of the outer wall of the branch, to the outer wall of the same branch one layer above. @@ -1937,9 +1643,9 @@ void TreeSupport::increaseAreas(std::unordered_map& to coord_t extra_speed = 5; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. coord_t extra_slow_speed = 0; // Only added to the slow movement distance. - const coord_t ceiled_parent_radius = m_volumes.ceilRadius(m_config.getCollisionRadius(*parent), parent->use_min_xy_dist); - coord_t projected_radius_increased = m_config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases); - coord_t projected_radius_delta = projected_radius_increased - m_config.getCollisionRadius(*parent); + const coord_t ceiled_parent_radius = volumes.ceilRadius(config.getCollisionRadius(parent.state), parent.state.use_min_xy_dist); + coord_t projected_radius_increased = config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases); + coord_t projected_radius_delta = projected_radius_increased - config.getCollisionRadius(parent.state); // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): /* @@ -1948,24 +1654,30 @@ void TreeSupport::increaseAreas(std::unordered_map& to * layer z-1:dddddxxxxxxxxxx * For more detailed visualisation see calculateWallRestrictions */ - const coord_t safe_movement_distance = (elem.use_min_xy_dist ? m_config.xy_min_distance : m_config.xy_distance) + (std::min(m_config.z_distance_top_layers, m_config.z_distance_bottom_layers) > 0 ? m_config.min_feature_size : 0); - if (ceiled_parent_radius == m_volumes.ceilRadius(projected_radius_increased, parent->use_min_xy_dist) || projected_radius_increased < m_config.increase_radius_until_radius) + const coord_t safe_movement_distance = + (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); + if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) || + projected_radius_increased < config.increase_radius_until_radius) // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall extra_speed += projected_radius_delta; else // if a guaranteed radius increase is not possible, only increase the slow speed - extra_slow_speed += std::min(projected_radius_delta, (m_config.maximum_move_distance + extra_speed) - (m_config.maximum_move_distance_slow + extra_slow_speed)); // Ensure that the slow movement distance can not become larger than the fast one. + // Ensure that the slow movement distance can not become larger than the fast one. + extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); - if (m_config.layer_start_bp_radius > layer_idx && m_config.recommendedMinRadius(layer_idx - 1) < m_config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { + if (config.layer_start_bp_radius > layer_idx && + config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.elephant_foot_increases)) { // can guarantee elephant foot radius increase - if (ceiled_parent_radius == m_volumes.ceilRadius(m_config.getRadius(parent->effective_radius_height + 1, parent->elephant_foot_increases + 1), parent->use_min_xy_dist)) - extra_speed += m_config.branch_radius * m_config.diameter_scale_bp_radius; + if (ceiled_parent_radius == volumes.ceilRadius(config.getRadius(parent.state.effective_radius_height + 1, parent.state.elephant_foot_increases + 1), parent.state.use_min_xy_dist)) + extra_speed += config.branch_radius * config.diameter_scale_bp_radius; else - extra_slow_speed += std::min(coord_t(m_config.branch_radius * m_config.diameter_scale_bp_radius), m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed)); + extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), + config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); } - const coord_t fast_speed = m_config.maximum_move_distance + extra_speed; - const coord_t slow_speed = m_config.maximum_move_distance_slow + extra_speed + extra_slow_speed; + const coord_t fast_speed = config.maximum_move_distance + extra_speed; + const coord_t slow_speed = config.maximum_move_distance_slow + extra_speed + extra_slow_speed; Polygons offset_slow, offset_fast; @@ -1984,54 +1696,53 @@ void TreeSupport::increaseAreas(std::unordered_map& to } }; - const bool parent_moved_slow = elem.last_area_increase.increase_speed < m_config.maximum_move_distance; + const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::Slow; - if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && !avoidance_speed_mismatch && (elem.distance_to_top >= m_config.tip_layers || parent_moved_slow)) - { + if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && !mergelayer && + !avoidance_speed_mismatch && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) { // assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. - insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < m_config.maximum_move_distance ? slow_speed : fast_speed, increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); - insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < m_config.maximum_move_distance ? slow_speed : fast_speed, !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); + insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, + increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); + insertSetting({ elem.last_area_increase.type, elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed, + !increase_radius, elem.last_area_increase.no_error, !use_min_radius, elem.last_area_increase.move }, true); } // branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. - if (!elem.can_use_safe_radius) - { - // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. + if (!elem.can_use_safe_radius) { + // if the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. + // This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. // order.emplace_back(AvoidanceType::Slow,!increase_radius,no_error,!use_min_radius,move); insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we go through the hole - // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. This CAN cause a branch to go though a hole it otherwise may have avoided. - if (elem.distance_to_top < round_up_divide(m_config.tip_layers, size_t(2))) + // in many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. + // This CAN cause a branch to go though a hole it otherwise may have avoided. + if (elem.distance_to_top < round_up_divide(config.tip_layers, size_t(2))) insertSetting({ AvoidanceType::Fast, slow_speed, increase_radius, no_error, !use_min_radius, !move }, true); insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, !move }, true); // did we manage to avoid the hole insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); - } - else - { + } else { insertSetting({ AvoidanceType::Slow, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, which looks similar to a layer shift and can reduce stability. + // while moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, + // which looks similar to a layer shift and can reduce stability. // as such idx have chosen to only use the user setting for radius increases as a friendly recommendation. insertSetting({ AvoidanceType::Slow, slow_speed, !increase_radius, no_error, !use_min_radius, move }, true); // a - if (elem.distance_to_top < m_config.tip_layers) - { + if (elem.distance_to_top < config.tip_layers) insertSetting({ AvoidanceType::FastSafe, slow_speed, increase_radius, no_error, !use_min_radius, move }, true); - } insertSetting({ AvoidanceType::FastSafe, fast_speed, increase_radius, no_error, !use_min_radius, move }, true); // b insertSetting({ AvoidanceType::FastSafe, fast_speed, !increase_radius, no_error, !use_min_radius, move }, true); } - if (elem.use_min_xy_dist) - { + if (elem.use_min_xy_dist) { std::vector new_order; - // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) - for (AreaIncreaseSettings settings : order) - { + // if the branch currently has to use min_xy_dist check if the configuration would also be valid + // with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) + for (AreaIncreaseSettings settings : order) { new_order.emplace_back(settings); new_order.push_back({ settings.type, settings.increase_speed, settings.increase_radius, settings.no_error, use_min_radius, settings.move }); } order = new_order; } - if (elem.to_buildplate || (elem.to_model_gracious && intersection(*parent->area, m_volumes.getPlaceableAreas(radius, layer_idx)).empty())) // error case - { + if (elem.to_buildplate || (elem.to_model_gracious && intersection(parent.influence_area, volumes.getPlaceableAreas(radius, layer_idx)).empty())) { + // error case // it is normal that we wont be able to find a new area at some point in time if we wont be able to reach layer 0 aka have to connect with the model insertSetting({ AvoidanceType::Fast, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move }, true); } @@ -2042,59 +1753,73 @@ void TreeSupport::increaseAreas(std::unordered_map& to Polygons inc_wo_collision; // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to calculate the fast one. // Calculated by comparing the steps saved when calcualting idependently with the saved steps when not. - bool offset_independant_faster = (radius / safe_movement_distance - (((m_config.maximum_move_distance + extra_speed) < (radius + safe_movement_distance)) ? 1 : 0)) > (round_up_divide((extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow), safe_movement_distance)); - for (AreaIncreaseSettings settings : order) - { + bool offset_independant_faster = radius / safe_movement_distance - int(config.maximum_move_distance + extra_speed < radius + safe_movement_distance) > + round_up_divide((extra_speed + extra_slow_speed + config.maximum_move_distance_slow), safe_movement_distance); + for (const AreaIncreaseSettings &settings : order) { if (settings.move) { - if (offset_slow.empty() && (settings.increase_speed == slow_speed || !offset_independant_faster)) { + if (offset_slow.empty() && (settings.increase_speed == slow_speed || ! offset_independant_faster)) { // offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. At this point one can see that the Polygons class // was never made for precision in the single digit micron range. - offset_slow = safeOffsetInc(*parent->area, extra_speed + extra_slow_speed + m_config.maximum_move_distance_slow, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); + offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow, + wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2); +#ifdef TREESUPPORT_DEBUG_SVG + SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-slow-%d-%ld.svg", layer_idx, int(merging_area_idx)), + { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(offset_slow) }, { "offset_slow", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif // TREESUPPORT_DEBUG_SVG } - - if ((settings.increase_speed != slow_speed) && offset_fast.empty()) { + if (offset_fast.empty() && settings.increase_speed != slow_speed) { if (offset_independant_faster) - offset_fast = safeOffsetInc(*parent->area, extra_speed + m_config.maximum_move_distance, wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); + offset_fast = safe_offset_inc(parent.influence_area, extra_speed + config.maximum_move_distance, + wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 1); else { - const coord_t delta_slow_fast = m_config.maximum_move_distance - (m_config.maximum_move_distance_slow + extra_slow_speed); - offset_fast = safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); + const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); + offset_fast = safe_offset_inc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1); } +#ifdef TREESUPPORT_DEBUG_SVG + SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-fast-%d-%ld.svg", layer_idx, int(merging_area_idx)), + { { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(offset_fast) }, { "offset_fast", "red", "black", "", scaled(0.1f), 0.5f } } }); +#endif // TREESUPPORT_DEBUG_SVG } } - std::optional result; + std::optional result; + inc_wo_collision.clear(); if (!settings.no_error) { // ERROR CASE // if the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased - Polygons lines_offset = offset(to_polylines(*parent->area), scaled(0.005), jtMiter, 1.2); - Polygons base_error_area = union_(*parent->area, lines_offset); - result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, (m_config.maximum_move_distance + extra_speed) * 1.5, mergelayer); + Polygons lines_offset = offset(to_polylines(parent.influence_area), scaled(0.005), jtMiter, 1.2); + Polygons base_error_area = union_(parent.influence_area, lines_offset); + result = increase_single_area(volumes, config, settings, layer_idx, parent, + base_error_area, to_bp_data, to_model_data, inc_wo_collision, (config.maximum_move_distance + extra_speed) * 1.5, mergelayer); #ifdef TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(error) #else // TREE_SUPPORT_SHOW_ERRORS BOOST_LOG_TRIVIAL(warning) #endif // TREE_SUPPORT_SHOW_ERRORS << "Influence area could not be increased! Data about the Influence area: " - "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.next_height << " Distance to top: " << elem.distance_to_top << + "Radius: " << radius << " at layer: " << layer_idx - 1 << " NextTarget: " << elem.layer_idx << " Distance to top: " << elem.distance_to_top << " Elephant foot increases " << elem.elephant_foot_increases << " use_min_xy_dist " << elem.use_min_xy_dist << " to buildplate " << elem.to_buildplate << " gracious " << elem.to_model_gracious << " safe " << elem.can_use_safe_radius << " until move " << elem.dont_move_until << " \n " - "Parent " << parent << ": Radius: " << m_config.getCollisionRadius(*parent) << " at layer: " << layer_idx << " NextTarget: " << parent->next_height << - " Distance to top: " << parent->distance_to_top << " Elephant foot increases " << parent->elephant_foot_increases << " use_min_xy_dist " << parent->use_min_xy_dist << - " to buildplate " << parent->to_buildplate << " gracious " << parent->to_model_gracious << " safe " << parent->can_use_safe_radius << " until move " << parent->dont_move_until; - showError("Potentially lost branch!", true); + "Parent " << &parent << ": Radius: " << config.getCollisionRadius(parent.state) << " at layer: " << layer_idx << " NextTarget: " << parent.state.layer_idx << + " Distance to top: " << parent.state.distance_to_top << " Elephant foot increases " << parent.state.elephant_foot_increases << " use_min_xy_dist " << parent.state.use_min_xy_dist << + " to buildplate " << parent.state.to_buildplate << " gracious " << parent.state.to_model_gracious << " safe " << parent.state.can_use_safe_radius << " until move " << parent.state.dont_move_until; + tree_supports_show_error("Potentially lost branch!", true); } else - result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); + result = increase_single_area(volumes, config, settings, layer_idx, parent, + settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, 0, mergelayer); - if (result) - { + if (result) { elem = *result; - radius = m_config.getCollisionRadius(elem); + radius = config.getCollisionRadius(elem); elem.last_area_increase = settings; add = true; - bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < m_config.tip_layers); // do not merge if the branch should not move or the priority has to be to get farther away from the model. + // do not merge if the branch should not move or the priority has to be to get farther away from the model. + bypass_merge = !settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); if (settings.move) elem.dont_move_until = 0; else - elem.result_on_layer = parent->result_on_layer; + elem.result_on_layer = parent.state.result_on_layer; elem.can_use_safe_radius = settings.type != AvoidanceType::Fast; @@ -2108,344 +1833,772 @@ void TreeSupport::increaseAreas(std::unordered_map& to #endif // TREE_SUPPORT_SHOW_ERRORS << "Trying to keep area by moving faster than intended: Success"; break; - } - else if (!settings.no_error) + } else if (!settings.no_error) BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: FAILURE! WRONG BRANCHES LIKLY!"; } if (add) { - Polygons max_influence_area = safeUnion(diff_clipped(inc_wo_collision, m_volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), safeUnion(to_bp_data, to_model_data)); // union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be - { - std::lock_guard critical_section_newLayer(critical_sections); - if (bypass_merge) { - validate_range(max_influence_area); - Polygons* new_area = new Polygons(max_influence_area); - SupportElement* next = new SupportElement(elem, new_area); - bypass_merge_areas.emplace_back(next); - } else { - influence_areas.emplace(elem, max_influence_area); - if (elem.to_buildplate) - to_bp_areas.emplace(elem, to_bp_data); - if (m_config.support_rests_on_model) - to_model_areas.emplace(elem, to_model_data); - } + // Union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be. + assert(! inc_wo_collision.empty() || ! to_bp_data.empty() || ! to_model_data.empty()); + Polygons max_influence_area = safe_union( + diff_clipped(inc_wo_collision, volumes.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), + safe_union(to_bp_data, to_model_data)); + merging_area.state = elem; + assert(!max_influence_area.empty()); + merging_area.set_bbox(get_extents(max_influence_area)); + merging_area.areas.influence_areas = std::move(max_influence_area); + if (! bypass_merge) { + if (elem.to_buildplate) + merging_area.areas.to_bp_areas = std::move(to_bp_data); + if (config.support_rests_on_model) + merging_area.areas.to_model_areas = std::move(to_model_data); } + } else { + // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. + // But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. + // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). + parent.state.result_on_layer_reset(); } - else - parent->result_on_layer = Point(-1, -1); // If the bottom most point of a branch is set, later functions will assume that the position is valid, and ignore it. But as branches connecting with the model that are to small have to be culled, the bottom most point has to be not set. A point can be set on the top most tip layer (maybe more if it should not move for a few layers). } }); } +[[nodiscard]] static SupportElementState merge_support_element_states( + const SupportElementState &first, const SupportElementState &second, const Point &next_position, const coord_t layer_idx, + const TreeSupportSettings &config) +{ + SupportElementState out; + out.next_position = next_position; + out.layer_idx = layer_idx; + out.use_min_xy_dist = first.use_min_xy_dist || second.use_min_xy_dist; + out.supports_roof = first.supports_roof || second.supports_roof; + out.dont_move_until = std::max(first.dont_move_until, second.dont_move_until); + out.can_use_safe_radius = first.can_use_safe_radius || second.can_use_safe_radius; + out.missing_roof_layers = std::min(first.missing_roof_layers, second.missing_roof_layers); + out.skip_ovalisation = false; + if (first.target_height > second.target_height) { + out.target_height = first.target_height; + out.target_position = first.target_position; + } else { + out.target_height = second.target_height; + out.target_position = second.target_position; + } + out.effective_radius_height = std::max(first.effective_radius_height, second.effective_radius_height); + out.distance_to_top = std::max(first.distance_to_top, second.distance_to_top); -void TreeSupport::createLayerPathing(std::vector>& move_bounds) + out.to_buildplate = first.to_buildplate && second.to_buildplate; + out.to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious + + out.elephant_foot_increases = 0; + if (config.diameter_scale_bp_radius > 0) { + coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(out)); + // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch + // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. + out.elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); + } + + // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. + out.last_area_increase = { + std::min(first.last_area_increase.type, second.last_area_increase.type), + std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), + first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, + first.last_area_increase.no_error || second.last_area_increase.no_error, + first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, + first.last_area_increase.move || second.last_area_increase.move }; + + return out; +} + +static bool merge_influence_areas_two_elements( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + SupportElementMerging &dst, SupportElementMerging &src) +{ + // Don't merge gracious with a non gracious area as bad placement could negatively impact reliability of the whole subtree. + const bool merging_gracious_and_non_gracious = dst.state.to_model_gracious != src.state.to_model_gracious; + // Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased + // by the delta to the larger it is engulfed by it already. But because a different collision + // may be removed from the in draw_area() generated circles, this assumption could be wrong. + const bool merging_min_and_regular_xy = dst.state.use_min_xy_dist != src.state.use_min_xy_dist; + + if (merging_gracious_and_non_gracious || merging_min_and_regular_xy) + return false; + + const bool dst_radius_bigger = config.getCollisionRadius(dst.state) > config.getCollisionRadius(src.state); + const SupportElementMerging &smaller_rad = dst_radius_bigger ? src : dst; + const SupportElementMerging &bigger_rad = dst_radius_bigger ? dst : src; + const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.state) - config.getRadius(smaller_rad.state)); + { + // Testing intersection of bounding boxes. + // Expand the smaller radius branch bounding box to match the lambda intersect_small_with_bigger() below. + // Because the lambda intersect_small_with_bigger() applies a rounded offset, a snug offset of the bounding box + // is sufficient. On the other side, if a mitered offset was used by the lambda, + // the bounding box expansion would have to account for the mitered extension of the sharp corners. + Eigen::AlignedBox smaller_bbox = smaller_rad.bbox(); + smaller_bbox.min() -= Point{ real_radius_delta, real_radius_delta }; + smaller_bbox.max() += Point{ real_radius_delta, real_radius_delta }; + if (! smaller_bbox.intersects(bigger_rad.bbox())) + return false; + } + + // Accumulator of a radius increase of a "to model" branch by merging in a "to build plate" branch. + coord_t increased_to_model_radius = 0; + const bool merging_to_bp = dst.state.to_buildplate && src.state.to_buildplate; + if (! merging_to_bp) { + // Get the real radius increase as the user does not care for the collision model. + if (dst.state.to_buildplate != src.state.to_buildplate) { + // Merging a "to build plate" branch with a "to model" branch. + // Don't allow merging a thick "to build plate" branch into a thinner "to model" branch. + const coord_t rdst = config.getRadius(dst.state); + const coord_t rsrc = config.getRadius(src.state); + if (dst.state.to_buildplate) { + if (rsrc < rdst) + increased_to_model_radius = src.state.increased_to_model_radius + rdst - rsrc; + } else { + if (rsrc > rdst) + increased_to_model_radius = dst.state.increased_to_model_radius + rsrc - rdst; + } + if (increased_to_model_radius > config.max_to_model_radius_increase) + return false; + } + // if a merge could place a stable branch on unstable ground, would be increasing the radius further + // than allowed to when merging to model and to_bp trees or would merge to model before it is known + // they will even been drawn the merge is skipped + if (! dst.state.supports_roof && ! src.state.supports_roof && + std::max(src.state.distance_to_top, dst.state.distance_to_top) < config.min_dtt_to_model) + return false; + } + + // Area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, + // so if that would change an invalid area may be created. + if (! bigger_rad.state.can_use_safe_radius && smaller_rad.state.can_use_safe_radius) + return false; + + // the bigger radius is used to verify that the area is still valid after the increase with the delta. + // If there were a point where the big influence area could be valid with can_use_safe_radius + // the element would already be can_use_safe_radius. + // the smaller radius, which gets increased by delta may reach into the area where use_min_xy_dist is no longer required. + const bool use_min_radius = bigger_rad.state.use_min_xy_dist && smaller_rad.state.use_min_xy_dist; + + // The idea is that the influence area with the smaller collision radius is increased by the radius difference. + // If this area has any intersections with the influence area of the larger collision radius, a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. + // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. + // Remember that collision radius <= real radius as otherwise this assumption would be false. + const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.state); + const Polygons &collision = volumes.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius); + auto intersect_small_with_bigger = [real_radius_delta, smaller_collision_radius, &collision, &config](const Polygons &small, const Polygons &bigger) { + return intersection( + safe_offset_inc( + small, real_radius_delta, collision, + // -3 avoids possible rounding errors + 2 * (config.xy_distance + smaller_collision_radius - 3), 0, 0), + bigger); + }; + Polygons intersect = intersect_small_with_bigger( + merging_to_bp ? smaller_rad.areas.to_bp_areas : smaller_rad.areas.to_model_areas, + merging_to_bp ? bigger_rad.areas.to_bp_areas : bigger_rad.areas.to_model_areas); + + // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) + // check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). + if (area(intersect) <= tiny_area_threshold) + return false; + + // While 0.025 was guessed as enough, i did not have reason to change it. + if (area(offset(intersect, scaled(-0.025), jtMiter, 1.2)) <= tiny_area_threshold) + return false; + + // Do the actual merge now that the branches are confirmed to be able to intersect. + // calculate which point is closest to the point of the last merge (or tip center if no merge above it has happened) + // used at the end to estimate where to best place the branch on the bottom most layer + // could be replaced with a random point inside the new area + Point new_pos = move_inside_if_outside(intersect, dst.state.next_position); + + SupportElementState new_state = merge_support_element_states(dst.state, src.state, new_pos, layer_idx - 1, config); + new_state.increased_to_model_radius = increased_to_model_radius == 0 ? + // increased_to_model_radius was not set yet. Propagate maximum. + std::max(dst.state.increased_to_model_radius, src.state.increased_to_model_radius) : + increased_to_model_radius; + + // Rather unioning with "intersect" due to some rounding errors. + Polygons influence_areas = safe_union( + intersect_small_with_bigger(smaller_rad.areas.influence_areas, bigger_rad.areas.influence_areas), + intersect); + + Polygons to_model_areas; + if (merging_to_bp && config.support_rests_on_model) + to_model_areas = new_state.to_model_gracious ? + // Rather unioning with "intersect" due to some rounding errors. + safe_union( + intersect_small_with_bigger(smaller_rad.areas.to_model_areas, bigger_rad.areas.to_model_areas), + intersect) : + influence_areas; + + dst.parents.insert(dst.parents.end(), src.parents.begin(), src.parents.end()); + dst.state = new_state; + dst.areas.influence_areas = std::move(influence_areas); + dst.areas.to_bp_areas.clear(); + dst.areas.to_model_areas.clear(); + if (merging_to_bp) { + dst.areas.to_bp_areas = std::move(intersect); + if (config.support_rests_on_model) + dst.areas.to_model_areas = std::move(to_model_areas); + } else + dst.areas.to_model_areas = std::move(intersect); + // Update the bounding box. + BoundingBox bbox(get_extents(dst.areas.influence_areas)); + bbox.merge(get_extents(dst.areas.to_bp_areas)); + bbox.merge(get_extents(dst.areas.to_model_areas)); + dst.set_bbox(bbox); + // Clear the source data. + src.areas.clear(); + src.parents.clear(); + return true; +} + +/*! + * \brief Merges Influence Areas if possible. + * + * Branches which do overlap have to be merged. This helper merges all elements in input with the elements into reduced_new_layer. + * Elements in input_aabb are merged together if possible, while elements reduced_new_layer_aabb are not checked against each other. + * + * \param reduced_aabb[in,out] The already processed elements. + * \param input_aabb[in] Not yet processed elements + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The influence areas without avoidance removed. + * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. + * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. + * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) + * \param erase[out] Elements that should be deleted from the above dictionaries. + * \param layer_idx[in] The Index of the current Layer. + */ + +static SupportElementMerging* merge_influence_areas_leaves( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + SupportElementMerging * const dst_begin, SupportElementMerging *dst_end) +{ + // Merging at the lowest level of the AABB tree. Checking one against each other, O(n^2). + assert(dst_begin < dst_end); + for (SupportElementMerging *i = dst_begin; i + 1 < dst_end;) { + for (SupportElementMerging *j = i + 1; j != dst_end;) + if (merge_influence_areas_two_elements(volumes, config, layer_idx, *i, *j)) { + // i was merged with j, j is empty. + if (j != -- dst_end) + *j = std::move(*dst_end); + goto merged; + } else + ++ j; + // not merged + ++ i; + merged: + ; + } + return dst_end; +} + +static SupportElementMerging* merge_influence_areas_two_sets( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + SupportElementMerging * const dst_begin, SupportElementMerging * dst_end, + SupportElementMerging * src_begin, SupportElementMerging * const src_end) +{ + // Merging src into dst. + // Areas of src should not overlap with areas of another elements of src. + // Areas of dst should not overlap with areas of another elements of dst. + // The memory from dst_begin to src_end is reserved for the merging operation, + // src follows dst. + assert(src_begin < src_end); + assert(dst_begin < dst_end); + assert(dst_end <= src_begin); + for (SupportElementMerging *src = src_begin; src != src_end; ++ src) { + SupportElementMerging *dst = dst_begin; + SupportElementMerging *merged = nullptr; + for (; dst != dst_end; ++ dst) + if (merge_influence_areas_two_elements(volumes, config, layer_idx, *dst, *src)) { + merged = dst ++; + if (src != src_begin) + // Compactify src. + *src = std::move(*src_begin); + ++ src_begin; + break; + } + for (; dst != dst_end;) + if (merge_influence_areas_two_elements(volumes, config, layer_idx, *merged, *dst)) { + // Compactify dst. + if (dst != -- dst_end) + *dst = std::move(*dst_end); + } else + ++ dst; + } + // Compactify src elements that were not merged with dst to the end of dst. + assert(dst_end <= src_begin); + if (dst_end == src_begin) + dst_end = src_end; + else + while (src_begin != src_end) + *dst_end ++ = std::move(*src_begin ++); + + return dst_end; +} + +/*! + * \brief Merges Influence Areas at one layer if possible. + * + * Branches which do overlap have to be merged. This manages the helper and uses a divide and conquer approach to parallelize this problem. This parallelization can at most accelerate the merging by a factor of 2. + * + * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. + * Value is the influence area where the center of a circle of support may be placed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. + * Value is the influence area where the center of a circle of support may be placed. + * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. + * Value is the influence area where the center of a circle of support may be placed. + * \param layer_idx[in] The current layer. + */ +static void merge_influence_areas( + const TreeModelVolumes &volumes, const TreeSupportSettings &config, const LayerIndex layer_idx, + std::vector &influence_areas) +{ + const size_t input_size = influence_areas.size(); + if (input_size == 0) + return; + + // Merging by divide & conquer. + // The majority of time is consumed by Clipper polygon operations, intersection is accelerated by bounding boxes. + // Sorting input into an AABB tree helps to perform most of the intersections at first iterations, + // thus reducing computation when merging larger subtrees. + // The actual merge logic is found in merge_influence_areas_two_sets. + + // Build an AABB tree over the influence areas. + //FIXME A full tree does not need to be built, the lowest level branches will be always bucketed. + // However the additional time consumed is negligible. + AABBTreeIndirect::Tree<2, coord_t> tree; + // Sort influence_areas in place. + tree.build_modify_input(influence_areas); + + // Prepare the initial buckets as ranges of influence areas. The initial buckets contain power of 2 influence areas to follow + // the branching of the AABB tree. + // Vectors of ranges of influence areas, following the branching of the AABB tree: + std::vector> buckets; + // Initial number of buckets for 1st round of merging. + size_t num_buckets_initial; + { + // How many buckets per first merge iteration? + const size_t num_threads = tbb::this_task_arena::max_concurrency(); + // 4 buckets per thread if possible, + const size_t num_buckets_min = (input_size + 2) / 4; + // 2 buckets per thread otherwise. + const size_t num_buckets_max = input_size / 2; + num_buckets_initial = num_buckets_min >= num_threads ? num_buckets_min : num_buckets_max; + const size_t bucket_size = num_buckets_min >= num_threads ? 4 : 2; + // Fill in the buckets. + SupportElementMerging *it = influence_areas.data(); + // Reserve one more bucket to keep a single influence area which will not be merged in the first iteration. + buckets.reserve(num_buckets_initial + 1); + for (size_t i = 0; i < num_buckets_initial; ++ i, it += bucket_size) + buckets.emplace_back(std::make_pair(it, it + bucket_size)); + SupportElementMerging *it_end = influence_areas.data() + influence_areas.size(); + if (buckets.back().second >= it_end) { + // Last bucket is less than size 4, but bigger than size 1. + buckets.back().second = std::min(buckets.back().second, it_end); + } else { + // Last bucket is size 1, it will not be merged in the first iteration. + assert(it + 1 == it_end); + buckets.emplace_back(std::make_pair(it, it_end)); + } + } + + // 1st merge iteration, merge one with each other. + tbb::parallel_for(tbb::blocked_range(0, num_buckets_initial), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + const size_t bucket_pair_idx = idx * 2; + // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets + buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second); + } + }); + + // Further merge iterations, merging one AABB subtree with another one, hopefully minimizing intersections between the elements + // of each of the subtree. + while (buckets.size() > 1) { + tbb::parallel_for(tbb::blocked_range(0, buckets.size() / 2), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx < range.end(); ++ idx) { + const size_t bucket_pair_idx = idx * 2; + // Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets + buckets[bucket_pair_idx].second = merge_influence_areas_two_sets(volumes, config, layer_idx, + buckets[bucket_pair_idx].first, buckets[bucket_pair_idx].second, + buckets[bucket_pair_idx + 1].first, buckets[bucket_pair_idx + 1].second); + } + }); + // Remove odd buckets, which were merged into even buckets. + size_t new_size = (buckets.size() + 1) / 2; + for (size_t i = 1; i < new_size; ++ i) + buckets[i] = std::move(buckets[i * 2]); + buckets.erase(buckets.begin() + new_size, buckets.end()); + } +} + +/*! + * \brief Propagates influence downwards, and merges overlapping ones. + * + * \param move_bounds[in,out] All currently existing influence areas + */ +static void create_layer_pathing(const TreeModelVolumes &volumes, const TreeSupportSettings &config, std::vector &move_bounds) { #ifdef SLIC3R_TREESUPPORTS_PROGRESS const double data_size_inverse = 1 / double(move_bounds.size()); double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES; #endif // SLIC3R_TREESUPPORTS_PROGRESS - auto dur_inc = std::chrono::duration_values::zero(); - auto dur_merge = std::chrono::duration_values::zero(); + auto dur_inc = std::chrono::duration_values::zero(); + auto dur_total = std::chrono::duration_values::zero(); - LayerIndex last_merge = move_bounds.size(); + LayerIndex last_merge_layer_idx = move_bounds.size(); bool new_element = false; - size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(m_config.maximum_move_distance, coord_t(100))), 1000 / std::max(m_config.maximum_move_distance_slow, coord_t(20))), 3000 / m_config.layer_height); // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. + // Ensures at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. + size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, coord_t(100))), 1000 / std::max(config.maximum_move_distance_slow, coord_t(20))), 3000 / config.layer_height); size_t merge_every_x_layers = 1; // Calculate the influence areas for each layer below (Top down) // This is done by first increasing the influence area by the allowed movement distance, and merging them with other influence areas if possible for (int layer_idx = int(move_bounds.size()) - 1; layer_idx > 0; -- layer_idx) - { - // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. - bool merge_this_layer = size_t(last_merge - layer_idx) >= merge_every_x_layers; + if (SupportElements &prev_layer = move_bounds[layer_idx]; ! prev_layer.empty()) { + // merging is expensive and only parallelized to a max speedup of 2. As such it may be useful in some cases to only merge every few layers to improve performance. + bool had_new_element = new_element; + const bool merge_this_layer = had_new_element || size_t(last_merge_layer_idx - layer_idx) >= merge_every_x_layers; + if (had_new_element) + merge_every_x_layers = 1; + const auto ta = std::chrono::high_resolution_clock::now(); - if (new_element) - { - merge_this_layer = true; - merge_every_x_layers = 1; - } - - std::map influence_areas; // Over this map will be iterated when merging, as such it has to be ordered to ensure deterministic results. - std::unordered_map to_bp_areas, to_model_areas; // The area of these SupportElement is not set, to avoid to much allocation and deallocation on the heap - std::vector bypass_merge_areas; // Different to the other maps of SupportElements as these here have the area already set, as they are already to be inserted into move_bounds. - - auto ta = std::chrono::high_resolution_clock::now(); - - std::vector last_layer; - last_layer.insert(last_layer.begin(), move_bounds[layer_idx].begin(), move_bounds[layer_idx].end()); - - // ### Increase the influence areas by the allowed movement distance - increaseAreas(to_bp_areas, to_model_areas, influence_areas, bypass_merge_areas, last_layer, layer_idx, merge_this_layer); - - auto tb = std::chrono::high_resolution_clock::now(); - if (merge_this_layer) - { - bool reduced_by_merging = false; - size_t count_before_merge = influence_areas.size(); - // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) - mergeInfluenceAreas(m_volumes, m_config, to_bp_areas, to_model_areas, influence_areas, layer_idx); - - last_merge = layer_idx; - reduced_by_merging = count_before_merge > influence_areas.size(); - if (!reduced_by_merging && !new_element) - { - merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); + // ### Increase the influence areas by the allowed movement distance + std::vector influence_areas; + influence_areas.reserve(prev_layer.size()); + for (int32_t element_idx = 0; element_idx < int32_t(prev_layer.size()); ++ element_idx) { + SupportElement &el = prev_layer[element_idx]; + assert(!el.influence_area.empty()); + SupportElement::ParentIndices parents; + parents.emplace_back(element_idx); + influence_areas.push_back({ el.state, parents }); } - } - auto tc = std::chrono::high_resolution_clock::now(); + increase_areas_one_layer(volumes, config, influence_areas, layer_idx, prev_layer, merge_this_layer); - dur_inc += tb - ta; - dur_merge += tc - tb; + // Place already fully constructed elements to the output, remove them from influence_areas. + SupportElements &this_layer = move_bounds[layer_idx - 1]; + influence_areas.erase(std::remove_if(influence_areas.begin(), influence_areas.end(), + [&this_layer, layer_idx](SupportElementMerging &elem) { + if (elem.areas.influence_areas.empty()) + // This area was removed completely due to collisions. + return true; + if (elem.areas.to_bp_areas.empty() && elem.areas.to_model_areas.empty()) { + if (area(elem.areas.influence_areas) < tiny_area_threshold) { + BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; + tree_supports_show_error("Insert error of area after bypassing merge.\n", true); + } + // Move the area to output. + this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(elem.areas.influence_areas)); + return true; + } + // Keep the area. + return false; + }), + influence_areas.end()); - new_element = !move_bounds[layer_idx - 1].empty(); - - // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. - for (const std::pair &tup : influence_areas) { - const SupportElement &elem = tup.first; - validate_range(tup.second); - validate_range(safeUnion(tup.second)); - Polygons* new_area = new Polygons(safeUnion(tup.second)); - SupportElement* next = new SupportElement(elem, new_area); - move_bounds[layer_idx - 1].emplace(next); - - if (area(*new_area) < tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.to_buildplate; - TreeSupport::showError("Insert error of area after merge.\n", true); + dur_inc += std::chrono::high_resolution_clock::now() - ta; + new_element = ! move_bounds[layer_idx - 1].empty(); + if (merge_this_layer) { + bool reduced_by_merging = false; + if (size_t count_before_merge = influence_areas.size(); count_before_merge > 1) { + // ### Calculate which influence areas overlap, and merge them into a new influence area (simplified: an intersection of influence areas that have such an intersection) + merge_influence_areas(volumes, config, layer_idx, influence_areas); + reduced_by_merging = count_before_merge > influence_areas.size(); + } + last_merge_layer_idx = layer_idx; + if (! reduced_by_merging && ! had_new_element) + merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); } + + dur_total += std::chrono::high_resolution_clock::now() - ta; + + // Save calculated elements to output, and allocate Polygons on heap, as they will not be changed again. + for (SupportElementMerging &elem : influence_areas) + if (! elem.areas.influence_areas.empty()) { + Polygons new_area = safe_union(elem.areas.influence_areas); + if (area(new_area) < tiny_area_threshold) { + BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area on layer " << layer_idx - 1 << ". Origin of " << elem.parents.size() << " areas. Was to bp " << elem.state.to_buildplate; + tree_supports_show_error("Insert error of area after merge.\n", true); + } + this_layer.emplace_back(elem.state, std::move(elem.parents), std::move(new_area)); + } + + #ifdef SLIC3R_TREESUPPORTS_PROGRESS + progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); + #endif } - // Place already fully constructed elements in the output. - for (SupportElement* elem : bypass_merge_areas) { - if (area(*elem->area) < tiny_area_threshold) { - BOOST_LOG_TRIVIAL(error) << "Insert Error of Influence area bypass on layer " << layer_idx - 1; - TreeSupport::showError("Insert error of area after bypassing merge.\n", true); - } - move_bounds[layer_idx - 1].emplace(elem); - } - -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - progress_total += data_size_inverse * TREE_PROGRESS_AREA_CALC; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); -#endif - } - - BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << " ms merging areas: " << dur_merge.count() / 1000000 << " ms"; + BOOST_LOG_TRIVIAL(info) << "Time spent with creating influence areas' subtasks: Increasing areas " << dur_inc.count() / 1000000 << + " ms merging areas: " << (dur_total - dur_inc).count() / 1000000 << " ms"; } - -void TreeSupport::setPointsOnAreas(const SupportElement* elem) +/*! + * \brief Sets the result_on_layer for all parents based on the SupportElement supplied. + * + * \param elem[in] The SupportElements, which parent's position should be determined. + */ +static void set_points_on_areas(const SupportElement &elem, SupportElements *layer_above) { - // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. + assert(!elem.state.deleted); + assert(layer_above != nullptr || elem.parents.empty()); - if (elem->result_on_layer == Point(-1, -1)) - { + // Based on the branch center point of the current layer, the point on the next (further up) layer is calculated. + if (! elem.state.result_on_layer_is_set()) { BOOST_LOG_TRIVIAL(error) << "Uninitialized support element"; - TreeSupport::showError("Uninitialized support element. A branch may be missing.\n", true); + tree_supports_show_error("Uninitialized support element. A branch may be missing.\n", true); return; } - for (SupportElement* next_elem : elem->parents) - { - if (next_elem->result_on_layer != Point(-1, -1)) // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. - continue; - - Point from = elem->result_on_layer; - if (! contains(*next_elem->area, from)) { - moveInside(*next_elem->area, from, 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 - // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. While this seems like a problem it may for example occur after merges. + if (layer_above) + for (int32_t next_elem_idx : elem.parents) { + assert(next_elem_idx >= 0); + SupportElement &next_elem = (*layer_above)[next_elem_idx]; + assert(! next_elem.state.deleted); + // if the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. + if (! next_elem.state.result_on_layer_is_set()) { + // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 + // it is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. + // While this seems like a problem it may for example occur after merges. + next_elem.state.result_on_layer = move_inside_if_outside(next_elem.influence_area, elem.state.result_on_layer); + // do not call recursive because then amount of layers would be restricted by the stack size + } } - next_elem->result_on_layer = from; - // do not call recursive because then amount of layers would be restricted by the stack size - } } -bool TreeSupport::setToModelContact(std::vector>& move_bounds, SupportElement* first_elem, const LayerIndex layer_idx) +static void set_to_model_contact_simple(SupportElement &elem) { - if (first_elem->to_model_gracious) + const Point best = move_inside_if_outside(elem.influence_area, elem.state.next_position); + elem.state.result_on_layer = best; + BOOST_LOG_TRIVIAL(debug) << "Added NON gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << elem.state.layer_idx; +} + +/*! + * \brief Get the best point to connect to the model and set the result_on_layer of the relevant SupportElement accordingly. + * + * \param move_bounds[in,out] All currently existing influence areas + * \param first_elem[in,out] SupportElement that did not have its result_on_layer set meaning that it does not have a child element. + * \param layer_idx[in] The current layer. + */ +static void set_to_model_contact_to_model_gracious( + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds, + SupportElement &first_elem) +{ + SupportElement *last_successfull_layer = nullptr; + + // check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest up layer index. { - SupportElement* check = first_elem; + SupportElement *elem = &first_elem; + for (LayerIndex layer_check = elem->state.layer_idx; + ! intersection(elem->influence_area, volumes.getPlaceableAreas(config.getCollisionRadius(elem->state), layer_check)).empty(); + elem = &move_bounds[++ layer_check][elem->parents.front()]) { + assert(elem->state.layer_idx == layer_check); + assert(! elem->state.deleted); + assert(elem->state.to_model_gracious); + last_successfull_layer = elem; + if (elem->parents.size() != 1) + // Reached merge point. + break; + } + } - std::vector checked; - LayerIndex last_successfull_layer = layer_idx; - bool set = false; - - // check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest up layer index. - - for (LayerIndex layer_check = layer_idx; check->next_height >= layer_check; layer_check++) + // Could not find valid placement, even though it should exist => error handling + if (last_successfull_layer == nullptr) { + BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << first_elem.state.layer_idx; + tree_supports_show_error("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); + first_elem.state.to_model_gracious = false; + set_to_model_contact_simple(first_elem); + } else { + // Found a gracious area above first_elem. Remove all below last_successfull_layer. { - if (! intersection(*check->area, m_volumes.getPlaceableAreas(m_config.getCollisionRadius(*check), layer_check)).empty()) { - set = true; - last_successfull_layer = layer_check; - } - checked.emplace_back(check); - if (check->parents.size() == 1) - { - check = check->parents[0]; - } - else - { - break; // reached merge point + LayerIndex parent_layer_idx = first_elem.state.layer_idx; + for (SupportElement *elem = &first_elem; elem != last_successfull_layer; elem = &move_bounds[++ parent_layer_idx][elem->parents.front()]) { + assert(! elem->state.deleted); + elem->state.deleted = true; } } - - // Could not find valid placement, even though it should exist => error handling - if (!set) - { - if (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL) - { - BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << layer_idx << ": REMOVING BRANCH"; - TreeSupport::showError("Could not fine valid placement on model! Removing this branch...", true); - for (LayerIndex layer = layer_idx; layer <= first_elem->next_height; layer++) - { - move_bounds[layer].erase(checked[layer - layer_idx]); - delete checked[layer - layer_idx]->area; - delete checked[layer - layer_idx]; - } - } - else - { - BOOST_LOG_TRIVIAL(warning) << "No valid placement found for to model gracious element on layer " << layer_idx; - TreeSupport::showError("Could not fine valid placement on model! Just placing it down anyway. Could cause floating branches.", true); - first_elem->to_model_gracious = false; - return setToModelContact(move_bounds, first_elem, layer_idx); - } - } - - for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; layer++) - { - move_bounds[layer].erase(checked[layer - layer_idx]); - delete checked[layer - layer_idx]->area; - delete checked[layer - layer_idx]; - } - // Guess a point inside the influence area, in which the branch will be placed in. - Point best = checked[last_successfull_layer - layer_idx]->next_position; - if (! contains(*checked[last_successfull_layer - layer_idx]->area, best)) - moveInside(*checked[last_successfull_layer - layer_idx]->area, best); - checked[last_successfull_layer - layer_idx]->result_on_layer = best; - + const Point best = move_inside_if_outside(last_successfull_layer->influence_area, last_successfull_layer->state.next_position); + last_successfull_layer->state.result_on_layer = best; BOOST_LOG_TRIVIAL(debug) << "Added gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << last_successfull_layer; - - return last_successfull_layer != layer_idx; - } - else // can not add graceful => just place it here and hope for the best - { - Point best = first_elem->next_position; - if (! contains(*first_elem->area, best)) - moveInside(*first_elem->area, best); - first_elem->result_on_layer = best; - first_elem->to_model_gracious = false; - BOOST_LOG_TRIVIAL(debug) << "Added NON gracious Support On Model Point (" << best.x() << "," << best.y() << "). The current layer is " << layer_idx; - return false; } } -void TreeSupport::createNodesFromArea(std::vector>& move_bounds) +// Remove elements marked as "deleted", update indices to parents. +static void remove_deleted_elements(std::vector &move_bounds) { - // Initialize points on layer 0, with a "random" point in the influence area. Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. - for (SupportElement* init : move_bounds[0]) { - Point p = init->next_position; - if (! contains(*init->area, p)) - moveInside(*init->area, p, 0); - init->result_on_layer = p; - setPointsOnAreas(init); // also set the parent nodes, as these will be required for the first iteration of the loop below + std::vector map_parents; + std::vector map_current; + for (LayerIndex layer_idx = LayerIndex(move_bounds.size()) - 1; layer_idx >= 0; -- layer_idx) { + SupportElements &layer = move_bounds[layer_idx]; + map_current.clear(); + for (int32_t i = 0; i < int32_t(layer.size());) { + SupportElement &element = layer[i]; + if (element.state.deleted) { + if (map_current.empty()) { + // Initialize with identity map. + map_current.assign(layer.size(), 0); + std::iota(map_current.begin(), map_current.end(), 0); + } + // Delete all "deleted" elements from the end of the layer vector. + while (i < layer.size() && layer.back().state.deleted) { + layer.pop_back(); + // Mark as deleted in the map. + map_current[layer.size()] = -1; + } + assert(i == layer.size() || i + 1 < layer.size()); + if (i + 1 < layer.size()) { + element = std::move(layer.back()); + layer.pop_back(); + // Mark the current element as deleted. + map_current[i] = -1; + // Mark the moved element as moved to index i. + map_current[layer.size()] = i; + } + } else { + // Current element is not deleted. Update its parent indices. + if (! map_parents.empty()) + for (int32_t &parent_idx : element.parents) + parent_idx = map_parents[parent_idx]; + ++ i; + } + } + std::swap(map_current, map_parents); + } +} + +/*! + * \brief Set the result_on_layer point for all influence areas + * + * \param move_bounds[in,out] All currently existing influence areas + */ +static void create_nodes_from_area( + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + std::vector &move_bounds) +{ + // Initialize points on layer 0, with a "random" point in the influence area. + // Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. + for (SupportElement &init : move_bounds.front()) { + init.state.result_on_layer = move_inside_if_outside(init.influence_area, init.state.next_position); + // Also set the parent nodes, as these will be required for the first iteration of the loop below. + set_points_on_areas(init, move_bounds.size() > 1 ? &move_bounds[1] : nullptr); } for (LayerIndex layer_idx = 1; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - std::unordered_set remove; - for (SupportElement* elem : move_bounds[layer_idx]) { - bool removed = false; + auto &layer = move_bounds[layer_idx]; + auto *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; + for (SupportElement &elem : layer) { + assert(! elem.state.deleted); + assert(elem.state.layer_idx == layer_idx); // check if the resulting center point is not yet set - if (elem->result_on_layer == Point(-1, -1)) { - if (elem->to_buildplate || (!elem->to_buildplate && elem->distance_to_top < m_config.min_dtt_to_model && !elem->supports_roof)) { - if (elem->to_buildplate) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem->target_position.x() << "," << elem->target_position.y() << ") " - "at target_height: " << elem->target_height << " layer: " << layer_idx; - TreeSupport::showError("Uninitialized support element! A branch could be missing or exist partially.", true); + if (! elem.state.result_on_layer_is_set()) { + if (elem.state.to_buildplate || (elem.state.distance_to_top < config.min_dtt_to_model && ! elem.state.supports_roof)) { + if (elem.state.to_buildplate) { + BOOST_LOG_TRIVIAL(error) << "Uninitialized Influence area targeting " << elem.state.target_position.x() << "," << elem.state.target_position.y() << ") " + "at target_height: " << elem.state.target_height << " layer: " << layer_idx; + tree_supports_show_error("Uninitialized support element! A branch could be missing or exist partially.", true); } - remove.emplace(elem); // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set - removed = true; - for (SupportElement* parent : elem->parents) - parent->result_on_layer = Point(-1, -1); // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. As this branch needs to be removed => all parents result_on_layer have to be invalidated. + // we dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set + elem.state.deleted = true; + for (int32_t parent_idx : elem.parents) + // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. + // As this branch needs to be removed => all parents result_on_layer have to be invalidated. + (*layer_above)[parent_idx].state.result_on_layer_reset(); continue; } else { // set the point where the branch will be placed on the model - removed = setToModelContact(move_bounds, elem, layer_idx); - if (removed) - remove.emplace(elem); + if (elem.state.to_model_gracious) + set_to_model_contact_to_model_gracious(volumes, config, move_bounds, elem); + else + set_to_model_contact_simple(elem); } } - - if (!removed) - setPointsOnAreas(elem); // element is valid now setting points in the layer above + if (! elem.state.deleted) + // element is valid now setting points in the layer above + set_points_on_areas(elem, layer_above); } - - // delete all not needed support elements - for (SupportElement* del : remove) { - move_bounds[layer_idx].erase(del); - delete del->area; - delete del; - } - remove.clear(); } + + remove_deleted_elements(move_bounds); } -void TreeSupport::generateBranchAreas( - std::vector> &linear_data, - std::vector> &layer_tree_polygons, - const std::map &inverse_tree_order) +// For producing circular / elliptical areas from SupportElements (one DrawArea per one SupportElement) +// and for smoothing those areas along the tree branches. +struct DrawArea +{ + // Element to be processed. + SupportElement *element; + // Element below, if there is such an element. nullptr if element is a root of a tree. + SupportElement *child_element; + // Polygons to be extruded for this element. + Polygons polygons; +}; + +/*! + * \brief Draws circles around result_on_layer points of the influence areas + * + * \param linear_data[in] All currently existing influence areas with the layer they are on + * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. + * \param inverse_tree_order[in] A mapping that returns the child of every influence area. + */ +static void generate_branch_areas(const TreeModelVolumes &volumes, const TreeSupportSettings &config, const std::vector &move_bounds, std::vector &linear_data) { #ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC; constexpr int progress_report_steps = 10; + const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; + std::mutex critical_sections; #endif // SLIC3R_TREESUPPORTS_PROGRESS // Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time. - const Polygon branch_circle = make_circle(m_config.branch_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); - std::vector linear_inserts(linear_data.size()); + const Polygon branch_circle = make_circle(config.branch_radius, SUPPORT_TREE_CIRCLE_RESOLUTION); -#ifdef SLIC3R_TREESUPPORTS_PROGRESS - const size_t progress_inserts_check_interval = linear_data.size() / progress_report_steps; -#endif // SLIC3R_TREESUPPORTS_PROGRESS - - std::mutex critical_sections; tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), - [&](const tbb::blocked_range &range) { + [&volumes, &config, &move_bounds, &linear_data, &branch_circle](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - const LayerIndex layer_idx = linear_data[idx].first; - const SupportElement *elem = linear_data[idx].second; - const auto it_elem = inverse_tree_order.find(const_cast(elem)); - const SupportElement* child_elem = it_elem == inverse_tree_order.end() ? nullptr : it_elem->second; - const coord_t radius = m_config.getRadius(*elem); + DrawArea &draw_area = linear_data[idx]; + const LayerIndex layer_idx = draw_area.element->state.layer_idx; + const coord_t radius = config.getRadius(*draw_area.element); bool parent_uses_min = false; // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; - if (!elem->skip_ovalisation) { - if (child_elem != nullptr) { - const Point movement = child_elem->result_on_layer - elem->result_on_layer; + if (! draw_area.element->state.skip_ovalisation) { + if (draw_area.child_element != nullptr) { + const Point movement = draw_area.child_element->state.result_on_layer - draw_area.element->state.result_on_layer; movement_directions.emplace_back(movement, radius); } - for (SupportElement *parent : elem->parents) { - const Point movement = parent->result_on_layer - elem->result_on_layer; - movement_directions.emplace_back(movement, std::max(m_config.getRadius(*parent), m_config.support_line_width)); - parent_uses_min |= parent->use_min_xy_dist; + const SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; + for (int32_t parent_idx : draw_area.element->parents) { + const SupportElement &parent = (*layer_above)[parent_idx]; + const Point movement = parent.state.result_on_layer - draw_area.element->state.result_on_layer; + //FIXME why max(..., config.support_line_width)? + movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); + parent_uses_min |= parent.state.use_min_xy_dist; } } - double max_speed = 0; - auto generateArea = [&volumes = m_volumes, layer_idx, elem, &branch_circle, branch_radius = m_config.branch_radius, support_line_width = m_config.support_line_width, &movement_directions, &max_speed, parent_uses_min]( - coord_t aoffset) { + const Polygons &collision = volumes.getCollision(0, layer_idx, parent_uses_min || draw_area.element->state.use_min_xy_dist); + auto generateArea = [&collision, &draw_area, &branch_circle, branch_radius = config.branch_radius, support_line_width = config.support_line_width, &movement_directions] + (coord_t aoffset, double &max_speed) { Polygons poly; - + max_speed = 0; for (std::pair movement : movement_directions) { max_speed = std::max(max_speed, movement.first.cast().norm()); // Visualization: https://jsfiddle.net/0zvcq39L/2/ // Ovalizes the circle to an ellipse, that contains both old center and new target position. double used_scale = (movement.second + aoffset) / (1.0 * branch_radius); - Point center_position = elem->result_on_layer + movement.first / 2; + Point center_position = draw_area.element->state.result_on_layer + movement.first / 2; const double moveX = movement.first.x() / (used_scale * branch_radius); const double moveY = movement.first.y() / (used_scale * branch_radius); const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); @@ -2462,47 +2615,49 @@ void TreeSupport::generateBranchAreas( poly.emplace_back(std::move(circle)); } - poly = diff_clipped(offset(union_(poly), std::min(coord_t(50), support_line_width / 4), jtMiter, 1.2), - volumes.getCollision(0, layer_idx, parent_uses_min || elem->use_min_xy_dist)); // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. + // There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. + // This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. This fixes it, and for every other part, 0.05mm will not be noticed. + poly = diff_clipped(offset(union_(poly), std::min(coord_t(50), support_line_width / 4), jtMiter, 1.2), collision); return poly; }; - bool fast_relative_movement = max_speed > radius * 0.75; + // Ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. + double max_speed; + Polygons polygons = generateArea(0, max_speed); + const bool fast_relative_movement = max_speed > radius * 0.75; - // ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. - linear_inserts[idx] = generateArea(0); - - if (fast_relative_movement || m_config.getRadius(*elem) - m_config.getCollisionRadius(*elem) > m_config.support_line_width) { - // simulate the path the nozzle will take on the outermost wall - // if multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air - ExPolygons nozzle_path = offset_ex(linear_inserts[idx], -m_config.support_line_width / 2); + if (fast_relative_movement || config.getRadius(*draw_area.element) - config.getCollisionRadius(draw_area.element->state) > config.support_line_width) { + // Simulate the path the nozzle will take on the outermost wall. + // If multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air. + ExPolygons nozzle_path = offset_ex(polygons, - config.support_line_width / 2); if (nozzle_path.size() > 1) { // Just try to make the area a tiny bit larger. - linear_inserts[idx] = generateArea(m_config.support_line_width / 2); - nozzle_path = offset_ex(linear_inserts[idx], -m_config.support_line_width / 2); - - // if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best + polygons = generateArea(config.support_line_width / 2, max_speed); + nozzle_path = offset_ex(polygons, -config.support_line_width / 2); + // If larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best. if (nozzle_path.size() > 1) { - Polygons polygons_with_correct_center; + ExPolygons polygons_with_correct_center; for (ExPolygon &part : nozzle_path) { - if (part.contains(elem->result_on_layer)) - polygons_with_correct_center = union_(polygons_with_correct_center, part); - else { + bool drop = false; + if (! part.contains(draw_area.element->state.result_on_layer)) { // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... - Point from = elem->result_on_layer; - Polygons to = to_polygons(std::move(part)); - moveInside(to, from, 0); - if ((elem->result_on_layer - from).cast().norm() < scaled(0.025)) - polygons_with_correct_center = union_(polygons_with_correct_center, to); + Point pt = draw_area.element->state.result_on_layer; + move_inside(to_polygons(part), pt, 0); + drop = (draw_area.element->state.result_on_layer - pt).cast().norm() >= scaled(0.025); } + if (! drop) + polygons_with_correct_center.emplace_back(std::move(part)); } // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. - linear_inserts[idx] = offset(polygons_with_correct_center, m_config.support_line_width / 2, jtMiter, 1.2); - linear_inserts[idx] = diff_clipped(linear_inserts[idx], m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); + polygons = diff_clipped(offset(polygons_with_correct_center, config.support_line_width / 2, jtMiter, 1.2), + //FIXME Vojtech: Clipping may split the region into multiple pieces again, reversing the fixing effort. + collision); } } } + draw_area.polygons = std::move(polygons); + #ifdef SLIC3R_TREESUPPORTS_PROGRESS if (idx % progress_inserts_check_interval == 0) { std::lock_guard critical_section_progress(critical_sections); @@ -2512,50 +2667,58 @@ void TreeSupport::generateBranchAreas( #endif } }); - - // single threaded combining all elements to the right layers. ONLY COPYS DATA! - for (coord_t i = 0; i < static_cast(linear_data.size()); i++) - layer_tree_polygons[linear_data[i].first].emplace(linear_data[i].second, linear_inserts[i]); } -void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons) +/*! + * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. + * + * \param layer_tree_polygons[in,out] Resulting branch areas with the layerindex they appear on. + */ +static void smooth_branch_areas( + const TreeSupportSettings &config, + std::vector &move_bounds, + std::vector &linear_data, + const std::vector &linear_data_layers) { #ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS; #endif // SLIC3R_TREESUPPORTS_PROGRESS - const coord_t max_radius_change_per_layer = 1 + m_config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors + const coord_t max_radius_change_per_layer = 1 + config.support_line_width / 2; // this is the upper limit a radius may change per layer. +1 to avoid rounding errors // smooth upwards - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()) - 1; ++ layer_idx) { - std::vector> processing; - processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); - std::vector>> update_next(processing.size()); // with this a lock can be avoided - - tbb::parallel_for(tbb::blocked_range(0, processing.size()), + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()) - 1; ++ layer_idx) { + const size_t processing_base = linear_data_layers[layer_idx]; + const size_t processing_base_above = linear_data_layers[layer_idx + 1]; + const SupportElements &layer_above = move_bounds[layer_idx + 1]; + tbb::parallel_for(tbb::blocked_range(0, processing_base_above - processing_base), [&](const tbb::blocked_range &range) { for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { - std::pair data_pair = processing[processing_idx]; - double max_outer_wall_distance = 0; - bool do_something = false; - for (SupportElement* parent : data_pair.first->parents) - if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) { + DrawArea &draw_area = linear_data[processing_base + processing_idx]; + double max_outer_wall_distance = 0; + bool do_something = false; + for (int32_t parent_idx : draw_area.element->parents) { + const SupportElement &parent = layer_above[parent_idx]; + if (config.getRadius(parent.state) != config.getCollisionRadius(parent.state)) { do_something = true; - max_outer_wall_distance = std::max(max_outer_wall_distance, (data_pair.first->result_on_layer - parent->result_on_layer).cast().norm() - (m_config.getRadius(*data_pair.first) - m_config.getRadius(*parent))); + max_outer_wall_distance = std::max(max_outer_wall_distance, (draw_area.element->state.result_on_layer - parent.state.result_on_layer).cast().norm() - (config.getRadius(*draw_area.element) - config.getRadius(parent))); } + } max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. if (do_something) { - Polygons max_allowed_area = offset(data_pair.second, float(max_outer_wall_distance), jtMiter, 1.2); - for (SupportElement* parent : data_pair.first->parents) - if (m_config.getRadius(*parent) != m_config.getCollisionRadius(*parent)) - update_next[processing_idx].emplace_back(std::pair(parent, intersection(layer_tree_polygons[layer_idx + 1][parent], max_allowed_area))); + Polygons max_allowed_area = offset(draw_area.polygons, float(max_outer_wall_distance), jtMiter, 1.2); + for (int32_t parent_idx : draw_area.element->parents) { + const SupportElement &parent = layer_above[parent_idx]; + if (config.getRadius(parent.state) != config.getCollisionRadius(parent.state)) { + // No other element on this layer than the current one may be connected to &parent, + // thus it is safe to update parent's DrawArea directly. + Polygons &dst = linear_data[processing_base_above + parent_idx].polygons; + dst = intersection(dst, max_allowed_area); + } + } } } }); - - for (std::vector> data_vector : update_next) - for (std::pair data_pair : data_vector) - layer_tree_polygons[layer_idx + 1][data_pair.first] = data_pair.second; } #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -2564,45 +2727,41 @@ void TreeSupport::smoothBranchAreas(std::vector updated_last_iteration; - for (int layer_idx = int(layer_tree_polygons.size()) - 2; layer_idx >= 0; -- layer_idx) { - std::vector> processing; - processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); - std::vector> update_next(processing.size(), std::pair(nullptr, Polygons())); // with this a lock can be avoided - - tbb::parallel_for(tbb::blocked_range(0, processing.size()), + for (auto& element : move_bounds.back()) + element.state.marked = false; + for (int layer_idx = int(move_bounds.size()) - 2; layer_idx >= 0; -- layer_idx) { + const size_t processing_base = linear_data_layers[layer_idx]; + const size_t processing_base_above = linear_data_layers[layer_idx + 1]; + const SupportElements &layer_above = move_bounds[layer_idx + 1]; + tbb::parallel_for(tbb::blocked_range(0, processing_base_above - processing_base), [&](const tbb::blocked_range &range) { for (size_t processing_idx = range.begin(); processing_idx < range.end(); ++ processing_idx) { - std::pair data_pair = processing[processing_idx]; - bool do_something = false; + DrawArea &draw_area = linear_data[processing_base + processing_idx]; + bool do_something = false; Polygons max_allowed_area; - for (size_t idx = 0; idx < data_pair.first->parents.size(); ++ idx) { - SupportElement* parent = data_pair.first->parents[idx]; + for (int32_t parent_idx : draw_area.element->parents) { + const SupportElement &parent = layer_above[parent_idx]; coord_t max_outer_line_increase = max_radius_change_per_layer; - Polygons result = offset(layer_tree_polygons[layer_idx + 1][parent], max_outer_line_increase, jtMiter, 1.2); - Point direction = data_pair.first->result_on_layer - parent->result_on_layer; + Polygons result = offset(linear_data[processing_base_above + parent_idx].polygons, max_outer_line_increase, jtMiter, 1.2); + Point direction = draw_area.element->state.result_on_layer - parent.state.result_on_layer; // move the polygons object - for (auto& outer : result) + for (auto &outer : result) for (Point& p : outer) p += direction; append(max_allowed_area, std::move(result)); - do_something = do_something || updated_last_iteration.count(parent) || m_config.getCollisionRadius(*parent) != m_config.getRadius(*parent); + do_something = do_something || parent.state.marked || config.getCollisionRadius(parent.state) != config.getRadius(parent.state); } - if (do_something) { - Polygons result = intersection(max_allowed_area, data_pair.second); - if (area(result) < area(data_pair.second)) - update_next[processing_idx] = std::pair(data_pair.first, result); + // Trim the current drawing areas with max_allowed_area. + Polygons result = intersection(max_allowed_area, draw_area.polygons); + if (area(result) < area(draw_area.polygons)) { + // Mark parent as modified to propagate down. + draw_area.element->state.marked = true; + draw_area.polygons = std::move(result); + } } } }); - - updated_last_iteration.clear(); - for (std::pair data_pair : update_next) - if (data_pair.first != nullptr) { - updated_last_iteration.emplace(data_pair.first); - layer_tree_polygons[layer_idx][data_pair.first] = data_pair.second; - } } #ifdef SLIC3R_TREESUPPORTS_PROGRESS @@ -2611,32 +2770,50 @@ void TreeSupport::smoothBranchAreas(std::vector> &layer_tree_polygons, - const std::vector> &linear_data, - std::vector>> &dropped_down_areas, - const std::map &inverse_tree_order) +/*! + * \brief Drop down areas that do rest non-gracefully on the model to ensure the branch actually rests on something. + * + * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. + * \param linear_data[in] All currently existing influence areas with the layer they are on + * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. + * \param inverse_tree_order[in] A mapping that returns the child of every influence area. + */ +static void drop_non_gracious_areas( + const TreeModelVolumes &volumes, + const std::vector &linear_data, + std::vector &support_layer_storage) { + std::vector>> dropped_down_areas(linear_data.size()); tbb::parallel_for(tbb::blocked_range(0, linear_data.size()), [&](const tbb::blocked_range &range) { - for (size_t idx = range.begin(); idx < range.end(); ++ idx) { - SupportElement* elem = linear_data[idx].second; - bool non_gracious_model_contact = !elem->to_model_gracious && !inverse_tree_order.count(elem); // if a element has no child, it connects to whatever is below as no support further down for it will exist. - if (non_gracious_model_contact) { - Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem]; - LayerIndex counter = 1; - while (area(rest_support) > tiny_area_threshold && counter < linear_data[idx].first) { - rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter, false)); - dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); - counter++; + for (size_t idx = range.begin(); idx < range.end(); ++ idx) + // If a element has no child, it connects to whatever is below as no support further down for it will exist. + if (const DrawArea &draw_element = linear_data[idx]; ! draw_element.element->state.to_model_gracious && draw_element.child_element == nullptr) { + Polygons rest_support; + const LayerIndex layer_idx_first = draw_element.element->state.layer_idx - 1; + for (LayerIndex layer_idx = layer_idx_first; area(rest_support) > tiny_area_threshold && layer_idx >= 0; -- layer_idx) { + rest_support = diff_clipped(layer_idx == layer_idx_first ? draw_element.polygons : rest_support, volumes.getCollision(0, layer_idx, false)); + dropped_down_areas[idx].emplace_back(layer_idx, rest_support); } } - } }); + + for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) + for (std::pair &pair : dropped_down_areas[i]) + append(support_layer_storage[pair.first], std::move(pair.second)); } -void TreeSupport::finalizeInterfaceAndSupportAreas( +/*! + * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage + * + * \param support_layer_storage[in] Areas where support should be generated. + * \param support_roof_storage[in] Areas where support was replaced with roof. + * \param storage[in,out] The storage where the support should be stored. + */ +static void finalize_interface_and_support_areas( const PrintObject &print_object, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, const std::vector &overhangs, std::vector &support_layer_storage, std::vector &support_roof_storage, @@ -2646,7 +2823,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( SupportGeneratorLayersPtr &intermediate_layers, SupportGeneratorLayerStorage &layer_storage) { - InterfacePreference interface_pref = m_config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; + InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SupportLinesOverwriteInterface; #ifdef SLIC3R_TREESUPPORTS_PROGRESS double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; @@ -2658,11 +2835,11 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( [&](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { // Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned. - support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), m_config.support_line_width); //FIXME was .smooth(50); + support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), config.support_line_width); //FIXME was .smooth(50); //smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) : // simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. - support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(m_config.resolution))); + support_layer_storage[layer_idx] = polygons_simplify(support_layer_storage[layer_idx], std::min(scaled(0.03), double(config.resolution))); // Subtract support lines of the branches from the roof SupportGeneratorLayer*& support_roof = top_contacts[layer_idx]; if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) { @@ -2675,60 +2852,60 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( if (! support_roof->polygons.empty() && area(intersection(support_layer_storage[layer_idx], support_roof->polygons)) > tiny_area_threshold) { switch (interface_pref) { - case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: + case InterfacePreference::InterfaceAreaOverwritesSupport: support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], support_roof->polygons); break; - case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: + case InterfacePreference::SupportAreaOverwritesInterface: support_roof->polygons = diff(support_roof->polygons, support_layer_storage[layer_idx]); break; //FIXME #if 1 - case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: - case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: + case InterfacePreference::InterfaceLinesOverwriteSupport: + case InterfacePreference::SupportLinesOverwriteInterface: assert(false); [[fallthrough]]; #else - case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: + case InterfacePreference::InterfaceLinesOverwriteSupport: { // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. Polygons interface_lines = offset(to_polylines( - generateSupportInfillLines(support_roof->polygons, true, layer_idx, m_config.support_roof_line_distance)), - m_config.support_roof_line_width / 2); + generate_support_infill_lines(support_roof->polygons, true, layer_idx, config.support_roof_line_distance)), + config.support_roof_line_width / 2); support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], interface_lines); break; } - case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: + case InterfacePreference::SupportLinesOverwriteInterface: { // Hatch the support roof interfaces, offset them by their line width and subtract them from support base. Polygons tree_lines = union_(offset(to_polylines( - generateSupportInfillLines(support_layer_storage[layer_idx], false, layer_idx, m_config.support_line_distance, true)), - m_config.support_line_width / 2)); + generate_support_infill_lines(support_layer_storage[layer_idx], false, layer_idx, config.support_line_distance, true)), + config.support_line_width / 2)); // do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. support_roof->polygons = diff(support_roof->polygons, tree_lines); break; } #endif - case InterfacePreference::NOTHING: + case InterfacePreference::Nothing: break; } } } // Subtract support floors from the support area and add them to the support floor instead. - if (m_config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { + if (config.support_bottom_layers > 0 && !support_layer_storage[layer_idx].empty()) { SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx]; Polygons layer_outset = diff_clipped( - m_config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], m_config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], - m_volumes.getCollision(0, layer_idx, false)); + config.support_bottom_offset > 0 ? offset(support_layer_storage[layer_idx], config.support_bottom_offset, jtMiter, 1.2) : support_layer_storage[layer_idx], + volumes.getCollision(0, layer_idx, false)); Polygons floor_layer; size_t layers_below = 0; - while (layers_below <= m_config.support_bottom_layers) { - // one sample at 0 layers below, another at m_config.support_bottom_layers. In-between samples at m_config.performance_interface_skip_layers distance from each other. - const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(m_config.z_distance_bottom_layers))); + while (layers_below <= config.support_bottom_layers) { + // one sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. + const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); //FIXME subtract the wipe tower append(floor_layer, intersection(layer_outset, overhangs[sample_layer])); - if (layers_below < m_config.support_bottom_layers) - layers_below = std::min(layers_below + m_config.performance_interface_skip_layers, m_config.support_bottom_layers); + if (layers_below < config.support_bottom_layers) + layers_below = std::min(layers_below + config.performance_interface_skip_layers, config.support_bottom_layers); else break; } @@ -2765,67 +2942,93 @@ void TreeSupport::finalizeInterfaceAndSupportAreas( }); } -void TreeSupport::drawAreas( - PrintObject &print_object, - const std::vector &overhangs, - std::vector> &move_bounds, +/*! + * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. + * + * \param move_bounds[in] All currently existing influence areas + * \param storage[in,out] The storage where the support should be stored. + */ +static void draw_areas( + PrintObject &print_object, + const TreeModelVolumes &volumes, + const TreeSupportSettings &config, + const std::vector &overhangs, + std::vector &move_bounds, - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage) + SupportGeneratorLayersPtr &bottom_contacts, + SupportGeneratorLayersPtr &top_contacts, + SupportGeneratorLayersPtr &intermediate_layers, + SupportGeneratorLayerStorage &layer_storage) { std::vector support_layer_storage(move_bounds.size()); std::vector support_roof_storage(move_bounds.size()); - std::map inverese_tree_order; // in the tree structure only the parents can be accessed. Inverse this to be able to access the children. - std::vector> linear_data; // All SupportElements are put into a layer independent storage to improve parallelization. Was added at a point in time where this function had performance issues. - // These were fixed by creating less initial points, but i do not see a good reason to remove a working performance optimization. - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { - for (SupportElement* elem : move_bounds[layer_idx]) { - if ((layer_idx > 0 && ((!inverese_tree_order.count(elem) && elem->target_height == layer_idx) || (inverese_tree_order.count(elem) && inverese_tree_order[elem]->result_on_layer == Point(-1, -1))))) // we either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure - continue; - for (SupportElement* par : elem->parents) - if (par->result_on_layer != Point(-1, -1)) - inverese_tree_order.emplace(par, elem); - linear_data.emplace_back(layer_idx, elem); + // All SupportElements are put into a layer independent storage to improve parallelization. + std::vector linear_data; + std::vector linear_data_layers; + { + std::vector> map_downwards_old; + std::vector> map_downwards_new; + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { + SupportElements *layer_above = layer_idx + 1 < move_bounds.size() ? &move_bounds[layer_idx + 1] : nullptr; + map_downwards_new.clear(); + linear_data_layers.emplace_back(linear_data.size()); + std::sort(map_downwards_old.begin(), map_downwards_old.end(), [](auto &l, auto &r) { return l.first < r.first; }); + for (SupportElement &elem : move_bounds[layer_idx]) { + SupportElement *child = nullptr; + if (layer_idx > 0) { + auto it = std::lower_bound(map_downwards_old.begin(), map_downwards_old.end(), &elem, [](auto &l, const SupportElement *r) { return l.first < r; }); + if (it != map_downwards_old.end() && it->first == &elem) { + child = it->second; + // Only one link points to a node above from below. + assert(! (++ it != map_downwards_old.end() && it->first == &elem)); + } + if ((! child && elem.state.target_height == layer_idx) || (child && !child->state.result_on_layer_is_set())) + // We either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure + continue; + } + for (int32_t parent_idx : elem.parents) { + SupportElement &parent = (*layer_above)[parent_idx]; + if (parent.state.result_on_layer_is_set()) + map_downwards_new.emplace_back(&parent, &elem); + } + linear_data.push_back({ &elem, child }); + } + std::swap(map_downwards_old, map_downwards_new); } + linear_data_layers.emplace_back(linear_data.size()); } - std::vector> layer_tree_polygons(move_bounds.size()); // reorder the processed data by layers again. The map also could be a vector>. auto t_start = std::chrono::high_resolution_clock::now(); // Generate the circles that will be the branches. - generateBranchAreas(linear_data, layer_tree_polygons, inverese_tree_order); + generate_branch_areas(volumes, config, move_bounds, linear_data); auto t_generate = std::chrono::high_resolution_clock::now(); // In some edgecases a branch may go though a hole, where the regular radius does not fit. This can result in an apparent jump in branch radius. As such this cases need to be caught and smoothed out. - smoothBranchAreas(layer_tree_polygons); + smooth_branch_areas(config, move_bounds, linear_data, linear_data_layers); auto t_smooth = std::chrono::high_resolution_clock::now(); // drop down all trees that connect non gracefully with the model - std::vector>> dropped_down_areas(linear_data.size()); - dropNonGraciousAreas(layer_tree_polygons, linear_data, dropped_down_areas, inverese_tree_order); + drop_non_gracious_areas(volumes, linear_data, support_layer_storage); auto t_drop = std::chrono::high_resolution_clock::now(); - // single threaded combining all dropped down support areas to the right layers. ONLY COPYS DATA! - for (coord_t i = 0; i < static_cast(dropped_down_areas.size()); i++) - for (std::pair &pair : dropped_down_areas[i]) - append(support_layer_storage[pair.first], std::move(pair.second)); - // single threaded combining all support areas to the right layers. ONLY COPYS DATA! - for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()); ++ layer_idx) { - auto &this_layer_tree_polygons = layer_tree_polygons[layer_idx]; - auto &this_roofs = support_roof_storage[layer_idx]; - auto &this_layers = support_layer_storage[layer_idx]; - size_t cnt_roofs = 0; - size_t cnt_layers = 0; - for (const std::pair &data_pair : this_layer_tree_polygons) - ++ (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? cnt_roofs : cnt_layers); - this_roofs.reserve(this_roofs.size() + cnt_roofs); - this_layers.reserve(this_layers.size() + cnt_layers); - for (const std::pair &data_pair : this_layer_tree_polygons) { - auto &src = const_cast(data_pair.second); - std::move(std::begin(src), std::end(src), std::back_inserter(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? this_roofs : this_layers)); + // Single threaded combining all support areas to the right layers. + { + auto begin = linear_data.begin(); + for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(move_bounds.size()); ++ layer_idx) { + size_t cnt_roofs = 0; + size_t cnt_layers = 0; + auto end = begin; + for (; end != linear_data.end() && end->element->state.layer_idx == layer_idx; ++ end) + ++ (end->element->state.missing_roof_layers > end->element->state.distance_to_top ? cnt_roofs : cnt_layers); + auto &this_roofs = support_roof_storage[layer_idx]; + auto &this_layers = support_layer_storage[layer_idx]; + this_roofs.reserve(this_roofs.size() + cnt_roofs); + this_layers.reserve(this_layers.size() + cnt_layers); + for (auto it = begin; it != end; ++ it) + std::move(std::begin(it->polygons), std::end(it->polygons), std::back_inserter(it->element->state.missing_roof_layers > it->element->state.distance_to_top ? this_roofs : this_layers)); + begin = end; } } - finalizeInterfaceAndSupportAreas(print_object, overhangs, support_layer_storage, support_roof_storage, + finalize_interface_and_support_areas(print_object, volumes, config, overhangs, support_layer_storage, support_roof_storage, bottom_contacts, top_contacts, intermediate_layers, layer_storage); auto t_end = std::chrono::high_resolution_clock::now(); @@ -2835,10 +3038,206 @@ void TreeSupport::drawAreas( auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); BOOST_LOG_TRIVIAL(info) << - "Time used for drawing subfuctions: generateBranchAreas: " << dur_gen_tips << " ms " - "smoothBranchAreas: " << dur_smooth << " ms " - "dropNonGraciousAreas: " << dur_drop << " ms " - "finalizeInterfaceAndSupportAreas " << dur_finalize << " ms"; + "Time used for drawing subfuctions: generate_branch_areas: " << dur_gen_tips << " ms " + "smooth_branch_areas: " << dur_smooth << " ms " + "drop_non_gracious_areas: " << dur_drop << " ms " + "finalize_interface_and_support_areas " << dur_finalize << " ms"; +} + +/*! + * \brief Create the areas that need support. + * + * These areas are stored inside the given SliceDataStorage object. + * \param storage The data storage where the mesh data is gotten from and + * where the resulting support areas are stored. + */ +static void generate_support_areas(Print &print, const BuildVolume &build_volume, const std::vector &print_object_ids, std::function throw_on_cancel) +{ + g_showed_critical_error = false; + g_showed_performance_warning = false; + + // Settings with the indexes of meshes that use these settings. + std::vector>> grouped_meshes = group_meshes(print, print_object_ids); + if (grouped_meshes.empty()) + return; + + size_t counter = 0; + + // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. + for (std::pair> &processing : grouped_meshes) + { + // process each combination of meshes + // this struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generate_initial_areas() have knowledge of the existence of multiple meshes being processed. + //FIXME this is a copy + // Contains config settings to avoid loading them in every function. This was done to improve readability of the code. + const TreeSupportSettings &config = processing.first; + BOOST_LOG_TRIVIAL(info) << "Processing support tree mesh group " << counter + 1 << " of " << grouped_meshes.size() << " containing " << grouped_meshes[counter].second.size() << " meshes."; + auto t_start = std::chrono::high_resolution_clock::now(); +#if 0 + std::vector exclude(num_support_layers); + // get all already existing support areas and exclude them + tbb::parallel_for(tbb::blocked_range(0, num_support_layers), + [&](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + Polygons exlude_at_layer; + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_bottom); + append(exlude_at_layer, storage.support.supportLayers[layer_idx].support_roof); + for (auto part : storage.support.supportLayers[layer_idx].support_infill_parts) + append(exlude_at_layer, part.outline); + exclude[layer_idx] = union_(exlude_at_layer); + } + }); +#endif +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + m_progress_multiplier = 1.0 / double(grouped_meshes.size()); + m_progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * m_progress_multiplier); +#endif // SLIC3R_TREESUPPORT_PROGRESS + PrintObject &print_object = *print.get_object(processing.second.front()); + // Generator for model collision, avoidance and internal guide volumes. + TreeModelVolumes volumes{ print_object, build_volume, config.maximum_move_distance, config.maximum_move_distance_slow, processing.second.front(), +#ifdef SLIC3R_TREESUPPORTS_PROGRESS + m_progress_multiplier, m_progress_offset, +#endif // SLIC3R_TREESUPPORTS_PROGRESS + /* additional_excluded_areas */{} }; + + //FIXME generating overhangs just for the furst mesh of the group. + assert(processing.second.size() == 1); + std::vector overhangs = generate_overhangs(*print.get_object(processing.second.front())); + + // ### Precalculate avoidances, collision etc. + size_t num_support_layers = precalculate(print, overhangs, processing.first, processing.second, volumes); + if (num_support_layers == 0) + continue; + + auto t_precalc = std::chrono::high_resolution_clock::now(); + + // value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas + std::vector move_bounds(num_support_layers); + + // ### Place tips of the support tree + SupportGeneratorLayersPtr bottom_contacts(num_support_layers, nullptr); + SupportGeneratorLayersPtr top_contacts(num_support_layers, nullptr); + SupportGeneratorLayersPtr top_interface_layers(num_support_layers, nullptr); + SupportGeneratorLayersPtr intermediate_layers(num_support_layers, nullptr); + SupportGeneratorLayerStorage layer_storage; + + for (size_t mesh_idx : processing.second) + generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs, move_bounds, top_contacts, top_interface_layers, layer_storage); + auto t_gen = std::chrono::high_resolution_clock::now(); + +#ifdef TREESUPPORT_DEBUG_SVG + for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { + Polygons polys; + for (auto& area : move_bounds[layer_idx]) + append(polys, area->influence_area); + if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) + SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), + { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius((*begin)->state), layer_idx, (*begin)->state.use_min_xy_dist)) }, + { "wall_restricrictions", "gray", 0.5f } }, + { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); + } +#endif // TREESUPPORT_DEBUG_SVG + + // ### Propagate the influence areas downwards. This is an inherently serial operation. + create_layer_pathing(volumes, config, move_bounds); + auto t_path = std::chrono::high_resolution_clock::now(); + + // ### Set a point in each influence area + create_nodes_from_area(volumes, config, move_bounds); + auto t_place = std::chrono::high_resolution_clock::now(); + + // ### draw these points as circles + draw_areas(*print.get_object(processing.second.front()), volumes, config, overhangs, move_bounds, + bottom_contacts, top_contacts, intermediate_layers, layer_storage); + + auto t_draw = std::chrono::high_resolution_clock::now(); + auto dur_pre_gen = 0.001 * std::chrono::duration_cast(t_precalc - t_start).count(); + auto dur_gen = 0.001 * std::chrono::duration_cast(t_gen - t_precalc).count(); + auto dur_path = 0.001 * std::chrono::duration_cast(t_path - t_gen).count(); + auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); + auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); + auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); + BOOST_LOG_TRIVIAL(info) << + "Total time used creating Tree support for the currently grouped meshes: " << dur_total << " ms. " + "Different subtasks:\nCalculating Avoidance: " << dur_pre_gen << " ms " + "Creating inital influence areas: " << dur_gen << " ms " + "Influence area creation: " << dur_path << "ms " + "Placement of Points in InfluenceAreas: " << dur_place << "ms " + "Drawing result as support " << dur_draw << " ms"; +// if (config.branch_radius==2121) +// BOOST_LOG_TRIVIAL(error) << "Why ask questions when you already know the answer twice.\n (This is not a real bug, please dont report it.)"; + + move_bounds.clear(); + + auto remove_undefined_layers = [](SupportGeneratorLayersPtr &layers) { + layers.erase(std::remove_if(layers.begin(), layers.end(), [](const SupportGeneratorLayer* ptr) { return ptr == nullptr; }), layers.end()); + }; + remove_undefined_layers(bottom_contacts); + remove_undefined_layers(top_contacts); + remove_undefined_layers(intermediate_layers); + + // Produce the support G-code. + // Used by both classic and tree supports. + SupportParameters support_params(print_object); + support_params.with_sheath = true; + support_params.support_density = 0; + SupportGeneratorLayersPtr interface_layers, base_interface_layers; + SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, support_params, print_object.slicing_parameters(), top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage); +#if 1 //#ifdef SLIC3R_DEBUG + SupportGeneratorLayersPtr layers_sorted = +#endif // SLIC3R_DEBUG + generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + // Don't fill in the tree supports, make them hollow with just a single sheath line. + generate_support_toolpaths(print_object.support_layers(), print_object.config(), support_params, print_object.slicing_parameters(), + raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers); + + #if 0 +//#ifdef SLIC3R_DEBUG + { + static int iRun = 0; + ++ iRun; + size_t layer_id = 0; + for (int i = 0; i < int(layers_sorted.size());) { + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. + int j = i + 1; + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + bool empty = layers_sorted[i]->polygons.empty(); + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) + if (!layers_sorted[j]->polygons.empty()) + empty = false; + if (!empty) { + export_print_z_polygons_to_svg( + debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), + layers_sorted.data() + i, j - i); + export_print_z_polygons_and_extrusions_to_svg( + debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), + layers_sorted.data() + i, j - i, + *print_object.support_layers()[layer_id]); + ++layer_id; + } + i = j; + } + } +#endif /* SLIC3R_DEBUG */ + + ++ counter; + } + +// storage.support.generated = true; +} + +} // namespace FFFTreeSupport + +void fff_tree_support_generate(PrintObject &print_object, std::function throw_on_cancel) +{ + size_t idx = 0; + for (PrintObject* po : print_object.print()->objects()) { + if (po == &print_object) + break; + ++idx; + } + FFFTreeSupport::generate_support_areas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -300., -300. }, Vec2d{ -300., +300. }, Vec2d{ +300., +300. }, Vec2d{ +300., -300. } }, 0.), { idx }, throw_on_cancel); } } // namespace Slic3r diff --git a/src/libslic3r/TreeSupport.hpp b/src/libslic3r/TreeSupport.hpp index 75c32e54d..0d5e967d9 100644 --- a/src/libslic3r/TreeSupport.hpp +++ b/src/libslic3r/TreeSupport.hpp @@ -12,15 +12,13 @@ #include "TreeModelVolumes.hpp" #include "Point.hpp" -#include // For combining hashes +#include #include "BoundingBox.hpp" #include "Utils.hpp" #define TREE_SUPPORT_SHOW_ERRORS -#define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle. - #ifdef SLIC3R_TREESUPPORTS_PROGRESS // The various stages of the process can be weighted differently in the progress bar. // These weights are obtained experimentally using a small sample size. Sensible weights can differ drastically based on the assumed default settings and model. @@ -35,842 +33,556 @@ #define TREE_PROGRESS_FINALIZE_BRANCH_AREAS TREE_PROGRESS_DRAW_AREAS / 3 #endif // SLIC3R_TREESUPPORTS_PROGRESS -#define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false -#define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true - namespace Slic3r { +// Forward declarations +class Print; +class PrintObject; +class SupportGeneratorLayer; +using SupportGeneratorLayerStorage = std::deque; +using SupportGeneratorLayersPtr = std::vector; + +namespace FFFTreeSupport +{ + using LayerIndex = int; static constexpr const double SUPPORT_TREE_EXPONENTIAL_FACTOR = 1.5; static constexpr const coord_t SUPPORT_TREE_EXPONENTIAL_THRESHOLD = scaled(1. * SUPPORT_TREE_EXPONENTIAL_FACTOR); static constexpr const coord_t SUPPORT_TREE_COLLISION_RESOLUTION = scaled(0.5); -//FIXME -class Print; -class PrintObject; -class SupportGeneratorLayer; -using SupportGeneratorLayerStorage = std::deque; -using SupportGeneratorLayersPtr = std::vector; -/*! - * \brief Generates a tree structure to support your models. - */ +// The number of vertices in each circle. +static constexpr const size_t SUPPORT_TREE_CIRCLE_RESOLUTION = 25; +static constexpr const bool SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true; -class TreeSupport +enum class InterfacePreference { -public: - using AvoidanceType = TreeModelVolumes::AvoidanceType; - enum class InterfacePreference - { - INTERFACE_AREA_OVERWRITES_SUPPORT, - SUPPORT_AREA_OVERWRITES_INTERFACE, - INTERFACE_LINES_OVERWRITE_SUPPORT, - SUPPORT_LINES_OVERWRITE_INTERFACE, - NOTHING - }; - - /*! - * \brief Creates an instance of the tree support generator. - */ - TreeSupport() = default; - - /*! - * \brief Create the areas that need support. - * - * These areas are stored inside the given SliceDataStorage object. - * \param storage The data storage where the mesh data is gotten from and - * where the resulting support areas are stored. - */ - void generateSupportAreas(Print &print, const BuildVolume &build_volume, const std::vector& print_object_ids); - void generateSupportAreas(PrintObject &print_object); - - //todo Remove! Only relevant for public BETA! - static bool inline showed_critical=false; - static bool inline showed_performance=false; - static void showError(std::string message,bool critical); - - struct TreeSupportSettings; // forward declaration as we need some config values in the merge case - - struct AreaIncreaseSettings - { - AvoidanceType type { AvoidanceType::Fast }; - coord_t increase_speed { 0 }; - bool increase_radius { false }; - bool no_error { false }; - bool use_min_distance { false }; - bool move { false }; - bool operator==(const AreaIncreaseSettings& other) const - { - return increase_radius == other.increase_radius && increase_speed == other.increase_speed && type == other.type && - no_error == other.no_error && use_min_distance == other.use_min_distance && move == other.move; - } - }; - - struct SupportElement - { - explicit SupportElement( - coord_t distance_to_top, size_t target_height, Point target_position, bool to_buildplate, bool to_model_gracious, bool use_min_xy_dist, size_t dont_move_until, - bool supports_roof, bool can_use_safe_radius, bool force_tips_to_roof, bool skip_ovalisation) : - target_height(target_height), target_position(target_position), next_position(target_position), next_height(target_height), effective_radius_height(distance_to_top), - to_buildplate(to_buildplate), distance_to_top(distance_to_top), area(nullptr), result_on_layer(target_position), increased_to_model_radius(0), to_model_gracious(to_model_gracious), - elephant_foot_increases(0), use_min_xy_dist(use_min_xy_dist), supports_roof(supports_roof), dont_move_until(dont_move_until), can_use_safe_radius(can_use_safe_radius), - last_area_increase(AreaIncreaseSettings{ AvoidanceType::Fast, 0, false, false, false, false }), missing_roof_layers(force_tips_to_roof ? dont_move_until : 0), skip_ovalisation(skip_ovalisation) - { - } - - - explicit SupportElement(const SupportElement& elem, Polygons* newArea = nullptr) - : // copy constructor with possibility to set a new area - target_height(elem.target_height), - target_position(elem.target_position), - next_position(elem.next_position), - next_height(elem.next_height), - effective_radius_height(elem.effective_radius_height), - to_buildplate(elem.to_buildplate), - distance_to_top(elem.distance_to_top), - area(newArea != nullptr ? newArea : elem.area), - result_on_layer(elem.result_on_layer), - increased_to_model_radius(elem.increased_to_model_radius), - to_model_gracious(elem.to_model_gracious), - elephant_foot_increases(elem.elephant_foot_increases), - use_min_xy_dist(elem.use_min_xy_dist), - supports_roof(elem.supports_roof), - dont_move_until(elem.dont_move_until), - can_use_safe_radius(elem.can_use_safe_radius), - last_area_increase(elem.last_area_increase), - missing_roof_layers(elem.missing_roof_layers), - skip_ovalisation(elem.skip_ovalisation) - - { - parents.insert(parents.begin(), elem.parents.begin(), elem.parents.end()); - } - - /*! - * \brief Create a new Element for one layer below the element of the pointer supplied. - */ - - explicit SupportElement(SupportElement* element_above) - : target_height(element_above->target_height), - target_position(element_above->target_position), - next_position(element_above->next_position), - next_height(element_above->next_height), - effective_radius_height(element_above->effective_radius_height), - to_buildplate(element_above->to_buildplate), - distance_to_top(element_above->distance_to_top + 1), - area(element_above->area), - result_on_layer(Point(-1, -1)), // set to invalid as we are a new node on a new layer - increased_to_model_radius(element_above->increased_to_model_radius), - to_model_gracious(element_above->to_model_gracious), - elephant_foot_increases(element_above->elephant_foot_increases), - use_min_xy_dist(element_above->use_min_xy_dist), - supports_roof(element_above->supports_roof), - dont_move_until(element_above->dont_move_until), - can_use_safe_radius(element_above->can_use_safe_radius), - last_area_increase(element_above->last_area_increase), - missing_roof_layers(element_above->missing_roof_layers), - skip_ovalisation(false) - { - parents = { element_above }; - } - - // ONLY to be called in merge as it assumes a few assurances made by it. - explicit SupportElement( - const SupportElement& first, const SupportElement& second, size_t next_height, Point next_position, - coord_t increased_to_model_radius, const TreeSupportSettings& config) : - next_position(next_position), next_height(next_height), area(nullptr), increased_to_model_radius(increased_to_model_radius), - use_min_xy_dist(first.use_min_xy_dist || second.use_min_xy_dist), supports_roof(first.supports_roof || second.supports_roof), - dont_move_until(std::max(first.dont_move_until, second.dont_move_until)), can_use_safe_radius(first.can_use_safe_radius || second.can_use_safe_radius), - missing_roof_layers(std::min(first.missing_roof_layers, second.missing_roof_layers)), skip_ovalisation(false) - - { - if (first.target_height > second.target_height) { - target_height = first.target_height; - target_position = first.target_position; - } else { - target_height = second.target_height; - target_position = second.target_position; - } - effective_radius_height = std::max(first.effective_radius_height, second.effective_radius_height); - distance_to_top = std::max(first.distance_to_top, second.distance_to_top); - - to_buildplate = first.to_buildplate && second.to_buildplate; - to_model_gracious = first.to_model_gracious && second.to_model_gracious; // valid as we do not merge non-gracious with gracious - - AddParents(first.parents); - AddParents(second.parents); - - elephant_foot_increases = 0; - if (config.diameter_scale_bp_radius > 0) { - coord_t foot_increase_radius = std::abs(std::max(config.getCollisionRadius(second), config.getCollisionRadius(first)) - config.getCollisionRadius(*this)); - // elephant_foot_increases has to be recalculated, as when a smaller tree with a larger elephant_foot_increases merge with a larger branch - // the elephant_foot_increases may have to be lower as otherwise the radius suddenly increases. This results often in a non integer value. - elephant_foot_increases = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); - } - - // set last settings to the best out of both parents. If this is wrong, it will only cause a small performance penalty instead of weird behavior. - last_area_increase = { - std::min(first.last_area_increase.type, second.last_area_increase.type), - std::min(first.last_area_increase.increase_speed, second.last_area_increase.increase_speed), - first.last_area_increase.increase_radius || second.last_area_increase.increase_radius, - first.last_area_increase.no_error || second.last_area_increase.no_error, - first.last_area_increase.use_min_distance && second.last_area_increase.use_min_distance, - first.last_area_increase.move || second.last_area_increase.move }; - } - - /*! - * \brief The layer this support elements wants reach - */ - LayerIndex target_height; - - /*! - * \brief The position this support elements wants to support on layer=target_height - */ - Point target_position; - - /*! - * \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area. - */ - Point next_position; - - - /*! - * \brief The next height this support elements wants to reach - */ - LayerIndex next_height; - - /*! - * \brief The Effective distance to top of this element regarding radius increases and collision calculations. - */ - - size_t effective_radius_height; - - /*! - * \brief The element trys to reach the buildplate - */ - - bool to_buildplate; - - /*! - * \brief All elements in the layer above the current one that are supported by this element - */ - std::vector parents; - - /*! - * \brief The amount of layers this element is below the topmost layer of this branch. - */ - size_t distance_to_top; - - /*! - * \brief The resulting influence area. - * Will only be set in the results of createLayerPathing, and will be nullptr inside! - */ - Polygons* area; - - /*! - * \brief The resulting center point around which a circle will be drawn later. - * Will be set by setPointsOnAreas - */ - Point result_on_layer = Point(-1, -1); - /*! - * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. - */ - coord_t increased_to_model_radius; // how much to model we increased only relevant for merging - /*! - * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? - */ - bool to_model_gracious; - - /*! - * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. - */ - double elephant_foot_increases; - - /*! - * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. - */ - bool use_min_xy_dist; - - /*! - * \brief True if this Element or any parent provides support to a support roof. - */ - bool supports_roof; - - /*! - * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. - */ - size_t dont_move_until; - - /*! - * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. - */ - bool can_use_safe_radius; - - /*! - * \brief Settings used to increase the influence area to its current state. - */ - AreaIncreaseSettings last_area_increase; - - /*! - * \brief Amount of roof layers that were not yet added, because the branch needed to move. - */ - size_t missing_roof_layers; - - /*! - * \brief Skip the ovalisation to parent and children when generating the final circles. - */ - bool skip_ovalisation; - - bool operator==(const SupportElement& other) const - { - return target_position == other.target_position && target_height == other.target_height; - } - - bool operator<(const SupportElement& other) const // true if me < other - { - return !(*this == other) && !(*this > other); - } - bool operator>(const SupportElement& other) const - { - // Doesn't really have to make sense, only required for ordering in maps to ensure deterministic behavior. - if (*this == other) - return false; - if (other.target_height != target_height) - return other.target_height < target_height; - return other.target_position.x() == target_position.x() ? other.target_position.y() < target_position.y() : other.target_position.x() < target_position.x(); - } - - void AddParents(const std::vector& adding) - { - for (SupportElement* ptr : adding) - { - parents.emplace_back(ptr); - } - } - }; - - /*! - * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. - */ - struct TreeSupportSettings - { - TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupport class. - - explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) - : angle(mesh_group_settings.support_tree_angle), - angle_slow(mesh_group_settings.support_tree_angle_slow), - support_line_width(mesh_group_settings.support_line_width), - layer_height(mesh_group_settings.layer_height), - branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), - min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance - maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), - maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), - support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), - tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large - diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius), - max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), - min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), - increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), - increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), - support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), - xy_distance(mesh_group_settings.support_xy_distance), - xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), - bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), - diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. - z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), - z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), - performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), -// support_infill_angles(mesh_group_settings.support_infill_angles), - support_roof_angles(mesh_group_settings.support_roof_angles), - roof_pattern(mesh_group_settings.support_roof_pattern), - support_pattern(mesh_group_settings.support_pattern), - support_roof_line_width(mesh_group_settings.support_roof_line_width), - support_line_spacing(mesh_group_settings.support_line_spacing), - support_bottom_offset(mesh_group_settings.support_bottom_offset), - support_wall_count(mesh_group_settings.support_wall_count), - resolution(mesh_group_settings.resolution), - support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. - settings(mesh_group_settings), - min_feature_size(mesh_group_settings.min_feature_size) - - - { - layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); - - if (TreeSupport::TreeSupportSettings::soluble) { - // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely - // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size - // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. - xy_min_distance = std::max(xy_min_distance, scaled(0.1)); - xy_distance = std::max(xy_distance, xy_min_distance); - } - - -// const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE }, { "interface_area_overwrite_support_area", InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT }, { "support_lines_overwrite_interface_area", InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE }, { "interface_lines_overwrite_support_area", InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT }, { "nothing", InterfacePreference::NOTHING } }; -// interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); -//FIXME this was the default -// interface_preference = InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; - interface_preference = InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE; - } - - private: - double angle; - double angle_slow; - std::vector known_z; - - public: - // some static variables dependent on other meshes that are not currently processed. - // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. - inline static bool soluble = false; - /*! - * \brief Width of a single line of support. - */ - coord_t support_line_width; - /*! - * \brief Height of a single layer - */ - coord_t layer_height; - /*! - * \brief Radius of a branch when it has left the tip. - */ - coord_t branch_radius; - /*! - * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed - */ - coord_t min_radius; - /*! - * \brief How far an influence area may move outward every layer at most. - */ - coord_t maximum_move_distance; - /*! - * \brief How far every influence area will move outward every layer if possible. - */ - coord_t maximum_move_distance_slow; - /*! - * \brief Amount of bottom layers. 0 if disabled. - */ - size_t support_bottom_layers; - /*! - * \brief Amount of effectiveDTT increases are required to reach branch radius. - */ - size_t tip_layers; - /*! - * \brief Factor by which to increase the branch radius. - */ - double diameter_angle_scale_factor; - /*! - * \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate. - */ - coord_t max_to_model_radius_increase; - /*! - * \brief If smaller (in layers) than that, all branches to model will be deleted - */ - size_t min_dtt_to_model; - /*! - * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. - */ - coord_t increase_radius_until_radius; - /*! - * \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached. - */ - size_t increase_radius_until_layer; - /*! - * \brief True if the branches may connect to the model. - */ - bool support_rests_on_model; - /*! - * \brief How far should support be from the model. - */ - coord_t xy_distance; - /*! - * \brief Radius a branch should have when reaching the buildplate. - */ - coord_t bp_radius; - /*! - * \brief The layer index at which an increase in radius may be required to reach the bp_radius. - */ - coord_t layer_start_bp_radius; - /*! - * \brief Factor by which to increase the branch radius to reach the required bp_radius at layer 0. Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound. - */ - double diameter_scale_bp_radius; - /*! - * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- - */ - coord_t xy_min_distance; - /*! - * \brief Amount of layers distance required the top of the support to the model - */ - size_t z_distance_top_layers; - /*! - * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. - */ - size_t z_distance_bottom_layers; - /*! - * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. - */ - size_t performance_interface_skip_layers; - /*! - * \brief User specified angles for the support infill. - */ -// std::vector support_infill_angles; - /*! - * \brief User specified angles for the support roof infill. - */ - std::vector support_roof_angles; - /*! - * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. - */ - SupportMaterialInterfacePattern roof_pattern; - /*! - * \brief Pattern used in the support infill. - */ - SupportMaterialPattern support_pattern; - /*! - * \brief Line width of the support roof. - */ - coord_t support_roof_line_width; - /*! - * \brief Distance between support infill lines. - */ - coord_t support_line_spacing; - /*! - * \brief Offset applied to the support floor area. - */ - coord_t support_bottom_offset; - /* - * \brief Amount of walls the support area will have. - */ - int support_wall_count; - /* - * \brief Maximum allowed deviation when simplifying. - */ - coord_t resolution; - /* - * \brief Distance between the lines of the roof. - */ - coord_t support_roof_line_distance; - /* - * \brief How overlaps of an interface area with a support area should be handled. - */ - InterfacePreference interface_preference; - - /* - * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. - */ - TreeSupportMeshGroupSettings settings; - - /* - * \brief Minimum thickness of any model features. - */ - coord_t min_feature_size; - - public: - bool operator==(const TreeSupportSettings& other) const - { - return branch_radius == other.branch_radius && tip_layers == other.tip_layers && diameter_angle_scale_factor == other.diameter_angle_scale_factor && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && diameter_scale_bp_radius == other.diameter_scale_bp_radius && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && // as a recalculation of the collision areas is required to set a new min_radius. - xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. - support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && - support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. - support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. - support_roof_angles == other.support_roof_angles && - //support_infill_angles == other.support_infill_angles && - increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. - support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference - && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. - // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry -#if 0 - && (interface_preference == InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT || interface_preference == InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE - // Perimeter generator parameters - || - (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && - settings.get("min_bead_width") == other.settings.get("min_bead_width") && - settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && - settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && - settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && - settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && - settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && - settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && - settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && - settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && - settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) - ) -#endif - ; - } - - - /*! - * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. - * \param elem[in] The SupportElement one wants to know the effectiveDTT - * \return The Effective DTT. - */ - [[nodiscard]] inline size_t getEffectiveDTT(const TreeSupport::SupportElement& elem) const - { - return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; - } - - /*! - * \brief Get the Radius part will have based on numeric values. - * \param distance_to_top[in] The effective distance_to_top of the element - * \param elephant_foot_increases[in] The elephant_foot_increases of the element. - * \return The radius an element with these attributes would have. - */ - [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const - { - return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip - branch_radius + // base - branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) - + // gradual increase - branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); - } - - /*! - * \brief Get the Radius, that this element will have. - * \param elem[in] The Element. - * \return The radius the element has. - */ - [[nodiscard]] inline coord_t getRadius(const TreeSupport::SupportElement& elem) const - { - return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); - } - - /*! - * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. - * \param elem[in] The Element. - * \return The collision radius the element has. - */ - [[nodiscard]] inline coord_t getCollisionRadius(const TreeSupport::SupportElement& elem) const - { - return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); - } - - /*! - * \brief Get the Radius an element should at least have at a given layer. - * \param layer_idx[in] The layer. - * \return The radius every element should aim to achieve. - */ - [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const - { - double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; - return scale > 0 ? branch_radius + branch_radius * scale : 0; - } - - /*! - * \brief Return on which z in microns the layer will be printed. Used only for support infill line generation. - * \param layer_idx[in] The layer. - * \return The radius every element should aim to achieve. - */ - [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) - { - return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; - } - - /*! - * \brief Set the z every Layer is printed at. Required for getActualZ to work - * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex - * \return The radius every element should aim to achieve. - */ - void setActualZ(std::vector& z) - { - known_z = z; - } - }; - -private: - /*! - * \brief Creates the initial influence areas (that can later be propagated down) by placing them below the overhang. - * - * Generates Points where the Model should be supported and creates the areas where these points have to be placed. - * - * \param mesh[in] The mesh that is currently processed. - * \param move_bounds[out] Storage for the influence areas. - * \param storage[in] Background storage, required for adding roofs. - */ - void generateInitialAreas(const PrintObject &print_object, - const std::vector &overhangs, - std::vector> &move_bounds, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &top_interface_layers, - SupportGeneratorLayerStorage &layer_storage); - - /*! - * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. - * - * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. - * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. - * - * \param settings[in] Which settings have to be used to check validity. - * \param layer_idx[in] Number of the current layer. - * \param parent[in] The metadata of the parents influence area. - * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! - * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. - * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. - * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. - */ - std::optional increaseSingleArea(AreaIncreaseSettings settings, LayerIndex layer_idx, SupportElement* parent, const Polygons& relevant_offset, Polygons& to_bp_data, Polygons& to_model_data, Polygons& increased, const coord_t overspeed, const bool mergelayer); - /*! - * \brief Increases influence areas as far as required. - * - * Calculates influence areas of the layer below, based on the influence areas of the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. - * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. - * - * \param to_bp_areas[out] Influence areas that can reach the buildplate - * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. - * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. - * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. - * \param last_layer[in] Influence areas of the current layer. - * \param layer_idx[in] Number of the current layer. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - */ - void increaseAreas(std::unordered_map& to_bp_areas, std::unordered_map& to_model_areas, std::map& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, const bool mergelayer); - - /*! - * \brief Propagates influence downwards, and merges overlapping ones. - * - * \param move_bounds[in,out] All currently existing influence areas - */ - void createLayerPathing(std::vector>& move_bounds); - - - /*! - * \brief Sets the result_on_layer for all parents based on the SupportElement supplied. - * - * \param elem[in] The SupportElements, which parent's position should be determined. - */ - void setPointsOnAreas(const SupportElement* elem); - /*! - * \brief Get the best point to connect to the model and set the result_on_layer of the relevant SupportElement accordingly. - * - * \param move_bounds[in,out] All currently existing influence areas - * \param first_elem[in,out] SupportElement that did not have its result_on_layer set meaning that it does not have a child element. - * \param layer_idx[in] The current layer. - * \return Should elem be deleted. - */ - bool setToModelContact(std::vector>& move_bounds, SupportElement* first_elem, const LayerIndex layer_idx); - - /*! - * \brief Set the result_on_layer point for all influence areas - * - * \param move_bounds[in,out] All currently existing influence areas - */ - void createNodesFromArea(std::vector>& move_bounds); - - /*! - * \brief Draws circles around result_on_layer points of the influence areas - * - * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. - */ - void generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order); - - /*! - * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. - * - * \param layer_tree_polygons[in,out] Resulting branch areas with the layerindex they appear on. - */ - void smoothBranchAreas(std::vector>& layer_tree_polygons); - - /*! - * \brief Drop down areas that do rest non-gracefully on the model to ensure the branch actually rests on something. - * - * \param layer_tree_polygons[in] Resulting branch areas with the layerindex they appear on. - * \param linear_data[in] All currently existing influence areas with the layer they are on - * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. - */ - void dropNonGraciousAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, const std::map& inverse_tree_order); - - /*! - * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage - * - * \param support_layer_storage[in] Areas where support should be generated. - * \param support_roof_storage[in] Areas where support was replaced with roof. - * \param storage[in,out] The storage where the support should be stored. - */ - void finalizeInterfaceAndSupportAreas( - const PrintObject &print_object, - const std::vector &overhangs, - std::vector &support_layer_storage, - std::vector &support_roof_storage, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage); - - /*! - * \brief Draws circles around result_on_layer points of the influence areas and applies some post processing. - * - * \param move_bounds[in] All currently existing influence areas - * \param storage[in,out] The storage where the support should be stored. - */ - void drawAreas( - PrintObject &print_object, - const std::vector &overhangs, - std::vector> &move_bounds, - - SupportGeneratorLayersPtr &bottom_contacts, - SupportGeneratorLayersPtr &top_contacts, - SupportGeneratorLayersPtr &intermediate_layers, - SupportGeneratorLayerStorage &layer_storage); - - /*! - * \brief Settings with the indexes of meshes that use these settings. - * - */ - std::vector>> m_grouped_meshes; - - /*! - * \brief Generator for model collision, avoidance and internal guide volumes. - * - */ - TreeModelVolumes m_volumes; - - /*! - * \brief Contains config settings to avoid loading them in every function. This was done to improve readability of the code. - */ - TreeSupportSettings m_config; - - /*! - * \brief The progress multiplier of all values added progress bar. - * Required for the progress bar the behave as expected when areas have to be calculated multiple times - */ - double m_progress_multiplier = 1; - - /*! - * \brief The progress offset added to all values communicated to the progress bar. - * Required for the progress bar the behave as expected when areas have to be calculated multiple times - */ - double m_progress_offset = 0; + InterfaceAreaOverwritesSupport, + SupportAreaOverwritesInterface, + InterfaceLinesOverwriteSupport, + SupportLinesOverwriteInterface, + Nothing }; +struct AreaIncreaseSettings +{ + AreaIncreaseSettings( + TreeModelVolumes::AvoidanceType type = TreeModelVolumes::AvoidanceType::Fast, coord_t increase_speed = 0, + bool increase_radius = false, bool no_error = false, bool use_min_distance = false, bool move = false) : + increase_speed{ increase_speed }, type{ type }, increase_radius{ increase_radius }, no_error{ no_error }, use_min_distance{ use_min_distance }, move{ move } {} + + coord_t increase_speed; + // Packing for smaller memory footprint of SupportElementState && SupportElementMerging + TreeModelVolumes::AvoidanceType type; + bool increase_radius : 1; + bool no_error : 1; + bool use_min_distance : 1; + bool move : 1; + bool operator==(const AreaIncreaseSettings& other) const + { + return type == other.type && + increase_speed == other.increase_speed && + increase_radius == other.increase_radius && + no_error == other.no_error && + use_min_distance == other.use_min_distance && + move == other.move; + } +}; + +struct TreeSupportSettings; + +// C++17 does not support in place initializers of bit values, thus a constructor zeroing the bits is provided. +struct SupportElementStateBits { + SupportElementStateBits() : + to_buildplate(false), + to_model_gracious(false), + use_min_xy_dist(false), + supports_roof(false), + can_use_safe_radius(false), + skip_ovalisation(false), + deleted(false), + marked(false) + {} + + /*! + * \brief The element trys to reach the buildplate + */ + bool to_buildplate : 1; + + /*! + * \brief Will the branch be able to rest completely on a flat surface, be it buildplate or model ? + */ + bool to_model_gracious : 1; + + /*! + * \brief Whether the min_xy_distance can be used to get avoidance or similar. Will only be true if support_xy_overrides_z=Z overrides X/Y. + */ + bool use_min_xy_dist : 1; + + /*! + * \brief True if this Element or any parent provides support to a support roof. + */ + bool supports_roof : 1; + + /*! + * \brief An influence area is considered safe when it can use the holefree avoidance <=> It will not have to encounter holes on its way downward. + */ + bool can_use_safe_radius : 1; + + /*! + * \brief Skip the ovalisation to parent and children when generating the final circles. + */ + bool skip_ovalisation : 1; + + // Not valid anymore, to be deleted. + bool deleted : 1; + + // General purpose flag marking a visited element. + bool marked : 1; +}; + +struct SupportElementState : public SupportElementStateBits +{ + /*! + * \brief The layer this support elements wants reach + */ + LayerIndex target_height; + + /*! + * \brief The position this support elements wants to support on layer=target_height + */ + Point target_position; + + /*! + * \brief The next position this support elements wants to reach. NOTE: This is mainly a suggestion regarding direction inside the influence area. + */ + Point next_position; + + /*! + * \brief The next height this support elements wants to reach + */ + LayerIndex layer_idx; + + /*! + * \brief The Effective distance to top of this element regarding radius increases and collision calculations. + */ + uint32_t effective_radius_height; + + /*! + * \brief The amount of layers this element is below the topmost layer of this branch. + */ + uint32_t distance_to_top; + + /*! + * \brief The resulting center point around which a circle will be drawn later. + * Will be set by setPointsOnAreas + */ + Point result_on_layer { std::numeric_limits::max(), std::numeric_limits::max() }; + bool result_on_layer_is_set() const { return this->result_on_layer != Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + void result_on_layer_reset() { this->result_on_layer = Point{ std::numeric_limits::max(), std::numeric_limits::max() }; } + /*! + * \brief The amount of extra radius we got from merging branches that could have reached the buildplate, but merged with ones that can not. + */ + coord_t increased_to_model_radius; // how much to model we increased only relevant for merging + + /*! + * \brief Counter about the times the elephant foot was increased. Can be fractions for merge reasons. + */ + double elephant_foot_increases; + + /*! + * \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move. + */ + uint32_t dont_move_until; + + /*! + * \brief Settings used to increase the influence area to its current state. + */ + AreaIncreaseSettings last_area_increase; + + /*! + * \brief Amount of roof layers that were not yet added, because the branch needed to move. + */ + uint32_t missing_roof_layers; + + // called by increase_single_area() and increaseAreas() + [[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src) + { + SupportElementState dst{ src }; + ++ dst.distance_to_top; + -- dst.layer_idx; + // set to invalid as we are a new node on a new layer + dst.result_on_layer_reset(); + dst.skip_ovalisation = false; + return dst; + } +}; + +struct SupportElement +{ + using ParentIndices = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + +// SupportElement(const SupportElementState &state) : SupportElementState(state) {} + SupportElement(const SupportElementState &state, Polygons &&influence_area) : state(state), influence_area(std::move(influence_area)) {} + SupportElement(const SupportElementState &state, ParentIndices &&parents, Polygons &&influence_area) : + state(state), parents(std::move(parents)), influence_area(std::move(influence_area)) {} + + SupportElementState state; + + /*! + * \brief All elements in the layer above the current one that are supported by this element + */ + ParentIndices parents; + + /*! + * \brief The resulting influence area. + * Will only be set in the results of createLayerPathing, and will be nullptr inside! + */ + Polygons influence_area; +}; + +/*! + * \brief This struct contains settings used in the tree support. Thanks to this most functions do not need to know of meshes etc. Also makes the code shorter. + */ +struct TreeSupportSettings +{ + TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupportGenerator class. + + explicit TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings) + : angle(mesh_group_settings.support_tree_angle), + angle_slow(mesh_group_settings.support_tree_angle_slow), + support_line_width(mesh_group_settings.support_line_width), + layer_height(mesh_group_settings.layer_height), + branch_radius(mesh_group_settings.support_tree_branch_diameter / 2), + min_radius(mesh_group_settings.support_tree_tip_diameter / 2), // The actual radius is 50 microns larger as the resulting branches will be increased by 50 microns to avoid rounding errors effectively increasing the xydistance + maximum_move_distance((angle < M_PI / 2.) ? (coord_t)(tan(angle) * layer_height) : std::numeric_limits::max()), + maximum_move_distance_slow((angle_slow < M_PI / 2.) ? (coord_t)(tan(angle_slow) * layer_height) : std::numeric_limits::max()), + support_bottom_layers(mesh_group_settings.support_bottom_enable ? (mesh_group_settings.support_bottom_height + layer_height / 2) / layer_height : 0), + tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)), // Ensure lines always stack nicely even if layer height is large + diameter_angle_scale_factor(sin(mesh_group_settings.support_tree_branch_diameter_angle) * layer_height / branch_radius), + max_to_model_radius_increase(mesh_group_settings.support_tree_max_diameter_increase_by_merges_when_support_to_model / 2), + min_dtt_to_model(round_up_divide(mesh_group_settings.support_tree_min_height_to_model, layer_height)), + increase_radius_until_radius(mesh_group_settings.support_tree_branch_diameter / 2), + increase_radius_until_layer(increase_radius_until_radius <= branch_radius ? tip_layers * (increase_radius_until_radius / branch_radius) : (increase_radius_until_radius - branch_radius) / (branch_radius * diameter_angle_scale_factor)), + support_rests_on_model(! mesh_group_settings.support_material_buildplate_only), + xy_distance(mesh_group_settings.support_xy_distance), + xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), + bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), + diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. + z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), + z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), + performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), +// support_infill_angles(mesh_group_settings.support_infill_angles), + support_roof_angles(mesh_group_settings.support_roof_angles), + roof_pattern(mesh_group_settings.support_roof_pattern), + support_pattern(mesh_group_settings.support_pattern), + support_roof_line_width(mesh_group_settings.support_roof_line_width), + support_line_spacing(mesh_group_settings.support_line_spacing), + support_bottom_offset(mesh_group_settings.support_bottom_offset), + support_wall_count(mesh_group_settings.support_wall_count), + resolution(mesh_group_settings.resolution), + support_roof_line_distance(mesh_group_settings.support_roof_line_distance), // in the end the actual infill has to be calculated to subtract interface from support areas according to interface_preference. + settings(mesh_group_settings), + min_feature_size(mesh_group_settings.min_feature_size) + + + { + layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); + + if (TreeSupportSettings::soluble) { + // safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely + // When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size + // This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance. + xy_min_distance = std::max(xy_min_distance, scaled(0.1)); + xy_distance = std::max(xy_distance, xy_min_distance); + } + + +// const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SupportAreaOverwritesInterface }, { "interface_area_overwrite_support_area", InterfacePreference::InterfaceAreaOverwritesSupport }, { "support_lines_overwrite_interface_area", InterfacePreference::SupportLinesOverwriteInterface }, { "interface_lines_overwrite_support_area", InterfacePreference::InterfaceLinesOverwriteSupport }, { "nothing", InterfacePreference::Nothing } }; +// interface_preference = interface_map.at(mesh_group_settings.get("support_interface_priority")); +//FIXME this was the default +// interface_preference = InterfacePreference::SupportLinesOverwriteInterface; + interface_preference = InterfacePreference::SupportAreaOverwritesInterface; + } + +private: + double angle; + double angle_slow; + std::vector known_z; + +public: + // some static variables dependent on other meshes that are not currently processed. + // Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy. + inline static bool soluble = false; + /*! + * \brief Width of a single line of support. + */ + coord_t support_line_width; + /*! + * \brief Height of a single layer + */ + coord_t layer_height; + /*! + * \brief Radius of a branch when it has left the tip. + */ + coord_t branch_radius; + /*! + * \brief smallest allowed radius, required to ensure that even at DTT 0 every circle will still be printed + */ + coord_t min_radius; + /*! + * \brief How far an influence area may move outward every layer at most. + */ + coord_t maximum_move_distance; + /*! + * \brief How far every influence area will move outward every layer if possible. + */ + coord_t maximum_move_distance_slow; + /*! + * \brief Amount of bottom layers. 0 if disabled. + */ + size_t support_bottom_layers; + /*! + * \brief Amount of effectiveDTT increases are required to reach branch radius. + */ + size_t tip_layers; + /*! + * \brief Factor by which to increase the branch radius. + */ + double diameter_angle_scale_factor; + /*! + * \brief How much a branch resting on the model may grow in radius by merging with branches that can reach the buildplate. + */ + coord_t max_to_model_radius_increase; + /*! + * \brief If smaller (in layers) than that, all branches to model will be deleted + */ + size_t min_dtt_to_model; + /*! + * \brief Increase radius in the resulting drawn branches, even if the avoidance does not allow it. Will be cut later to still fit. + */ + coord_t increase_radius_until_radius; + /*! + * \brief Same as increase_radius_until_radius, but contains the DTT at which the radius will be reached. + */ + size_t increase_radius_until_layer; + /*! + * \brief True if the branches may connect to the model. + */ + bool support_rests_on_model; + /*! + * \brief How far should support be from the model. + */ + coord_t xy_distance; + /*! + * \brief Radius a branch should have when reaching the buildplate. + */ + coord_t bp_radius; + /*! + * \brief The layer index at which an increase in radius may be required to reach the bp_radius. + */ + coord_t layer_start_bp_radius; + /*! + * \brief Factor by which to increase the branch radius to reach the required bp_radius at layer 0. Note that this radius increase will not happen in the tip, to ensure the tip is structurally sound. + */ + double diameter_scale_bp_radius; + /*! + * \brief minimum xy_distance. Only relevant when Z overrides XY, otherwise equal to xy_distance- + */ + coord_t xy_min_distance; + /*! + * \brief Amount of layers distance required the top of the support to the model + */ + size_t z_distance_top_layers; + /*! + * \brief Amount of layers distance required from the top of the model to the bottom of a support structure. + */ + size_t z_distance_bottom_layers; + /*! + * \brief used for performance optimization at the support floor. Should have no impact on the resulting tree. + */ + size_t performance_interface_skip_layers; + /*! + * \brief User specified angles for the support infill. + */ +// std::vector support_infill_angles; + /*! + * \brief User specified angles for the support roof infill. + */ + std::vector support_roof_angles; + /*! + * \brief Pattern used in the support roof. May contain non relevant data if support roof is disabled. + */ + SupportMaterialInterfacePattern roof_pattern; + /*! + * \brief Pattern used in the support infill. + */ + SupportMaterialPattern support_pattern; + /*! + * \brief Line width of the support roof. + */ + coord_t support_roof_line_width; + /*! + * \brief Distance between support infill lines. + */ + coord_t support_line_spacing; + /*! + * \brief Offset applied to the support floor area. + */ + coord_t support_bottom_offset; + /* + * \brief Amount of walls the support area will have. + */ + int support_wall_count; + /* + * \brief Maximum allowed deviation when simplifying. + */ + coord_t resolution; + /* + * \brief Distance between the lines of the roof. + */ + coord_t support_roof_line_distance; + /* + * \brief How overlaps of an interface area with a support area should be handled. + */ + InterfacePreference interface_preference; + + /* + * \brief The infill class wants a settings object. This one will be the correct one for all settings it uses. + */ + TreeSupportMeshGroupSettings settings; + + /* + * \brief Minimum thickness of any model features. + */ + coord_t min_feature_size; + + public: + bool operator==(const TreeSupportSettings& other) const + { + return branch_radius == other.branch_radius && tip_layers == other.tip_layers && diameter_angle_scale_factor == other.diameter_angle_scale_factor && layer_start_bp_radius == other.layer_start_bp_radius && bp_radius == other.bp_radius && diameter_scale_bp_radius == other.diameter_scale_bp_radius && min_radius == other.min_radius && xy_min_distance == other.xy_min_distance && // as a recalculation of the collision areas is required to set a new min_radius. + xy_distance - xy_min_distance == other.xy_distance - other.xy_min_distance && // if the delta of xy_min_distance and xy_distance is different the collision areas have to be recalculated. + support_rests_on_model == other.support_rests_on_model && increase_radius_until_layer == other.increase_radius_until_layer && min_dtt_to_model == other.min_dtt_to_model && max_to_model_radius_increase == other.max_to_model_radius_increase && maximum_move_distance == other.maximum_move_distance && maximum_move_distance_slow == other.maximum_move_distance_slow && z_distance_bottom_layers == other.z_distance_bottom_layers && support_line_width == other.support_line_width && + support_line_spacing == other.support_line_spacing && support_roof_line_width == other.support_roof_line_width && // can not be set on a per-mesh basis currently, so code to enable processing different roof line width in the same iteration seems useless. + support_bottom_offset == other.support_bottom_offset && support_wall_count == other.support_wall_count && support_pattern == other.support_pattern && roof_pattern == other.roof_pattern && // can not be set on a per-mesh basis currently, so code to enable processing different roof patterns in the same iteration seems useless. + support_roof_angles == other.support_roof_angles && + //support_infill_angles == other.support_infill_angles && + increase_radius_until_radius == other.increase_radius_until_radius && support_bottom_layers == other.support_bottom_layers && layer_height == other.layer_height && z_distance_top_layers == other.z_distance_top_layers && resolution == other.resolution && // Infill generation depends on deviation and resolution. + support_roof_line_distance == other.support_roof_line_distance && interface_preference == other.interface_preference + && min_feature_size == other.min_feature_size // interface_preference should be identical to ensure the tree will correctly interact with the roof. + // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry +#if 0 + && (interface_preference == InterfacePreference::InterfaceAreaOverwritesSupport || interface_preference == InterfacePreference::SupportAreaOverwritesInterface + // Perimeter generator parameters + || + (settings.get("fill_outline_gaps") == other.settings.get("fill_outline_gaps") && + settings.get("min_bead_width") == other.settings.get("min_bead_width") && + settings.get("wall_transition_angle") == other.settings.get("wall_transition_angle") && + settings.get("wall_transition_length") == other.settings.get("wall_transition_length") && + settings.get("wall_split_middle_threshold") == other.settings.get("wall_split_middle_threshold") && + settings.get("wall_add_middle_threshold") == other.settings.get("wall_add_middle_threshold") && + settings.get("wall_distribution_count") == other.settings.get("wall_distribution_count") && + settings.get("wall_transition_filter_distance") == other.settings.get("wall_transition_filter_distance") && + settings.get("wall_transition_filter_deviation") == other.settings.get("wall_transition_filter_deviation") && + settings.get("wall_line_width_x") == other.settings.get("wall_line_width_x") && + settings.get("meshfix_maximum_extrusion_area_deviation") == other.settings.get("meshfix_maximum_extrusion_area_deviation")) + ) +#endif + ; + } + + /*! + * \brief Get the Distance to top regarding the real radius this part will have. This is different from distance_to_top, which is can be used to calculate the top most layer of the branch. + * \param elem[in] The SupportElement one wants to know the effectiveDTT + * \return The Effective DTT. + */ + [[nodiscard]] inline size_t getEffectiveDTT(const SupportElementState &elem) const + { + return elem.effective_radius_height < increase_radius_until_layer ? (elem.distance_to_top < increase_radius_until_layer ? elem.distance_to_top : increase_radius_until_layer) : elem.effective_radius_height; + } + + /*! + * \brief Get the Radius part will have based on numeric values. + * \param distance_to_top[in] The effective distance_to_top of the element + * \param elephant_foot_increases[in] The elephant_foot_increases of the element. + * \return The radius an element with these attributes would have. + */ + [[nodiscard]] inline coord_t getRadius(size_t distance_to_top, const double elephant_foot_increases = 0) const + { + return (distance_to_top <= tip_layers ? min_radius + (branch_radius - min_radius) * distance_to_top / tip_layers : // tip + branch_radius + // base + branch_radius * (distance_to_top - tip_layers) * diameter_angle_scale_factor) + + // gradual increase + branch_radius * elephant_foot_increases * (std::max(diameter_scale_bp_radius - diameter_angle_scale_factor, 0.0)); + } + + /*! + * \brief Get the Radius, that this element will have. + * \param elem[in] The Element. + * \return The radius the element has. + */ + [[nodiscard]] inline coord_t getRadius(const SupportElementState &elem) const + { return getRadius(getEffectiveDTT(elem), elem.elephant_foot_increases); } + [[nodiscard]] inline coord_t getRadius(const SupportElement &elem) const + { return this->getRadius(elem.state); } + + /*! + * \brief Get the collision Radius of this Element. This can be smaller then the actual radius, as the drawAreas will cut off areas that may collide with the model. + * \param elem[in] The Element. + * \return The collision radius the element has. + */ + [[nodiscard]] inline coord_t getCollisionRadius(const SupportElementState &elem) const + { + return getRadius(elem.effective_radius_height, elem.elephant_foot_increases); + } + + /*! + * \brief Get the Radius an element should at least have at a given layer. + * \param layer_idx[in] The layer. + * \return The radius every element should aim to achieve. + */ + [[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const + { + double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius; + return scale > 0 ? branch_radius + branch_radius * scale : 0; + } + + /*! + * \brief Return on which z in microns the layer will be printed. Used only for support infill line generation. + * \param layer_idx[in] The layer. + * \return The radius every element should aim to achieve. + */ + [[nodiscard]] inline coord_t getActualZ(LayerIndex layer_idx) + { + return layer_idx < coord_t(known_z.size()) ? known_z[layer_idx] : (layer_idx - known_z.size()) * layer_height + known_z.size() ? known_z.back() : 0; + } + + /*! + * \brief Set the z every Layer is printed at. Required for getActualZ to work + * \param z[in] The z every LayerIndex is printed. Vector is used as a map with the index of each element being the corresponding LayerIndex + * \return The radius every element should aim to achieve. + */ + void setActualZ(std::vector& z) + { + known_z = z; + } +}; + +// todo Remove! ONLY FOR PUBLIC BETA!! +void tree_supports_show_error(std::string message, bool critical); + +} // namespace FFFTreeSupport + +void fff_tree_support_generate(PrintObject &print_object, std::function throw_on_cancel = []{}); } // namespace Slic3r -namespace std -{ -template <> -struct hash -{ - size_t operator()(const Slic3r::TreeSupport::SupportElement& node) const - { - size_t hash_node = Slic3r::PointHash{}(node.target_position); - boost::hash_combine(hash_node, size_t(node.target_height)); - return hash_node; - } -}; -} // namespace std - #endif /* slic3r_TreeSupport_hpp */ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 0cddcc366..61d403d95 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -378,6 +378,14 @@ bool TriangleMesh::is_splittable() const return its_is_splittable(this->its); } +bool TriangleMesh::has_zero_volume() const +{ + const Vec3d sz = size(); + const double volume_val = sz.x() * sz.y() * sz.z(); + + return is_approx(volume_val, 0.0); +} + std::vector TriangleMesh::split() const { std::vector itss = its_split(this->its); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 8f1198757..ec405073e 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -139,6 +139,7 @@ public: bool empty() const { return this->facets_count() == 0; } bool repaired() const; bool is_splittable() const; + bool has_zero_volume() const; // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation. size_t memsize() const; diff --git a/src/platform/osx/Info.plist.in b/src/platform/osx/Info.plist.in index b53111d1d..d3c913604 100644 --- a/src/platform/osx/Info.plist.in +++ b/src/platform/osx/Info.plist.in @@ -111,7 +111,7 @@ LSMinimumSystemVersion - 10.10 + 10.12 NSPrincipalClass NSApplication NSHighResolutionCapable diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 04ce05ab5..1de709078 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -69,80 +69,7 @@ bool Version::is_current_slic3r_downgrade() const { return Slic3r::SEMVER < min_slic3r_version; } -#if 0 -//TODO: This test should be moved to a unit test, once we have C++ unit tests in place. -static int version_test() -{ - Version v; - v.config_version = *Semver::parse("1.1.2"); - v.min_slic3r_version = *Semver::parse("1.38.0"); - v.max_slic3r_version = Semver::inf(); - assert(v.is_slic3r_supported(*Semver::parse("1.38.0"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); - // Test the prerelease status. - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0"))); - v.config_version = *Semver::parse("1.1.2-alpha"); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); - v.config_version = *Semver::parse("1.1.2-alpha1"); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); - v.config_version = *Semver::parse("1.1.2-beta"); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); - v.config_version = *Semver::parse("1.1.2-rc"); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); - v.config_version = *Semver::parse("1.1.2-rc2"); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); - assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); - // Test the upper boundary. - v.config_version = *Semver::parse("1.1.2"); - v.max_slic3r_version = *Semver::parse("1.39.3-beta1"); - assert(v.is_slic3r_supported(*Semver::parse("1.38.0"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1"))); - assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); - return 0; -} -static int version_test_run = version_test(); -#endif + inline char* left_trim(char *c) { diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 173d34570..275fcdc98 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -211,6 +211,12 @@ void BackgroundSlicingProcess::thread_proc() set_current_thread_name("slic3r_BgSlcPcs"); name_tbb_thread_pool_threads_set_locale(); + // Set "C" locales and enforce OSX QoS level on all threads entering an arena. + // The cost of the callback is quite low: The callback is called once per thread + // entering a parallel loop and the callback is guarded with a thread local + // variable to be executed just once. + TBBLocalesSetter setter; + assert(m_print != nullptr); assert(m_print == m_fff_print || m_print == m_sla_print); std::unique_lock lck(m_mutex); diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 7fb0ce635..4a9057514 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -34,8 +34,11 @@ void BitmapCache::clear() { for (std::pair &bitmap : m_map) delete bitmap.second; - m_map.clear(); + + for (std::pair &bitmap_bundle : m_bndl_map) + delete bitmap_bundle.second; + m_bndl_map.clear(); } static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1.0f) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b2b4b25ea..be232c178 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -116,11 +116,7 @@ void GCodeViewer::IBuffer::reset() count = 0; } -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const -#else -bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) const -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC { auto matches_percent = [](float value1, float value2, float max_percent) { return std::abs(value2 - value1) / value1 <= max_percent; @@ -137,7 +133,6 @@ bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) co case EMoveType::Seam: case EMoveType::Extrude: { // use rounding to reduce the number of generated paths -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC if (account_for_volumetric_rate) return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && @@ -147,12 +142,6 @@ bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move) co return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && height == round_to_bin(move.height) && width == round_to_bin(move.width); -#else - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_bin(move.height) && width == round_to_bin(move.width) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; @@ -261,7 +250,6 @@ void GCodeViewer::COG::render() //ImGui::PopStyleVar(); } -#if ENABLE_PREVIEW_LAYER_TIME float GCodeViewer::Extrusions::Range::step_size(EType type) const { switch (type) @@ -273,12 +261,8 @@ float GCodeViewer::Extrusions::Range::step_size(EType type) const } ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const -#else -ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const -#endif // ENABLE_PREVIEW_LAYER_TIME { // Input value scaled to the colors range -#if ENABLE_PREVIEW_LAYER_TIME float global_t = 0.0f; const float step = step_size(type); if (step > 0.0f) { @@ -289,10 +273,6 @@ ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value) const case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; } } } -#else - const float step = step_size(); - const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f -#endif // ENABLE_PREVIEW_LAYER_TIME const size_t color_max_idx = Range_Colors.size() - 1; @@ -771,19 +751,12 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr #endif // ENABLE_LEGACY_OPENGL_REMOVAL { // avoid processing if called with the same gcode_result -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC if (m_last_result_id == gcode_result.id && (m_last_view_type == m_view_type || (m_last_view_type != EViewType::VolumetricRate && m_view_type != EViewType::VolumetricRate))) return; -#else - if (m_last_result_id == gcode_result.id) - return; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC m_last_result_id = gcode_result.id; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC m_last_view_type = m_view_type; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC // release gpu memory, if used reset(); @@ -919,7 +892,6 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v } } -#if ENABLE_PREVIEW_LAYER_TIME for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) { m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times; } @@ -929,28 +901,16 @@ void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::v m_extrusions.ranges.layer_time[i].update_from(time); } } -#endif // ENABLE_PREVIEW_LAYER_TIME #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // update buffers' render paths -#if ENABLE_PREVIEW_LAYOUT refresh_render_paths(false, false); -#else - refresh_render_paths(); -#endif // ENABLE_PREVIEW_LAYOUT log_memory_used("Refreshed G-code extrusion paths, "); } -#if !ENABLE_PREVIEW_LAYOUT -void GCodeViewer::refresh_render_paths() -{ - refresh_render_paths(false, false); -} -#endif // !ENABLE_PREVIEW_LAYOUT - void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config) { if (config != nullptr) @@ -978,20 +938,16 @@ void GCodeViewer::reset() m_layers_z_range = { 0, 0 }; m_roles = std::vector(); m_print_statistics.reset(); -#if ENABLE_PREVIEW_LAYER_TIME for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_layers_times[i] = std::vector(); } -#endif // ENABLE_PREVIEW_LAYER_TIME m_custom_gcode_per_print_z = std::vector(); m_sequential_view.gcode_window.reset(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS m_contained_in_bed = true; -#if ENABLE_PREVIEW_LAYOUT m_legend_resizer.reset(); -#endif // ENABLE_PREVIEW_LAYOUT } void GCodeViewer::render() @@ -1104,9 +1060,6 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::CenterOfGravity), m_cog.is_visible()); flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); -#if !ENABLE_PREVIEW_LAYOUT - flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); -#endif // !ENABLE_PREVIEW_LAYOUT return flags; } @@ -1128,9 +1081,6 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) m_cog.set_visible(is_flag_set(static_cast(Preview::OptionType::CenterOfGravity))); m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); -#if !ENABLE_PREVIEW_LAYOUT - enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); -#endif // !ENABLE_PREVIEW_LAYOUT } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) @@ -1360,15 +1310,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) // add current vertex add_vertex(curr); }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { -#else - auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC // add starting index indices.push_back(static_cast(indices.size())); buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); @@ -1387,13 +1331,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) }; // format data into the buffers to be rendered as solid -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) { -#else - auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer, - unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { // append position vertices.push_back(position.x()); @@ -1405,11 +1344,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) vertices.push_back(normal.z()); }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { -#else - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1); buffer.paths.back().sub_paths.back().first.position = prev.position; } @@ -1454,15 +1389,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) { -#else - auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, - const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, - IndexBuffer& indices, size_t move_id) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC static Vec3f prev_dir; static Vec3f prev_up; static float sq_prev_length; @@ -1507,11 +1436,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) { -#else - if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); buffer.paths.back().sub_paths.back().first.position = prev.position; } @@ -1595,11 +1520,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) vbuffer_size += 6; } -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate))) -#else - if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC // ending cap triangles append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); @@ -1737,9 +1658,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) m_sequential_view.gcode_ids.push_back(move.gcode_id); } -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC std::vector vertices(m_buffers.size()); std::vector indices(m_buffers.size()); @@ -1803,11 +1722,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) v_multibuffer.push_back(VertexBuffer()); if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { Path& last_path = t_buffer.paths.back(); -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate)) -#else - if (prev.type == curr.type && last_path.matches(curr)) -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC last_path.add_sub_path(prev, static_cast(v_multibuffer.size()) - 1, 0, move_id - 1); } } @@ -1817,11 +1732,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) switch (t_buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; } -#else - case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, move_id); break; } -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC case TBuffer::ERenderPrimitiveType::InstancedModel: { add_model_instance(curr, inst_buffer, inst_id_buffer, move_id); @@ -2220,20 +2131,12 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) switch (t_buffer.render_primitive_type) { case TBuffer::ERenderPrimitiveType::Line: { -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); -#else - add_indices_as_line(prev, curr, t_buffer, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC curr_vertex_buffer.second += t_buffer.max_vertices_per_segment(); break; } case TBuffer::ERenderPrimitiveType::Triangle: { -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate); -#else - add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, move_id); -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC break; } case TBuffer::ERenderPrimitiveType::BatchedModel: { @@ -2500,7 +2403,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } -#if ENABLE_PREVIEW_LAYER_TIME case EViewType::LayerTimeLinear: case EViewType::LayerTimeLogarithmic: { const Path::Sub_Path& sub_path = path.sub_paths.front(); @@ -2519,7 +2421,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } break; } -#endif // ENABLE_PREVIEW_LAYER_TIME case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { @@ -3514,11 +3415,7 @@ void GCodeViewer::render_legend(float& legend_height) const float max_height = 0.75f * static_cast(cnv_size.get_height()); const float child_height = 0.3333f * max_height; ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); -#if ENABLE_PREVIEW_LAYOUT imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); -#else - imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); -#endif // ENABLE_PREVIEW_LAYOUT enum class EItemType : unsigned char { @@ -3529,14 +3426,9 @@ void GCodeViewer::render_legend(float& legend_height) }; const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; -#if ENABLE_PREVIEW_LAYER_TIME bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); -#else - bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); -#endif // ENABLE_PREVIEW_LAYER_TIME const float icon_size = ImGui::GetTextLineHeight(); const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); @@ -3622,7 +3514,6 @@ void GCodeViewer::render_legend(float& legend_height) } else { imgui.text(label); -#if ENABLE_TRAVEL_TIME if (!time.empty()) { ImGui::SameLine(offsets[0]); imgui.text(time); @@ -3638,9 +3529,6 @@ void GCodeViewer::render_legend(float& legend_height) ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); } else if (used_filament_m > 0.0) { -#else - if (used_filament_m > 0.0) { -#endif // ENABLE_TRAVEL_TIME char buf[64]; ImGui::SameLine(offsets[0]); ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); @@ -3678,7 +3566,6 @@ void GCodeViewer::render_legend(float& legend_height) } }; -#if ENABLE_PREVIEW_LAYER_TIME auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) { auto append_range_item = [append_item](int i, float value) { std::string str_value = get_time_dhms(value); @@ -3709,7 +3596,6 @@ void GCodeViewer::render_legend(float& legend_height) } } }; -#endif // ENABLE_PREVIEW_LAYER_TIME auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { size_t i = 0; @@ -3883,7 +3769,6 @@ void GCodeViewer::render_legend(float& legend_height) offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); } -#if ENABLE_PREVIEW_LAYOUT // selection section bool view_type_changed = false; int old_view_type = static_cast(get_view_type()); @@ -3901,10 +3786,8 @@ void GCodeViewer::render_legend(float& legend_height) _u8L("Fan speed (%)"), _u8L("Temperature (°C)"), _u8L("Volumetric flow rate (mm³/s)"), -#if ENABLE_PREVIEW_LAYER_TIME _u8L("Layer time (linear)"), _u8L("Layer time (logarithmic)"), -#endif // ENABLE_PREVIEW_LAYER_TIME _u8L("Tool"), _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest); ImGui::PopStyleColor(2); @@ -3923,164 +3806,124 @@ void GCodeViewer::render_legend(float& legend_height) append_headers({ _u8L(""), _u8L("Used filament"), _u8L(""), _u8L("") }, offsets); else ImGui::Separator(); -#else - // extrusion paths section -> title - switch (m_view_type) - { - case EViewType::FeatureType: - { - append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); - break; - } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } - case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } - case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } -#if ENABLE_PREVIEW_LAYER_TIME - case EViewType::LayerTimeLinear: { imgui.title(_u8L("Layer time (linear)")); break; } - case EViewType::LayerTimeLogarithmic: { imgui.title(_u8L("Layer time (logarithmic)")); break; } -#endif // ENABLE_PREVIEW_LAYER_TIME - case EViewType::Tool: { - append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); - break; - } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } - default: { break; } - } -#endif // ENABLE_PREVIEW_LAYOUT -#if ENABLE_PREVIEW_LAYOUT if (!view_type_changed) { -#endif // ENABLE_PREVIEW_LAYOUT - // extrusion paths section -> items - switch (m_view_type) - { - case EViewType::FeatureType: - { -#if ENABLE_TRAVEL_TIME - max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); -#endif // ENABLE_TRAVEL_TIME + // extrusion paths section -> items + switch (m_view_type) + { + case EViewType::FeatureType: + { + max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time); - for (size_t i = 0; i < m_roles.size(); ++i) { - ExtrusionRole role = m_roles[i]; - if (role >= erCount) - continue; - const bool visible = is_visible(role); - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { - m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); - // update buffers' render paths - refresh_render_paths(false, false); - wxGetApp().plater()->update_preview_moves_slider(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); -#if !ENABLE_PREVIEW_LAYOUT - wxGetApp().plater()->update_preview_bottom_toolbar(); -#endif // !ENABLE_PREVIEW_LAYOUT - } - ); - } - -#if ENABLE_TRAVEL_TIME - if (m_buffers[buffer_id(EMoveType::Travel)].visible) - append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)), - time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); -#endif // ENABLE_TRAVEL_TIME - - break; - } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } - case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } -#if ENABLE_PREVIEW_LAYER_TIME - case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } - case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } -#endif // ENABLE_PREVIEW_LAYER_TIME - case EViewType::Tool: { - // shows only extruders actually used - for (unsigned char extruder_id : m_extruder_ids) { - 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; - } - case EViewType::ColorPrint: - { - const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - size_t total_items = 1; - for (unsigned char i : m_extruder_ids) { - total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); - } - - const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; - - // add scrollable region, if needed - if (need_scrollable) - ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); - if (m_extruders_count == 1) { // single extruder use case - const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); - else { - for (int i = items_cnt; i >= 0; --i) { - // create label for color change item - if (i == 0) { - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); - break; + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role >= erCount) + continue; + const bool visible = is_visible(role); + append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], + visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { + m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + // update buffers' render paths + refresh_render_paths(false, false); + wxGetApp().plater()->update_preview_moves_slider(); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); } - else if (i == items_cnt) { - append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); - continue; - } - append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); - } + ); } + + if (m_buffers[buffer_id(EMoveType::Travel)].visible) + append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)), + time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f); + + break; } - else { // multi extruder use case + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; } + case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } + case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; } + case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; } + case EViewType::Tool: { // shows only extruders actually used + for (unsigned char extruder_id : m_extruder_ids) { + 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; + } + case EViewType::ColorPrint: + { + const std::vector& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; + size_t total_items = 1; for (unsigned char i : m_extruder_ids) { - const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); + } + + const bool need_scrollable = static_cast(total_items) * icon_size + (static_cast(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height; + + // add scrollable region, if needed + if (need_scrollable) + ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); + if (m_extruders_count == 1) { // single extruder use case + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) - // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode + append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); else { - for (int j = items_cnt; j >= 0; --j) { + for (int i = items_cnt; i >= 0; --i) { // create label for color change item - std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); - if (j == 0) { - label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Rect, m_tool_colors[i], label); + if (i == 0) { + append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); break; } - else if (j == items_cnt) { - label += " " + above_label(cp_values[j - 1].second.second); - append_item(EItemType::Rect, cp_values[j - 1].first, label); + else if (i == items_cnt) { + append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); continue; } - - label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); - append_item(EItemType::Rect, cp_values[j - 1].first, label); + append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); } } } - } - if (need_scrollable) - ImGui::EndChild(); + else { // multi extruder use case + // shows only extruders actually used + for (unsigned char i : m_extruder_ids) { + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) + // There are no color changes, but there are some pause print or custom Gcode + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + else { + for (int j = items_cnt; j >= 0; --j) { + // create label for color change item + std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); + if (j == 0) { + label += " " + upto_label(cp_values.front().second.first); + append_item(EItemType::Rect, m_tool_colors[i], label); + break; + } + else if (j == items_cnt) { + label += " " + above_label(cp_values[j - 1].second.second); + append_item(EItemType::Rect, cp_values[j - 1].first, label); + continue; + } - break; + label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); + append_item(EItemType::Rect, cp_values[j - 1].first, label); + } + } + } + } + if (need_scrollable) + ImGui::EndChild(); + + break; + } + default: { break; } + } } - default: { break; } - } -#if ENABLE_PREVIEW_LAYOUT - } -#endif // ENABLE_PREVIEW_LAYOUT // partial estimated printing time section if (m_view_type == EViewType::ColorPrint) { @@ -4257,89 +4100,14 @@ void GCodeViewer::render_legend(float& legend_height) } } -#if !ENABLE_PREVIEW_LAYOUT - // travel paths section - if (m_buffers[buffer_id(EMoveType::Travel)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { - break; - } - default: { - // title - ImGui::Spacing(); - imgui.title(_u8L("Travel")); - - // items - append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); - append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); - append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); - - break; - } - } - } - - // wipe paths section - if (m_buffers[buffer_id(EMoveType::Wipe)].visible) { - switch (m_view_type) - { - case EViewType::Feedrate: - case EViewType::Tool: - case EViewType::ColorPrint: { break; } - default: { - // title - ImGui::Spacing(); - imgui.title(_u8L("Wipe")); - - // items - append_item(EItemType::Line, Wipe_Color, _u8L("Wipe")); - - break; - } - } - } - - auto any_option_available = [this]() { - auto available = [this](EMoveType type) { - const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.has_data(); - }; - - return available(EMoveType::Color_change) || - available(EMoveType::Custom_GCode) || - available(EMoveType::Pause_Print) || - available(EMoveType::Retract) || - available(EMoveType::Tool_change) || - available(EMoveType::Unretract) || - available(EMoveType::Seam); + auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + imgui.text_colored(col_1_color, col_1.c_str()); + ImGui::TableSetColumnIndex(1); + imgui.text_colored(col_2_color, col_2.c_str()); }; - auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { - const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.has_data()) - append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); - }; - - // options section - if (any_option_available()) { - // title - ImGui::Spacing(); - imgui.title(_u8L("Options")); - - // items - add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); - add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Deretractions")); - add_option(EMoveType::Seam, EOptionsColors::Seams, _u8L("Seams")); - add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); - add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); - add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Print pauses")); - add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom G-codes")); - } -#endif // !ENABLE_PREVIEW_LAYOUT - // settings section bool has_settings = false; has_settings |= !m_settings_ids.print.empty(); @@ -4354,47 +4122,37 @@ void GCodeViewer::render_legend(float& legend_height) show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool); show_settings &= has_settings; if (show_settings) { - auto calc_offset = [this]() { - float ret = 0.0f; - if (!m_settings_ids.printer.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Printer") + std::string(":")).c_str()).x); - if (!m_settings_ids.print.empty()) - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Print settings") + std::string(":")).c_str()).x); - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - ret = std::max(ret, ImGui::CalcTextSize((_u8L("Filament") + " " + std::to_string(i + 1) + ":").c_str()).x); - } - } - if (ret > 0.0f) - ret += 2.0f * ImGui::GetStyle().ItemSpacing.x; - return ret; - }; - ImGui::Spacing(); imgui.title(_u8L("Settings")); - float offset = calc_offset(); + auto trim_text_if_needed = [](const std::string& txt) { + const float max_length = 250.0f; + const float length = ImGui::CalcTextSize(txt.c_str()).x; + if (length > max_length) { + const size_t new_len = txt.length() * max_length / length; + return txt.substr(0, new_len) + "..."; + } + return txt; + }; - if (!m_settings_ids.printer.empty()) { - imgui.text(_u8L("Printer") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.printer); - } - if (!m_settings_ids.print.empty()) { - imgui.text(_u8L("Print settings") + ":"); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.print); - } - if (!m_settings_ids.filament.empty()) { - for (unsigned char i : m_extruder_ids) { - if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { - std::string txt = _u8L("Filament"); - txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); - imgui.text(txt); - ImGui::SameLine(offset); - imgui.text(m_settings_ids.filament[i]); + if (ImGui::BeginTable("Settings", 2)) { + if (!m_settings_ids.printer.empty()) + add_strings_row_to_table(_u8L("Printer") + ":", ImGuiWrapper::COL_ORANGE_LIGHT, + trim_text_if_needed(m_settings_ids.printer), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + if (!m_settings_ids.print.empty()) + add_strings_row_to_table(_u8L("Print settings") + ":", ImGuiWrapper::COL_ORANGE_LIGHT, + trim_text_if_needed(m_settings_ids.print), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + if (!m_settings_ids.filament.empty()) { + for (unsigned char i : m_extruder_ids) { + if (i < static_cast(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) { + std::string txt = _u8L("Filament"); + txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1); + add_strings_row_to_table(txt, ImGuiWrapper::COL_ORANGE_LIGHT, + trim_text_if_needed(m_settings_ids.filament[i]), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + } } } + ImGui::EndTable(); } } @@ -4428,33 +4186,24 @@ void GCodeViewer::render_legend(float& legend_height) imgui.title(time_title + ":"); - std::string first_str = _u8L("First layer"); - std::string total_str = _u8L("Total"); + if (ImGui::BeginTable("Times", 2)) { + if (!time_mode.layers_times.empty()) { + add_strings_row_to_table(_u8L("First layer") + ":", ImGuiWrapper::COL_ORANGE_LIGHT, + short_time(get_time_dhms(time_mode.layers_times.front())), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); + } - float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x; - if (time_mode.layers_times.empty()) - max_len += ImGui::CalcTextSize(total_str.c_str()).x; - else - max_len += std::max(ImGui::CalcTextSize(first_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x); + add_strings_row_to_table(_u8L("Total") + ":", ImGuiWrapper::COL_ORANGE_LIGHT, + short_time(get_time_dhms(time_mode.time)), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE())); - if (!time_mode.layers_times.empty()) { - imgui.text(first_str + ":"); - ImGui::SameLine(max_len); - imgui.text(short_time(get_time_dhms(time_mode.layers_times.front()))); + ImGui::EndTable(); } - imgui.text(total_str + ":"); - ImGui::SameLine(max_len); - imgui.text(short_time(get_time_dhms(time_mode.time))); - auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { if (can_show_mode_button(mode)) { if (imgui.button(label)) { m_time_estimate_mode = mode; -#if ENABLE_PREVIEW_LAYER_TIME if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic) refresh_render_paths(false, false); -#endif // ENABLE_PREVIEW_LAYER_TIME imgui.set_requires_extra_frame(); } } @@ -4473,7 +4222,6 @@ void GCodeViewer::render_legend(float& legend_height) } } -#if ENABLE_PREVIEW_LAYOUT // toolbar section auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name, std::function draw_callback) { @@ -4513,7 +4261,6 @@ void GCodeViewer::render_legend(float& legend_height) } }; -#if ENABLE_LEGEND_TOOLBAR_ICONS auto image_icon = [&imgui](ImGuiWindow& window, const ImVec2& pos, float size, const wchar_t& icon_id) { ImGuiIO& io = ImGui::GetIO(); const ImTextureID tex_id = io.Fonts->TexID; @@ -4524,163 +4271,59 @@ void GCodeViewer::render_legend(float& legend_height) const ImVec2 uv1 = { static_cast(rect->X + rect->Width) / tex_w, static_cast(rect->Y + rect->Height) / tex_h }; window.DrawList->AddImage(tex_id, pos, { pos.x + size, pos.y + size }, uv0, uv1, ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f })); }; -#else - auto circle_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { - const float margin = 3.0f; - const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); - window.DrawList->AddCircleFilled(center, 0.5f * (size - 2.0f * margin), ImGuiWrapper::to_ImU32(color), 16); - }; - auto line_icon = [](ImGuiWindow& window, const ImVec2& pos, float size, const ColorRGBA& color) { - const float margin = 3.0f; - window.DrawList->AddLine({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin, pos.y + margin }, ImGuiWrapper::to_ImU32(color), 3.0f); - }; -#endif // ENABLE_LEGEND_TOOLBAR_ICONS ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendTravel); -#else - toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - line_icon(window, pos, size, Travel_Colors[0]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendWipe); -#else - toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [line_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - line_icon(window, pos, size, Wipe_Color); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendRetract); -#else - toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Retractions)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendDeretract); -#else - toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Unretractions)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendSeams); -#else - toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::Seams)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendToolChanges); -#else - toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ToolChanges)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendColorChanges); -#else - toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::ColorChanges)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendPausePrints); -#else - toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::PausePrints)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendCustomGCodes); -#else - toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [circle_icon](ImGuiWindow& window, const ImVec2& pos, float size) { - circle_icon(window, pos, size, Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendCOG); }); -#else - toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 black = ImGuiWrapper::to_ImU32({ 0.0f, 0.0f, 0.0f, 1.0f }); - const ImU32 white = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); - const float margin = 3.0f; - const ImVec2 center(0.5f * (pos.x + pos.x + size), 0.5f * (pos.y + pos.y + size)); - const float radius = 0.5f * (size - 2.0f * margin); - window.DrawList->PathArcToFast(center, radius, 0, 3); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(black); - window.DrawList->PathArcToFast(center, radius, 3, 6); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(white); - window.DrawList->PathArcToFast(center, radius, 6, 9); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(black); - window.DrawList->PathArcToFast(center, radius, 9, 12); - window.DrawList->PathLineTo(center); - window.DrawList->PathFillConvex(white); - window.DrawList->AddCircle(center, radius, black, 16); - }); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS ImGui::SameLine(); if (!wxGetApp().is_gcode_viewer()) { -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendShells); -#else - toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 1.0f }); - const float margin = 3.0f; - const float proj = 0.25f * size; - window.DrawList->AddRect({ pos.x + margin, pos.y + size - margin }, { pos.x + size - margin - proj, pos.y + margin + proj }, color); - window.DrawList->AddLine({ pos.x + margin, pos.y + margin + proj }, { pos.x + margin + proj, pos.y + margin }, color); - window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + margin + proj }, { pos.x + size - margin, pos.y + margin }, color); - window.DrawList->AddLine({ pos.x + size - margin - proj, pos.y + size - margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); - window.DrawList->AddLine({ pos.x + margin + proj, pos.y + margin }, { pos.x + size - margin, pos.y + margin }, color); - window.DrawList->AddLine({ pos.x + size - margin, pos.y + margin }, { pos.x + size - margin, pos.y + size - margin - proj }, color); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); ImGui::SameLine(); } -#if ENABLE_LEGEND_TOOLBAR_ICONS toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [image_icon](ImGuiWindow& window, const ImVec2& pos, float size) { image_icon(window, pos, size, ImGui::LegendToolMarker); -#else - toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [](ImGuiWindow& window, const ImVec2& pos, float size) { - const ImU32 color = ImGuiWrapper::to_ImU32({ 1.0f, 1.0f, 1.0f, 0.8f }); - const float margin = 3.0f; - const ImVec2 p1(0.5f * (pos.x + pos.x + size), pos.y + size - margin); - const ImVec2 p2(p1.x + 0.25f * size, p1.y - 0.25f * size); - const ImVec2 p3(p1.x - 0.25f * size, p1.y - 0.25f * size); - window.DrawList->AddTriangleFilled(p1, p2, p3, color); - const float mid_x = 0.5f * (pos.x + pos.x + size); - window.DrawList->AddRectFilled({ mid_x - 0.09375f * size, p1.y - 0.25f * size }, { mid_x + 0.09375f * size, pos.y + margin }, color); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS }); bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth(); @@ -4689,7 +4332,6 @@ void GCodeViewer::render_legend(float& legend_height) wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } m_legend_resizer.dirty = size_dirty; -#endif // ENABLE_PREVIEW_LAYOUT legend_height = ImGui::GetWindowHeight(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 81cc6e39a..0785f6a04 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -220,11 +220,7 @@ class GCodeViewer unsigned char cp_color_id{ 0 }; std::vector sub_paths; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const; -#else - bool matches(const GCodeProcessorResult::MoveVertex& move) const; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC size_t vertices_count() const { return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; } @@ -436,13 +432,11 @@ class GCodeViewer { struct Range { -#if ENABLE_PREVIEW_LAYER_TIME enum class EType : unsigned char { Linear, Logarithmic }; -#endif // ENABLE_PREVIEW_LAYER_TIME float min; float max; @@ -458,13 +452,8 @@ class GCodeViewer } void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } -#if ENABLE_PREVIEW_LAYER_TIME float step_size(EType type = EType::Linear) const; ColorRGBA get_color_at(float value, EType type = EType::Linear) const; -#else - float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } - ColorRGBA get_color_at(float value) const; -#endif // ENABLE_PREVIEW_LAYER_TIME }; struct Ranges @@ -481,10 +470,8 @@ class GCodeViewer Range volumetric_rate; // Color mapping by extrusion temperature. Range temperature; -#if ENABLE_PREVIEW_LAYER_TIME // Color mapping by layer time. std::array(PrintEstimatedStatistics::ETimeMode::Count)> layer_time; -#endif // ENABLE_PREVIEW_LAYER_TIME void reset() { height.reset(); @@ -493,11 +480,9 @@ class GCodeViewer fan_speed.reset(); volumetric_rate.reset(); temperature.reset(); -#if ENABLE_PREVIEW_LAYER_TIME for (auto& range : layer_time) { range.reset(); } -#endif // ENABLE_PREVIEW_LAYER_TIME } }; @@ -756,10 +741,8 @@ public: FanSpeed, Temperature, VolumetricRate, -#if ENABLE_PREVIEW_LAYER_TIME LayerTimeLinear, LayerTimeLogarithmic, -#endif // ENABLE_PREVIEW_LAYER_TIME Tool, ColorPrint, Count @@ -768,9 +751,7 @@ public: private: bool m_gl_data_initialized{ false }; unsigned int m_last_result_id{ 0 }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC EViewType m_last_view_type{ EViewType::Count }; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC size_t m_moves_count{ 0 }; std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths @@ -792,14 +773,12 @@ private: COG m_cog; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; -#if ENABLE_PREVIEW_LAYOUT struct LegendResizer { bool dirty{ true }; void reset() { dirty = true; } }; LegendResizer m_legend_resizer; -#endif // ENABLE_PREVIEW_LAYOUT PrintEstimatedStatistics m_print_statistics; PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -807,9 +786,7 @@ private: #endif // ENABLE_GCODE_VIEWER_STATISTICS GCodeProcessorResult::SettingsIds m_settings_ids; std::array m_sequential_range_caps; -#if ENABLE_PREVIEW_LAYER_TIME std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times; -#endif // ENABLE_PREVIEW_LAYER_TIME std::vector m_custom_gcode_per_print_z; @@ -829,11 +806,7 @@ public: #endif // ENABLE_LEGACY_OPENGL_REMOVAL // recalculate ranges in dependence of what is visible and sets tool/print colors void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); -#if ENABLE_PREVIEW_LAYOUT void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; -#else - void refresh_render_paths(); -#endif // ENABLE_PREVIEW_LAYOUT void update_shells_color_by_extruder(const DynamicPrintConfig* config); void reset(); @@ -878,9 +851,7 @@ public: std::vector& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; } size_t get_extruders_count() { return m_extruders_count; } -#if ENABLE_PREVIEW_LAYOUT void invalidate_legend() { m_legend_resizer.reset(); } -#endif // ENABLE_PREVIEW_LAYOUT private: void load_toolpaths(const GCodeProcessorResult& gcode_result); @@ -889,9 +860,6 @@ private: #else void load_shells(const Print& print, bool initialized); #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_PREVIEW_LAYOUT - void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; -#endif // !ENABLE_PREVIEW_LAYOUT void render_toolpaths(); void render_shells(); void render_legend(float& legend_height); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dbfa09f02..2b5875815 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2512,21 +2512,12 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, co request_extra_frame(); } -#if ENABLE_PREVIEW_LAYOUT void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) { m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); set_as_dirty(); request_extra_frame(); } -#else -void GLCanvas3D::refresh_gcode_preview_render_paths() -{ - m_gcode_viewer.refresh_render_paths(); - set_as_dirty(); - request_extra_frame(); -} -#endif // ENABLE_PREVIEW_LAYOUT void GLCanvas3D::load_sla_preview() { @@ -2833,15 +2824,8 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; } case 'L': case 'l': { - if (!m_main_toolbar.is_enabled()) { -#if ENABLE_PREVIEW_LAYOUT + if (!m_main_toolbar.is_enabled()) show_legend(!is_legend_shown()); -#else - m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); - m_dirty = true; - wxGetApp().plater()->update_preview_bottom_toolbar(); -#endif // ENABLE_PREVIEW_LAYOUT - } break; } case 'O': @@ -4296,9 +4280,7 @@ void GLCanvas3D::set_cursor(ECursorType type) void GLCanvas3D::msw_rescale() { -#if ENABLE_PREVIEW_LAYOUT m_gcode_viewer.invalidate_legend(); -#endif // ENABLE_PREVIEW_LAYOUT } void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ec6f82011..0adf1cb26 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -817,11 +817,7 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); void load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); -#if ENABLE_PREVIEW_LAYOUT void refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last); -#else - void refresh_gcode_preview_render_paths(); -#endif // ENABLE_PREVIEW_LAYOUT void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } void load_sla_preview(); @@ -915,10 +911,8 @@ public: bool are_labels_shown() const { return m_labels.is_shown(); } void show_labels(bool show) { m_labels.show(show); } -#if ENABLE_PREVIEW_LAYOUT bool is_legend_shown() const { return m_gcode_viewer.is_legend_enabled(); } void show_legend(bool show) { m_gcode_viewer.enable_legend(show); m_dirty = true; } -#endif // ENABLE_PREVIEW_LAYOUT bool is_using_slope() const { return m_slope.is_used(); } void use_slope(bool use) { m_slope.use(use); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 4989d12ee..7989c3cdc 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -208,75 +208,6 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_layers_slider_sizer = create_layers_slider_sizer(); wxGetApp().UpdateDarkUI(m_bottom_toolbar_panel = new wxPanel(this)); -#if !ENABLE_PREVIEW_LAYOUT - m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View")); -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(m_choice_view_type = new BitmapComboBox(m_bottom_toolbar_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY)); -#else - m_choice_view_type = new wxComboBox(m_bottom_toolbar_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); -#endif - m_choice_view_type->Append(_L("Feature type")); - m_choice_view_type->Append(_L("Height")); - m_choice_view_type->Append(_L("Width")); - m_choice_view_type->Append(_L("Speed")); - m_choice_view_type->Append(_L("Fan speed")); - m_choice_view_type->Append(_L("Temperature")); - m_choice_view_type->Append(_L("Volumetric flow rate")); -#if ENABLE_PREVIEW_LAYER_TIME - m_choice_view_type->Append(_L("Layer time (linear)")); - m_choice_view_type->Append(_L("Layer time (logarithmic)")); -#endif // ENABLE_PREVIEW_LAYER_TIME - m_choice_view_type->Append(_L("Tool")); - m_choice_view_type->Append(_L("Color Print")); - m_choice_view_type->SetSelection(0); - - m_label_show = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("Show")); - -#ifdef _WIN32 - long combo_style = wxCB_READONLY | wxBORDER_SIMPLE; //set border allows use default color instead of theme color wich is allways light under MSW -#else - long combo_style = wxCB_READONLY; -#endif - - m_combochecklist_features = new wxComboCtrl(); - m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, combo_style); - std::string feature_items = GUI::into_u8( - _L("Unknown") + "|1|" + - _L("Perimeter") + "|1|" + - _L("External perimeter") + "|1|" + - _L("Overhang perimeter") + "|1|" + - _L("Internal infill") + "|1|" + - _L("Solid infill") + "|1|" + - _L("Top solid infill") + "|1|" + - _L("Ironing") + "|1|" + - _L("Bridge infill") + "|1|" + - _L("Gap fill") + "|1|" + - _L("Skirt/Brim") + "|1|" + - _L("Support material") + "|1|" + - _L("Support material interface") + "|1|" + - _L("Wipe tower") + "|1|" + - _L("Custom") + "|1" - ); - Slic3r::GUI::create_combochecklist(m_combochecklist_features, GUI::into_u8(_L("Feature types")), feature_items); - - m_combochecklist_options = new wxComboCtrl(); - m_combochecklist_options->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, combo_style); - std::string options_items = GUI::into_u8( - get_option_type_string(OptionType::Travel) + "|0|" + - get_option_type_string(OptionType::Wipe) + "|0|" + - get_option_type_string(OptionType::Retractions) + "|0|" + - get_option_type_string(OptionType::Unretractions) + "|0|" + - get_option_type_string(OptionType::Seams) + "|0|" + - get_option_type_string(OptionType::ToolChanges) + "|0|" + - get_option_type_string(OptionType::ColorChanges) + "|0|" + - get_option_type_string(OptionType::PausePrints) + "|0|" + - get_option_type_string(OptionType::CustomGCodes) + "|0|" + - get_option_type_string(OptionType::Shells) + "|0|" + - get_option_type_string(OptionType::ToolMarker) + "|1|" + - get_option_type_string(OptionType::Legend) + "|1" - ); - Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); -#endif // !ENABLE_PREVIEW_LAYOUT m_left_sizer = new wxBoxSizer(wxVERTICAL); m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); @@ -288,19 +219,6 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model) m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); -#if !ENABLE_PREVIEW_LAYOUT - bottom_toolbar_sizer->AddSpacer(5); - bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0); - bottom_toolbar_sizer->AddSpacer(5); - bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); - bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0); - // change the following number if editing the layout of the bottom toolbar sizer. It is used into update_bottom_toolbar() - m_combochecklist_features_pos = 6; - bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); - bottom_toolbar_sizer->Hide(m_combochecklist_features); - bottom_toolbar_sizer->AddSpacer(5); -#endif // !ENABLE_PREVIEW_LAYOUT bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0); m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer); @@ -362,9 +280,6 @@ void Preview::load_print(bool keep_z_range) else if (tech == ptSLA) load_print_as_sla(); -#if !ENABLE_PREVIEW_LAYOUT - update_bottom_toolbar(); -#endif // !ENABLE_PREVIEW_LAYOUT Layout(); } @@ -407,12 +322,6 @@ void Preview::refresh_print() void Preview::msw_rescale() { -#if !ENABLE_PREVIEW_LAYOUT -#ifdef _WIN32 - m_choice_view_type->Rescale(); - m_choice_view_type->SetMinSize(m_choice_view_type->GetSize()); -#endif -#endif // !ENABLE_PREVIEW_LAYOUT // rescale slider if (m_layers_slider != nullptr) m_layers_slider->msw_rescale(); if (m_moves_slider != nullptr) m_moves_slider->msw_rescale(); @@ -428,16 +337,8 @@ void Preview::sys_color_changed() { #ifdef _WIN32 wxWindowUpdateLocker noUpdates(this); - wxGetApp().UpdateAllStaticTextDarkUI(m_bottom_toolbar_panel); -#if !ENABLE_PREVIEW_LAYOUT - wxGetApp().UpdateDarkUI(m_choice_view_type); - wxGetApp().UpdateDarkUI(m_combochecklist_features); - wxGetApp().UpdateDarkUI(static_cast(m_combochecklist_features->GetPopupControl())); - wxGetApp().UpdateDarkUI(m_combochecklist_options); - wxGetApp().UpdateDarkUI(static_cast(m_combochecklist_options->GetPopupControl())); -#endif // !ENABLE_PREVIEW_LAYOUT -#endif +#endif // _WIN32 if (m_layers_slider != nullptr) m_layers_slider->sys_color_changed(); @@ -461,22 +362,12 @@ void Preview::edit_layers_slider(wxKeyEvent& evt) void Preview::bind_event_handlers() { Bind(wxEVT_SIZE, &Preview::on_size, this); -#if !ENABLE_PREVIEW_LAYOUT - m_choice_view_type->Bind(wxEVT_COMBOBOX, &Preview::on_choice_view_type, this); - m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); - m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); -#endif // !ENABLE_PREVIEW_LAYOUT m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } void Preview::unbind_event_handlers() { Unbind(wxEVT_SIZE, &Preview::on_size, this); -#if !ENABLE_PREVIEW_LAYOUT - m_choice_view_type->Unbind(wxEVT_COMBOBOX, &Preview::on_choice_view_type, this); - m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); - m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); -#endif // !ENABLE_PREVIEW_LAYOUT m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); } @@ -497,75 +388,6 @@ void Preview::on_size(wxSizeEvent& evt) Refresh(); } -#if !ENABLE_PREVIEW_LAYOUT -void Preview::on_choice_view_type(wxCommandEvent& evt) -{ - int selection = m_choice_view_type->GetCurrentSelection(); - if (0 <= selection && selection < static_cast(GCodeViewer::EViewType::Count)) { - m_canvas->set_toolpath_view_type(static_cast(selection)); - m_keep_current_preview_type = true; - } - refresh_print(); -} - -void Preview::on_combochecklist_features(wxCommandEvent& evt) -{ - unsigned int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features); - m_canvas->set_toolpath_role_visibility_flags(flags); - refresh_print(); -} - -void Preview::on_combochecklist_options(wxCommandEvent& evt) -{ - const unsigned int curr_flags = m_canvas->get_gcode_options_visibility_flags(); - const unsigned int new_flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options); - if (curr_flags == new_flags) - return; - - m_canvas->set_gcode_options_visibility_from_flags(new_flags); - if (m_canvas->get_gcode_view_type() == GCodeViewer::EViewType::Feedrate) { - const unsigned int diff_flags = curr_flags ^ new_flags; - if ((diff_flags & (1 << static_cast(Preview::OptionType::Travel))) != 0) - refresh_print(); - else - m_canvas->refresh_gcode_preview_render_paths(); - } - else - m_canvas->refresh_gcode_preview_render_paths(); - - update_moves_slider(); -} - -void Preview::update_bottom_toolbar() -{ - combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags()); - combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags()); - - // updates visibility of features combobox - if (m_bottom_toolbar_panel->IsShown()) { - wxSizer* sizer = m_bottom_toolbar_panel->GetSizer(); - bool show = !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType; - - if (show) { - if (sizer->GetItem(m_combochecklist_features) == nullptr) { - sizer->Insert(m_combochecklist_features_pos, m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); - sizer->Show(m_combochecklist_features); - sizer->Layout(); - Refresh(); - } - } - else { - if (sizer->GetItem(m_combochecklist_features) != nullptr) { - sizer->Hide(m_combochecklist_features); - sizer->Detach(m_combochecklist_features); - sizer->Layout(); - Refresh(); - } - } - } -} -#endif // !ENABLE_PREVIEW_LAYOUT - wxBoxSizer* Preview::create_layers_slider_sizer() { wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); @@ -1024,7 +846,6 @@ void Preview::load_print_as_fff(bool keep_z_range) m_canvas->get_custom_gcode_per_print_z(); const bool contains_color_gcodes = std::any_of(std::begin(gcodes), std::end(gcodes), [] (auto const& item) { return item.type == CustomGCode::Type::ColorChange; }); -#if ENABLE_PREVIEW_LAYOUT const GCodeViewer::EViewType choice = contains_color_gcodes ? GCodeViewer::EViewType::ColorPrint : (number_extruders > 1) ? GCodeViewer::EViewType::Tool : GCodeViewer::EViewType::FeatureType; @@ -1034,21 +855,6 @@ void Preview::load_print_as_fff(bool keep_z_range) m_keep_current_preview_type = true; refresh_print(); } -#else - const wxString choice = contains_color_gcodes ? - _L("Color Print") : - (number_extruders > 1) ? _L("Tool") : _L("Feature type"); - int type = m_choice_view_type->FindString(choice); - if (m_choice_view_type->GetSelection() != type) { - if (0 <= type && type < static_cast(GCodeViewer::EViewType::Count)) { - m_choice_view_type->SetSelection(type); - m_canvas->set_gcode_view_preview_type(static_cast(type)); - if (wxGetApp().is_gcode_viewer()) - m_keep_current_preview_type = true; - refresh_print(); - } - } -#endif // ENABLE_PREVIEW_LAYOUT } if (zs.empty()) { @@ -1123,27 +929,5 @@ void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) m_canvas->render(); } -#if !ENABLE_PREVIEW_LAYOUT -wxString Preview::get_option_type_string(OptionType type) const -{ - switch (type) - { - case OptionType::Travel: { return _L("Travel"); } - case OptionType::Wipe: { return _L("Wipe"); } - case OptionType::Retractions: { return _L("Retractions"); } - case OptionType::Unretractions: { return _L("Deretractions"); } - case OptionType::Seams: { return _L("Seams"); } - case OptionType::ToolChanges: { return _L("Tool changes"); } - case OptionType::ColorChanges: { return _L("Color changes"); } - case OptionType::PausePrints: { return _L("Print pauses"); } - case OptionType::CustomGCodes: { return _L("Custom G-codes"); } - case OptionType::Shells: { return _L("Shells"); } - case OptionType::ToolMarker: { return _L("Tool marker"); } - case OptionType::Legend: { return _L("Legend/Estimated printing time"); } - default: { return ""; } - } -} -#endif // !ENABLE_PREVIEW_LAYOUT - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 1137f11b9..25d9fe084 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -80,18 +80,6 @@ class Preview : public wxPanel wxBoxSizer* m_left_sizer { nullptr }; wxBoxSizer* m_layers_slider_sizer { nullptr }; wxPanel* m_bottom_toolbar_panel { nullptr }; -#if !ENABLE_PREVIEW_LAYOUT - wxStaticText* m_label_view_type { nullptr }; -#ifdef _WIN32 - BitmapComboBox* m_choice_view_type { nullptr }; -#else - wxComboBox* m_choice_view_type { nullptr }; -#endif - wxStaticText* m_label_show{ nullptr }; - wxComboCtrl* m_combochecklist_features { nullptr }; - size_t m_combochecklist_features_pos { 0 }; - wxComboCtrl* m_combochecklist_options { nullptr }; -#endif // !ENABLE_PREVIEW_LAYOUT DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; @@ -129,9 +117,6 @@ public: CenterOfGravity, Shells, ToolMarker, -#if !ENABLE_PREVIEW_LAYOUT - Legend -#endif // !ENABLE_PREVIEW_LAYOUT }; Preview(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, @@ -159,17 +144,12 @@ public: bool is_loaded() const { return m_loaded; } -#if !ENABLE_PREVIEW_LAYOUT - void update_bottom_toolbar(); -#endif // !ENABLE_PREVIEW_LAYOUT void update_moves_slider(); void enable_moves_slider(bool enable); void move_moves_slider(wxKeyEvent& evt); void hide_layers_slider(); -#if ENABLE_PREVIEW_LAYOUT void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; } -#endif // ENABLE_PREVIEW_LAYOUT private: bool init(wxWindow* parent, Bed3D& bed, Model* model); @@ -178,11 +158,6 @@ private: void unbind_event_handlers(); void on_size(wxSizeEvent& evt); -#if !ENABLE_PREVIEW_LAYOUT - void on_choice_view_type(wxCommandEvent& evt); - void on_combochecklist_features(wxCommandEvent& evt); - void on_combochecklist_options(wxCommandEvent& evt); -#endif // !ENABLE_PREVIEW_LAYOUT // Create/Update/Reset double slider on 3dPreview wxBoxSizer* create_layers_slider_sizer(); @@ -199,9 +174,6 @@ private: void on_layers_slider_scroll_changed(wxCommandEvent& event); void on_moves_slider_scroll_changed(wxCommandEvent& event); -#if !ENABLE_PREVIEW_LAYOUT - wxString get_option_type_string(OptionType type) const; -#endif // !ENABLE_PREVIEW_LAYOUT }; } // namespace GUI diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 22451c60c..3bdc80abd 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -64,20 +64,18 @@ static const std::map font_icons = { }; static const std::map font_icons_large = { -#if ENABLE_LEGEND_TOOLBAR_ICONS - {ImGui::LegendTravel , "legend_travel" }, - {ImGui::LegendWipe , "legend_wipe" }, - {ImGui::LegendRetract , "legend_retract" }, - {ImGui::LegendDeretract , "legend_deretract" }, - {ImGui::LegendSeams , "legend_seams" }, - {ImGui::LegendToolChanges , "legend_toolchanges" }, - {ImGui::LegendColorChanges , "legend_colorchanges" }, - {ImGui::LegendPausePrints , "legend_pauseprints" }, - {ImGui::LegendCustomGCodes , "legend_customgcodes" }, - {ImGui::LegendCOG , "legend_cog" }, - {ImGui::LegendShells , "legend_shells" }, - {ImGui::LegendToolMarker , "legend_toolmarker" }, -#endif // ENABLE_LEGEND_TOOLBAR_ICONS + {ImGui::LegendTravel , "legend_travel" }, + {ImGui::LegendWipe , "legend_wipe" }, + {ImGui::LegendRetract , "legend_retract" }, + {ImGui::LegendDeretract , "legend_deretract" }, + {ImGui::LegendSeams , "legend_seams" }, + {ImGui::LegendToolChanges , "legend_toolchanges" }, + {ImGui::LegendColorChanges , "legend_colorchanges" }, + {ImGui::LegendPausePrints , "legend_pauseprints" }, + {ImGui::LegendCustomGCodes , "legend_customgcodes" }, + {ImGui::LegendCOG , "legend_cog" }, + {ImGui::LegendShells , "legend_shells" }, + {ImGui::LegendToolMarker , "legend_toolmarker" }, {ImGui::CloseNotifButton , "notification_close" }, {ImGui::CloseNotifHoverButton , "notification_close_hover" }, {ImGui::EjectButton , "notification_eject_sd" }, @@ -396,7 +394,6 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active) return ImGui::RadioButton(label_utf8.c_str(), active); } -#if ENABLE_PREVIEW_LAYOUT bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback) { @@ -430,7 +427,6 @@ bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool a IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window.DC.LastItemStatusFlags); return pressed; } -#endif // ENABLE_PREVIEW_LAYOUT bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { @@ -575,7 +571,6 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); ImGui::SameLine(); -#if ENABLE_LEGEND_TOOLBAR_ICONS ImGuiIO& io = ImGui::GetIO(); assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0); float inv_tex_w = 1.0f / float(io.Fonts->TexWidth); @@ -585,13 +580,11 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float const ImVec2 size = { float(rect->Width), float(rect->Height) }; const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h); const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.4f, 0.4f, 0.4f, 1.0f }); -#if ENABLE_LEGEND_TOOLBAR_ICONS const ImTextureID tex_id = io.Fonts->TexID; if (image_button(tex_id, size, uv0, uv1, -1, ImVec4(0.0, 0.0, 0.0, 0.0), ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags_PressedOnClick)) { if (!slider_editing) @@ -600,13 +593,6 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float ImGui::ClearActiveID(); this->set_requires_extra_frame(); } -#else - std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label); - if (ImGui::Button(into_u8(btn_name).c_str())) { - ImGui::SetKeyboardFocusHere(-1); - this->set_requires_extra_frame(); - } -#endif // ENABLE_LEGEND_TOOLBAR_ICONS ImGui::PopStyleColor(3); @@ -688,14 +674,10 @@ bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection, ImGuiComboFlags flags) { // this is to force the label to the left of the widget: -#if ENABLE_PREVIEW_LAYOUT if (!label.empty()) { -#endif // ENABLE_PREVIEW_LAYOUT text(label); ImGui::SameLine(); -#if ENABLE_PREVIEW_LAYOUT } -#endif // ENABLE_PREVIEW_LAYOUT int selection_out = selection; bool res = false; @@ -1145,13 +1127,11 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } -#if ENABLE_LEGEND_TOOLBAR_ICONS ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) { auto item = m_custom_glyph_rects_ids.find(tex_id); return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr; } -#endif // ENABLE_LEGEND_TOOLBAR_ICONS ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color) { @@ -1270,7 +1250,6 @@ void ImGuiWrapper::init_font(bool compress) int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next // add rectangles for the icons to the font atlas -#if ENABLE_LEGEND_TOOLBAR_ICONS for (auto& icon : font_icons) { m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); @@ -1282,15 +1261,7 @@ void ImGuiWrapper::init_font(bool compress) for (auto& icon : font_icons_extra_large) { m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); -} -#else - for (auto& icon : font_icons) - io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); - for (auto& icon : font_icons_large) - io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); - for (auto& icon : font_icons_extra_large) - io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS + } // Build texture atlas unsigned char* pixels; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index f461bc970..769deccb8 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -21,9 +21,7 @@ class wxString; class wxMouseEvent; class wxKeyEvent; -#if ENABLE_PREVIEW_LAYOUT struct IMGUI_API ImGuiWindow; -#endif // ENABLE_PREVIEW_LAYOUT namespace Slic3r { namespace GUI { @@ -40,9 +38,7 @@ class ImGuiWrapper bool m_disabled{ false }; bool m_new_frame_open{ false }; bool m_requires_extra_frame{ false }; -#if ENABLE_LEGEND_TOOLBAR_ICONS std::map m_custom_glyph_rects_ids; -#endif // ENABLE_LEGEND_TOOLBAR_ICONS std::string m_clipboard_text; public: @@ -92,9 +88,7 @@ public: bool button(const wxString &label); bool button(const wxString& label, float width, float height); bool radio_button(const wxString &label, bool active); -#if ENABLE_PREVIEW_LAYOUT bool draw_radio_button(const std::string& name, float size, bool active, std::function draw_callback); -#endif // ENABLE_PREVIEW_LAYOUT bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); @@ -143,9 +137,7 @@ public: static ColorRGBA from_ImU32(const ImU32& color); static ColorRGBA from_ImVec4(const ImVec4& color); -#if ENABLE_LEGEND_TOOLBAR_ICONS ImFontAtlasCustomRect* GetTextureCustomRect(const wchar_t& tex_id); -#endif // ENABLE_LEGEND_TOOLBAR_ICONS static const ImVec4 COL_GREY_DARK; static const ImVec4 COL_GREY_LIGHT; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index df531c562..96959f33b 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1494,11 +1494,9 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("Show &Labels") + sep + "E", _L("Show object/instance labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); -#if ENABLE_PREVIEW_LAYOUT append_menu_check_item(viewMenu, wxID_ANY, _L("Show Legen&d") + sep + "L", _L("Show legend in preview"), [this](wxCommandEvent&) { m_plater->show_legend(!m_plater->is_legend_shown()); }, this, [this]() { return m_plater->is_preview_shown(); }, [this]() { return m_plater->is_legend_shown(); }, this); -#endif // ENABLE_PREVIEW_LAYOUT append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse Sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); @@ -1616,12 +1614,10 @@ void MainFrame::init_menubar_as_gcodeviewer() if (m_plater != nullptr) { viewMenu = new wxMenu(); add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); -#if ENABLE_PREVIEW_LAYOUT viewMenu->AppendSeparator(); append_menu_check_item(viewMenu, wxID_ANY, _L("Show legen&d") + sep + "L", _L("Show legend"), [this](wxCommandEvent&) { m_plater->show_legend(!m_plater->is_legend_shown()); }, this, [this]() { return m_plater->is_preview_shown(); }, [this]() { return m_plater->is_legend_shown(); }, this); -#endif // ENABLE_PREVIEW_LAYOUT } // helpmenu diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bb3f6e41a..12a654175 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1768,10 +1768,8 @@ struct Plater::priv bool are_view3D_labels_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->are_labels_shown(); } void show_view3D_labels(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_labels(show); } -#if ENABLE_PREVIEW_LAYOUT bool is_legend_shown() const { return (current_panel == preview) && preview->get_canvas3d()->is_legend_shown(); } void show_legend(bool show) { if (current_panel == preview) preview->get_canvas3d()->show_legend(show); } -#endif // ENABLE_PREVIEW_LAYOUT bool is_sidebar_collapsed() const { return sidebar->is_collapsed(); } void collapse_sidebar(bool collapse); @@ -1786,9 +1784,6 @@ struct Plater::priv bool init_view_toolbar(); bool init_collapse_toolbar(); -#if !ENABLE_PREVIEW_LAYOUT - void update_preview_bottom_toolbar(); -#endif // !ENABLE_PREVIEW_LAYOUT void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); @@ -4762,13 +4757,6 @@ bool Plater::priv::init_collapse_toolbar() return true; } -#if !ENABLE_PREVIEW_LAYOUT -void Plater::priv::update_preview_bottom_toolbar() -{ - preview->update_bottom_toolbar(); -} -#endif // !ENABLE_PREVIEW_LAYOUT - void Plater::priv::update_preview_moves_slider() { preview->update_moves_slider(); @@ -5747,10 +5735,8 @@ bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); } bool Plater::are_view3D_labels_shown() const { return p->are_view3D_labels_shown(); } void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); } -#if ENABLE_PREVIEW_LAYOUT bool Plater::is_legend_shown() const { return p->is_legend_shown(); } void Plater::show_legend(bool show) { p->show_legend(show); } -#endif // ENABLE_PREVIEW_LAYOUT bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); } void Plater::collapse_sidebar(bool show) { p->collapse_sidebar(show); } @@ -7117,13 +7103,6 @@ GLToolbar& Plater::get_collapse_toolbar() return p->collapse_toolbar; } -#if !ENABLE_PREVIEW_LAYOUT -void Plater::update_preview_bottom_toolbar() -{ - p->update_preview_bottom_toolbar(); -} -#endif // !ENABLE_PREVIEW_LAYOUT - void Plater::update_preview_moves_slider() { p->update_preview_moves_slider(); @@ -7232,12 +7211,10 @@ bool Plater::is_render_statistic_dialog_visible() const return p->show_render_statistic_dialog; } -#if ENABLE_PREVIEW_LAYOUT void Plater::set_keep_current_preview_type(bool value) { p->preview->set_keep_current_preview_type(value); } -#endif // ENABLE_PREVIEW_LAYOUT Plater::TakeSnapshot::TakeSnapshot(Plater *plater, const std::string &snapshot_name) : TakeSnapshot(plater, from_u8(snapshot_name)) {} diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 1d7576266..9dc9f6316 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -225,10 +225,8 @@ public: bool are_view3D_labels_shown() const; void show_view3D_labels(bool show); -#if ENABLE_PREVIEW_LAYOUT bool is_legend_shown() const; void show_legend(bool show); -#endif // ENABLE_PREVIEW_LAYOUT bool is_sidebar_collapsed() const; void collapse_sidebar(bool show); @@ -390,9 +388,6 @@ public: const GLToolbar& get_collapse_toolbar() const; GLToolbar& get_collapse_toolbar(); -#if !ENABLE_PREVIEW_LAYOUT - void update_preview_bottom_toolbar(); -#endif // !ENABLE_PREVIEW_LAYOUT void update_preview_moves_slider(); void enable_preview_moves_slider(bool enable); @@ -458,9 +453,7 @@ public: void toggle_render_statistic_dialog(); bool is_render_statistic_dialog_visible() const; -#if ENABLE_PREVIEW_LAYOUT void set_keep_current_preview_type(bool value); -#endif // ENABLE_PREVIEW_LAYOUT // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu. bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition); diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index a1230ac03..775796ba7 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -66,7 +66,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { GIVEN("polyline") { Polyline polyline { { 50, 150 }, { 300, 150 } }; WHEN("intersection_pl") { - Polylines result = Slic3r::intersection_pl({ polyline }, { square, hole_in_square }); + Polylines result = Slic3r::intersection_pl(polyline, ExPolygon{ square, hole_in_square }); THEN("correct number of result lines") { REQUIRE(result.size() == 2); } @@ -99,7 +99,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { { 74730000, 74730000 }, { 55270000, 74730000 }, { 55270000, 68063296 }, { 44730000, 68063296 }, { 44730000, 74730000 }, { 25270000, 74730000 }, { 25270000, 55270000 }, { 31936670, 55270000 }, { 31936670, 44730000 }, { 25270000, 44730000 }, { 25270000, 25270000 }, { 44730000, 25270000 }, { 44730000, 31936670 } }; Slic3r::Polygon clip { {75200000, 45200000}, {54800000, 45200000}, {54800000, 24800000}, {75200000, 24800000} }; - Slic3r::Polylines result = Slic3r::intersection_pl(subject, { clip }); + Slic3r::Polylines result = Slic3r::intersection_pl(subject, ExPolygon{ clip }); THEN("intersection_pl - result is not empty") { REQUIRE(result.size() == 1); } @@ -117,7 +117,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") { GIVEN("Clipper bug #126") { Slic3r::Polyline subject { { 200000, 19799999 }, { 200000, 200000 }, { 24304692, 200000 }, { 15102879, 17506106 }, { 13883200, 19799999 }, { 200000, 19799999 } }; Slic3r::Polygon clip { { 15257205, 18493894 }, { 14350057, 20200000 }, { -200000, 20200000 }, { -200000, -200000 }, { 25196917, -200000 } }; - Slic3r::Polylines result = Slic3r::intersection_pl(subject, { clip }); + Slic3r::Polylines result = Slic3r::intersection_pl(subject, ExPolygon{ clip }); THEN("intersection_pl - result is not empty") { REQUIRE(result.size() == 1); } diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index e9fcf84f1..892e7a8a7 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -2,6 +2,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp slic3r_jobs_tests.cpp + slic3r_version_tests.cpp ) # mold linker for successful linking needs also to link TBB library and link it before libslic3r. diff --git a/tests/slic3rutils/slic3r_version_tests.cpp b/tests/slic3rutils/slic3r_version_tests.cpp new file mode 100644 index 000000000..14f4f5dc0 --- /dev/null +++ b/tests/slic3rutils/slic3r_version_tests.cpp @@ -0,0 +1,83 @@ +#include "catch2/catch.hpp" + +#include "slic3r/Config/Version.hpp" + + +TEST_CASE("Check parsing and comparing of config versions", "[Version]") { + using namespace Slic3r; + + GUI::Config::Version v; + + v.config_version = *Semver::parse("1.1.2"); + v.min_slic3r_version = *Semver::parse("1.38.0"); + v.max_slic3r_version = Semver::inf(); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.38.0"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); + + // Test the prerelease status. + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0"))); + + v.config_version = *Semver::parse("1.1.2-alpha"); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + + v.config_version = *Semver::parse("1.1.2-alpha1"); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + + v.config_version = *Semver::parse("1.1.2-beta"); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + + v.config_version = *Semver::parse("1.1.2-rc"); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + + v.config_version = *Semver::parse("1.1.2-rc2"); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-rc"))); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.39.0"))); + + // Test the upper boundary. + v.config_version = *Semver::parse("1.1.2"); + v.max_slic3r_version = *Semver::parse("1.39.3-beta1"); + REQUIRE(v.is_slic3r_supported(*Semver::parse("1.38.0"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1"))); + REQUIRE(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha"))); +}