diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index a342da831..0a0062a75 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -275,6 +275,8 @@ set(SLIC3R_SOURCES TriangleSelector.hpp TriangleSetSampling.cpp TriangleSetSampling.hpp + TriangleSelectorWrapper.cpp + TriangleSelectorWrapper.hpp MTUtils.hpp Zipper.hpp Zipper.cpp diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4bcf74fe5..cf6f168df 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -402,12 +402,38 @@ void PrintObject::find_supportable_issues() BOOST_LOG_TRIVIAL(debug) << "Searching supportable issues - start"; //TODO status number? - m_print->set_status(70, L("Searching supportable issues")); - - if (this->has_support()) { + m_print->set_status(75, L("Searching supportable issues")); + if (!this->m_config.support_material) { + std::vector problematic_layers = SupportableIssues::quick_search(this); + if (!problematic_layers.empty()){ + //TODO report problems + } } else { - SupportableIssues::quick_search(this); + SupportableIssues::Issues issues = SupportableIssues::full_search(this); + if (!issues.supports_nedded.empty()) { + auto obj_transform = this->trafo_centered(); + for (ModelVolume *model_volume : this->model_object()->volumes) { + if (model_volume->type() == ModelVolumeType::MODEL_PART) { + Transform3d model_transformation = model_volume->get_matrix(); + Transform3d inv_transform = (obj_transform * model_transformation).inverse(); + TriangleSelectorWrapper selector { model_volume->mesh() }; + + for (const Vec3f &support_point : issues.supports_nedded) { + selector.enforce_spot(Vec3f(inv_transform.cast() * support_point), 1.0f); + } + + model_volume->supported_facets.set(selector.selector); + +#if 1 + indexed_triangle_set copy = model_volume->mesh().its; + its_transform(copy, obj_transform * model_transformation); + its_write_obj(copy, + debug_out_path(("model" + std::to_string(model_volume->id().id) + ".obj").c_str()).c_str()); +#endif + } + } + } } m_print->throw_if_canceled(); @@ -417,6 +443,7 @@ void PrintObject::find_supportable_issues() } } + void PrintObject::generate_support_material() { if (this->set_started(posSupportMaterial)) { diff --git a/src/libslic3r/SupportableIssuesSearch.cpp b/src/libslic3r/SupportableIssuesSearch.cpp index bace1aea0..cdd3130a0 100644 --- a/src/libslic3r/SupportableIssuesSearch.cpp +++ b/src/libslic3r/SupportableIssuesSearch.cpp @@ -4,18 +4,31 @@ #include "tbb/blocked_range.h" #include "tbb/parallel_reduce.h" #include +#include #include "libslic3r/Layer.hpp" #include "libslic3r/EdgeGrid.hpp" #include "libslic3r/ClipperUtils.hpp" +#define DEBUG_FILES + +#ifdef DEBUG_FILES +#include +#endif + namespace Slic3r { namespace SupportableIssues { -struct Params { - float bridge_distance = 5.0f; - float printable_protrusion_distance = 1.0f; -}; +void Issues::add(const Issues &layer_issues) { + supports_nedded.insert(supports_nedded.end(), + layer_issues.supports_nedded.begin(), layer_issues.supports_nedded.end()); + curling_up.insert(curling_up.end(), layer_issues.curling_up.begin(), + layer_issues.curling_up.end()); +} + +bool Issues::empty() const { + return supports_nedded.empty() && curling_up.empty(); +} namespace Impl { @@ -34,62 +47,218 @@ struct EdgeGridWrapper { } EdgeGrid::Grid grid; ExPolygons ex_polys; -} -; +}; -EdgeGridWrapper compute_layer_merged_edge_grid(const Layer *layer) { - static const float eps = float(scale_(layer->object()->config().slice_closing_radius.value)); - // merge with offset - ExPolygons merged = layer->merged(eps); - // ofsset back - ExPolygons layer_outline = offset_ex(merged, -eps); +#ifdef DEBUG_FILES +void debug_export(Issues issues, std::string file_name) { + Slic3r::CNumericLocalesSetter locales_setter; - float min_region_flow_width { }; - for (const auto *region : layer->regions()) { - min_region_flow_width = std::max(min_region_flow_width, region->flow(FlowRole::frExternalPerimeter).width()); - } - std::cout << "min_region_flow_width: " << min_region_flow_width << std::endl; - return EdgeGridWrapper(scale_(min_region_flow_width), layer_outline); -} - -void check_extrusion_entity_stability(const ExtrusionEntity *entity, const EdgeGridWrapper &supported_grid, - const Params ¶ms) { - if (entity->is_collection()){ - for (const auto* e: static_cast(entity).entities){ - check_extrusion_entity_stability(e, supported_grid, params); + { + FILE *fp = boost::nowide::fopen(debug_out_path((file_name + "_supports.obj").c_str()).c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Debug files: Couldn't open " << file_name << " for writing"; + return; } - } else { //single extrusion path, with possible varying parameters - entity->as_polyline().points; - } + for (size_t i = 0; i < issues.supports_nedded.size(); ++i) { + fprintf(fp, "v %f %f %f %f %f %f\n", + issues.supports_nedded[i](0), issues.supports_nedded[i](1), issues.supports_nedded[i](2), + 1.0, 0.0, 0.0 + ); + } + + fclose(fp); + } + { + FILE *fp = boost::nowide::fopen(debug_out_path((file_name + "_curling.obj").c_str()).c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) + << "Debug files: Couldn't open " << file_name << " for writing"; + return; + } + + for (size_t i = 0; i < issues.curling_up.size(); ++i) { + fprintf(fp, "v %f %f %f %f %f %f\n", + issues.curling_up[i](0), issues.curling_up[i](1), issues.curling_up[i](2), + 0.0, 1.0, 0.0 + ); + } + fclose(fp); + } } +#endif -void check_layer_stability(const PrintObject *po, size_t layer_idx, const Params ¶ms) { - if (layer_idx == 0) { - // first layer is usually ok - return; +EdgeGridWrapper compute_layer_edge_grid(const Layer *layer) { + float min_region_flow_width { 1.0f }; + for (const auto *region : layer->regions()) { + min_region_flow_width = std::min(min_region_flow_width, region->flow(FlowRole::frExternalPerimeter).width()); } - const Layer *layer = po->get_layer(layer_idx); - const Layer *prev_layer = layer->lower_layer; - EdgeGridWrapper supported_grid = compute_layer_merged_edge_grid(prev_layer); - + ExPolygons ex_polygons; for (const LayerRegion *layer_region : layer->regions()) { - coordf_t flow_width = coordf_t( - scale_(layer_region->flow(FlowRole::frExternalPerimeter).width())); for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { - check_extrusion_entity_stability(perimeter, supported_grid, params); + ex_polygons.push_back(ExPolygon { perimeter->as_polyline().points }); } // ex_perimeter } // perimeter } // ex_entity } + + return EdgeGridWrapper(scale_(min_region_flow_width), ex_polygons); +} + +Issues check_extrusion_entity_stability(const ExtrusionEntity *entity, size_t layer_idx, + float slice_z, + coordf_t flow_width, + const EdgeGridWrapper &supported_grid, + const Params ¶ms) { + + Issues issues { }; + if (entity->is_collection()) { + for (const auto *e : static_cast(entity)->entities) { + issues.add(check_extrusion_entity_stability(e, layer_idx, slice_z, flow_width, supported_grid, params)); + } + } else { //single extrusion path, with possible varying parameters + Points points = entity->as_polyline().points; + float unsupported_distance = params.bridge_distance + 1.0f; + float curvature = 0; + float max_curvature = 0; + Vec2f tmp = unscale(points[0]).cast(); + Vec3f prev_point = Vec3f(tmp.x(), tmp.y(), slice_z); + + for (size_t point_index = 0; point_index < points.size(); ++point_index) { + std::cout << "index: " << point_index << " dist: " << unsupported_distance << " curvature: " + << curvature << " max curvature: " << max_curvature << std::endl; + + Vec2f tmp = unscale(points[point_index]).cast(); + Vec3f u_point = Vec3f(tmp.x(), tmp.y(), slice_z); + + coordf_t dist_from_prev_layer { 0 }; + if (!supported_grid.grid.signed_distance(points[point_index], flow_width, dist_from_prev_layer)) { + issues.supports_nedded.push_back(u_point); + continue; + } + + constexpr float limit_overlap_factor = 0.5; + + if (dist_from_prev_layer > flow_width) { //unsupported + std::cout << "index: " << point_index << " unsupported " << std::endl; + unsupported_distance += (u_point - prev_point).norm(); + } else { + std::cout << "index: " << point_index << " grounded " << std::endl; + unsupported_distance = 0; + } + + std::cout << "index: " << point_index << " dfromprev: " << dist_from_prev_layer << std::endl; + + if (dist_from_prev_layer > flow_width * limit_overlap_factor && point_index < points.size() - 1) { + const Vec2f v1 = (u_point - prev_point).head<2>(); + const Vec2f v2 = unscale(points[point_index + 1]).cast() - u_point.head<2>(); + float dot = v1(0) * v2(0) + v1(1) * v2(1); + float cross = v1(0) * v2(1) - v1(1) * v2(0); + float angle = float(atan2(float(cross), float(dot))); + + std::cout << "index: " << point_index << " angle: " << angle << std::endl; + + curvature += angle; + max_curvature = std::max(abs(curvature), max_curvature); + } + + if (!(dist_from_prev_layer > flow_width * limit_overlap_factor)) { + std::cout << "index: " << point_index << " reset curvature" << std::endl; + max_curvature = 0; + curvature = 0; + } + + if (unsupported_distance > params.bridge_distance / (1 + int(max_curvature * 9 / PI))) { + issues.supports_nedded.push_back(u_point); + unsupported_distance = 0; + curvature = 0; + max_curvature = 0; + } + + if (max_curvature / (PI * unsupported_distance) > params.limit_curvature) { + issues.curling_up.push_back(u_point); + curvature = 0; + max_curvature = 0; + } + + prev_point = u_point; + } + } + return issues; +} + +//TODO needs revision +coordf_t get_flow_width(const LayerRegion *region, ExtrusionRole role) { + switch (role) { + case ExtrusionRole::erBridgeInfill: + return region->flow(FlowRole::frExternalPerimeter).scaled_width(); + case ExtrusionRole::erExternalPerimeter: + return region->flow(FlowRole::frExternalPerimeter).scaled_width(); + case ExtrusionRole::erGapFill: + return region->flow(FlowRole::frInfill).scaled_width(); + case ExtrusionRole::erPerimeter: + return region->flow(FlowRole::frPerimeter).scaled_width(); + case ExtrusionRole::erSolidInfill: + return region->flow(FlowRole::frSolidInfill).scaled_width(); + default: + return region->flow(FlowRole::frExternalPerimeter).scaled_width(); + } +} + +Issues check_layer_stability(const PrintObject *po, size_t layer_idx, bool full_check, const Params ¶ms) { + std::cout << "Checking: " << layer_idx << std::endl; + if (layer_idx == 0) { + // first layer is usually ok + return {}; + } + const Layer *layer = po->get_layer(layer_idx); + EdgeGridWrapper supported_grid = compute_layer_edge_grid(layer->lower_layer); + + Issues issues { }; + if (full_check) { + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { + issues.add(check_extrusion_entity_stability(perimeter, layer_idx, + layer->slice_z, get_flow_width(layer_region, perimeter->role()), + supported_grid, params)); + } // perimeter + } // ex_entity + for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { + for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { + if (fill->role() == ExtrusionRole::erGapFill || fill->role() == ExtrusionRole::erBridgeInfill) { + issues.add(check_extrusion_entity_stability(fill, layer_idx, + layer->slice_z, get_flow_width(layer_region, fill->role()), + supported_grid, params)); + } + } // fill + } // ex_entity + } // region + } else { //check only external perimeters + for (const LayerRegion *layer_region : layer->regions()) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { + if (perimeter->role() == ExtrusionRole::erExternalPerimeter) { + std::cout << "checking ex perimeter " << std::endl; + issues.add(check_extrusion_entity_stability(perimeter, layer_idx, + layer->slice_z, get_flow_width(layer_region, perimeter->role()), + supported_grid, params)); + }; // ex_perimeter + } // perimeter + } // ex_entity + } //region + } + + return issues; } } //Impl End -void quick_search(const PrintObject *po, const Params ¶ms = Params { }) { +std::vector quick_search(const PrintObject *po, const Params ¶ms) { using namespace Impl; std::vector descriptors(po->layer_count()); @@ -129,20 +298,75 @@ void quick_search(const PrintObject *po, const Params ¶ms = Params { }) { } // thread ); - for (size_t desc_idx = 0; desc_idx < descriptors.size(); ++desc_idx) { + std::vector suspicious_layers_indices { }; + + for (size_t desc_idx = 1; desc_idx < descriptors.size(); ++desc_idx) { + const LayerDescriptor &prev = descriptors[desc_idx - 1]; const LayerDescriptor &descriptor = descriptors[desc_idx]; + if (descriptor.segments_count - prev.segments_count != 0 + || + std::abs(descriptor.perimeter_length - prev.perimeter_length) + > params.perimeter_length_diff_tolerance || + (descriptor.centroid - prev.centroid).norm() > params.centroid_offset_tolerance + ) { + suspicious_layers_indices.push_back(desc_idx); + } +#ifdef DEBUG_FILES std::cout << "SIS layer idx: " << desc_idx << " reg count: " << descriptor.segments_count << " len: " << descriptor.perimeter_length << " centroid: " << descriptor.centroid.x() << " | " << descriptor.centroid.y() << std::endl; - if (desc_idx > 0) { - const LayerDescriptor &prev = descriptors[desc_idx - 1]; - std::cout << "SIS diff: " << desc_idx << " reg count: " - << (int(descriptor.segments_count) - int(prev.segments_count)) << - " len: " << (descriptor.perimeter_length - prev.perimeter_length) << - " centroid: " << (descriptor.centroid - prev.centroid).norm() << std::endl; + std::cout << "SIS diff: " << desc_idx << " reg count: " + << (int(descriptor.segments_count) - int(prev.segments_count)) << + " len: " << (descriptor.perimeter_length - prev.perimeter_length) << + " centroid: " << (descriptor.centroid - prev.centroid).norm() << std::endl; +#endif + } + + std::vector layer_needs_supports(suspicious_layers_indices.size(), false); + tbb::parallel_for(tbb::blocked_range(0, suspicious_layers_indices.size()), + [&](tbb::blocked_range r) { + for (size_t suspicious_index = r.begin(); suspicious_index < r.end(); ++suspicious_index) { + auto layer_issues = check_layer_stability(po, suspicious_layers_indices[suspicious_index], + false, + params); + if (!layer_issues.supports_nedded.empty()) { + layer_needs_supports[suspicious_index] = true; + } + } + }); + + std::vector problematic_layers; + + for (size_t index = suspicious_layers_indices.size() - 1; index <= 0; ++index) { + if (!layer_needs_supports[index]) { + problematic_layers.push_back(suspicious_layers_indices[index]); + } + } + return problematic_layers; +} + +Issues full_search(const PrintObject *po, const Params ¶ms) { + using namespace Impl; + Issues issues { }; + for (size_t layer_idx = 1; layer_idx < po->layer_count(); ++layer_idx) { + auto layer_issues = check_layer_stability(po, layer_idx, true, params); + if (!layer_issues.empty()) { + issues.add(layer_issues); } } +#ifdef DEBUG_FILES + Impl::debug_export(issues, "issues"); +#endif + +// tbb::parallel_for(tbb::blocked_range(0, suspicious_layers_indices.size()), +// [&](tbb::blocked_range r) { +// for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { +// check_layer_stability(po, suspicious_layers_indices[layer_idx], params); +// } +// }); + + return issues; } } diff --git a/src/libslic3r/SupportableIssuesSearch.hpp b/src/libslic3r/SupportableIssuesSearch.hpp index dbec52aff..22289c923 100644 --- a/src/libslic3r/SupportableIssuesSearch.hpp +++ b/src/libslic3r/SupportableIssuesSearch.hpp @@ -7,7 +7,25 @@ namespace Slic3r { namespace SupportableIssues { -void quick_search(const PrintObject *po); +struct Params { + float bridge_distance = 5.0f; + float limit_curvature = 0.25f; + + float perimeter_length_diff_tolerance = 8.0f; + float centroid_offset_tolerance = 1.0f; +}; + + +struct Issues { + std::vector supports_nedded; + std::vector curling_up; + + void add(const Issues &layer_issues); + bool empty() const; +}; + +std::vector quick_search(const PrintObject *po, const Params ¶ms = Params { }); +Issues full_search(const PrintObject *po, const Params ¶ms = Params { }); } diff --git a/src/libslic3r/TriangleSelectorWrapper.cpp b/src/libslic3r/TriangleSelectorWrapper.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/libslic3r/TriangleSelectorWrapper.cpp @@ -0,0 +1 @@ + diff --git a/src/libslic3r/TriangleSelectorWrapper.hpp b/src/libslic3r/TriangleSelectorWrapper.hpp new file mode 100644 index 000000000..f818d8be2 --- /dev/null +++ b/src/libslic3r/TriangleSelectorWrapper.hpp @@ -0,0 +1,42 @@ +#ifndef SRC_LIBSLIC3R_TRIANGLESELECTORWRAPPER_HPP_ +#define SRC_LIBSLIC3R_TRIANGLESELECTORWRAPPER_HPP_ + +#include + +#include "TriangleSelector.hpp" +#include "AABBTreeIndirect.hpp" + +namespace Slic3r { + +class TriangleSelectorWrapper { +public: + const TriangleMesh &mesh; + TriangleSelector selector; + AABBTreeIndirect::Tree<3, float> triangles_tree; + + TriangleSelectorWrapper(const TriangleMesh &mesh) : + mesh(mesh), selector(mesh), triangles_tree(AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices)) { + + } + + void enforce_spot(const Vec3f &point, float radius) { + size_t hit_face_index; + Vec3f hit_point; + auto dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(mesh.its.vertices, mesh.its.indices, + triangles_tree, + point, hit_face_index, hit_point); + if (dist < 0 || dist > radius) + return; + + std::unique_ptr cursor = std::make_unique(point, point, + radius, Transform3d::Identity(), TriangleSelector::ClippingPlane { }); + + selector.select_patch(hit_face_index, std::move(cursor), EnforcerBlockerType::ENFORCER, Transform3d::Identity(), true, + 0.0f); + } + +}; + +} + +#endif /* SRC_LIBSLIC3R_TRIANGLESELECTORWRAPPER_HPP_ */