WIP Tree supports: It compiles and it produced first trees.

This commit is contained in:
Vojtech Bubnik 2022-07-29 13:15:01 +02:00
parent 9e6871e5b8
commit 5868028a7e
6 changed files with 235 additions and 158 deletions

View file

@ -137,7 +137,8 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern)
static const t_config_enum_values s_keys_map_SupportMaterialStyle {
{ "grid", smsGrid },
{ "snug", smsSnug }
{ "snug", smsSnug },
{ "tree", smsTree }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle)

View file

@ -573,71 +573,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// intermediate_layers.clear();
// interface_layers.clear();
// Install support layers into the object.
// A support layer installed on a PrintObject has a unique print_z.
SupportGeneratorLayersPtr layers_sorted;
layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size());
layers_append(layers_sorted, raft_layers);
layers_append(layers_sorted, bottom_contacts);
layers_append(layers_sorted, top_contacts);
layers_append(layers_sorted, intermediate_layers);
layers_append(layers_sorted, interface_layers);
layers_append(layers_sorted, base_interface_layers);
// Sort the layers lexicographically by a raising print_z and a decreasing height.
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
int layer_id = 0;
int layer_id_interface = 0;
assert(object.support_layers().empty());
for (size_t i = 0; i < 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.
size_t j = i + 1;
coordf_t zmax = layers_sorted[i]->print_z + EPSILON;
for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ;
// Assign an average print_z to the set of layers with nearly equal print_z.
coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z);
coordf_t height_min = layers_sorted[i]->height;
bool empty = true;
// For snug supports, layers where the direction of the support interface shall change are accounted for.
size_t num_interfaces = 0;
size_t num_top_contacts = 0;
double top_contact_bottom_z = 0;
for (size_t u = i; u < j; ++u) {
SupportGeneratorLayer &layer = *layers_sorted[u];
if (! layer.polygons.empty()) {
empty = false;
num_interfaces += one_of(layer.layer_type, support_types_interface);
if (layer.layer_type == SupporLayerType::TopContact) {
++ num_top_contacts;
assert(num_top_contacts <= 1);
// All top contact layers sharing this print_z shall also share bottom_z.
//assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON);
top_contact_bottom_z = layer.bottom_z;
}
}
layer.print_z = zavg;
height_min = std::min(height_min, layer.height);
}
if (! empty) {
// Here the upper_layer and lower_layer pointers are left to null at the support layers,
// as they are never used. These pointers are candidates for removal.
bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces;
size_t this_layer_id_interface = layer_id_interface;
if (this_layer_contacts_only) {
// Find a supporting layer for its interface ID.
for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it)
if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) {
// other_layer supports this top contact layer. Assign a different support interface direction to this layer
// from the layer that supports it.
this_layer_id_interface = other_layer.interface_id() + 1;
}
}
object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg);
if (num_interfaces && ! this_layer_contacts_only)
++ layer_id_interface;
}
i = j;
}
generate_support_layers(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers);
BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths";
@ -3911,6 +3847,82 @@ void modulate_extrusion_by_overlapping_layers(
extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height);
}
void generate_support_layers(
PrintObject &object,
const SupportGeneratorLayersPtr &raft_layers,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &intermediate_layers,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers)
{
// Install support layers into the object.
// A support layer installed on a PrintObject has a unique print_z.
SupportGeneratorLayersPtr layers_sorted;
layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size() + base_interface_layers.size());
layers_append(layers_sorted, raft_layers);
layers_append(layers_sorted, bottom_contacts);
layers_append(layers_sorted, top_contacts);
layers_append(layers_sorted, intermediate_layers);
layers_append(layers_sorted, interface_layers);
layers_append(layers_sorted, base_interface_layers);
// Sort the layers lexicographically by a raising print_z and a decreasing height.
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
int layer_id = 0;
int layer_id_interface = 0;
assert(object.support_layers().empty());
for (size_t i = 0; i < 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.
size_t j = i + 1;
coordf_t zmax = layers_sorted[i]->print_z + EPSILON;
for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ;
// Assign an average print_z to the set of layers with nearly equal print_z.
coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z);
coordf_t height_min = layers_sorted[i]->height;
bool empty = true;
// For snug supports, layers where the direction of the support interface shall change are accounted for.
size_t num_interfaces = 0;
size_t num_top_contacts = 0;
double top_contact_bottom_z = 0;
for (size_t u = i; u < j; ++u) {
SupportGeneratorLayer &layer = *layers_sorted[u];
if (! layer.polygons.empty()) {
empty = false;
num_interfaces += one_of(layer.layer_type, support_types_interface);
if (layer.layer_type == SupporLayerType::TopContact) {
++ num_top_contacts;
assert(num_top_contacts <= 1);
// All top contact layers sharing this print_z shall also share bottom_z.
//assert(num_top_contacts == 1 || (top_contact_bottom_z - layer.bottom_z) < EPSILON);
top_contact_bottom_z = layer.bottom_z;
}
}
layer.print_z = zavg;
height_min = std::min(height_min, layer.height);
}
if (! empty) {
// Here the upper_layer and lower_layer pointers are left to null at the support layers,
// as they are never used. These pointers are candidates for removal.
bool this_layer_contacts_only = num_top_contacts > 0 && num_top_contacts == num_interfaces;
size_t this_layer_id_interface = layer_id_interface;
if (this_layer_contacts_only) {
// Find a supporting layer for its interface ID.
for (auto it = object.support_layers().rbegin(); it != object.support_layers().rend(); ++ it)
if (const SupportLayer &other_layer = **it; std::abs(other_layer.print_z - top_contact_bottom_z) < EPSILON) {
// other_layer supports this top contact layer. Assign a different support interface direction to this layer
// from the layer that supports it.
this_layer_id_interface = other_layer.interface_id() + 1;
}
}
object.add_support_layer(layer_id ++, this_layer_id_interface, height_min, zavg);
if (num_interfaces && ! this_layer_contacts_only)
++ layer_id_interface;
}
i = j;
}
}
void generate_support_toolpaths(
SupportLayerPtrs &support_layers,
const PrintObjectConfig &config,

View file

@ -147,6 +147,15 @@ struct SupportParameters {
bool with_sheath;
};
void generate_support_layers(
PrintObject &object,
const SupportGeneratorLayersPtr &raft_layers,
const SupportGeneratorLayersPtr &bottom_contacts,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &intermediate_layers,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers);
// Produce the support G-code.
// Used by both classic and tree supports.
void generate_support_toolpaths(

View file

@ -80,12 +80,17 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
static Polygons calculateMachineBorderCollision(Polygon machine_border)
{
Polygons machine_volume_border;
// Put a border of 1m around the print volume so that we don't collide.
append(machine_volume_border, offset(machine_border, scaled<float>(1000.)));
#if 1
//FIXME just returning no border will let tree support legs collide with print bed boundary
return {};
#else
//FIXME offsetting by 1000mm easily overflows int32_tr coordinate.
Polygons out = offset(machine_border, scaled<float>(1000.), jtMiter, 1.2);
machine_border.reverse(); // Makes the polygon negative so that we subtract the actual volume from the collision area.
machine_volume_border.emplace_back(std::move(machine_border));
return machine_volume_border;
out.emplace_back(std::move(machine_border));
return out;
#endif
}
TreeModelVolumes::TreeModelVolumes(
@ -545,7 +550,6 @@ void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys)
const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance;
const size_t z_distance_bottom_layers = round_up_divide(z_distance_bottom, layer_height);
const coord_t z_distance_top_layers = round_up_divide(m_layer_outlines[outline_idx].first.support_top_distance, layer_height);
const LayerIndex max_anti_overhang_layer = m_anti_overhang.size() - 1;
const LayerIndex max_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers);
const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : m_layer_outlines[outline_idx].first.support_xy_distance;
// technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time.
@ -566,49 +570,48 @@ void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys)
if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size())
append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]);
// jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model.
collision_areas = offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2);
append(data[key], collision_areas); // if a key does not exist when it is accessed it is added!
// if a key does not exist when it is accessed it is added!
append(data[key], offset(union_ex(collision_areas), radius + xy_distance, ClipperLib::jtMiter, 1.2));
}
// Add layers below, to ensure correct support_bottom_distance. Also save placeable areas of radius 0, if required for this mesh.
for (LayerIndex layer_idx = max_required_layer; layer_idx >= min_layer_bottom; -- layer_idx) {
for (int layer_idx = int(max_required_layer); layer_idx >= min_layer_bottom; -- layer_idx) {
key.second = layer_idx;
for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset)
append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]);
if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) {
data[key] = union_(data[key]);
Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)];
RadiusLayerPair key_next_layer(radius, layer_idx + 1);
//data[key] = union_(data[key]);
Polygons above = data[key_next_layer];
// just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly.
above = max_anti_overhang_layer >= layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above);
Polygons placeable = diff(data[key], above);
data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = union_(data_placeable[RadiusLayerPair(radius, layer_idx + 1)], placeable);
//FIXME Vojtech: Why m_anti_overhang.size() > layer_idx + 1? Why +1?
above = m_anti_overhang.size() > layer_idx + 1 ? union_(above, m_anti_overhang[layer_idx]) : union_(above);
data_placeable[key_next_layer] = union_(data_placeable[key_next_layer], diff(data[key], above));
}
}
// Add collision layers above to ensure correct support_top_distance.
for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) {
for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; ++ layer_idx) {
key.second = layer_idx;
for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; layer_offset++)
append(data[key], data[RadiusLayerPair(radius, layer_idx + layer_offset)]);
data[key] = max_anti_overhang_layer >= layer_idx ? union_(data[key], offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(data[key]);
Polygons collisions = std::move(data[key]);
for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; ++ layer_offset)
append(collisions, data[RadiusLayerPair(radius, layer_idx + layer_offset)]);
data[key] = m_anti_overhang.size() > layer_idx ? union_(collisions, offset(union_ex(m_anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions);
}
for (LayerIndex layer_idx = max_required_layer; layer_idx > keys[i].second; layer_idx--) {
for (int layer_idx = int(max_required_layer); layer_idx > keys[i].second; -- layer_idx) {
// all these dont have the correct z_distance_top_layers as they can still have areas above them
auto it = data.find(RadiusLayerPair(radius, layer_idx));
if (it != data.end())
data.erase(it);
}
for (auto pair : data) {
pair.second = simplify(pair.second, m_min_resolution);
data_outer[pair.first] = union_(data_outer[pair.first], pair.second);
for (auto pair : data)
data_outer[pair.first] = union_(data_outer[pair.first], simplify(pair.second, m_min_resolution));
if (radius == 0) {
for (auto pair : data_placeable)
data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], simplify(pair.second, m_min_resolution));
}
if (radius == 0)
for (auto pair : data_placeable) {
pair.second = simplify(pair.second, m_min_resolution);
data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], pair.second);
}
}
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
@ -648,7 +651,7 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque<RadiusLayerPair> ke
coord_t radius = key.first;
coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true);
// this union is important as otherwise holes(in form of lines that will increase to holes in a later step) can get unioned onto the area.
Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound);
Polygons col = offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, scaled<float>(0.01));
col = simplify(col, m_min_resolution);
data[RadiusLayerPair(radius, layer_idx)] = col;
}
@ -663,11 +666,11 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque<RadiusLayerPair> ke
static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision)
{
const size_t steps = std::abs(distance / max_safe_step_distance);
assert(distance * max_safe_step_distance >= 0);
assert(int64_t(distance) * int64_t(max_safe_step_distance) >= 0);
ExPolygons ret = union_ex(me);
for (size_t i = 0; i < steps; ++ i)
ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, 1.2), collision));
return union_(offset(ret, distance % max_safe_step_distance, jt, 1.2), collision);
ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled<float>(0.01) : 1.2), collision));
return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled<float>(0.01) : 1.2), collision);
}
void TreeModelVolumes::calculateAvoidance(std::deque<RadiusLayerPair> keys)
@ -768,7 +771,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque<RadiusLayerPair> keys)
key.second = layer;
Polygons placeable = getPlaceableAreas(0, layer);
placeable = simplify(placeable, m_min_resolution); // it is faster to do this here in each thread than once in calculateCollision.
placeable = offset(union_ex(placeable), - radius);
placeable = offset(union_ex(placeable), - radius, jtMiter, 1.2);
data[layer] = std::pair<RadiusLayerPair, Polygons>(key, placeable);
}

View file

@ -27,7 +27,9 @@
#include <boost/log/trivial.hpp>
#include <tbb/global_control.h>
#include <tbb/parallel_for.h>
#include <tbb/spin_mutex.h>
namespace Slic3r
{
@ -124,7 +126,7 @@ void TreeSupport::showError(std::string message, bool critical)
static bool layer_has_overhangs(const Layer &layer)
{
for (const LayerRegion* layerm : layer.regions())
if (layerm->slices.has(stBottom) || layerm->slices.has(stBottom))
if (layerm->slices.has(stBottom) || layerm->slices.has(stBottomBridge))
return true;
return false;
}
@ -172,7 +174,7 @@ void TreeSupport::generateSupportAreas(PrintObject& print_object)
break;
++ idx;
}
this->generateSupportAreas(*print_object.print(), BuildVolume(Pointfs{ Vec2d{ -1000., -1000. }, Vec2d{ -1000., +1000. }, Vec2d{ +1000., +1000. }, Vec2d{ +1000., -1000. } }, 0.), { 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<size_t> &print_object_ids)
@ -267,9 +269,17 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo
}
}
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.
SupportGeneratorLayersPtr raft_layers, interface_layers, base_interface_layers;
generate_support_layers(print_object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers);
generate_support_toolpaths(print_object.support_layers(), print_object.config(), SupportParameters(print_object), print_object.slicing_parameters(),
raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers, base_interface_layers);
@ -527,7 +537,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in
{
Polylines result;
for (Polyline part : input) {
if (part.empty() == 0)
if (part.empty())
continue;
double len = length(part.points);
@ -537,7 +547,7 @@ static [[nodiscard]] Polylines ensureMaximumDistancePolyline(const Polylines &in
{
// Insert the opposite point of the first one.
//FIXME pretty expensive
Polyline pl(line.points);
Polyline pl(part);
pl.clip_end(len / 2);
line.points.emplace_back(pl.points.back());
}
@ -673,7 +683,7 @@ static [[nodiscard]] Polylines generateSupportInfillLines(
filler->layer_id = layer_idx;
filler->spacing = flow.spacing();
fill_params.density = float(roof ? support_params.interface_density : float(filler->spacing) / float(support_infill_distance));
fill_params.density = float(roof ? support_params.interface_density : scaled<float>(filler->spacing) / float(support_infill_distance));
fill_params.dont_adjust = true;
Polylines out;
@ -722,7 +732,7 @@ static [[nodiscard]] Polygons safeUnion(const Polygons first, const Polygons sec
if (result.empty()) {
BOOST_LOG_TRIVIAL(debug) << "Caught an area destroying union, enlarging areas a bit.";
// just take the few lines we have, and offset them a tiny bit. Needs to be offsetPolylines, as offset may aleady have problems with the area.
result = union_(offset(to_polylines(first), scaled<float>(0.002)), offset(to_polylines(second), scaled<float>(0.002)));
result = union_(offset(to_polylines(first), scaled<float>(0.002), jtMiter, 1.2), offset(to_polylines(second), scaled<float>(0.002), jtMiter, 1.2));
}
}
@ -772,13 +782,16 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance
}
// offset in steps
for (size_t i = 0; i < steps; i++) {
ret = diff(offset(ret, step_size, ClipperLib::jtRound), collision);
ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled<float>(0.01)), collision);
// ensure that if many offsets are done the performance does not suffer extremely by the new vertices of jtRound.
if (i % 10 == 7)
ret = polygons_simplify(ret, scaled<double>(0.015));
}
// offset the remainder
ret = polygons_simplify(offset(ret, distance - steps * step_size, ClipperLib::jtRound), scaled<double>(0.015));
float last_offset = distance - steps * step_size;
if (last_offset > SCALED_EPSILON)
ret = offset(ret, distance - steps * step_size, ClipperLib::jtRound, scaled<float>(0.01));
ret = polygons_simplify(ret, scaled<double>(0.015));
if (do_final_difference)
ret = diff(ret, collision);
@ -787,12 +800,36 @@ static [[nodiscard]] Polygons safeOffsetInc(const Polygons& me, coord_t distance
// Using the std::deque as an allocator.
inline SupportGeneratorLayer& layer_allocate(
std::deque<SupportGeneratorLayer>& layer_storage,
SupporLayerType layer_type)
std::deque<SupportGeneratorLayer> &layer_storage,
SupporLayerType layer_type,
const SlicingParameters &slicing_params,
size_t layer_idx)
{
layer_storage.push_back(SupportGeneratorLayer());
layer_storage.back().layer_type = layer_type;
return layer_storage.back();
SupportGeneratorLayer *layer_new = &layer_storage.back();
layer_new->layer_type = layer_type;
layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height;
layer_new->height = slicing_params.layer_height;
layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height;
return *layer_new;
}
inline SupportGeneratorLayer& layer_allocate(
std::deque<SupportGeneratorLayer> &layer_storage,
tbb::spin_mutex& layer_storage_mutex,
SupporLayerType layer_type,
const SlicingParameters &slicing_params,
size_t layer_idx)
{
layer_storage_mutex.lock();
layer_storage.push_back(SupportGeneratorLayer());
SupportGeneratorLayer *layer_new = &layer_storage.back();
layer_storage_mutex.unlock();
layer_new->layer_type = layer_type;
layer_new->print_z = slicing_params.first_print_layer_height + std::max(0, int(layer_idx) - 1) * slicing_params.layer_height;
layer_new->height = slicing_params.layer_height;
layer_new->bottom_z = layer_idx == 0 ? 0. : layer_new->print_z - slicing_params.layer_height;
return *layer_new;
}
void TreeSupport::generateInitialAreas(
@ -802,6 +839,8 @@ void TreeSupport::generateInitialAreas(
SupportGeneratorLayersPtr &top_interface_layers,
SupportGeneratorLayerStorage &layer_storage)
{
tbb::global_control(tbb::global_control::max_allowed_parallelism, 1);
Polygon base_circle;
const int base_radius = 10;
for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) {
@ -841,10 +880,13 @@ void TreeSupport::generateInitialAreas(
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
if (! layer_has_overhangs(*print_object.get_layer(layer_idx + z_distance_delta)))
continue;
Polygons relevant_forbidden = (mesh_config.support_rests_on_model ? (SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z)); // take the least restrictive avoidance possible
// take the least restrictive avoidance possible
Polygons relevant_forbidden = (mesh_config.support_rests_on_model ?
(SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL ? m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides_z) :
m_volumes.getCollision(mesh_config.getRadius(0), layer_idx, !xy_overrides_z)) :
m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides_z));
// 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), scaled<float>(0.005));
relevant_forbidden = offset(union_ex(relevant_forbidden), scaled<float>(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;
@ -921,13 +963,15 @@ void TreeSupport::generateInitialAreas(
roof_circle.points.emplace_back(p.first + corner * mesh_config.min_radius / base_radius);
added_roofs.emplace_back(roof_circle);
}
added_roofs = union_(added_roofs);
{
std::lock_guard<std::mutex> critical_section_storage(critical_sections);
SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip];
if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact);
append(l->polygons, std::move(added_roofs));
if (! added_roofs.empty()) {
added_roofs = union_(added_roofs);
{
std::lock_guard<std::mutex> critical_section_storage(critical_sections);
SupportGeneratorLayer *&l = top_contacts[insert_layer_idx - dtt_roof_tip];
if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), insert_layer_idx - dtt_roof_tip);
append(l->polygons, std::move(added_roofs));
}
}
}
@ -945,7 +989,7 @@ void TreeSupport::generateInitialAreas(
Polygons overhang_raw = layer_overhangs(*print_object.get_layer(layer_idx + z_distance_delta));
Polygons overhang_regular = safeOffsetInc(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(offset(union_ex(overhang_raw), mesh_group_settings.support_offset), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5)), relevant_forbidden);
Polygons remaining_overhang = intersection(diff(offset(union_ex(overhang_raw), mesh_group_settings.support_offset, jtMiter, 1.2), offset(union_ex(overhang_regular), mesh_config.support_line_width * 0.5, jtMiter, 1.2)), relevant_forbidden);
coord_t extra_total_offset_acc = 0;
// Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible.
@ -1032,7 +1076,7 @@ void TreeSupport::generateInitialAreas(
// here the roof is handled. If roof can not be added the branches will try to not move instead
Polygons forbidden_next = (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, !xy_overrides_z) : m_volumes.getCollision(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), !xy_overrides_z)) : m_volumes.getAvoidance(mesh_config.getRadius(0), layer_idx - (dtt_roof + 1), AvoidanceType::FAST, false, !xy_overrides_z));\
// prevent rounding errors down the line
forbidden_next = offset(union_ex(forbidden_next), scaled<float>(0.005));
forbidden_next = offset(union_ex(forbidden_next), scaled<float>(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
@ -1066,13 +1110,14 @@ void TreeSupport::generateInitialAreas(
{
std::lock_guard<std::mutex> critical_section_storage(critical_sections);
for (size_t idx = 0; idx < dtt_roof; idx++) {
SupportGeneratorLayer *&l = top_contacts[layer_idx - idx];
if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact);
// will be unioned in finalizeInterfaceAndSupportAreas
append(l->polygons, std::move(added_roofs[idx]));
}
for (size_t idx = 0; idx < dtt_roof; ++ idx)
if (! added_roofs[idx].empty()) {
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
append(l->polygons, std::move(added_roofs[idx]));
}
}
if (overhang_lines.empty()) {
@ -1088,9 +1133,9 @@ void TreeSupport::generateInitialAreas(
// I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them
// 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<36> 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);
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))) < 1 ?
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)) < 1 ?
reduced_overhang_outset :
overhang_outset),
connect_length, min_support_points);
@ -1099,11 +1144,11 @@ void TreeSupport::generateInitialAreas(
overhang_lines = convertLinesToInternal(m_volumes, m_config, polylines, last_insert_layer);
}
if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part) { // reached buildplate
if (int(dtt_roof) >= layer_idx && roof_allowed_for_this_part && ! overhang_outset.empty()) { // reached buildplate
std::lock_guard<std::mutex> critical_section_storage(critical_sections);
SupportGeneratorLayer*& l = top_contacts[0];
if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::TopContact);
l = &layer_allocate(layer_storage, SupporLayerType::TopContact, print_object.slicing_parameters(), 0);
append(l->polygons, std::move(overhang_outset));
} else // normal trees have to be generated
addLinesAsInfluenceAreas(overhang_lines, force_tip_to_roof ? support_roof_layers - dtt_roof : 0, layer_idx - dtt_roof, dtt_roof > 0, roof_enabled ? support_roof_layers - dtt_roof : 0);
@ -1314,7 +1359,7 @@ static void mergeHelper(
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<float>(-0.025))) <= 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.
if (area(offset(intersect, scaled<float>(-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.
@ -1365,7 +1410,7 @@ static void mergeHelper(
erase.emplace_back(reduced_check_iter->first);
erase.emplace_back(influence_iter->first);
Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound), volumes.getCollision(0, layer_idx - 1)); // 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.
Polygons merge = diff(offset(union_(intersect, intersect_sec), config.getRadius(key), ClipperLib::jtRound, scaled<float>(0.01)), volumes.getCollision(0, layer_idx - 1)); // 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));
@ -1569,8 +1614,11 @@ std::optional<TreeSupport::SupportElement> TreeSupport::increaseSingleArea(AreaI
radius = m_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));
double planned_foot_increase = std::min(1.0, double(m_config.recommendedMinRadius(layer_idx - 1) - m_config.getRadius(current_elem)) / foot_radius_increase); // 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.
// 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);
//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))) {
@ -1749,7 +1797,7 @@ void TreeSupport::increaseAreas(std::unordered_map<SupportElement, Polygons>& to
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<float>(0.005));
Polygons lines_offset = offset(to_polylines(*parent->area), scaled<float>(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);
BOOST_LOG_TRIVIAL(error) <<
@ -1832,7 +1880,7 @@ void TreeSupport::createLayerPathing(std::vector<std::set<SupportElement*>>& mov
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 (LayerIndex layer_idx = move_bounds.size() - 1; layer_idx > 0; layer_idx--)
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;
@ -2138,7 +2186,8 @@ void TreeSupport::generateBranchAreas(std::vector<std::pair<LayerIndex, SupportE
poly.emplace_back(std::move(circle));
}
poly = diff(offset(union_(poly), std::min(coord_t(50), m_config.support_line_width / 4)), m_volumes.getCollision(0, linear_data[idx].first, 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.
poly = diff(offset(union_(poly), std::min(coord_t(50), m_config.support_line_width / 4), jtMiter, 1.2),
m_volumes.getCollision(0, linear_data[idx].first, 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.
return poly;
};
@ -2171,7 +2220,7 @@ void TreeSupport::generateBranchAreas(std::vector<std::pair<LayerIndex, SupportE
}
}
// 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);
linear_inserts[idx] = offset(polygons_with_correct_center, m_config.support_line_width / 2, jtMiter, 1.2);
linear_inserts[idx] = diff(linear_inserts[idx], m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist));
}
}
@ -2201,7 +2250,7 @@ void TreeSupport::smoothBranchAreas(std::vector<std::unordered_map<SupportElemen
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
// smooth upwards
for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()) - 1; layer_idx++) {
for (LayerIndex layer_idx = 0; layer_idx < LayerIndex(layer_tree_polygons.size()) - 1; ++ layer_idx) {
std::vector<std::pair<SupportElement*, Polygons>> processing;
processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end());
std::vector<std::vector<std::pair<SupportElement*, Polygons>>> update_next(processing.size()); // with this a lock can be avoided
@ -2219,7 +2268,7 @@ void TreeSupport::smoothBranchAreas(std::vector<std::unordered_map<SupportElemen
}
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));
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<SupportElement*, Polygons>(parent, intersection(layer_tree_polygons[layer_idx + 1][parent], max_allowed_area)));
@ -2239,7 +2288,7 @@ void TreeSupport::smoothBranchAreas(std::vector<std::unordered_map<SupportElemen
// smooth downwards
std::unordered_set<SupportElement*> updated_last_iteration;
for (LayerIndex layer_idx = layer_tree_polygons.size() - 2; layer_idx >= 0; layer_idx--) {
for (int layer_idx = int(layer_tree_polygons.size()) - 2; layer_idx >= 0; -- layer_idx) {
std::vector<std::pair<SupportElement*, Polygons>> processing;
processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end());
std::vector<std::pair<SupportElement*, Polygons>> update_next(processing.size(), std::pair<SupportElement*, Polygons>(nullptr, Polygons())); // with this a lock can be avoided
@ -2253,7 +2302,7 @@ void TreeSupport::smoothBranchAreas(std::vector<std::unordered_map<SupportElemen
for (size_t idx = 0; idx < data_pair.first->parents.size(); ++ idx) {
SupportElement* parent = data_pair.first->parents[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);
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;
// move the polygons object
for (auto& outer : result)
@ -2326,7 +2375,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(
#endif // SLIC3R_TREESUPPORTS_PROGRESS
// Iterate over the generated circles in parallel and clean them up. Also add support floor.
std::mutex critical_sections;
tbb::spin_mutex layer_storage_mutex;
tbb::parallel_for(tbb::blocked_range<size_t>(0, support_layer_storage.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
@ -2338,7 +2387,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(
SupportGeneratorLayer*& support_roof = top_contacts[layer_idx];
if (! support_roof_storage[layer_idx].empty() || support_roof != nullptr) {
if (support_roof == nullptr) {
support_roof = &layer_allocate(layer_storage, SupporLayerType::TopContact);
support_roof = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::TopContact, print_object.slicing_parameters(), layer_idx);
support_roof->polygons = union_(support_roof_storage[layer_idx]);
} else
support_roof->polygons = union_(support_roof->polygons, support_roof_storage[layer_idx]);
@ -2383,10 +2432,10 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(
// 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()) {
SupportGeneratorLayer*& support_bottom = bottom_contacts[layer_idx];
if (support_bottom == nullptr)
support_bottom = &layer_allocate(layer_storage, SupporLayerType::BottomContact);
Polygons floor_layer = std::move(support_bottom->polygons);
Polygons layer_outset = diff(offset(support_layer_storage[layer_idx], m_config.support_bottom_offset), m_volumes.getCollision(0, layer_idx, false));
Polygons layer_outset = diff(
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));
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.
@ -2398,14 +2447,18 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(
else
break;
}
support_bottom->polygons = union_(floor_layer);
support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled<float>(0.01))); // Subtract the support floor from the normal support.
if (! floor_layer.empty()) {
if (support_bottom == nullptr)
support_bottom = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::BottomContact, print_object.slicing_parameters(), layer_idx);
support_bottom->polygons = union_(floor_layer, support_bottom->polygons);
support_layer_storage[layer_idx] = diff(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled<float>(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support.
}
}
{
if (! support_layer_storage[layer_idx].empty()) {
SupportGeneratorLayer *&l = intermediate_layers[layer_idx];
if (l == nullptr)
l = &layer_allocate(layer_storage, SupporLayerType::Base);
l = &layer_allocate(layer_storage, layer_storage_mutex, SupporLayerType::Base, print_object.slicing_parameters(), layer_idx);
append(l->polygons, union_(support_layer_storage[layer_idx]));
}

View file

@ -200,10 +200,11 @@ public:
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 = foot_increase_radius / (config.branch_radius * (config.diameter_scale_bp_radius - config.diameter_angle_scale_factor)); // 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 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),
@ -398,9 +399,7 @@ public:
// 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.
if (has_to_rely_on_min_xy_dist_only)
{
xy_min_distance = std::max(coord_t(100), xy_min_distance); // If set to low rounding errors WILL cause errors. Best to keep it above 25.
}
xy_distance = std::max(xy_distance, xy_min_distance);
@ -678,7 +677,7 @@ public:
*/
[[nodiscard]] inline coord_t recommendedMinRadius(LayerIndex layer_idx) const
{
double scale = (layer_start_bp_radius - layer_idx) * diameter_scale_bp_radius;
double scale = (layer_start_bp_radius - int(layer_idx)) * diameter_scale_bp_radius;
return scale > 0 ? branch_radius + branch_radius * scale : 0;
}