WIP Tree Supports: Bunch of fixes and optimizations

This commit is contained in:
Vojtech Bubnik 2022-08-15 08:41:24 +02:00
parent b0af552455
commit af7108f792
6 changed files with 330 additions and 187 deletions

View File

@ -61,7 +61,7 @@ namespace Slic3r {
//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
#ifdef SLIC3R_DEBUG
#if 1 //#ifdef SLIC3R_DEBUG
const char* support_surface_type_to_color_name(const SupporLayerType surface_type)
{
switch (surface_type) {
@ -543,7 +543,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled.
// There is also a 1st intermediate layer containing bases of support columns.
// Inflate the bases of the support columns and create the raft base under the object.
SupportGeneratorLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage);
SupportGeneratorLayersPtr raft_layers = generate_raft_base(object, m_support_params, m_slicing_params, top_contacts, interface_layers, base_interface_layers, intermediate_layers, layer_storage);
#ifdef SLIC3R_DEBUG
for (const SupportGeneratorLayer *l : interface_layers)
@ -573,6 +573,9 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// intermediate_layers.clear();
// interface_layers.clear();
#ifdef SLIC3R_DEBUG
SupportGeneratorLayersPtr layers_sorted =
#endif // SLIC3R_DEBUG
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";
@ -585,7 +588,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// 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 = true;
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;
@ -615,7 +618,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
// 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 = true;
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;
@ -2866,13 +2869,15 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end";
}
SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base(
SupportGeneratorLayersPtr generate_raft_base(
const PrintObject &object,
const SupportParameters &support_params,
const SlicingParameters &slicing_params,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers,
const SupportGeneratorLayersPtr &base_layers,
SupportGeneratorLayerStorage &layer_storage) const
SupportGeneratorLayerStorage &layer_storage)
{
// If there is brim to be generated, calculate the trimming regions.
Polygons brim;
@ -2904,22 +2909,22 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base(
}
// How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed.
const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON));
const float inflate_factor_fine = float(scale_((slicing_params.raft_layers() > 1) ? 0.5 : EPSILON));
const float inflate_factor_1st_layer = std::max(0.f, float(scale_(object.config().raft_first_layer_expansion)) - inflate_factor_fine);
SupportGeneratorLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front();
SupportGeneratorLayer *interfaces = interface_layers .empty() ? nullptr : interface_layers .front();
SupportGeneratorLayer *base_interfaces = base_interface_layers.empty() ? nullptr : base_interface_layers.front();
SupportGeneratorLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front();
if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON)
if (contacts != nullptr && contacts->print_z > std::max(slicing_params.first_print_layer_height, slicing_params.raft_contact_top_z) + EPSILON)
// This is not the raft contact layer.
contacts = nullptr;
if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON)
if (interfaces != nullptr && interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON)
// This is not the raft column base layer.
interfaces = nullptr;
if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON)
if (base_interfaces != nullptr && base_interfaces->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON)
// This is not the raft column base layer.
base_interfaces = nullptr;
if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON)
if (columns_base != nullptr && columns_base->bottom_print_z() > slicing_params.raft_interface_top_z + EPSILON)
// This is not the raft interface layer.
columns_base = nullptr;
@ -2934,7 +2939,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base(
// Output vector.
SupportGeneratorLayersPtr raft_layers;
if (m_slicing_params.raft_layers() > 1) {
if (slicing_params.raft_layers() > 1) {
Polygons base;
Polygons columns;
if (columns_base != nullptr) {
@ -2951,30 +2956,30 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base(
// Do not add the raft contact layer, only add the raft layers below the contact layer.
// Insert the 1st layer.
{
SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface);
SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, (slicing_params.base_raft_layers > 0) ? SupporLayerType::RaftBase : SupporLayerType::RaftInterface);
raft_layers.push_back(&new_layer);
new_layer.print_z = m_slicing_params.first_print_layer_height;
new_layer.height = m_slicing_params.first_print_layer_height;
new_layer.print_z = slicing_params.first_print_layer_height;
new_layer.height = slicing_params.first_print_layer_height;
new_layer.bottom_z = 0.;
new_layer.polygons = inflate_factor_1st_layer > 0 ? expand(base, inflate_factor_1st_layer) : base;
}
// Insert the base layers.
for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) {
for (size_t i = 1; i < slicing_params.base_raft_layers; ++ i) {
coordf_t print_z = raft_layers.back()->print_z;
SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftBase);
raft_layers.push_back(&new_layer);
new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height;
new_layer.height = m_slicing_params.base_raft_layer_height;
new_layer.print_z = print_z + slicing_params.base_raft_layer_height;
new_layer.height = slicing_params.base_raft_layer_height;
new_layer.bottom_z = print_z;
new_layer.polygons = base;
}
// Insert the interface layers.
for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) {
for (size_t i = 1; i < slicing_params.interface_raft_layers; ++ i) {
coordf_t print_z = raft_layers.back()->print_z;
SupportGeneratorLayer &new_layer = layer_allocate(layer_storage, SupporLayerType::RaftInterface);
raft_layers.push_back(&new_layer);
new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height;
new_layer.height = m_slicing_params.interface_raft_layer_height;
new_layer.print_z = print_z + slicing_params.interface_raft_layer_height;
new_layer.height = slicing_params.interface_raft_layer_height;
new_layer.bottom_z = print_z;
new_layer.polygons = interface_polygons;
//FIXME misusing contact_polygons for support columns.
@ -2984,10 +2989,10 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::generate_raft_base(
if (columns_base != nullptr) {
// Expand the bases of the support columns in the 1st layer.
Polygons &raft = columns_base->polygons;
Polygons trimming = offset(m_object->layers().front()->lslices, (float)scale_(m_support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS);
Polygons trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (inflate_factor_1st_layer > SCALED_EPSILON) {
// Inflate in multiple steps to avoid leaking of the support 1st layer through object walls.
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / m_support_params.first_layer_flow.scaled_width())));
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width())));
float step = inflate_factor_1st_layer / nsteps;
for (int i = 0; i < nsteps; ++ i)
raft = diff(expand(raft, step), trimming);
@ -3847,7 +3852,7 @@ 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(
SupportGeneratorLayersPtr generate_support_layers(
PrintObject &object,
const SupportGeneratorLayersPtr &raft_layers,
const SupportGeneratorLayersPtr &bottom_contacts,
@ -3921,6 +3926,7 @@ void generate_support_layers(
}
i = j;
}
return layers_sorted;
}
void generate_support_toolpaths(

View File

@ -147,7 +147,20 @@ struct SupportParameters {
bool with_sheath;
};
void generate_support_layers(
// Generate raft layers, also expand the 1st support layer
// in case there is no raft layer to improve support adhesion.
SupportGeneratorLayersPtr generate_raft_base(
const PrintObject &object,
const SupportParameters &support_params,
const SlicingParameters &slicing_params,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers,
const SupportGeneratorLayersPtr &base_layers,
SupportGeneratorLayerStorage &layer_storage);
// returns sorted layers
SupportGeneratorLayersPtr generate_support_layers(
PrintObject &object,
const SupportGeneratorLayersPtr &raft_layers,
const SupportGeneratorLayersPtr &bottom_contacts,
@ -170,6 +183,9 @@ void generate_support_toolpaths(
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers);
void export_print_z_polygons_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers);
void export_print_z_polygons_and_extrusions_to_svg(const char *path, SupportGeneratorLayer ** const layers, size_t n_layers, SupportLayer& support_layer);
// This class manages raft and supports for a single PrintObject.
// Instantiated by Slic3r::Print::Object->_support_material()
// This class is instantiated before the slicing starts as Object.pm will query
@ -226,16 +242,6 @@ private:
SupportGeneratorLayersPtr &intermediate_layers,
const std::vector<Polygons> &layer_support_areas) const;
// Generate raft layers, also expand the 1st support layer
// in case there is no raft layer to improve support adhesion.
SupportGeneratorLayersPtr generate_raft_base(
const PrintObject &object,
const SupportGeneratorLayersPtr &top_contacts,
const SupportGeneratorLayersPtr &interface_layers,
const SupportGeneratorLayersPtr &base_interface_layers,
const SupportGeneratorLayersPtr &base_layers,
SupportGeneratorLayerStorage &layer_storage) const;
// Turn some of the base layers into base interface layers.
// For soluble interfaces with non-soluble bases, print maximum two first interface layers with the base
// extruder to improve adhesion of the soluble filament to the base.

View File

@ -43,7 +43,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
}
this->layer_height = scaled<coord_t>(config.layer_height.value);
this->resolution = scaled<coord_t>(print_config.resolution.value);
this->resolution = scaled<coord_t>(print_config.gcode_resolution.value);
this->min_feature_size = scaled<coord_t>(config.min_feature_size.value);
this->support_angle = M_PI / 2. - config.support_material_angle * M_PI / 180.;
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
@ -127,12 +127,6 @@ TreeModelVolumes::TreeModelVolumes(
}
m_current_outline_idx = mesh_to_layeroutline_idx[current_mesh_idx];
m_support_rests_on_model = false;
m_min_resolution = std::numeric_limits<coord_t>::max();
for (auto data_pair : m_layer_outlines) {
m_support_rests_on_model |= ! data_pair.first.support_material_buildplate_only;
m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution);
}
#else
{
m_anti_overhang = print_object.slice_support_blockers();
@ -141,13 +135,23 @@ TreeModelVolumes::TreeModelVolumes(
m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{});
m_current_outline_idx = 0;
std::vector<Polygons> &outlines = m_layer_outlines.front().second;
outlines.reserve(print_object.layer_count());
for (const Layer *layer : print_object.layers())
outlines.emplace_back(to_polygons(expolygons_simplify(layer->lslices, mesh_settings.resolution)));
outlines.assign(print_object.layer_count(), Polygons{});
tbb::parallel_for(tbb::blocked_range<size_t>(0, print_object.layer_count(), std::min<size_t>(1, std::max<size_t>(16, print_object.layer_count() / (8 * tbb::this_task_arena::max_concurrency())))),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
outlines[layer_idx] = to_polygons(expolygons_simplify(print_object.get_layer(layer_idx)->lslices, mesh_settings.resolution));
});
}
#endif
const TreeSupport::TreeSupportSettings &config = m_layer_outlines[m_current_outline_idx].first;
m_support_rests_on_model = false;
m_min_resolution = std::numeric_limits<coord_t>::max();
for (auto data_pair : m_layer_outlines) {
m_support_rests_on_model |= ! data_pair.first.support_material_buildplate_only;
m_min_resolution = std::min(m_min_resolution, data_pair.first.resolution);
}
const TreeSupport::TreeSupportSettings config{ m_layer_outlines[m_current_outline_idx].first };
if (! config.support_xy_overrides_z) {
m_current_min_xy_dist = config.xy_min_distance;
if (TreeSupport::TreeSupportSettings::has_to_rely_on_min_xy_dist_only)
@ -492,12 +496,6 @@ coord_t TreeModelVolumes::getRadiusNextCeil(coord_t radius, bool min_xy_dist) co
return ceiled_radius;
}
[[nodiscard]] static inline Polygons simplify(const Polygons &polygons, coord_t resolution)
{
//FIXME
return polygons;
}
#if 0
Polygons TreeModelVolumes::extractOutlineFromMesh(const PrintObject &print_object, LayerIndex layer_idx) const
{
@ -535,13 +533,13 @@ void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys)
{
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i) {
coord_t radius = keys[i].first;
for (size_t i = range.begin(); i != range.end(); ++ i) {
const coord_t radius = keys[i].first;
const size_t layer_idx = keys[i].second;
RadiusLayerPair key(radius, 0);
RadiusLayerPolygonCache data_outer;
RadiusLayerPolygonCache data_placeable_outer;
for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); outline_idx++)
{
for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) {
RadiusLayerPolygonCache data;
RadiusLayerPolygonCache data_placeable;
@ -550,7 +548,7 @@ 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_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers);
const LayerIndex max_required_layer = layer_idx + 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.
// avoiding this would require saving each collision for each outline_idx separately.
@ -564,6 +562,7 @@ void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys)
if (min_layer_bottom < 0)
min_layer_bottom = 0;
//FIXME parallel_for
for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) {
key.second = layer_idx;
Polygons collision_areas = m_machine_border;
@ -607,10 +606,10 @@ void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys)
}
for (auto pair : data)
data_outer[pair.first] = union_(data_outer[pair.first], simplify(pair.second, m_min_resolution));
data_outer[pair.first] = union_(data_outer[pair.first], polygons_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));
data_placeable_outer[pair.first] = union_(data_placeable_outer[pair.first], polygons_simplify(pair.second, m_min_resolution));
}
}
@ -651,8 +650,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, scaled<float>(0.01));
col = simplify(col, m_min_resolution);
Polygons col = polygons_simplify(offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)), 5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution), m_min_resolution);
data[RadiusLayerPair(radius, layer_idx)] = col;
}
@ -662,17 +660,6 @@ void TreeModelVolumes::calculateCollisionHolefree(std::deque<RadiusLayerPair> ke
});
}
// ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls defined by the collision when offsetting to fast
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(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, 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)
{
// For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation.
@ -695,9 +682,7 @@ void TreeModelVolumes::calculateAvoidance(std::deque<RadiusLayerPair> keys)
continue;
const coord_t offset_speed = slow ? m_max_move_slow : m_max_move;
const coord_t max_step_move = std::max(1.9 * radius, m_current_min_xy_dist * 1.9);
RadiusLayerPair key(radius, 0);
Polygons latest_avoidance;
LayerIndex start_layer;
{
std::lock_guard<std::mutex> critical_section(*(slow ? m_critical_avoidance_cache_slow : holefree ? m_critical_avoidance_cache_holefree : m_critical_avoidance_cache));
@ -707,19 +692,19 @@ void TreeModelVolumes::calculateAvoidance(std::deque<RadiusLayerPair> keys)
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
continue;
}
start_layer = std::max(start_layer, LayerIndex(1)); // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1
std::vector<std::pair<RadiusLayerPair, Polygons>> data(max_required_layer + 1, std::pair<RadiusLayerPair, Polygons>(RadiusLayerPair(radius, -1), Polygons()));
latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision.
// Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1
start_layer = std::max(start_layer, LayerIndex(1));
// minDist as the delta was already added, also avoidance for layer 0 will return the collision.
Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true);
// ### main loop doing the calculation
for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) {
for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) {
key.second = layer;
Polygons col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ?
const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ?
getCollisionHolefree(radius, layer, true) :
getCollision(radius, layer, true);
latest_avoidance = safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col);
latest_avoidance = simplify(latest_avoidance, m_min_resolution);
latest_avoidance = polygons_simplify(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), m_min_resolution);
data[layer] = std::pair<RadiusLayerPair, Polygons>(key, latest_avoidance);
}
@ -770,7 +755,7 @@ void TreeModelVolumes::calculatePlaceables(std::deque<RadiusLayerPair> keys)
for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) {
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 = polygons_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, jtMiter, 1.2);
data[layer] = std::pair<RadiusLayerPair, Polygons>(key, placeable);
}
@ -814,8 +799,6 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque<RadiusLayerPair> key
getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated
const coord_t offset_speed = slow ? m_max_move_slow : m_max_move;
const coord_t max_step_move = std::max(1.9 * radius, m_current_min_xy_dist * 1.9);
Polygons latest_avoidance;
std::vector<std::pair<RadiusLayerPair, Polygons>> data(max_required_layer + 1, std::pair<RadiusLayerPair, Polygons>(RadiusLayerPair(radius, -1), Polygons()));
RadiusLayerPair key(radius, 0);
@ -829,27 +812,17 @@ void TreeModelVolumes::calculateAvoidanceToModel(std::deque<RadiusLayerPair> key
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
continue;
}
// Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1
start_layer = std::max(start_layer, LayerIndex(1));
latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision.
// minDist as the delta was already added, also avoidance for layer 0 will return the collision.
Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, type, true, true);
// ### main loop doing the calculation
for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++)
{
for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) {
key.second = layer;
Polygons col = getCollision(radius, layer, true);
if ((slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree)
{
col = getCollisionHolefree(radius, layer, true);
}
else
{
col = getCollision(radius, layer, true);
}
latest_avoidance = diff(safeOffset(latest_avoidance, -offset_speed, ClipperLib::jtRound, -max_step_move, col), getPlaceableAreas(radius, layer));
latest_avoidance = simplify(latest_avoidance, m_min_resolution);
const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ?
getCollisionHolefree(radius, layer, true) :
getCollision(radius, layer, true);
latest_avoidance = polygons_simplify(diff(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), getPlaceableAreas(radius, layer)), m_min_resolution);
data[layer] = std::pair<RadiusLayerPair, Polygons>(key, latest_avoidance);
}
@ -929,12 +902,12 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque<RadiusLayerPair> key
key.second = layer_idx;
LayerIndex layer_idx_below = layer_idx - 1;
Polygons wall_restriction = intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx_below, true)); // radius contains m_current_min_xy_dist_delta already if required
wall_restriction = simplify(wall_restriction, m_min_resolution);
wall_restriction = polygons_simplify(wall_restriction, m_min_resolution);
data.emplace(key, wall_restriction);
if (m_current_min_xy_dist_delta > 0)
{
Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true));
wall_restriction = simplify(wall_restriction_min, m_min_resolution);
wall_restriction = polygons_simplify(wall_restriction_min, m_min_resolution);
data_min.emplace(key, wall_restriction_min);
}
}

View File

@ -30,7 +30,7 @@ class PrintObject;
struct TreeSupportMeshGroupSettings {
TreeSupportMeshGroupSettings() = default;
TreeSupportMeshGroupSettings(const PrintObject &print_object);
explicit TreeSupportMeshGroupSettings(const PrintObject &print_object);
/*********************************************************************/
/* Print parameters, not support specific: */
@ -153,7 +153,7 @@ struct TreeSupportMeshGroupSettings {
// Tree Support Branch Distance
// How far apart the branches need to be when they touch the model. Making this distance small will cause
// the tree support to touch the model at more points, causing better overhang but making support harder to remove.
coord_t support_tree_branch_distance { scaled<coord_t>(50.) };
coord_t support_tree_branch_distance { scaled<coord_t>(1.) };
// Tree Support Branch Diameter
// The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this.
// minimum: 0.001, minimum warning: support_line_width * 2
@ -204,7 +204,7 @@ class TreeModelVolumes
{
public:
TreeModelVolumes() = default;
TreeModelVolumes(const PrintObject &print_object, const BuildVolume &build_volume,
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<Polygons> &additional_excluded_areas = {});
TreeModelVolumes(TreeModelVolumes&&) = default;
TreeModelVolumes& operator=(TreeModelVolumes&&) = default;
@ -582,7 +582,7 @@ private:
std::unique_ptr<std::mutex> m_critical_progress { std::make_unique<std::mutex>() };
};
static Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision);
Polygons safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision);
}

View File

@ -16,6 +16,7 @@
#include "MultiPoint.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "MutablePolygon.hpp"
#include "SupportMaterial.hpp"
#include <cassert>
@ -92,6 +93,76 @@ static inline void validate_range(const LineInformations &lines)
validate_range(l);
}
static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Polygon &out)
{
out.clear();
const size_t cnt = src.points.size();
if (cnt < 3)
return;
enum class Side {
Left = 1,
Right = 2,
Top = 4,
Bottom = 8
};
auto sides = [bbox](const Point &p) {
return int(p.x() < bbox.min.x()) * int(Side::Left) +
int(p.x() > bbox.max.x()) * int(Side::Right) +
int(p.y() < bbox.min.y()) * int(Side::Bottom) +
int(p.y() > bbox.max.y()) * int(Side::Top);
};
int sides_prev = sides(src.points.back());
int sides_this = sides(src.points.front());
const size_t last = cnt - 1;
for (size_t i = 0; i < last; ++ i) {
int sides_next = sides(src.points[i + 1]);
if (// This point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0) {
out.points.emplace_back(src.points[i]);
sides_prev = sides_this;
} else {
// All the three points (this, prev, next) are outside at the same side.
// Ignore this point.
}
sides_this = sides_next;
}
// For the last point, if src is completely outside bbox, then out.points will be empty. Just use the first point instead.
int sides_next = sides(out.points.empty() ? src.points.front() : out.points.front());
if (// The last point is inside. Take it.
sides_this == 0 ||
// Either this point is outside and previous or next is inside, or
// the edge possibly cuts corner of the bounding box.
(sides_prev & sides_this & sides_next) == 0)
out.points.emplace_back(src.points.back());
}
[[nodiscard]] static inline Polygon clip_for_diff(const Polygon &src, const BoundingBox &bbox)
{
Polygon out;
clip_for_diff(src, bbox, out);
return out;
}
[[nodiscard]] static inline Polygons clip_for_diff(const Polygons &src, const BoundingBox &bbox)
{
Polygons out;
out.reserve(src.size());
for (const Polygon &p : src)
out.emplace_back(clip_for_diff(p, bbox));
return out;
}
[[nodiscard]] static inline Polygons diff_clipped(const Polygons &src, const Polygons &clipping)
{
return diff(src, clip_for_diff(clipping, get_extents(src).inflated(SCALED_EPSILON)));
}
static constexpr const auto tiny_area_threshold = sqr(scaled<double>(0.001));
static std::vector<std::pair<TreeSupport::TreeSupportSettings, std::vector<size_t>>> group_meshes(const Print &print, const std::vector<size_t> &print_object_ids)
@ -121,7 +192,7 @@ static std::vector<std::pair<TreeSupport::TreeSupportSettings, std::vector<size_
assert(object_config.support_material_style == smsTree);
bool found_existing_group = false;
TreeSupport::TreeSupportSettings next_settings(print_object);
TreeSupport::TreeSupportSettings next_settings{ TreeSupportMeshGroupSettings{ print_object } };
//FIXME for now only a single object per group is enabled.
#if 0
for (size_t idx = 0; idx < grouped_meshes.size(); ++ idx)
@ -176,7 +247,7 @@ void TreeSupport::showError(std::string message, bool critical)
bool show = (critical && !g_showed_critical_error) || (!critical && !g_showed_performance_warning);
(critical ? g_showed_critical_error : g_showed_performance_warning) = true;
#ifdef _WIN32
#if defined(_WIN32) && defined(TREE_SUPPORT_SHOW_ERRORS)
if (show)
MessageBoxA(nullptr, std::string("TreeSupport_2 MOD detected an error while generating the tree support.\nPlease report this back to me with profile and model.\nRevision 5.0\n" + message + "\n" + bugtype).c_str(),
"Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
@ -338,11 +409,45 @@ void TreeSupport::generateSupportAreas(Print &print, const BuildVolume &build_vo
// Produce the support G-code.
// Used by both classic and tree supports.
SupportGeneratorLayersPtr raft_layers, interface_layers, base_interface_layers;
SupportGeneratorLayersPtr interface_layers, base_interface_layers;
SupportGeneratorLayersPtr raft_layers = generate_raft_base(print_object, SupportParameters(print_object), 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);
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);
#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;
}
@ -713,7 +818,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
support_params.base_angle;
fill_params.density = float(roof ? support_params.interface_density : scaled<float>(filler->spacing) / float(support_infill_distance));
fill_params.density = float(roof ? support_params.interface_density : scaled<float>(filler->spacing) / (scaled<float>(filler->spacing) + float(support_infill_distance)));
fill_params.dont_adjust = true;
Polylines out;
@ -786,6 +891,19 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
return result;
}
// ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls defined by the collision when offsetting to fast
[[nodiscard]] 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(int64_t(distance) * int64_t(max_safe_step_distance) >= 0);
ExPolygons ret = union_ex(me);
Polygons collision_trimmed = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON));
for (size_t i = 0; i < steps; ++ i)
ret = union_ex(union_(offset(ret, max_safe_step_distance, jt, jt == jtRound ? scaled<float>(0.01) : 1.2), collision_trimmed));
return union_(offset(ret, distance % max_safe_step_distance, jt, jt == jtRound ? scaled<float>(0.01) : 1.2), collision_trimmed);
}
/*!
* \brief Offsets (increases the area of) a polygons object in multiple steps to ensure that it does not lag through over a given obstacle.
* \param me[in] Polygons object that has to be offset.
@ -799,12 +917,21 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
{
bool do_final_difference = last_step_offset_without_check == 0;
Polygons ret = safeUnion(me); // ensure sane input
// Trim the collision polygons with the region of interest for diff() efficiency.
Polygons collision_trimmed_buffer;
auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& {
if (collision_trimmed_buffer.empty() && ! collision.empty())
collision_trimmed_buffer = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON));
return collision_trimmed_buffer;
};
if (distance == 0)
return do_final_difference ? diff(ret, collision) : union_(ret);
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);
return do_final_difference ? diff(ret, collision) : union_(ret);
return do_final_difference ? diff(ret, collision_trimmed()) : union_(ret);
}
coord_t step_size = safe_step_size;
@ -829,7 +956,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
}
// offset in steps
for (size_t i = 0; i < steps; i++) {
ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled<float>(0.01)), collision);
ret = diff(offset(ret, step_size, ClipperLib::jtRound, scaled<float>(0.01)), collision_trimmed());
// 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));
@ -841,10 +968,23 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
ret = polygons_simplify(ret, scaled<double>(0.015));
if (do_final_difference)
ret = diff(ret, collision);
ret = diff(ret, collision_trimmed());
return union_(ret);
}
static inline SupportGeneratorLayer& layer_initialize(
SupportGeneratorLayer &layer_new,
const SupporLayerType layer_type,
const SlicingParameters &slicing_params,
const size_t layer_idx)
{
layer_new.layer_type = layer_type;
layer_new.print_z = slicing_params.object_print_z_min + slicing_params.first_object_layer_height + layer_idx * slicing_params.layer_height;
layer_new.height = layer_idx == 0 ? slicing_params.first_object_layer_height : slicing_params.layer_height;
layer_new.bottom_z = layer_idx == 0 ? slicing_params.object_print_z_min : layer_new.print_z - layer_new.height;
return layer_new;
}
// Using the std::deque as an allocator.
inline SupportGeneratorLayer& layer_allocate(
std::deque<SupportGeneratorLayer> &layer_storage,
@ -852,13 +992,9 @@ inline SupportGeneratorLayer& layer_allocate(
const SlicingParameters &slicing_params,
size_t layer_idx)
{
//FIXME take raft into account.
layer_storage.push_back(SupportGeneratorLayer());
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;
return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx);
}
inline SupportGeneratorLayer& layer_allocate(
@ -868,15 +1004,9 @@ inline SupportGeneratorLayer& layer_allocate(
const SlicingParameters &slicing_params,
size_t layer_idx)
{
layer_storage_mutex.lock();
tbb::spin_mutex::scoped_lock lock(layer_storage_mutex);
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;
return layer_initialize(layer_storage.back(), layer_type, slicing_params, layer_idx);
}
void TreeSupport::generateInitialAreas(
@ -886,16 +1016,14 @@ 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;
const auto base_radius = scaled<int>(0.01);
for (unsigned int i = 0; i < SUPPORT_TREE_CIRCLE_RESOLUTION; ++ i) {
const AngleRadians angle = static_cast<double>(i) / SUPPORT_TREE_CIRCLE_RESOLUTION * (2.0 * M_PI);
base_circle.points.emplace_back(coord_t(cos(angle) * base_radius), coord_t(sin(angle) * base_radius));
}
TreeSupportSettings mesh_config(print_object);
TreeSupportMeshGroupSettings mesh_group_settings(print_object);
TreeSupportSettings mesh_config{ mesh_group_settings };
SupportParameters support_params(print_object);
const size_t z_distance_delta = mesh_config.z_distance_top_layers + 1; // To ensure z_distance_top_layers are left empty between the overhang (zeroth empty layer), the support has to be added z_distance_top_layers+1 layers below
@ -906,7 +1034,7 @@ void TreeSupport::generateInitialAreas(
return;
#endif
const coord_t connect_length = (mesh_config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2 * mesh_config.min_radius - 1.0 * mesh_config.support_line_width, 0.0);
const coord_t connect_length = (mesh_config.support_line_width * 100. / mesh_group_settings.support_tree_top_rate) + std::max(2. * mesh_config.min_radius - 1.0 * mesh_config.support_line_width, 0.0);
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)); // As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. 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<39> angle between both remains.
const coord_t extra_outset = std::max(coord_t(0), mesh_config.min_radius - mesh_config.support_line_width) + (xy_overrides_z ? 0 : 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.
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;
@ -1191,7 +1319,7 @@ void TreeSupport::generateInitialAreas(
// 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)) < 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)) < sqr(scaled<double>(0.001)) ?
reduced_overhang_outset :
overhang_outset),
connect_length, min_support_points);
@ -1466,7 +1594,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, 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.
Polygons merge = diff_clipped(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));
@ -1547,7 +1675,7 @@ static void mergeInfluenceAreas(
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<SupportElement, Polygons>& input_pair : buckets_area[bucket_idx])
for (const std::pair<const SupportElement, Polygons>& 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)));
}
});
@ -1577,11 +1705,11 @@ static void mergeInfluenceAreas(
to_model_areas.erase(del);
influence_areas.erase(del);
}
for (const std::pair<SupportElement, Polygons> &tup : insert_main[i / 2])
for (const std::pair<const SupportElement, Polygons> &tup : insert_main[i / 2])
to_bp_areas.emplace(std::move(tup));
for (const std::pair<SupportElement, Polygons> &tup : insert_secondary[i / 2])
for (const std::pair<const SupportElement, Polygons> &tup : insert_secondary[i / 2])
to_model_areas.emplace(std::move(tup));
for (const std::pair<SupportElement, Polygons> &tup : insert_influence[i / 2])
for (const std::pair<const SupportElement, Polygons> &tup : insert_influence[i / 2])
influence_areas.emplace(std::move(tup));
}
@ -1614,7 +1742,7 @@ std::optional<TreeSupport::SupportElement> TreeSupport::increaseSingleArea(AreaI
increased = *parent->area;
if (mergelayer || current_elem.to_buildplate) {
to_bp_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance)));
to_bp_data = safeUnion(diff_clipped(increased, m_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.
@ -1623,14 +1751,14 @@ std::optional<TreeSupport::SupportElement> TreeSupport::increaseSingleArea(AreaI
}
if (m_config.support_rests_on_model) {
if (mergelayer || current_elem.to_model_gracious)
to_model_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, true, settings.use_min_distance)));
to_model_data = safeUnion(diff_clipped(increased, m_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;
} else
to_model_data = safeUnion(diff(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance)));
to_model_data = safeUnion(diff_clipped(increased, m_volumes.getCollision(radius, layer_idx - 1, settings.use_min_distance)));
}
}
@ -1643,10 +1771,10 @@ std::optional<TreeSupport::SupportElement> TreeSupport::increaseSingleArea(AreaI
Polygons to_bp_data_2;
if (current_elem.to_buildplate)
to_bp_data_2 = diff(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)
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)
Polygons to_model_data_2;
if (m_config.support_rests_on_model && !current_elem.to_buildplate)
to_model_data_2 = diff(increased,
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));
@ -1683,9 +1811,9 @@ std::optional<TreeSupport::SupportElement> TreeSupport::increaseSingleArea(AreaI
if (ceil_radius_before != m_volumes.ceilRadius(radius, settings.use_min_distance)) {
if (current_elem.to_buildplate)
to_bp_data = safeUnion(diff(increased, m_volumes.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance)));
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(increased,
to_model_data = safeUnion(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)
@ -1854,8 +1982,12 @@ void TreeSupport::increaseAreas(std::unordered_map<SupportElement, Polygons>& to
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) <<
"Influence area could not be increased! Data about the Influence area: "
#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 <<
" 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 "
@ -1883,7 +2015,12 @@ void TreeSupport::increaseAreas(std::unordered_map<SupportElement, Polygons>& to
if (!settings.use_min_distance)
elem.use_min_xy_dist = false;
if (!settings.no_error)
BOOST_LOG_TRIVIAL(error) << "Trying to keep area by moving faster than intended: Success";
#ifdef TREE_SUPPORT_SHOW_ERRORS
BOOST_LOG_TRIVIAL(error)
#else // TREE_SUPPORT_SHOW_ERRORS
BOOST_LOG_TRIVIAL(info)
#endif // TREE_SUPPORT_SHOW_ERRORS
<< "Trying to keep area by moving faster than intended: Success";
break;
}
else if (!settings.no_error)
@ -1891,7 +2028,7 @@ void TreeSupport::increaseAreas(std::unordered_map<SupportElement, Polygons>& to
}
if (add) {
Polygons max_influence_area = safeUnion(diff(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
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<std::mutex> critical_section_newLayer(critical_sections);
if (bypass_merge) {
@ -1981,8 +2118,8 @@ void TreeSupport::createLayerPathing(std::vector<std::set<SupportElement*>>& mov
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 (std::pair<SupportElement, Polygons> tup : influence_areas) {
const SupportElement elem = tup.first;
for (const std::pair<const SupportElement, Polygons> &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));
@ -2172,7 +2309,10 @@ void TreeSupport::createNodesFromArea(std::vector<std::set<SupportElement*>>& mo
}
}
void TreeSupport::generateBranchAreas(std::vector<std::pair<LayerIndex, SupportElement*>>& linear_data, std::vector<std::unordered_map<SupportElement*, Polygons>>& layer_tree_polygons, const std::map<SupportElement*, SupportElement*>& inverse_tree_order)
void TreeSupport::generateBranchAreas(
std::vector<std::pair<LayerIndex, SupportElement*>> &linear_data,
std::vector<std::unordered_map<SupportElement*, Polygons>> &layer_tree_polygons,
const std::map<SupportElement*, SupportElement*> &inverse_tree_order)
{
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC;
@ -2195,28 +2335,30 @@ void TreeSupport::generateBranchAreas(std::vector<std::pair<LayerIndex, SupportE
tbb::parallel_for(tbb::blocked_range<size_t>(0, linear_data.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
SupportElement* elem = linear_data[idx].second;
coord_t radius = m_config.getRadius(*elem);
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<SupportElement*>(elem));
const SupportElement* child_elem = it_elem == inverse_tree_order.end() ? nullptr : it_elem->second;
const coord_t radius = m_config.getRadius(*elem);
bool parent_uses_min = false;
SupportElement* child_elem = inverse_tree_order.count(elem) ? inverse_tree_order.at(elem) : nullptr;
// 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<std::pair<Point, coord_t>> movement_directions{ std::pair<Point, coord_t>(Point(0, 0), radius) };
if (!elem->skip_ovalisation) {
if (child_elem != nullptr) {
Point movement = (child_elem->result_on_layer - elem->result_on_layer);
const Point movement = child_elem->result_on_layer - elem->result_on_layer;
movement_directions.emplace_back(movement, radius);
}
for (SupportElement* parent : elem->parents) {
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));
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;
}
}
double max_speed = 0;
auto generateArea = [&](coord_t aoffset) {
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) {
Polygons poly;
for (std::pair<Point, coord_t> movement : movement_directions) {
@ -2224,10 +2366,10 @@ void TreeSupport::generateBranchAreas(std::vector<std::pair<LayerIndex, SupportE
// 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 * m_config.branch_radius);
double used_scale = (movement.second + aoffset) / (1.0 * branch_radius);
Point center_position = elem->result_on_layer + movement.first / 2;
const double moveX = movement.first.x() / (used_scale * m_config.branch_radius);
const double moveY = movement.first.y() / (used_scale * m_config.branch_radius);
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));
double matrix[] = {
@ -2242,8 +2384,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), 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.
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.
return poly;
};
@ -2277,7 +2419,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, 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));
linear_inserts[idx] = diff_clipped(linear_inserts[idx], m_volumes.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist));
}
}
}
@ -2405,7 +2547,7 @@ void TreeSupport::dropNonGraciousAreas(
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(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter));
rest_support = diff_clipped(rest_support, m_volumes.getCollision(0, linear_data[idx].first - counter));
dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support);
counter++;
}
@ -2436,7 +2578,9 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(
[&](const tbb::blocked_range<size_t> &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] = union_(support_layer_storage[layer_idx]); //FIXME .smooth(50);
support_layer_storage[layer_idx] = smooth_outward(union_(support_layer_storage[layer_idx]), m_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<double>(0.03), double(m_config.resolution)));
// Subtract support lines of the branches from the roof
@ -2488,7 +2632,7 @@ 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];
Polygons layer_outset = diff(
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));
Polygons floor_layer;
@ -2507,7 +2651,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(
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.
support_layer_storage[layer_idx] = diff_clipped(support_layer_storage[layer_idx], offset(support_bottom->polygons, scaled<float>(0.01), jtMiter, 1.2)); // Subtract the support floor from the normal support.
}
}
@ -2579,9 +2723,21 @@ void TreeSupport::drawAreas(
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)
for (std::pair<SupportElement*, Polygons> data_pair : layer_tree_polygons[layer_idx])
append(data_pair.first->missing_roof_layers > data_pair.first->distance_to_top ? support_roof_storage[layer_idx] : support_layer_storage[layer_idx], std::move(data_pair.second));
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<SupportElement*, Polygons> &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<SupportElement*, Polygons> &data_pair : this_layer_tree_polygons) {
auto &src = const_cast<Polygons&>(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));
}
}
finalizeInterfaceAndSupportAreas(print_object, support_layer_storage, support_roof_storage,
bottom_contacts, top_contacts, intermediate_layers, layer_storage);

View File

@ -16,6 +16,8 @@
#include "BoundingBox.hpp"
// #define TREE_SUPPORT_SHOW_ERRORS
#define SUPPORT_TREE_CIRCLE_RESOLUTION 25 // The number of vertices in each circle.
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
@ -108,7 +110,7 @@ public:
struct SupportElement
{
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),
@ -119,7 +121,7 @@ public:
}
SupportElement(const SupportElement& elem, Polygons* newArea = nullptr)
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),
@ -149,7 +151,7 @@ public:
* \brief Create a new Element for one layer below the element of the pointer supplied.
*/
SupportElement(SupportElement* element_above)
explicit SupportElement(SupportElement* element_above)
: target_height(element_above->target_height),
target_position(element_above->target_position),
next_position(element_above->next_position),
@ -174,7 +176,7 @@ public:
}
// ONLY to be called in merge as it assumes a few assurances made by it.
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)
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)
@ -353,7 +355,7 @@ public:
{
TreeSupportSettings() = default; // required for the definition of the config variable in the TreeSupport class.
TreeSupportSettings(const TreeSupportMeshGroupSettings& mesh_group_settings)
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),