diff --git a/doc/gradual_overhang_speed.svg b/doc/gradual_overhang_speed.svg
new file mode 100644
index 0000000000..cec3bfbd83
--- /dev/null
+++ b/doc/gradual_overhang_speed.svg
@@ -0,0 +1,857 @@
+
+
+
+
diff --git a/include/FanSpeedLayerTime.h b/include/FanSpeedLayerTime.h
index bc6c00fc73..05554a886d 100644
--- a/include/FanSpeedLayerTime.h
+++ b/include/FanSpeedLayerTime.h
@@ -1,5 +1,5 @@
-//Copyright (c) 2020 Ultimaker B.V.
-//CuraEngine is released under the terms of the AGPLv3 or higher.
+// Copyright (c) 2020 Ultimaker B.V.
+// CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef FAN_SPEED_LAYER_TIME_H
#define FAN_SPEED_LAYER_TIME_H
@@ -8,7 +8,7 @@
#include "settings/types/LayerIndex.h"
#include "settings/types/Velocity.h"
-namespace cura
+namespace cura
{
/*!
@@ -19,7 +19,7 @@ namespace cura
* store these settings over and over again for each part, even though the
* settings may be different for each part on a layer.
*/
-struct FanSpeedLayerTimeSettings
+struct FanSpeedLayerTimeSettings
{
public:
/*!
@@ -28,6 +28,11 @@ struct FanSpeedLayerTimeSettings
*/
Duration cool_min_layer_time;
+ /*!
+ * Similar to Minimum layer time, but to be applied for layers that contain overhanging extrusion.
+ */
+ Duration cool_min_layer_time_overhang;
+
/*!
* "Regular/Maximum Fan Speed Threshold". If the layers take longer to print
* than this, they'll use the regular fan speed. If they take shorter, we'll
diff --git a/include/LayerPlan.h b/include/LayerPlan.h
index 47cea1aa19..449a3a3127 100644
--- a/include/LayerPlan.h
+++ b/include/LayerPlan.h
@@ -60,6 +60,12 @@ class LayerPlan : public NoCopy
#endif
public:
+ struct OverhangMask
+ {
+ Shape supported_region;
+ Ratio speed_ratio;
+ };
+
const PathConfigStorage configs_storage_; //!< The line configs for this layer for each feature type
const coord_t z_;
coord_t final_travel_z_;
@@ -115,8 +121,10 @@ class LayerPlan : public NoCopy
Comb* comb_;
coord_t comb_move_inside_distance_; //!< Whenever using the minimum boundary for combing it tries to move the coordinates inside by this distance after calculating the combing.
Shape bridge_wall_mask_; //!< The regions of a layer part that are not supported, used for bridging
- Shape overhang_mask_; //!< The regions of a layer part where the walls overhang
+ std::vector overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most
+ //!< overhanging. For a visual explanation of the result, see doc/gradual_overhang_speed.svg
Shape seam_overhang_mask_; //!< The regions of a layer part where the walls overhang, specifically as defined for the seam
+ bool contains_overhang_{ false }; //!< Indicates whether this plan contains any overhanging extrusion
Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air
bool min_layer_time_used = false; //!< Wether or not the minimum layer time (cool_min_layer_time) was actually used in this layerplan.
@@ -295,11 +303,11 @@ class LayerPlan : public NoCopy
void setBridgeWallMask(const Shape& polys);
/*!
- * Set overhang_mask.
+ * Set overhang_masks.
*
- * \param polys The overhung areas of the part currently being processed that will require modified print settings
+ * \param masks The overhung areas of the part currently being processed that will require modified print settings
*/
- void setOverhangMask(const Shape& polys);
+ void setOverhangMasks(const std::vector& masks);
/*!
* Set seam_overhang_mask.
@@ -393,6 +401,17 @@ class LayerPlan : public NoCopy
const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT,
const bool travel_to_z = true);
+ void addExtrusionMoveWithGradualOverhang(
+ const Point3LL& p,
+ const GCodePathConfig& config,
+ const SpaceFillType space_fill_type,
+ const Ratio& flow = 1.0_r,
+ const Ratio width_factor = 1.0_r,
+ const bool spiralize = false,
+ const Ratio speed_factor = 1.0_r,
+ const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT,
+ const bool travel_to_z = true);
+
/*!
* Add polygon to the gcode starting at vertex \p startIdx
* \param polygon The polygon
@@ -1000,13 +1019,6 @@ class LayerPlan : public NoCopy
* \return The distance from the start of the current wall line to the first bridge segment
*/
coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const;
-
- /*!
- * \brief Calculates whether the given segment is to be treated as overhanging
- * \param p0 The start point of the segment
- * \param p1 The end point of the segment
- */
- bool segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const;
};
} // namespace cura
diff --git a/include/geometry/Shape.h b/include/geometry/Shape.h
index 552325676f..4397036633 100644
--- a/include/geometry/Shape.h
+++ b/include/geometry/Shape.h
@@ -256,6 +256,14 @@ class Shape : public LinesSet
*/
void simplify(ClipperLib::PolyFillType fill_type = ClipperLib::pftEvenOdd);
+ /*!
+ * Calculates the intersections between the given segment and all the segments of the shape
+ * @param start The start position of the segment
+ * @param end The end position of the segment
+ * @return The parameters of the intersections on the segment (intersection = start + t * (end - start)), unsorted
+ */
+ std::vector intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const;
+
#ifdef BUILD_TESTS
/*!
* @brief Import the polygon from a WKT string
diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp
index 76b3d1b719..469d5f4854 100644
--- a/src/FffGcodeWriter.cpp
+++ b/src/FffGcodeWriter.cpp
@@ -318,6 +318,7 @@ void FffGcodeWriter::setConfigFanSpeedLayerTime()
fan_speed_layer_time_settings_per_extruder.emplace_back();
FanSpeedLayerTimeSettings& fan_speed_layer_time_settings = fan_speed_layer_time_settings_per_extruder.back();
fan_speed_layer_time_settings.cool_min_layer_time = train.settings_.get("cool_min_layer_time");
+ fan_speed_layer_time_settings.cool_min_layer_time_overhang = train.settings_.get("cool_min_layer_time_overhang");
fan_speed_layer_time_settings.cool_min_layer_time_fan_speed_max = train.settings_.get("cool_min_layer_time_fan_speed_max");
fan_speed_layer_time_settings.cool_fan_speed_0 = train.settings_.get("cool_fan_speed_0") * 100.0;
fan_speed_layer_time_settings.cool_fan_speed_min = train.settings_.get("cool_fan_speed_min") * 100.0;
@@ -3080,21 +3081,52 @@ bool FffGcodeWriter::processInsets(
gcode_layer.setBridgeWallMask(Shape());
}
- const auto get_overhang_region = [&](const AngleDegrees overhang_angle) -> Shape
+ const Shape fully_supported_region = outlines_below.offset(-half_outer_wall_width);
+ const Shape part_print_region = part.outline.offset(-half_outer_wall_width);
+
+ const auto get_supported_region = [&fully_supported_region, &layer_height](const AngleDegrees& overhang_angle) -> Shape
{
- if (overhang_angle >= 90)
- {
- return Shape(); // keep empty to disable overhang detection
- }
// the overhang mask is set to the area of the current part's outline minus the region that is considered to be supported
// the supported region is made up of those areas that really are supported by either model or support on the layer below
// expanded to take into account the overhang angle, the greater the overhang angle, the larger the supported area is
// considered to be
- const coord_t overhang_width = layer_height * std::tan(overhang_angle / (180 / std::numbers::pi));
- return part.outline.offset(-half_outer_wall_width).difference(outlines_below.offset(10 + overhang_width - half_outer_wall_width)).offset(10);
+ const coord_t overhang_width = layer_height * std::tan(AngleRadians(overhang_angle));
+ return fully_supported_region.offset(overhang_width + 10);
};
- gcode_layer.setOverhangMask(get_overhang_region(mesh.settings.get("wall_overhang_angle")));
- gcode_layer.setSeamOverhangMask(get_overhang_region(mesh.settings.get("seam_overhang_angle")));
+
+ // Build supported regions for all the overhang speeds. For a visual explanation of the result, see doc/gradual_overhang_speed.svg
+ std::vector overhang_masks;
+ const auto overhang_speed_factors = mesh.settings.get>("wall_overhang_speed_factors");
+ const size_t overhang_angles_count = overhang_speed_factors.size();
+ const auto wall_overhang_angle = mesh.settings.get("wall_overhang_angle");
+ if (overhang_angles_count > 0 && wall_overhang_angle < 90.0)
+ {
+ const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast(overhang_angles_count);
+ for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index)
+ {
+ const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast(angle_index) * overhang_step;
+ const Ratio speed_factor = angle_index == 0 ? 1.0_r : overhang_speed_factors[angle_index - 1];
+
+ overhang_masks.push_back(LayerPlan::OverhangMask{ get_supported_region(actual_wall_overhang_angle), speed_factor });
+ }
+
+ // Add an empty region, which actually means everything and should be ignored anyway
+ overhang_masks.push_back(LayerPlan::OverhangMask{ Shape(), overhang_speed_factors.back() });
+ }
+ gcode_layer.setOverhangMasks(overhang_masks);
+
+ // the seam overhang mask is set to the area of the current part's outline minus the region that is considered to be supported,
+ // which will then be empty if everything is considered supported i.r.t. the angle
+ const AngleDegrees seam_overhang_angle = mesh.settings.get("seam_overhang_angle");
+ if (seam_overhang_angle < 90.0)
+ {
+ const Shape supported_region_seam = get_supported_region(seam_overhang_angle);
+ gcode_layer.setSeamOverhangMask(part_print_region.difference(supported_region_seam).offset(10));
+ }
+ else
+ {
+ gcode_layer.setSeamOverhangMask(Shape());
+ }
const auto roofing_mask_fn = [&]() -> Shape
{
@@ -3125,7 +3157,7 @@ bool FffGcodeWriter::processInsets(
// clear to disable use of bridging settings
gcode_layer.setBridgeWallMask(Shape());
// clear to disable overhang detection
- gcode_layer.setOverhangMask(Shape());
+ gcode_layer.setOverhangMasks({});
// clear to disable overhang detection
gcode_layer.setSeamOverhangMask(Shape());
// clear to disable use of roofing settings
diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp
index 39f3e1b510..dcc92a4f40 100644
--- a/src/LayerPlan.cpp
+++ b/src/LayerPlan.cpp
@@ -57,14 +57,15 @@ GCodePath* LayerPlan::getLatestPathWithConfig(
{
return &paths.back();
}
- paths.emplace_back(GCodePath{ .z_offset = z_offset,
- .config = config,
- .mesh = current_mesh_,
- .space_fill_type = space_fill_type,
- .flow = flow,
- .width_factor = width_factor,
- .spiralize = spiralize,
- .speed_factor = speed_factor });
+ paths.emplace_back(
+ GCodePath{ .z_offset = z_offset,
+ .config = config,
+ .mesh = current_mesh_,
+ .space_fill_type = space_fill_type,
+ .flow = flow,
+ .width_factor = width_factor,
+ .spiralize = spiralize,
+ .speed_factor = speed_factor });
GCodePath* ret = &paths.back();
ret->skip_agressive_merge_hint = mode_skip_agressive_merge_;
@@ -557,6 +558,127 @@ void LayerPlan::addExtrusionMove(
last_planned_position_ = p.toPoint2LL();
}
+void LayerPlan::addExtrusionMoveWithGradualOverhang(
+ const Point3LL& p,
+ const GCodePathConfig& config,
+ const SpaceFillType space_fill_type,
+ const Ratio& flow,
+ const Ratio width_factor,
+ const bool spiralize,
+ const Ratio speed_factor,
+ const double fan_speed,
+ const bool travel_to_z)
+{
+ const auto add_extrusion_move = [&](const Point3LL& target, const std::optional speed_region_index = std::nullopt)
+ {
+ const Ratio overhang_speed_factor = speed_region_index.has_value() ? overhang_masks_[speed_region_index.value()].speed_ratio : 1.0_r;
+ if (speed_region_index.has_value() && speed_region_index.value() > 0)
+ {
+ contains_overhang_ = true;
+ }
+ addExtrusionMove(target, config, space_fill_type, flow, width_factor, spiralize, speed_factor * overhang_speed_factor, fan_speed, travel_to_z);
+ };
+
+ if (overhang_masks_.empty() || ! last_planned_position_.has_value())
+ {
+ // Unable to apply gradual overhanging (probably just disabled), just add the basic extrusion move
+ add_extrusion_move(p);
+ return;
+ }
+
+ // First, find the speed region where the segment starts
+ const Point2LL start = last_planned_position_.value();
+ size_t actual_speed_region_index = overhang_masks_.size() - 1; // Default to last region, which is infinity and beyond
+ for (const auto& [index, overhang_region] : overhang_masks_ | ranges::views::drop_last(1) | ranges::views::enumerate)
+ {
+ if (overhang_region.supported_region.inside(start, true))
+ {
+ actual_speed_region_index = index;
+ break;
+ }
+ }
+
+ // Pre-calculate the intersections of the segment with all regions (except last one, you cannot intersect an infinite plane)
+ const Point2LL end = p.toPoint2LL();
+ const Point2LL vector = end - start;
+ std::vector> speed_regions_intersections;
+ speed_regions_intersections.reserve(overhang_masks_.size() - 1);
+ for (const OverhangMask& overhang_region : overhang_masks_ | ranges::views::drop_last(1))
+ {
+ std::vector intersections = overhang_region.supported_region.intersectionsWithSegment(start, end);
+ ranges::sort(intersections);
+ speed_regions_intersections.push_back(intersections);
+ }
+
+ const auto remove_previous_intersections = [&speed_regions_intersections](const float current_intersection)
+ {
+ for (std::vector& intersections : speed_regions_intersections)
+ {
+ auto iterator = ranges::find_if(
+ intersections,
+ [¤t_intersection](const float next_intersection)
+ {
+ return next_intersection > current_intersection;
+ });
+
+ intersections.erase(intersections.begin(), iterator);
+ }
+ };
+
+ const std::vector colors = { SVG::Color::RED, SVG::Color::GREEN, SVG::Color::BLUE, SVG::Color::YELLOW };
+
+ // Now move along segment and split it where we cross speed regions
+ while (true)
+ {
+ // First, see if we cross either the border or our current region (go out) or the border of the inner region (go in)
+ auto get_first_intersection = [](const std::vector* intersections) -> std::optional
+ {
+ return intersections != nullptr && ! intersections->empty() ? std::make_optional(intersections->front()) : std::nullopt;
+ };
+
+ std::vector* intersections_current_region
+ = actual_speed_region_index < speed_regions_intersections.size() ? &speed_regions_intersections[actual_speed_region_index] : nullptr;
+ const std::optional first_intersection_current_region = get_first_intersection(intersections_current_region);
+
+ std::vector* intersections_inner_region = actual_speed_region_index > 0 ? &speed_regions_intersections[actual_speed_region_index - 1] : nullptr;
+ const std::optional first_intersection_inner_region = get_first_intersection(intersections_inner_region);
+
+ if (first_intersection_current_region.has_value() || first_intersection_inner_region.has_value())
+ {
+ float intersection_parameter;
+ size_t next_speed_region_index;
+
+ if (first_intersection_current_region.has_value()
+ && (! first_intersection_inner_region.has_value() || first_intersection_inner_region.value() > first_intersection_current_region.value()))
+ {
+ // We crossed the border of the current region, which means we are getting out of it to an outer region
+ intersection_parameter = first_intersection_current_region.value();
+ next_speed_region_index = actual_speed_region_index + 1;
+ }
+ else
+ {
+ // We crossed the border of the inner region, which means we are getting inside of it
+ intersection_parameter = first_intersection_inner_region.value();
+ next_speed_region_index = actual_speed_region_index - 1;
+ }
+
+ // Move to intersection at current region speed
+ const Point2LL split_position = start + vector * intersection_parameter;
+ add_extrusion_move(split_position, actual_speed_region_index);
+
+ // Prepare for next move in different region
+ actual_speed_region_index = next_speed_region_index;
+ remove_previous_intersections(intersection_parameter);
+ }
+ else
+ {
+ // We cross no border, which means we can reach the end of the segment within the current speed region, so we are done
+ add_extrusion_move(p, actual_speed_region_index);
+ return;
+ }
+ }
+}
+
template
void LayerPlan::addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position)
{
@@ -724,14 +846,13 @@ void LayerPlan::addWallLine(
double distance_to_bridge_start,
const bool travel_to_z)
{
- const coord_t min_line_len = 5; // we ignore lines less than 5um long
- const double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length
- const double acceleration_factor = 0.75; // must be < 1, the larger the value, the slower the acceleration
- const bool spiralize = false;
+ constexpr coord_t min_line_len = 5; // we ignore lines less than 5um long
+ constexpr double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length
+ constexpr double acceleration_factor = 0.75; // must be < 1, the larger the value, the slower the acceleration
+ constexpr bool spiralize = false;
const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length");
const Ratio bridge_wall_coast = settings.get("bridge_wall_coast");
- const Ratio overhang_speed_factor = settings.get("wall_overhang_speed_factor");
Point3LL cur_point = p0;
@@ -797,14 +918,14 @@ void LayerPlan::addWallLine(
else
{
// no coasting required, just normal segment using non-bridge config
- addExtrusionMove(
+ addExtrusionMoveWithGradualOverhang(
segment_end,
default_config,
SpaceFillType::Polygons,
segment_flow,
width_factor,
spiralize,
- segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor,
+ speed_factor,
GCodePathConfig::FAN_SPEED_DEFAULT,
travel_to_z);
}
@@ -814,14 +935,14 @@ void LayerPlan::addWallLine(
else
{
// no coasting required, just normal segment using non-bridge config
- addExtrusionMove(
+ addExtrusionMoveWithGradualOverhang(
segment_end,
default_config,
SpaceFillType::Polygons,
segment_flow,
width_factor,
spiralize,
- segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor,
+ speed_factor,
GCodePathConfig::FAN_SPEED_DEFAULT,
travel_to_z);
}
@@ -919,14 +1040,14 @@ void LayerPlan::addWallLine(
else if (bridge_wall_mask_.empty())
{
// no bridges required
- addExtrusionMove(
+ addExtrusionMoveWithGradualOverhang(
p1,
default_config,
SpaceFillType::Polygons,
flow,
width_factor,
spiralize,
- segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor,
+ speed_factor,
GCodePathConfig::FAN_SPEED_DEFAULT,
travel_to_z);
}
@@ -985,7 +1106,16 @@ void LayerPlan::addWallLine(
if (bridge_line_len > min_line_len)
{
- addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z);
+ addExtrusionMoveWithGradualOverhang(
+ b1,
+ bridge_config,
+ SpaceFillType::Polygons,
+ flow,
+ width_factor,
+ spiralize,
+ 1.0_r,
+ GCodePathConfig::FAN_SPEED_DEFAULT,
+ travel_to_z);
non_bridge_line_volume = 0;
cur_point = b1;
// after a bridge segment, start slow and accelerate to avoid under-extrusion due to extruder lag
@@ -1009,7 +1139,7 @@ void LayerPlan::addWallLine(
else if (bridge_wall_mask_.inside(p0.toPoint2LL(), true) && (p0 - p1).vSize() >= min_bridge_line_len)
{
// both p0 and p1 must be above air (the result will be ugly!)
- addExtrusionMove(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor);
+ addExtrusionMoveWithGradualOverhang(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor);
non_bridge_line_volume = 0;
}
else
@@ -1322,7 +1452,7 @@ std::vector
for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse
| ranges::views::chunk_by(
- [](const auto&path_a, const auto&path_b)
+ [](const auto& path_a, const auto& path_b)
{
return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath();
}))
@@ -1946,13 +2076,6 @@ void LayerPlan::addLinesInGivenOrder(
}
}
-bool LayerPlan::segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const
-{
- const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() };
- const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment });
- return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5;
-}
-
void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed)
{
Application::getInstance().communication_->sendLineTo(
@@ -2444,7 +2567,9 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position)
{
other_extr_plan_time += extruder_plan.estimates_.getTotalTime();
}
- maximum_cool_min_layer_time = std::max(maximum_cool_min_layer_time, extruder_plan.fan_speed_layer_time_settings_.cool_min_layer_time);
+
+ const FanSpeedLayerTimeSettings& settings = extruder_plan.fan_speed_layer_time_settings_;
+ maximum_cool_min_layer_time = std::max(maximum_cool_min_layer_time, contains_overhang_ ? settings.cool_min_layer_time_overhang : settings.cool_min_layer_time);
// Modify fan speeds for the first layer(s)
extruder_plan.processFanSpeedForFirstLayers();
@@ -3154,9 +3279,9 @@ void LayerPlan::setBridgeWallMask(const Shape& polys)
bridge_wall_mask_ = polys;
}
-void LayerPlan::setOverhangMask(const Shape& polys)
+void LayerPlan::setOverhangMasks(const std::vector& masks)
{
- overhang_mask_ = polys;
+ overhang_masks_ = masks;
}
void LayerPlan::setSeamOverhangMask(const Shape& polys)
diff --git a/src/geometry/Shape.cpp b/src/geometry/Shape.cpp
index 61d029c4e2..b16d68b336 100644
--- a/src/geometry/Shape.cpp
+++ b/src/geometry/Shape.cpp
@@ -890,6 +890,25 @@ void Shape::simplify(ClipperLib::PolyFillType fill_type)
}
}
+std::vector Shape::intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const
+{
+ std::vector result;
+
+ for (const Polygon& polygon : getLines())
+ {
+ for (auto iterator = polygon.beginSegments(); iterator != polygon.endSegments(); ++iterator)
+ {
+ float t, u;
+ if (LinearAlg2D::segmentSegmentIntersection(start, end, (*iterator).start, (*iterator).end, t, u))
+ {
+ result.push_back(t);
+ }
+ }
+ }
+
+ return result;
+}
+
void Shape::ensureManifold()
{
std::vector duplicate_locations;
diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp
index 4e39ef8555..4904e2e13c 100644
--- a/src/settings/Settings.cpp
+++ b/src/settings/Settings.cpp
@@ -183,7 +183,7 @@ Acceleration Settings::get(const std::string& key) const
template<>
Ratio Settings::get(const std::string& key) const
{
- return get(key) / 100.0; // The settings are all in percentages, but we need to interpret them as radians.
+ return get(key) / 100.0; // The settings are all in percentages, but we need to interpret them as ratios.
}
template<>
@@ -787,6 +787,22 @@ std::vector Settings::get>(const std::string& key) c
return result;
}
+template<>
+std::vector Settings::get>(const std::string& key) const
+{
+ auto values_double = get>(key);
+
+ // The settings are all in percentages, but we need to interpret them as ratios.
+ std::vector result;
+ result.reserve(values_double.size());
+ for (const auto& value_double : values_double)
+ {
+ result.emplace_back(value_double / 100.0);
+ }
+
+ return result;
+}
+
template<>
std::vector Settings::get>(const std::string& key) const
{