WIP TreeSupports: Improved speed of TreeModelVolumes by better

parallelization, cleaned up the code by better structuring
the collision caches with their mutexes.
This commit is contained in:
Vojtech Bubnik 2022-08-23 11:37:06 +02:00
parent 9aee934d53
commit b9e7cd2d7b
4 changed files with 454 additions and 491 deletions

View File

@ -26,6 +26,10 @@
namespace Slic3r namespace Slic3r
{ {
// or warning
// had to use a define beacuse the macro processing inside macro BOOST_LOG_TRIVIAL()
#define error_level_not_in_cache error
TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object)
{ {
const PrintConfig &print_config = print_object.print()->config(); const PrintConfig &print_config = print_object.print()->config();
@ -37,10 +41,11 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
assert(config.support_material); assert(config.support_material);
assert(config.support_material_style == smsTree); assert(config.support_material_style == smsTree);
// Calculate maximum external perimeter width over all printing regions, taking into account the default layer height.
coordf_t external_perimeter_width = 0.; coordf_t external_perimeter_width = 0.;
for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) {
const PrintRegion &region = print_object.printing_region(region_id); const PrintRegion &region = print_object.printing_region(region_id);
external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(print_object, frExternalPerimeter, config.layer_height).width())); external_perimeter_width = std::max<coordf_t>(external_perimeter_width, region.flow(print_object, frExternalPerimeter, config.layer_height).width());
} }
this->layer_height = scaled<coord_t>(config.layer_height.value); this->layer_height = scaled<coord_t>(config.layer_height.value);
@ -59,6 +64,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
this->support_material_buildplate_only = config.support_material_buildplate_only; this->support_material_buildplate_only = config.support_material_buildplate_only;
// this->support_xy_overrides_z = // this->support_xy_overrides_z =
this->support_xy_distance = scaled<coord_t>(config.support_material_xy_spacing.get_abs_value(external_perimeter_width)); this->support_xy_distance = scaled<coord_t>(config.support_material_xy_spacing.get_abs_value(external_perimeter_width));
// Separation of interfaces, it is likely smaller than support_xy_distance.
this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled<coord_t>(0.5 * external_perimeter_width)); this->support_xy_distance_overhang = std::min(this->support_xy_distance, scaled<coord_t>(0.5 * external_perimeter_width));
this->support_top_distance = scaled<coord_t>(slicing_params.gap_support_object); this->support_top_distance = scaled<coord_t>(slicing_params.gap_support_object);
this->support_bottom_distance = scaled<coord_t>(slicing_params.gap_object_support); this->support_bottom_distance = scaled<coord_t>(slicing_params.gap_object_support);
@ -79,6 +85,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
// this->support_offset = // this->support_offset =
} }
//FIXME Machine border is currently ignored.
static Polygons calculateMachineBorderCollision(Polygon machine_border) static Polygons calculateMachineBorderCollision(Polygon machine_border)
{ {
// Put a border of 1m around the print volume so that we don't collide. // Put a border of 1m around the print volume so that we don't collide.
@ -99,13 +106,13 @@ TreeModelVolumes::TreeModelVolumes(
const BuildVolume &build_volume, const BuildVolume &build_volume,
const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx, const coord_t max_move, const coord_t max_move_slow, size_t current_mesh_idx,
double progress_multiplier, double progress_offset, const std::vector<Polygons>& additional_excluded_areas) : double progress_multiplier, double progress_offset, const std::vector<Polygons>& additional_excluded_areas) :
m_max_move{ std::max(max_move - 2, coord_t(0)) }, m_max_move_slow{ std::max(max_move_slow - 2, coord_t(0)) }, m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset },
// -2 to avoid rounding errors // -2 to avoid rounding errors
m_max_move{ std::max<coord_t>(max_move - 2, 0) }, m_max_move_slow{ std::max<coord_t>(max_move_slow - 2, 0) },
m_progress_multiplier{ progress_multiplier }, m_progress_offset{ progress_offset },
m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) } m_machine_border{ calculateMachineBorderCollision(build_volume.polygon()) }
{ {
std::unordered_map<size_t, size_t> mesh_to_layeroutline_idx;
#if 0 #if 0
std::unordered_map<size_t, size_t> mesh_to_layeroutline_idx;
for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); ++ mesh_idx) { for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); ++ mesh_idx) {
SliceMeshStorage mesh = storage.meshes[mesh_idx]; SliceMeshStorage mesh = storage.meshes[mesh_idx];
bool added = false; bool added = false;
@ -131,7 +138,6 @@ TreeModelVolumes::TreeModelVolumes(
#else #else
{ {
m_anti_overhang = print_object.slice_support_blockers(); m_anti_overhang = print_object.slice_support_blockers();
mesh_to_layeroutline_idx[0] = 0;
TreeSupportMeshGroupSettings mesh_settings(print_object); TreeSupportMeshGroupSettings mesh_settings(print_object);
m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{}); m_layer_outlines.emplace_back(mesh_settings, std::vector<Polygons>{});
m_current_outline_idx = 0; m_current_outline_idx = 0;
@ -195,222 +201,188 @@ TreeModelVolumes::TreeModelVolumes(
#endif #endif
} }
void TreeModelVolumes::precalculate(coord_t max_layer) void TreeModelVolumes::precalculate(const coord_t max_layer)
{ {
auto t_start = std::chrono::high_resolution_clock::now(); auto t_start = std::chrono::high_resolution_clock::now();
m_precalculated = true; m_precalculated = true;
// Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant. Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex like inital layer diameter are only done in once. // Get the config corresponding to one mesh that is in the current group. Which one has to be irrelevant.
// Not the prettiest way to do this, but it ensures some calculations that may be a bit more complex
// like inital layer diameter are only done in once.
TreeSupport::TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first); TreeSupport::TreeSupportSettings config(m_layer_outlines[m_current_outline_idx].first);
{
// calculate which radius each layer in the tip may have. // calculate which radius each layer in the tip may have.
std::unordered_set<coord_t> possible_tip_radiis; std::vector<coord_t> possible_tip_radiis;
for (size_t dtt = 0; dtt <= config.tip_layers; dtt++) { for (size_t distance_to_top = 0; distance_to_top <= config.tip_layers; ++ distance_to_top) {
possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt))); possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top)));
possible_tip_radiis.emplace(ceilRadius(config.getRadius(dtt) + m_current_min_xy_dist_delta)); possible_tip_radiis.emplace_back(ceilRadius(config.getRadius(distance_to_top) + m_current_min_xy_dist_delta));
}
sort_remove_duplicates(possible_tip_radiis);
// It theoretically may happen in the tip, that the radius can change so much in-between 2 layers,
// that a ceil step is skipped (as in there is a radius r so that ceilRadius(radius(dtt))<ceilRadius(r)<ceilRadius(radius(dtt+1))).
// As such a radius will not reasonable happen in the tree and it will most likely not be requested,
// there is no need to calculate them. So just skip these.
for (coord_t radius_eval = m_radius_0; radius_eval <= config.branch_radius; radius_eval = ceilRadius(radius_eval + 1))
if (! std::binary_search(possible_tip_radiis.begin(), possible_tip_radiis.end(), radius_eval))
m_ignorable_radii.emplace_back(radius_eval);
} }
// It theoretically may happen in the tip, that the radius can change so much in-between 2 layers, that a ceil step is skipped (as in there is a radius r so that ceilRadius(radius(dtt))<ceilRadius(r)<ceilRadius(radius(dtt+1))). As such a radius will not reasonable happen in the tree and it will most likely not be requested, there is no need to calculate them. So just skip these.
for (coord_t radius_eval = ceilRadius(1); radius_eval <= config.branch_radius; radius_eval = ceilRadius(radius_eval + 1))
if (!possible_tip_radiis.count(radius_eval))
m_ignorable_radii.emplace(radius_eval);
// it may seem that the required avoidance can be of a smaller radius when going to model (no initial layer diameter for to model branches) // it may seem that the required avoidance can be of a smaller radius when going to model (no initial layer diameter for to model branches)
// but as for every branch going towards the bp, the to model avoidance is required to check for possible merges with to model branches, this assumption is in-fact wrong. // but as for every branch going towards the bp, the to model avoidance is required to check for possible merges with to model branches, this assumption is in-fact wrong.
std::unordered_map<coord_t, LayerIndex> radius_until_layer; std::unordered_map<coord_t, LayerIndex> radius_until_layer;
// while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if something of the radius calculation is changed. // while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if something of the radius calculation is changed.
// Overhead with an assumed worst case of 6600 layers was about 2ms // Overhead with an assumed worst case of 6600 layers was about 2ms
for (LayerIndex simulated_dtt = 0; simulated_dtt <= max_layer; simulated_dtt++) { for (LayerIndex distance_to_top = 0; distance_to_top <= max_layer; ++ distance_to_top) {
const LayerIndex current_layer = max_layer - simulated_dtt; const LayerIndex current_layer = max_layer - distance_to_top;
const coord_t max_regular_radius = ceilRadius(config.getRadius(simulated_dtt, 0) + m_current_min_xy_dist_delta); auto update_radius_until_layer = [&radius_until_layer, current_layer](coord_t r) {
const coord_t max_min_radius = ceilRadius(config.getRadius(simulated_dtt, 0)); // the maximal radius that the radius with the min_xy_dist can achieve auto it = radius_until_layer.find(r);
const coord_t max_initial_layer_diameter_radius = ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta); if (it == radius_until_layer.end())
if (!radius_until_layer.count(max_regular_radius)) radius_until_layer.emplace_hint(it, r, current_layer);
radius_until_layer[max_regular_radius] = current_layer; };
if (!radius_until_layer.count(max_min_radius)) // regular radius
radius_until_layer[max_min_radius] = current_layer; update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0) + m_current_min_xy_dist_delta));
if (!radius_until_layer.count(max_initial_layer_diameter_radius)) // the maximum radius that the radius with the min_xy_dist can achieve
radius_until_layer[max_initial_layer_diameter_radius] = current_layer; update_radius_until_layer(ceilRadius(config.getRadius(distance_to_top, 0)));
update_radius_until_layer(ceilRadius(config.recommendedMinRadius(current_layer) + m_current_min_xy_dist_delta));
} }
// Copy to deque to use in parallel for later. // Copy to deque to use in parallel for later.
std::deque<RadiusLayerPair> relevant_avoidance_radiis; std::vector<RadiusLayerPair> relevant_avoidance_radiis{ radius_until_layer.begin(), radius_until_layer.end() };
std::deque<RadiusLayerPair> relevant_avoidance_radiis_to_model;
relevant_avoidance_radiis.insert(relevant_avoidance_radiis.end(), radius_until_layer.begin(), radius_until_layer.end());
relevant_avoidance_radiis_to_model.insert(relevant_avoidance_radiis_to_model.end(), radius_until_layer.begin(), radius_until_layer.end());
// Append additional radiis needed for collision. // Append additional radiis needed for collision.
// To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required.
radius_until_layer[ceilRadius(m_increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius m_increase_until_radius will be required. radius_until_layer[ceilRadius(m_increase_until_radius + m_current_min_xy_dist_delta)] = max_layer;
// Collision for radius 0 needs to be calculated everywhere, as it will be used to ensure valid xy_distance in drawAreas. // Collision for radius 0 needs to be calculated everywhere, as it will be used to ensure valid xy_distance in drawAreas.
radius_until_layer[0] = max_layer; radius_until_layer[0] = max_layer;
if (m_current_min_xy_dist_delta != 0) if (m_current_min_xy_dist_delta != 0)
radius_until_layer[m_current_min_xy_dist_delta] = max_layer; radius_until_layer[m_current_min_xy_dist_delta] = max_layer;
std::deque<RadiusLayerPair> relevant_collision_radiis; // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy.
relevant_collision_radiis.insert(relevant_collision_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); // Now that required_avoidance_limit contains the maximum of ild and regular required radius just copy. std::vector<RadiusLayerPair> relevant_collision_radiis{ radius_until_layer.begin(), radius_until_layer.end() };
// Calculate the relevant collisions
// ### Calculate the relevant collisions
calculateCollision(relevant_collision_radiis); calculateCollision(relevant_collision_radiis);
// calculate a separate Collisions with all holes removed. These are relevant for some avoidances that try to avoid holes (called safe) // calculate a separate Collisions with all holes removed. These are relevant for some avoidances that try to avoid holes (called safe)
std::deque<RadiusLayerPair> relevant_hole_collision_radiis; std::vector<RadiusLayerPair> relevant_hole_collision_radiis;
for (RadiusLayerPair key : relevant_avoidance_radiis) for (RadiusLayerPair key : relevant_avoidance_radiis)
if (key.first < m_increase_until_radius + m_current_min_xy_dist_delta) if (key.first < m_increase_until_radius + m_current_min_xy_dist_delta)
relevant_hole_collision_radiis.emplace_back(key); relevant_hole_collision_radiis.emplace_back(key);
// ### Calculate collisions without holes, build from regular collision // Calculate collisions without holes, built from regular collision
calculateCollisionHolefree(relevant_hole_collision_radiis); calculateCollisionHolefree(relevant_hole_collision_radiis);
// Let placables be calculated from calculateAvoidance() for better parallelization.
if (m_support_rests_on_model)
calculatePlaceables(relevant_avoidance_radiis);
auto t_coll = std::chrono::high_resolution_clock::now(); auto t_coll = std::chrono::high_resolution_clock::now();
// ### Calculate the relevant avoidances in parallel as far as possible // Calculate the relevant avoidances in parallel as far as possible
{ {
tbb::task_group task_group; tbb::task_group task_group;
task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis); }); task_group.run([this, relevant_avoidance_radiis]{ calculateAvoidance(relevant_avoidance_radiis, true, m_support_rests_on_model); });
task_group.run([this, relevant_avoidance_radiis]{ calculateWallRestrictions(relevant_avoidance_radiis); }); task_group.run([this, relevant_avoidance_radiis]{ calculateWallRestrictions(relevant_avoidance_radiis); });
if (m_support_rests_on_model)
task_group.run([this, relevant_avoidance_radiis_to_model]{
calculatePlaceables(relevant_avoidance_radiis_to_model);
calculateAvoidanceToModel(relevant_avoidance_radiis_to_model);
});
task_group.wait(); task_group.wait();
} }
auto t_end = std::chrono::high_resolution_clock::now(); auto t_end = std::chrono::high_resolution_clock::now();
auto dur_col = 0.001 * std::chrono::duration_cast<std::chrono::microseconds>(t_coll - t_start).count(); auto dur_col = 0.001 * std::chrono::duration_cast<std::chrono::microseconds>(t_coll - t_start).count();
auto dur_avo = 0.001 * std::chrono::duration_cast<std::chrono::microseconds>(t_end - t_coll).count(); auto dur_avo = 0.001 * std::chrono::duration_cast<std::chrono::microseconds>(t_end - t_coll).count();
// m_precalculated = true;
BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms.";
} }
const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const
{ {
coord_t orig_radius = radius; const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist);
if (!min_xy_dist) if (std::optional<std::reference_wrapper<const Polygons>> result = m_collision_cache.getArea({ radius, layer_idx }); result)
radius += m_current_min_xy_dist_delta;
// special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible.
if (orig_radius != 0)
radius = ceilRadius(radius);
RadiusLayerPair key{ radius, layer_idx };
std::optional<std::reference_wrapper<const Polygons>> result = m_collision_cache.getArea(key);
if (result)
return result.value().get(); return result.value().get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
TreeSupport::showError("Not precalculated Collision requested.", false); TreeSupport::showError("Not precalculated Collision requested.", false);
} }
const_cast<TreeModelVolumes*>(this)->calculateCollision(key); const_cast<TreeModelVolumes*>(this)->calculateCollision(radius, layer_idx);
return getCollision(orig_radius, layer_idx, min_xy_dist); return getCollision(orig_radius, layer_idx, min_xy_dist);
} }
const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const // Private. Only called internally by calculateAvoidance() and calculateAvoidanceToModel(), radius is already snapped to grid.
const Polygons& TreeModelVolumes::getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const
{ {
coord_t orig_radius = radius; assert(radius == this->ceilRadius(radius));
if (!min_xy_dist) assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta);
radius += m_current_min_xy_dist_delta; if (std::optional<std::reference_wrapper<const Polygons>> result = m_collision_cache_holefree.getArea({ radius, layer_idx }); result)
if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta)
return getCollision(orig_radius, layer_idx, min_xy_dist);
// not needed as the radius should already be adjusted.
// radius = ceilRadius(radius);
RadiusLayerPair key{ radius, layer_idx };
std::optional<std::reference_wrapper<const Polygons>> result = m_collision_cache_holefree.getArea(key);
if (result)
return result.value().get(); return result.value().get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(warning) << "Had to calculate collision holefree at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate collision holefree at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
TreeSupport::showError("Not precalculated Holefree Collision requested.", false); TreeSupport::showError("Not precalculated Holefree Collision requested.", false);
} }
const_cast<TreeModelVolumes*>(this)->calculateCollisionHolefree(key); const_cast<TreeModelVolumes*>(this)->calculateCollisionHolefree({ radius, layer_idx });
return getCollisionHolefree(orig_radius, layer_idx, min_xy_dist); return getCollisionHolefree(radius, layer_idx);
} }
const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerIndex layer_idx, AvoidanceType type, bool to_model, bool min_xy_dist) const
{ {
if (layer_idx == 0) // What on the layer directly above buildplate do i have to avoid to reach the buildplate ... if (layer_idx == 0) // What on the layer directly above buildplate do i have to avoid to reach the buildplate ...
return getCollision(radius, layer_idx, min_xy_dist); return getCollision(orig_radius, layer_idx, min_xy_dist);
coord_t orig_radius = radius; const coord_t radius = this->ceilRadius(orig_radius, min_xy_dist);
if (type == AvoidanceType::FastSafe && radius >= m_increase_until_radius + m_current_min_xy_dist_delta)
if (!min_xy_dist) // no holes anymore by definition at this request
radius += m_current_min_xy_dist_delta;
radius = ceilRadius(radius);
if (radius >= m_increase_until_radius + m_current_min_xy_dist_delta && type == AvoidanceType::FastSafe) // no holes anymore by definition at this request
type = AvoidanceType::Fast; type = AvoidanceType::Fast;
const RadiusLayerPair key{ radius, layer_idx }; if (std::optional<std::reference_wrapper<const Polygons>> result =
this->avoidance_cache(type, to_model).getArea({ radius, layer_idx });
const RadiusLayerPolygonCache *cache_ptr = nullptr; result)
if (!to_model && type == AvoidanceType::Fast) {
cache_ptr = &m_avoidance_cache;
} else if (!to_model && type == AvoidanceType::Slow) {
cache_ptr = &m_avoidance_cache_slow;
} else if (!to_model && type == AvoidanceType::FastSafe) {
cache_ptr = &m_avoidance_cache_holefree;
} else if (to_model && type == AvoidanceType::Fast) {
cache_ptr = &m_avoidance_cache_to_model;
} else if (to_model && type == AvoidanceType::Slow) {
cache_ptr = &m_avoidance_cache_to_model_slow;
} else if (to_model && type == AvoidanceType::FastSafe) {
cache_ptr = &m_avoidance_cache_holefree_to_model;
} else {
BOOST_LOG_TRIVIAL(error) << "Invalid Avoidance Request";
TreeSupport::showError("Invalid Avoidance Request.\n", true);
}
std::optional<std::reference_wrapper<const Polygons>> result = cache_ptr->getArea(key);
if (result)
return result.value().get(); return result.value().get();
if (m_precalculated) {
if (to_model) { if (to_model) {
if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance to model at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance to model at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!";
TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false); TreeSupport::showError("Not precalculated Avoidance(to model) requested.", false);
}
const_cast<TreeModelVolumes*>(this)->calculateAvoidanceToModel(key);
} else { } else {
if (m_precalculated) { BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Avoidance at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
BOOST_LOG_TRIVIAL(warning) << "Had to calculate Avoidance at radius " << key.first << " and layer " << key.second << ", but precalculate was called. Performance may suffer!";
TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false); TreeSupport::showError("Not precalculated Avoidance(to buildplate) requested.", false);
} }
const_cast<TreeModelVolumes*>(this)->calculateAvoidance(key);
} }
return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist); // retrive failed and correct result was calculated. Now it has to be retrived. const_cast<TreeModelVolumes*>(this)->calculateAvoidance({ radius, layer_idx }, ! to_model, to_model);
// Retrive failed and correct result was calculated. Now it has to be retrived.
return getAvoidance(orig_radius, layer_idx, type, to_model, min_xy_dist);
} }
const Polygons& TreeModelVolumes::getPlaceableAreas(coord_t orig_radius, LayerIndex layer_idx) const const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx) const
{ {
if (orig_radius == 0)
return this->getCollision(0, layer_idx, true);
const coord_t radius = ceilRadius(orig_radius); const coord_t radius = ceilRadius(orig_radius);
std::optional<std::reference_wrapper<const Polygons>> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); if (std::optional<std::reference_wrapper<const Polygons>> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); result)
if (result)
return result.value().get(); return result.value().get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(warning) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
TreeSupport::showError("Not precalculated Placeable areas requested.", false); TreeSupport::showError("Not precalculated Placeable areas requested.", false);
} }
if (orig_radius > 0) const_cast<TreeModelVolumes*>(this)->calculatePlaceables(radius, layer_idx);
const_cast<TreeModelVolumes*>(this)->calculatePlaceables({ radius, layer_idx });
else
getCollision(0, layer_idx, true);
return getPlaceableAreas(orig_radius, layer_idx); return getPlaceableAreas(orig_radius, layer_idx);
} }
const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const const Polygons& TreeModelVolumes::getWallRestriction(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const
{ {
if (layer_idx == 0) // Should never be requested as there will be no going below layer 0 ..., but just to be sure some semi-sane catch. Alternative would be empty Polygon. assert(layer_idx > 0);
if (layer_idx == 0)
// Should never be requested as there will be no going below layer 0 ...,
// but just to be sure some semi-sane catch. Alternative would be empty Polygon.
return getCollision(orig_radius, layer_idx, min_xy_dist); return getCollision(orig_radius, layer_idx, min_xy_dist);
min_xy_dist = min_xy_dist && m_current_min_xy_dist_delta > 0; min_xy_dist &= m_current_min_xy_dist_delta > 0;
coord_t radius = ceilRadius(orig_radius); const coord_t radius = ceilRadius(orig_radius);
std::optional<std::reference_wrapper<const Polygons>> result = if (std::optional<std::reference_wrapper<const Polygons>> result =
(min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx }); (min_xy_dist ? m_wall_restrictions_cache_min : m_wall_restrictions_cache).getArea({ radius, layer_idx });
if (result) result)
return result.value().get(); return result.value().get();
if (m_precalculated) { if (m_precalculated) {
BOOST_LOG_TRIVIAL(warning) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!"; BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Wall restricions at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
TreeSupport::showError( TreeSupport::showError(
min_xy_dist ? min_xy_dist ?
"Not precalculated Wall restriction of minimum xy distance requested )." : "Not precalculated Wall restriction of minimum xy distance requested )." :
@ -421,85 +393,115 @@ const Polygons& TreeModelVolumes::getWallRestriction(coord_t orig_radius, LayerI
return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved. return getWallRestriction(orig_radius, layer_idx, min_xy_dist); // Retrieve failed and correct result was calculated. Now it has to be retrieved.
} }
void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys) void TreeModelVolumes::calculateCollision(const std::vector<RadiusLayerPair> &keys)
{ {
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&](const tbb::blocked_range<size_t> &range) { [&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i != range.end(); ++ i) { for (size_t ikey = range.begin(); ikey != range.end(); ++ ikey) {
const coord_t radius = keys[i].first; const LayerIndex radius = keys[ikey].first;
const size_t layer_idx = keys[i].second; const size_t max_layer_idx = keys[ikey].second;
RadiusLayerPair key(radius, 0); // recursive call to parallel_for.
RadiusLayerPolygonCacheData data_outer; calculateCollision(radius, max_layer_idx);
RadiusLayerPolygonCacheData data_placeable_outer; }
for (size_t outline_idx = 0; outline_idx < m_layer_outlines.size(); ++outline_idx) { });
RadiusLayerPolygonCacheData data; }
RadiusLayerPolygonCacheData data_placeable;
const coord_t layer_height = m_layer_outlines[outline_idx].first.layer_height; void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx)
const bool support_rests_on_this_model = ! m_layer_outlines[outline_idx].first.support_material_buildplate_only; {
const coord_t z_distance_bottom = m_layer_outlines[outline_idx].first.support_bottom_distance; assert(radius == this->ceilRadius(radius));
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); // Process the outlines from least layers to most layers so that the final union will run over the longest vector.
const LayerIndex max_required_layer = layer_idx + std::max(coord_t(1), z_distance_top_layers); std::vector<size_t> layer_outline_indices(m_layer_outlines.size(), 0);
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; std::iota(layer_outline_indices.begin(), layer_outline_indices.end(), 0);
// 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. std::sort(layer_outline_indices.begin(), layer_outline_indices.end(),
[this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); });
const LayerIndex min_layer_last = m_collision_cache.getMaxCalculatedLayer(radius);
std::vector<Polygons> data(max_layer_idx + 1 - min_layer_last, Polygons{});
const bool calculate_placable = m_support_rests_on_model && radius == 0;
std::vector<Polygons> data_placeable;
if (calculate_placable)
data_placeable = std::vector<Polygons>(max_layer_idx + 1 - min_layer_last, Polygons{});
for (size_t outline_idx : layer_outline_indices)
if (const std::vector<Polygons> &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) {
const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first;
const coord_t layer_height = settings.layer_height;
const bool support_rests_on_model = ! settings.support_material_buildplate_only;
const coord_t z_distance_bottom = settings.support_bottom_distance;
const int z_distance_bottom_layers = round_up_divide<int>(z_distance_bottom, layer_height);
const int z_distance_top_layers = round_up_divide<int>(settings.support_top_distance, layer_height);
const LayerIndex max_required_layer = std::min<LayerIndex>(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers));
const LayerIndex min_layer_bottom = std::max<LayerIndex>(0, min_layer_last - int(z_distance_bottom_layers));
// 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. // avoiding this would require saving each collision for each outline_idx separately.
// and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later.
// so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. // so avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance.
coord_t min_layer_bottom = m_collision_cache.getMaxCalculatedLayer(radius) - int(z_distance_bottom_layers); const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : settings.support_xy_distance;
if (min_layer_bottom < 0)
min_layer_bottom = 0; // 1) Calculate offsets of collision areas in parallel.
//FIXME parallel_for std::vector<Polygons> collision_areas_offsetted(max_required_layer + 1 - min_layer_bottom);
for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; layer_idx++) { tbb::parallel_for(tbb::blocked_range<LayerIndex>(min_layer_bottom, max_required_layer + 1),
key.second = layer_idx; [&outlines, &machine_border = m_machine_border, offset_value = radius + xy_distance, min_layer_bottom, &collision_areas_offsetted]
Polygons collision_areas = m_machine_border; (const tbb::blocked_range<LayerIndex> &range) {
if (size_t(layer_idx) < m_layer_outlines[outline_idx].second.size()) for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
append(collision_areas, m_layer_outlines[outline_idx].second[layer_idx]); Polygons collision_areas = machine_border;
append(collision_areas, outlines[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. // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model.
// 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)); collision_areas_offsetted[layer_idx - min_layer_bottom] = offset_value == 0 ? union_(collision_areas) : offset(union_ex(collision_areas), offset_value, 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. // 2) Sum over top / bottom ranges.
for (int layer_idx = int(max_required_layer); layer_idx >= min_layer_bottom; -- layer_idx) { const bool last = outline_idx == layer_outline_indices.size();
key.second = layer_idx; tbb::parallel_for(tbb::blocked_range<LayerIndex>(min_layer_last + 1, max_layer_idx + 1),
for (size_t layer_offset = 1; layer_offset <= z_distance_bottom_layers && layer_idx - coord_t(layer_offset) > min_layer_bottom; ++ layer_offset) [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, min_layer_last, last]
append(data[key], data[RadiusLayerPair(radius, layer_idx - layer_offset)]); (const tbb::blocked_range<LayerIndex>& range) {
if (support_rests_on_this_model && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++layer_idx) {
RadiusLayerPair key_next_layer(radius, layer_idx + 1); Polygons collisions;
//data[key] = union_(data[key]); for (int i = -z_distance_bottom_layers; i <= z_distance_top_layers; ++ i) {
Polygons above = data[key_next_layer]; int j = layer_idx + i - min_layer_bottom;
// just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. if (j >= 0 && j < collision_areas_offsetted.size())
//FIXME Vojtech: Why m_anti_overhang.size() > layer_idx + 1? Why +1? append(collisions, collision_areas_offsetted[j]);
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));
} }
collisions = last && layer_idx < anti_overhang.size() ? union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions);
auto &dst = data[layer_idx - (min_layer_last + 1)];
if (last) {
if (! dst.empty())
collisions = union_(collisions, dst);
dst = polygons_simplify(collisions, min_resolution);
} else
append(dst, collisions);
} }
});
// Add collision layers above to ensure correct support_top_distance. // 3) Optionally calculate placables.
for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= max_required_layer; ++ layer_idx) { if (calculate_placable) {
key.second = layer_idx; // Calculating both the collision areas and placable areas.
Polygons collisions = std::move(data[key]); tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(min_layer_last + 1, z_distance_bottom_layers + 1), max_layer_idx + 1),
for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx <= max_required_layer; ++ layer_offset) [&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, radius, z_distance_bottom_layers, z_distance_top_layers, last, min_resolution = m_min_resolution, &data_placeable, min_layer_last]
append(collisions, data[RadiusLayerPair(radius, layer_idx + layer_offset)]); (const tbb::blocked_range<LayerIndex>& range) {
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 = range.begin(); layer_idx != range.end(); ++ layer_idx) {
LayerIndex layer_idx_below = layer_idx - (z_distance_bottom_layers + 1) - min_layer_bottom;
assert(layer_idx_below >= 0);
auto &current = collision_areas_offsetted[layer_idx - min_layer_bottom];
auto &below = collision_areas_offsetted[layer_idx_below];
auto placable = diff(below, layer_idx < anti_overhang.size() ? union_(current, anti_overhang[layer_idx - (z_distance_bottom_layers + 1)]) : current);
auto &dst = data_placeable[layer_idx - (min_layer_last + 1)];
if (last) {
if (! dst.empty())
placable = union_(placable, dst);
dst = polygons_simplify(placable, min_resolution);
} else
append(dst, placable);
} }
});
for (int layer_idx = int(max_required_layer); layer_idx > keys[i].second; -- layer_idx) { } else {
// all these dont have the correct z_distance_top_layers as they can still have areas above them // Calculating just the collision areas.
auto it = data.find(RadiusLayerPair(radius, layer_idx));
if (it != data.end())
data.erase(it);
}
for (auto pair : data)
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], polygons_simplify(pair.second, m_min_resolution));
} }
} }
#ifdef SLIC3R_TREESUPPORTS_PROGRESS #ifdef SLIC3R_TREESUPPORTS_PROGRESS
{ {
std::lock_guard<std::mutex> critical_section(*m_critical_progress); std::lock_guard<std::mutex> critical_section(*m_critical_progress);
@ -509,127 +511,145 @@ void TreeModelVolumes::calculateCollision(std::deque<RadiusLayerPair> keys)
} }
} }
#endif #endif
m_collision_cache.insert(std::move(data), min_layer_last + 1, radius);
m_collision_cache.insert(std::move(data_outer)); if (calculate_placable)
if (radius == 0) m_placeable_areas_cache.insert(std::move(data_placeable), min_layer_last + 1, radius);
m_placeable_areas_cache.insert(std::move(data_placeable_outer));
}
});
} }
void TreeModelVolumes::calculateCollisionHolefree(std::deque<RadiusLayerPair> keys) void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerPair> &keys)
{ {
LayerIndex max_layer = 0; LayerIndex max_layer = 0;
for (long long unsigned int i = 0; i < keys.size(); i++) for (long long unsigned int i = 0; i < keys.size(); i++)
max_layer = std::max(max_layer, keys[i].second); max_layer = std::max(max_layer, keys[i].second);
tbb::parallel_for(tbb::blocked_range<size_t>(0, max_layer + 1), tbb::parallel_for(tbb::blocked_range<size_t>(0, max_layer + 1, keys.size()),
[&](const tbb::blocked_range<size_t> &range) { [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
RadiusLayerPolygonCacheData data; RadiusLayerPolygonCacheData data;
for (RadiusLayerPair key : keys) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (RadiusLayerPair key : keys)
if (layer_idx <= key.second) {
// Logically increase the collision by m_increase_until_radius // Logically increase the collision by m_increase_until_radius
coord_t radius = key.first; coord_t radius = key.first;
coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - ceilRadius(radius, true); assert(radius == this->ceilRadius(radius));
assert(radius < m_increase_until_radius + m_current_min_xy_dist_delta);
coord_t increase_radius_ceil = ceilRadius(m_increase_until_radius, false) - radius;
assert(increase_radius_ceil > 0);
// 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. // 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 = 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)] = polygons_simplify(
data[RadiusLayerPair(radius, layer_idx)] = col; offset(union_ex(getCollision(m_increase_until_radius, layer_idx, false)),
5 - increase_radius_ceil, ClipperLib::jtRound, m_min_resolution),
m_min_resolution);
}
} }
m_collision_cache_holefree.insert(std::move(data)); m_collision_cache_holefree.insert(std::move(data));
}
}); });
} }
void TreeModelVolumes::calculateAvoidance(std::deque<RadiusLayerPair> keys) void TreeModelVolumes::calculateAvoidance(const std::vector<RadiusLayerPair> &keys, bool to_build_plate, bool to_model)
{ {
// For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better paralellisation. // For every RadiusLayer pair there are 3 avoidances that have to be calculated.
const std::vector<AvoidanceType> all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast }; // Prepare tasks for parallelization.
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size() * 3), struct AvoidanceTask {
[&, keys, all_types](const tbb::blocked_range<size_t> &range) { AvoidanceType type;
for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) { coord_t radius;
size_t key_idx = iter_idx / 3; LayerIndex max_required_layer;
{ bool to_model;
size_t type_idx = iter_idx % all_types.size(); LayerIndex start_layer;
AvoidanceType type = all_types[type_idx];
const bool slow = type == AvoidanceType::Slow;
const bool holefree = type == AvoidanceType::FastSafe;
coord_t radius = keys[key_idx].first; bool slow() const { return this->type == AvoidanceType::Slow; }
LayerIndex max_required_layer = keys[key_idx].second; bool holefree() const { return this->type == AvoidanceType::FastSafe; }
};
// do not calculate not needed safe avoidances std::vector<AvoidanceTask> avoidance_tasks;
if (holefree && radius >= m_increase_until_radius + m_current_min_xy_dist_delta) avoidance_tasks.reserve((int(to_build_plate) + int(to_model)) * keys.size() * size_t(AvoidanceType::Count));
continue;
const coord_t offset_speed = slow ? m_max_move_slow : m_max_move; for (int iter_idx = 0; iter_idx < 2 * keys.size() * size_t(AvoidanceType::Count); ++ iter_idx) {
RadiusLayerPair key(radius, 0); AvoidanceTask task{
LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).getMaxCalculatedLayer(radius); AvoidanceType(iter_idx % int(AvoidanceType::Count)),
if (start_layer > max_required_layer) { keys[iter_idx / 6].first, // radius
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; keys[iter_idx / 6].second, // max_layer
((iter_idx / 3) & 1) != 0 // to_model
};
// Ensure start_layer is at least 1 as if no avoidance was calculated yet getMaxCalculatedLayer() returns -1.
task.start_layer = std::max<LayerIndex>(1, 1 + avoidance_cache(task.type, task.to_model).getMaxCalculatedLayer(task.radius));
if (task.start_layer > task.max_required_layer) {
BOOST_LOG_TRIVIAL(debug) << "Calculation requested for value already calculated?";
continue; continue;
} }
std::vector<std::pair<RadiusLayerPair, Polygons>> data(max_required_layer + 1, std::pair<RadiusLayerPair, Polygons>(RadiusLayerPair(radius, -1), Polygons())); if (! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta)
avoidance_tasks.emplace_back(task);
}
// Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 tbb::parallel_for(tbb::blocked_range<size_t>(0, avoidance_tasks.size(), 1),
start_layer = std::max(start_layer, LayerIndex(1)); [this, &avoidance_tasks](const tbb::blocked_range<size_t> &range) {
for (size_t task_idx = range.begin(); task_idx < range.end(); ++ task_idx) {
const AvoidanceTask &task = avoidance_tasks[task_idx];
assert(! task.holefree() || task.radius < m_increase_until_radius + m_current_min_xy_dist_delta);
if (task.to_model)
// ensuring Placeableareas are calculated
getPlaceableAreas(task.radius, task.max_required_layer);
// The following loop propagating avoidance regions bottom up is inherently serial.
const bool collision_holefree = (task.slow() || task.holefree()) && task.radius < m_increase_until_radius + m_current_min_xy_dist_delta;
const float max_move = task.slow() ? m_max_move_slow : m_max_move;
// 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, false, true); Polygons latest_avoidance = getAvoidance(task.radius, task.start_layer - 1, task.type, task.to_model, true);
// ### main loop doing the calculation std::vector<std::pair<RadiusLayerPair, Polygons>> data;
for (LayerIndex layer = start_layer; layer <= max_required_layer; ++ layer) { data.reserve(task.max_required_layer + 1 - task.start_layer);
key.second = layer; for (LayerIndex layer_idx = task.start_layer; layer_idx <= task.max_required_layer; ++ layer_idx) {
const Polygons &col = (slow && radius < m_increase_until_radius + m_current_min_xy_dist_delta) || holefree ? latest_avoidance = union_(
getCollisionHolefree(radius, layer, true) : // Propagate avoidance region from the layers below, adjust for allowed tilt of the tree branch.
getCollision(radius, layer, true); offset(union_ex(latest_avoidance), -max_move, ClipperLib::jtRound, m_min_resolution),
latest_avoidance = polygons_simplify(union_(offset(union_ex(latest_avoidance), -offset_speed, ClipperLib::jtRound, m_min_resolution), col), m_min_resolution); // Add current layer collisions.
data[layer] = std::pair<RadiusLayerPair, Polygons>(key, latest_avoidance); collision_holefree ? getCollisionHolefree(task.radius, layer_idx) : getCollision(task.radius, layer_idx, true));
if (task.to_model)
latest_avoidance = diff(latest_avoidance, getPlaceableAreas(task.radius, layer_idx));
latest_avoidance = polygons_simplify(latest_avoidance, m_min_resolution);
data.emplace_back(RadiusLayerPair{task.radius, layer_idx}, latest_avoidance);
} }
#ifdef SLIC3R_TREESUPPORTS_PROGRESS #ifdef SLIC3R_TREESUPPORTS_PROGRESS
{ {
std::lock_guard<std::mutex> critical_section(*m_critical_progress); std::lock_guard<std::mutex> critical_section(*m_critical_progress);
if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) { if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) {
m_precalculation_progress += m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3); m_precalculation_progress += to_model ?
0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3) :
m_support_rests_on_model ? 0.4 : 1 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3);
Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL); Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL);
} }
} }
#endif #endif
avoidance_cache(task.type, task.to_model).insert(std::move(data));
(slow ? m_avoidance_cache_slow : holefree ? m_avoidance_cache_holefree : m_avoidance_cache).insert(std::move(data));
}
} }
}); });
} }
void TreeModelVolumes::calculatePlaceables(std::deque<RadiusLayerPair> keys)
void TreeModelVolumes::calculatePlaceables(const std::vector<RadiusLayerPair> &keys)
{ {
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&, keys](const tbb::blocked_range<size_t>& range) { [&, keys](const tbb::blocked_range<size_t>& range) {
for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx)
const coord_t radius = keys[key_idx].first; this->calculatePlaceables(keys[key_idx].first, keys[key_idx].second);
const LayerIndex max_required_layer = keys[key_idx].second; });
std::vector<std::pair<RadiusLayerPair, Polygons>> data(max_required_layer + 1, std::pair<RadiusLayerPair, Polygons>(RadiusLayerPair(radius, -1), Polygons())); }
RadiusLayerPair key(radius, 0);
void TreeModelVolumes::calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer)
{
LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius); LayerIndex start_layer = 1 + m_placeable_areas_cache.getMaxCalculatedLayer(radius);
if (start_layer > max_required_layer) { if (start_layer > max_required_layer) {
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?"; BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
continue; return;
} }
if (start_layer == 0) { std::vector<Polygons> data(max_required_layer + 1 - start_layer, Polygons{});
data[0] = std::pair<RadiusLayerPair, Polygons>(key, diff(m_machine_border, getCollision(radius, 0, true)));
start_layer = 1;
}
for (LayerIndex layer = start_layer; layer <= max_required_layer; layer++) { if (start_layer == 0)
key.second = layer; data[0] = diff(m_machine_border, getCollision(radius, 0, true));
Polygons placeable = getPlaceableAreas(0, layer);
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);
}
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(1, start_layer), max_required_layer + 1),
[this, &data, radius, start_layer](const tbb::blocked_range<LayerIndex>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
data[layer_idx - start_layer] = offset(union_ex(getPlaceableAreas(0, layer_idx)), - radius, jtMiter, 1.2);
});
#ifdef SLIC3R_TREESUPPORTS_PROGRESS #ifdef SLIC3R_TREESUPPORTS_PROGRESS
{ {
std::lock_guard<std::mutex> critical_section(*m_critical_progress); std::lock_guard<std::mutex> critical_section(*m_critical_progress);
@ -639,71 +659,10 @@ void TreeModelVolumes::calculatePlaceables(std::deque<RadiusLayerPair> keys)
} }
} }
#endif #endif
m_placeable_areas_cache.insert(std::move(data), start_layer, radius);
m_placeable_areas_cache.insert(std::move(data));
}
});
} }
void TreeModelVolumes::calculateAvoidanceToModel(std::deque<RadiusLayerPair> keys) void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPair> &keys)
{
// For every RadiusLayer pair there are 3 avoidances that have to be calculated, calculated in the same parallel_for loop for better parallelization.
const std::vector<AvoidanceType> all_types = { AvoidanceType::Slow, AvoidanceType::FastSafe, AvoidanceType::Fast };
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size() * 3),
[&, keys, all_types](const tbb::blocked_range<size_t> &range) {
for (size_t iter_idx = range.begin(); iter_idx < range.end(); ++ iter_idx) {
size_t key_idx = iter_idx / 3;
size_t type_idx = iter_idx % all_types.size();
AvoidanceType type = all_types[type_idx];
bool slow = type == AvoidanceType::Slow;
bool holefree = type == AvoidanceType::FastSafe;
coord_t radius = keys[key_idx].first;
LayerIndex max_required_layer = keys[key_idx].second;
// do not calculate not needed safe avoidances
if (holefree && radius >= m_increase_until_radius + m_current_min_xy_dist_delta)
continue;
getPlaceableAreas(radius, max_required_layer); // ensuring Placeableareas are calculated
const coord_t offset_speed = slow ? m_max_move_slow : m_max_move;
std::vector<std::pair<RadiusLayerPair, Polygons>> data(max_required_layer + 1, std::pair<RadiusLayerPair, Polygons>(RadiusLayerPair(radius, -1), Polygons()));
RadiusLayerPair key(radius, 0);
LayerIndex start_layer = 1 + (slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).getMaxCalculatedLayer(radius);
if (start_layer > max_required_layer) {
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));
// 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) {
key.second = layer;
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);
}
#ifdef SLIC3R_TREESUPPORTS_PROGRESS
{
std::lock_guard<std::mutex> critical_section(*m_critical_progress);
if (m_precalculated && m_precalculation_progress < TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_PRECALC_AVO) {
m_precalculation_progress += 0.4 * TREE_PROGRESS_PRECALC_AVO / (keys.size() * 3);
Progress::messageProgress(Progress::Stage::SUPPORT, m_precalculation_progress * m_progress_multiplier + m_progress_offset, TREE_PROGRESS_TOTAL);
}
}
#endif
(slow ? m_avoidance_cache_to_model_slow : holefree ? m_avoidance_cache_holefree_to_model : m_avoidance_cache_to_model).insert(std::move(data));
}
});
}
void TreeModelVolumes::calculateWallRestrictions(std::deque<RadiusLayerPair> keys)
{ {
// Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall. // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall.
// As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty space,d = blocked area because of z distance) Assume maximum movement distance is two characters and maximum safe movement distance of one character // As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty space,d = blocked area because of z distance) Assume maximum movement distance is two characters and maximum safe movement distance of one character
@ -742,65 +701,62 @@ void TreeModelVolumes::calculateWallRestrictions(std::deque<RadiusLayerPair> key
tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()), tbb::parallel_for(tbb::blocked_range<size_t>(0, keys.size()),
[&, keys](const tbb::blocked_range<size_t> &range) { [&, keys](const tbb::blocked_range<size_t> &range) {
for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) { for (size_t key_idx = range.begin(); key_idx < range.end(); ++ key_idx) {
coord_t radius = keys[key_idx].first; const coord_t radius = keys[key_idx].first;
RadiusLayerPair key(radius, 0); const LayerIndex max_required_layer = keys[key_idx].second;
RadiusLayerPolygonCacheData data; const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius));
RadiusLayerPolygonCacheData data_min; const size_t buffer_size = max_required_layer + 1 - min_layer_bottom;
coord_t min_layer_bottom = m_wall_restrictions_cache.getMaxCalculatedLayer(radius); std::vector<Polygons> data(buffer_size, Polygons{});
if (min_layer_bottom < 1) std::vector<Polygons> data_min;
min_layer_bottom = 1; if (m_current_min_xy_dist_delta > 0)
data_min.assign(buffer_size, Polygons{});
for (LayerIndex layer_idx = min_layer_bottom; layer_idx <= keys[key_idx].second; layer_idx++) { tbb::parallel_for(tbb::blocked_range<LayerIndex>(min_layer_bottom, max_required_layer + 1),
key.second = layer_idx; [this, &data, &data_min, radius, min_layer_bottom](const tbb::blocked_range<LayerIndex> &range) {
LayerIndex layer_idx_below = layer_idx - 1; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
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 data[layer_idx - min_layer_bottom] = polygons_simplify(
wall_restriction = polygons_simplify(wall_restriction, m_min_resolution); // radius contains m_current_min_xy_dist_delta already if required
data.emplace(key, wall_restriction); intersection(getCollision(0, layer_idx, false), getCollision(radius, layer_idx - 1, true)),
if (m_current_min_xy_dist_delta > 0) { m_min_resolution);
Polygons wall_restriction_min = intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx_below, true)); if (! data_min.empty())
wall_restriction = polygons_simplify(wall_restriction_min, m_min_resolution); data_min[layer_idx - min_layer_bottom] =
data_min.emplace(key, wall_restriction_min); polygons_simplify(
intersection(getCollision(0, layer_idx, true), getCollision(radius, layer_idx - 1, true)),
m_min_resolution);
} }
} });
m_wall_restrictions_cache.insert(std::move(data)); m_wall_restrictions_cache.insert(std::move(data), min_layer_bottom, radius);
m_wall_restrictions_cache_min.insert(std::move(data_min)); if (! data_min.empty())
m_wall_restrictions_cache_min.insert(std::move(data_min), min_layer_bottom, radius);
} }
}); });
} }
coord_t TreeModelVolumes::ceilRadius(coord_t radius) const coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const
{ {
if (radius == 0) if (radius == 0)
return 0; return 0;
if (radius <= m_radius_0) coord_t out = m_radius_0;
return m_radius_0; if (radius > m_radius_0) {
if (SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION)
{
// generate SUPPORT_TREE_PRE_EXPONENTIAL_STEPS of radiis before starting to exponentially increase them. // generate SUPPORT_TREE_PRE_EXPONENTIAL_STEPS of radiis before starting to exponentially increase them.
coord_t initial_radius_delta = SUPPORT_TREE_EXPONENTIAL_THRESHOLD - m_radius_0;
coord_t exponential_result = SUPPORT_TREE_EXPONENTIAL_THRESHOLD * SUPPORT_TREE_EXPONENTIAL_FACTOR; auto ignore = [this](coord_t r) { return std::binary_search(m_ignorable_radii.begin(), m_ignorable_radii.end(), r); };
const coord_t stepsize = (exponential_result - m_radius_0) / (SUPPORT_TREE_PRE_EXPONENTIAL_STEPS + 1); if (initial_radius_delta > SUPPORT_TREE_COLLISION_RESOLUTION) {
coord_t result = m_radius_0; const int num_steps = round_up_divide(initial_radius_delta, SUPPORT_TREE_EXPONENTIAL_THRESHOLD);
for (size_t step = 0; step < SUPPORT_TREE_PRE_EXPONENTIAL_STEPS; step++) { const int stepsize = initial_radius_delta / num_steps;
result += stepsize; out += stepsize;
if (result >= radius && !m_ignorable_radii.count(result)) for (auto step = 0; step < num_steps; ++ step) {
return result; if (out >= radius && ! ignore(out))
return out;
out += stepsize;
} }
} else
while (exponential_result < radius || m_ignorable_radii.count(exponential_result)) out += SUPPORT_TREE_COLLISION_RESOLUTION;
exponential_result = std::max(coord_t(exponential_result * SUPPORT_TREE_EXPONENTIAL_FACTOR), exponential_result + SUPPORT_TREE_COLLISION_RESOLUTION); while (out < radius || ignore(out)) {
return exponential_result; assert(out * SUPPORT_TREE_EXPONENTIAL_FACTOR > out + SUPPORT_TREE_COLLISION_RESOLUTION);
out = out * SUPPORT_TREE_EXPONENTIAL_FACTOR;
} }
else
{ // generates equidistant steps of size SUPPORT_TREE_COLLISION_RESOLUTION starting from m_radius_0. If SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION then this code is dead, and can safely be removed.
coord_t ceil_step_n = (radius - m_radius_0) / SUPPORT_TREE_COLLISION_RESOLUTION;
coord_t resulting_ceil = m_radius_0 + (ceil_step_n + ((radius - m_radius_0) % SUPPORT_TREE_COLLISION_RESOLUTION != 0)) * SUPPORT_TREE_COLLISION_RESOLUTION;
return
radius <= m_radius_0 && radius != 0 ? m_radius_0 :
m_ignorable_radii.count(resulting_ceil) ? ceilRadius(resulting_ceil + 1) : resulting_ceil;
} }
return out;
} }
} }

View File

@ -211,7 +211,8 @@ public:
{ {
Slow, Slow,
FastSafe, FastSafe,
Fast Fast,
Count
}; };
/*! /*!
@ -220,7 +221,7 @@ public:
* Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed. * Knowledge about branch angle is used to only calculate avoidances and collisions that may actually be needed.
* Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores! * Not calling precalculate() will cause the class to lazily calculate avoidances and collisions as needed, which will be a lot slower on systems with more then one or two cores!
*/ */
void precalculate(coord_t max_layer); void precalculate(const coord_t max_layer);
/*! /*!
* \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. * \brief Provides the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer.
@ -233,7 +234,7 @@ public:
* \param min_xy_dist Is the minimum xy distance used. * \param min_xy_dist Is the minimum xy distance used.
* \return Polygons object * \return Polygons object
*/ */
const Polygons& getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; const Polygons& getCollision(const coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const;
/*! /*!
* \brief Provides the areas that have to be avoided by the tree's branches * \brief Provides the areas that have to be avoided by the tree's branches
@ -277,8 +278,12 @@ public:
* \param min_xy_dist is the minimum xy distance used. * \param min_xy_dist is the minimum xy distance used.
* \return The rounded radius * \return The rounded radius
*/ */
coord_t ceilRadius(coord_t radius, bool min_xy_dist) const { coord_t ceilRadius(const coord_t radius, const bool min_xy_dist) const {
return this->ceilRadius(min_xy_dist ? radius : radius + m_current_min_xy_dist_delta); assert(radius >= 0);
return min_xy_dist ?
this->ceilRadius(radius) :
// special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible.
radius > 0 ? this->ceilRadius(radius + m_current_min_xy_dist_delta) : m_current_min_xy_dist_delta;
} }
/*! /*!
* \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one. * \brief Round \p radius upwards to the maximum that would still round up to the same value as the provided one.
@ -288,6 +293,7 @@ public:
* \return The maximum radius, resulting in the same rounding. * \return The maximum radius, resulting in the same rounding.
*/ */
coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const { coord_t getRadiusNextCeil(coord_t radius, bool min_xy_dist) const {
assert(radius > 0);
return min_xy_dist ? return min_xy_dist ?
this->ceilRadius(radius) : this->ceilRadius(radius) :
this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta; this->ceilRadius(radius + m_current_min_xy_dist_delta) - m_current_min_xy_dist_delta;
@ -318,6 +324,17 @@ private:
for (auto& d : in) for (auto& d : in)
this->data.emplace(d.first, std::move(d.second)); this->data.emplace(d.first, std::move(d.second));
} }
// by layer
void insert(std::vector<std::pair<coord_t, Polygons>> &&in, coord_t radius) {
std::lock_guard<std::mutex> guard(this->mutex);
for (auto &d : in)
this->data.emplace(RadiusLayerPair{ radius, d.first }, std::move(d.second));
}
void insert(std::vector<Polygons> &&in, coord_t first_layer_idx, coord_t radius) {
std::lock_guard<std::mutex> guard(this->mutex);
for (auto &d : in)
this->data.emplace(RadiusLayerPair{ radius, first_layer_idx ++ }, std::move(d));
}
/*! /*!
* \brief Checks a cache for a given RadiusLayerPair and returns it if it is found * \brief Checks a cache for a given RadiusLayerPair and returns it if it is found
* \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. * \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer.
@ -359,19 +376,20 @@ private:
* The result is a 2D area that would cause nodes of given radius to * The result is a 2D area that would cause nodes of given radius to
* collide with the model or be inside a hole. * collide with the model or be inside a hole.
* A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall.
* minimum xy distance is always used.
* \param radius The radius of the node of interest * \param radius The radius of the node of interest
* \param layer_idx The layer of interest * \param layer_idx The layer of interest
* \param min_xy_dist Is the minimum xy distance used. * \param min_xy_dist Is the minimum xy distance used.
* \return Polygons object * \return Polygons object
*/ */
const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) const; const Polygons& getCollisionHolefree(coord_t radius, LayerIndex layer_idx) const;
/*! /*!
* \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value * \brief Round \p radius upwards to either a multiple of m_radius_sample_resolution or a exponentially increasing value
* *
* \param radius The radius of the node of interest * \param radius The radius of the node of interest
*/ */
coord_t ceilRadius(coord_t radius) const; coord_t ceilRadius(const coord_t radius) const;
/*! /*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer.
@ -380,18 +398,8 @@ private:
* collide with the model. Result is saved in the cache. * collide with the model. Result is saved in the cache.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/ */
void calculateCollision(std::deque<RadiusLayerPair> keys); void calculateCollision(const std::vector<RadiusLayerPair> &keys);
/*! void calculateCollision(const coord_t radius, const LayerIndex max_layer_idx);
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer.
*
* The result is a 2D area that would cause nodes of given radius to
* collide with the model. Result is saved in the cache.
* \param key RadiusLayerPairs the requested areas. The radius will be calculated up to the provided layer.
*/
void calculateCollision(RadiusLayerPair key)
{
calculateCollision(std::deque<RadiusLayerPair>{ RadiusLayerPair(key) });
}
/*! /*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed.
* *
@ -400,7 +408,7 @@ private:
* A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall. * A Hole is defined as an area, in which a branch with m_increase_until_radius radius would collide with the wall.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/ */
void calculateCollisionHolefree(std::deque<RadiusLayerPair> keys); void calculateCollisionHolefree(const std::vector<RadiusLayerPair> &keys);
/*! /*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed. * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model on this layer. Holes are removed.
@ -412,7 +420,7 @@ private:
*/ */
void calculateCollisionHolefree(RadiusLayerPair key) void calculateCollisionHolefree(RadiusLayerPair key)
{ {
calculateCollisionHolefree(std::deque<RadiusLayerPair>{ RadiusLayerPair(key) }); calculateCollisionHolefree(std::vector<RadiusLayerPair>{ RadiusLayerPair(key) });
} }
/*! /*!
@ -422,7 +430,7 @@ private:
* collide with the model. Result is saved in the cache. * collide with the model. Result is saved in the cache.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/ */
void calculateAvoidance(std::deque<RadiusLayerPair> keys); void calculateAvoidance(const std::vector<RadiusLayerPair> &keys, bool to_build_plate, bool to_model);
/*! /*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model. * \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model.
@ -431,9 +439,9 @@ private:
* collide with the model. Result is saved in the cache. * collide with the model. Result is saved in the cache.
* \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer.
*/ */
void calculateAvoidance(RadiusLayerPair key) void calculateAvoidance(RadiusLayerPair key, bool to_build_plate, bool to_model)
{ {
calculateAvoidance(std::deque<RadiusLayerPair>{ RadiusLayerPair(key) }); calculateAvoidance(std::vector<RadiusLayerPair>{ RadiusLayerPair(key) }, to_build_plate, to_model);
} }
/*! /*!
@ -441,38 +449,16 @@ private:
* Result is saved in the cache. * Result is saved in the cache.
* \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer. * \param key RadiusLayerPair of the requested areas. It will be calculated up to the provided layer.
*/ */
void calculatePlaceables(RadiusLayerPair key) void calculatePlaceables(const coord_t radius, const LayerIndex max_required_layer);
{
calculatePlaceables(std::deque<RadiusLayerPair>{ key });
}
/*! /*!
* \brief Creates the areas where a branch of a given radius can be placed on the model. * \brief Creates the areas where a branch of a given radius can be placed on the model.
* Result is saved in the cache. * Result is saved in the cache.
* \param keys RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer. * \param keys RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer.
*/ */
void calculatePlaceables(std::deque<RadiusLayerPair> keys); void calculatePlaceables(const std::vector<RadiusLayerPair> &keys);
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer.
*
* The result is a 2D area that would cause nodes of radius \p radius to
* collide with the model in a not wanted way. Result is saved in the cache.
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/
void calculateAvoidanceToModel(std::deque<RadiusLayerPair> keys);
/*!
* \brief Creates the areas that have to be avoided by the tree's branches to prevent collision with the model without being able to place a branch with given radius on a single layer.
*
* The result is a 2D area that would cause nodes of radius \p radius to
* collide with the model in a not wanted way. Result is saved in the cache.
* \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer.
*/
void calculateAvoidanceToModel(RadiusLayerPair key)
{
calculateAvoidanceToModel(std::deque<RadiusLayerPair>{ RadiusLayerPair(key) });
}
/*! /*!
* \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object). * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object).
* *
@ -480,7 +466,7 @@ private:
* *
* \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer. * \param keys RadiusLayerPairs of all requested areas. Every radius will be calculated up to the provided layer.
*/ */
void calculateWallRestrictions(std::deque<RadiusLayerPair> keys); void calculateWallRestrictions(const std::vector<RadiusLayerPair> &keys);
/*! /*!
* \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object). * \brief Creates the areas that can not be passed when expanding an area downwards. As such these areas are an somewhat abstract representation of a wall (as in a printed object).
@ -489,7 +475,7 @@ private:
*/ */
void calculateWallRestrictions(RadiusLayerPair key) void calculateWallRestrictions(RadiusLayerPair key)
{ {
calculateWallRestrictions(std::deque<RadiusLayerPair>{ RadiusLayerPair(key) }); calculateWallRestrictions(std::vector<RadiusLayerPair>{ RadiusLayerPair(key) });
} }
/*! /*!
@ -556,9 +542,9 @@ private:
*/ */
std::vector<Polygons> m_anti_overhang; std::vector<Polygons> m_anti_overhang;
/*! /*!
* \brief Radii that can be ignored by ceilRadius as they will never be requested. * \brief Radii that can be ignored by ceilRadius as they will never be requested, sorted.
*/ */
std::unordered_set<coord_t> m_ignorable_radii; std::vector<coord_t> m_ignorable_radii;
/*! /*!
* \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0. * \brief Smallest radius a branch can have. This is the radius of a SupportElement with DTT=0.
@ -584,6 +570,27 @@ private:
RadiusLayerPolygonCache m_avoidance_cache_holefree; RadiusLayerPolygonCache m_avoidance_cache_holefree;
RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model; RadiusLayerPolygonCache m_avoidance_cache_holefree_to_model;
RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) {
if (to_model) {
switch (type) {
case AvoidanceType::Fast: return m_avoidance_cache_to_model;
case AvoidanceType::Slow: return m_avoidance_cache_to_model_slow;
case AvoidanceType::FastSafe: return m_avoidance_cache_holefree_to_model;
}
} else {
switch (type) {
case AvoidanceType::Fast: return m_avoidance_cache;
case AvoidanceType::Slow: return m_avoidance_cache_slow;
case AvoidanceType::FastSafe: return m_avoidance_cache_holefree;
}
}
assert(false);
return m_avoidance_cache;
}
const RadiusLayerPolygonCache& avoidance_cache(const AvoidanceType type, const bool to_model) const {
return const_cast<TreeModelVolumes*>(this)->avoidance_cache(type, to_model);
}
/*! /*!
* \brief Caches to represent walls not allowed to be passed over. * \brief Caches to represent walls not allowed to be passed over.
*/ */

View File

@ -874,9 +874,9 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
if (area(expoly) <= 0.) if (area(expoly) <= 0.)
::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); ::MessageBoxA(nullptr, "TreeSupport infill negative area", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // _WIN32 #endif // _WIN32
assert(intersecting_edges(expoly).empty()); assert(intersecting_edges(to_polygons(expoly)).empty());
#ifdef _WIN32 #ifdef _WIN32
if (! intersecting_edges(expoly).empty()) if (! intersecting_edges(to_polygons(expoly)).empty())
::MessageBoxA(nullptr, "TreeSupport infill self intersections", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING); ::MessageBoxA(nullptr, "TreeSupport infill self intersections", "Bug detected!", MB_OK | MB_SYSTEMMODAL | MB_SETFOREGROUND | MB_ICONWARNING);
#endif // _WIN32 #endif // _WIN32
Surface surface(stInternal, std::move(expoly)); Surface surface(stInternal, std::move(expoly));
@ -1926,13 +1926,13 @@ void TreeSupport::increaseAreas(std::unordered_map<SupportElement, Polygons>& to
constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability constexpr bool increase_radius = true, no_error = true, use_min_radius = true, move = true; // aliases for better readability
// Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found
std::deque<AreaIncreaseSettings> order; std::vector<AreaIncreaseSettings> order;
auto insertSetting = [&](AreaIncreaseSettings settings, bool back) { auto insertSetting = [&](AreaIncreaseSettings settings, bool back) {
if (std::find(order.begin(), order.end(), settings) == order.end()) { if (std::find(order.begin(), order.end(), settings) == order.end()) {
if (back) if (back)
order.emplace_back(settings); order.emplace_back(settings);
else else
order.emplace_front(settings); order.insert(order.begin(), settings);
} }
}; };
@ -1973,7 +1973,7 @@ void TreeSupport::increaseAreas(std::unordered_map<SupportElement, Polygons>& to
if (elem.use_min_xy_dist) if (elem.use_min_xy_dist)
{ {
std::deque<AreaIncreaseSettings> new_order; std::vector<AreaIncreaseSettings> new_order;
// if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy ) // if the branch currently has to use min_xy_dist check if the configuration would also be valid with the regular xy_distance before checking with use_min_radius (Only happens when Support Distance priority is z overrides xy )
for (AreaIncreaseSettings settings : order) for (AreaIncreaseSettings settings : order)
{ {
@ -2444,15 +2444,16 @@ void TreeSupport::generateBranchAreas(
// if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best // if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best
if (nozzle_path.size() > 1) { if (nozzle_path.size() > 1) {
Polygons polygons_with_correct_center; Polygons polygons_with_correct_center;
for (const ExPolygon &part : nozzle_path) { for (ExPolygon &part : nozzle_path) {
if (part.contains(elem->result_on_layer)) if (part.contains(elem->result_on_layer))
polygons_with_correct_center = union_(polygons_with_correct_center, part); polygons_with_correct_center = union_(polygons_with_correct_center, part);
else { else {
// try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... // try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors...
Point from = elem->result_on_layer; Point from = elem->result_on_layer;
moveInside(part, from, 0); Polygons &to = to_polygons(std::move(part));
moveInside(to, from, 0);
if ((elem->result_on_layer - from).cast<double>().norm() < scaled<double>(0.025)) if ((elem->result_on_layer - from).cast<double>().norm() < scaled<double>(0.025))
polygons_with_correct_center = union_(polygons_with_correct_center, part); polygons_with_correct_center = union_(polygons_with_correct_center, to);
} }
} }
// Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above.

View File

@ -37,17 +37,16 @@
#define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false #define SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL false
#define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true #define SUPPORT_TREE_AVOID_SUPPORT_BLOCKER true
#define SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION true
#define SUPPORT_TREE_EXPONENTIAL_THRESHOLD 1000
#define SUPPORT_TREE_EXPONENTIAL_FACTOR 1.5
#define SUPPORT_TREE_PRE_EXPONENTIAL_STEPS 1
#define SUPPORT_TREE_COLLISION_RESOLUTION 500 // Only has an effect if SUPPORT_TREE_USE_EXPONENTIAL_COLLISION_RESOLUTION is false
namespace Slic3r namespace Slic3r
{ {
using LayerIndex = int; using LayerIndex = int;
static constexpr const double SUPPORT_TREE_EXPONENTIAL_FACTOR = 1.5;
static constexpr const coord_t SUPPORT_TREE_EXPONENTIAL_THRESHOLD = scaled<coord_t>(1. * SUPPORT_TREE_EXPONENTIAL_FACTOR);
static constexpr const coord_t SUPPORT_TREE_COLLISION_RESOLUTION = scaled<coord_t>(0.5);
//FIXME //FIXME
class Print; class Print;
class PrintObject; class PrintObject;