diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 8b0c28cd6..939ec7450 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -323,7 +323,7 @@ namespace Slic3r { typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; - typedef std::map> IdToSlaSupportPointsMap; + typedef std::map> IdToSlaSupportPointsMap; // Version of the 3mf file unsigned int m_version; @@ -776,10 +776,19 @@ namespace Slic3r { std::vector objects; boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + // Info on format versioning - see 3mf.hpp + int version = 0; + if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + for (const std::string& object : objects) { std::vector object_data; boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + if (object_data.size() != 2) { add_error("Error while reading object data"); @@ -811,10 +820,24 @@ namespace Slic3r { std::vector object_data_points; boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); - std::vector sla_support_points; + std::vector sla_support_points; - for (unsigned int i=0; i()); + if (version == 0) { + for (unsigned int i=0; i& sla_support_points = object->sla_support_points; + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { sprintf(buffer, "object_id=%d|", count); @@ -1970,7 +1993,7 @@ namespace Slic3r { // Store the layer height profile as a single space separated list. for (size_t i = 0; i < sla_support_points.size(); ++i) { - sprintf(buffer, (i==0 ? "%f %f %f" : " %f %f %f"), sla_support_points[i](0), sla_support_points[i](1), sla_support_points[i](2)); + sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); out += buffer; } out += "\n"; @@ -1979,6 +2002,9 @@ namespace Slic3r { if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; + if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add sla support points file to archive"); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 44b37c1ae..b5927651e 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -3,6 +3,23 @@ namespace Slic3r { + /* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used. + * Examples of the Slic3r_PE_sla_support_points.txt for historically used versions: + + * version 0 : object_id=1|-12.055421 -2.658771 10.000000 + object_id=2|-14.051745 -3.570338 5.000000 + // no header and x,y,z positions of the points) + + * version 1 : ThreeMF_support_points_version=1 + object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0 + object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0 + // introduced header with version number; x,y,z,head_size,is_new_island) + */ + + enum { + support_points_format_version = 1 + }; + class Model; class DynamicPrintConfig; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 9791b1502..81ca67129 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -583,7 +583,7 @@ void AMFParserContext::endElement(const char * /* name */) else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. unsigned char coord_idx = 0; - Vec3f point(Vec3f::Zero()); + Eigen::Matrix point(Eigen::Matrix::Zero()); char *p = const_cast(m_value[1].c_str()); for (;;) { char *end = strchr(p, ';'); @@ -591,8 +591,8 @@ void AMFParserContext::endElement(const char * /* name */) *end = 0; point(coord_idx) = atof(p); - if (++coord_idx == 3) { - m_object->sla_support_points.push_back(point); + if (++coord_idx == 5) { + m_object->sla_support_points.push_back(sla::SupportPoint(point)); coord_idx = 0; } if (end == nullptr) @@ -900,14 +900,14 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) } //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) - const std::vector& sla_support_points = object->sla_support_points; + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { // Store the SLA supports as a single semicolon separated list. stream << " "; for (size_t i = 0; i < sla_support_points.size(); ++i) { if (i != 0) stream << ";"; - stream << sla_support_points[i](0) << ";" << sla_support_points[i](1) << ";" << sla_support_points[i](2); + stream << sla_support_points[i].pos(0) << ";" << sla_support_points[i].pos(1) << ";" << sla_support_points[i].pos(2) << ";" << sla_support_points[i].head_front_radius << ";" << sla_support_points[i].is_new_island; } stream << "\n \n"; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b998fbb7d..5d6f2a9db 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -12,6 +12,7 @@ #include #include #include "Geometry.hpp" +#include namespace Slic3r { @@ -175,7 +176,8 @@ public: // This vector holds position of selected support points for SLA. The data are // saved in mesh coordinates to allow using them for several instances. - std::vector sla_support_points; + // The format is (x, y, z, point_size, supports_island) + std::vector sla_support_points; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d456fb9c6..a03e6f436 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -22,6 +22,7 @@ typedef Point Vector; // Vector types with a fixed point coordinate base type. typedef Eigen::Matrix Vec2crd; typedef Eigen::Matrix Vec3crd; +typedef Eigen::Matrix Vec2i; typedef Eigen::Matrix Vec3i; typedef Eigen::Matrix Vec2i64; typedef Eigen::Matrix Vec3i64; diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 84d04d26f..f67ec4b72 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -244,8 +244,9 @@ public: // Bitmap of flags. enum FlagBits { DEFAULT, - NO_RELOAD_SCENE = 0, - RELOAD_SCENE = 1, + NO_RELOAD_SCENE = 0, + RELOAD_SCENE = 1 << 1, + RELOAD_SLA_SUPPORT_POINTS = 1 << 2, }; // Bitmap of FlagBits unsigned int flags; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3e1ee9f9f..c01a1f6aa 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2630,28 +2630,19 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->default_value = new ConfigOptionFloat(5.0); - def = this->add("support_density_at_horizontal", coInt); - def->label = L("Density on horizontal surfaces"); + def = this->add("support_points_density_relative", coInt); + def->label = L("Support points density"); def->category = L("Supports"); - def->tooltip = L("How many support points (approximately) should be placed on horizontal surface."); - def->sidetext = L("points per square dm"); + def->tooltip = L("This is a relative measure of support points density."); + def->sidetext = L("%"); def->cli = ""; def->min = 0; - def->default_value = new ConfigOptionInt(500); + def->default_value = new ConfigOptionInt(100); - def = this->add("support_density_at_45", coInt); - def->label = L("Density on surfaces at 45 degrees"); + def = this->add("support_points_minimal_distance", coFloat); + def->label = L("Minimal distance of the support points"); def->category = L("Supports"); - def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees."); - def->sidetext = L("points per square dm"); - def->cli = ""; - def->min = 0; - def->default_value = new ConfigOptionInt(250); - - def = this->add("support_minimal_z", coFloat); - def->label = L("Minimal support point height"); - def->category = L("Supports"); - def->tooltip = L("No support points will be placed lower than this value from the bottom."); + def->tooltip = L("No support points will be placed closer than this threshold."); def->sidetext = L("mm"); def->cli = ""; def->min = 0; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 20c089d1c..c6c917515 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1002,9 +1002,8 @@ public: ConfigOptionFloat support_object_elevation /*= 5.0*/; /////// Following options influence automatic support points placement: - ConfigOptionInt support_density_at_horizontal; - ConfigOptionInt support_density_at_45; - ConfigOptionFloat support_minimal_z; + ConfigOptionInt support_points_density_relative; + ConfigOptionFloat support_points_minimal_distance; // Now for the base pool (pad) ///////////////////////////////////////////// @@ -1040,9 +1039,8 @@ protected: OPT_PTR(support_base_height); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); - OPT_PTR(support_density_at_horizontal); - OPT_PTR(support_density_at_45); - OPT_PTR(support_minimal_z); + OPT_PTR(support_points_density_relative); + OPT_PTR(support_points_minimal_distance); OPT_PTR(support_object_elevation); OPT_PTR(pad_enable); OPT_PTR(pad_wall_thickness); diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 98313be3f..1f6dcc2b7 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -1,47 +1,23 @@ #include "igl/random_points_on_mesh.h" #include "igl/AABB.h" +#include + #include "SLAAutoSupports.hpp" #include "Model.hpp" #include "ExPolygon.hpp" #include "SVG.hpp" #include "Point.hpp" #include "ClipperUtils.hpp" +#include "Tesselate.hpp" +#include "libslic3r.h" #include #include namespace Slic3r { -SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, - const Config& config, std::function throw_on_cancel) -: m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel) -{ - // FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably - // not always thread-safe and can be slow if it is. - srand(time(NULL)); // rand() is used by igl::random_point_on_mesh - - // Find all separate islands that will need support. The coord_t number denotes height - // of a point just below the mesh (so that we can later project the point precisely - // on the mesh by raycasting (done by igl) and not risking we will place the point inside). - std::vector> islands = find_islands(slices, heights); - - // Uniformly cover each of the islands with support points. - for (const auto& island : islands) { - std::vector points = uniformly_cover(island); - m_throw_on_cancel(); - project_upward_onto_mesh(points); - m_output.insert(m_output.end(), points.begin(), points.end()); - m_throw_on_cancel(); - } - - // We are done with the islands. Let's sprinkle the rest of the mesh. - // The function appends to m_output. - sprinkle_mesh(mesh); -} - - -float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) +/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) { n1.normalize(); n2.normalize(); @@ -59,115 +35,6 @@ float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3 } -void SLAAutoSupports::sprinkle_mesh(const TriangleMesh& mesh) -{ - std::vector points; - // Loads the ModelObject raw_mesh and transforms it by first instance's transformation matrix (disregarding translation). - // Instances only differ in z-rotation, so it does not matter which of them will be used for the calculation. - // The supports point will be calculated on this mesh (so scaling ang vertical direction is correctly accounted for). - // Results will be inverse-transformed to raw_mesh coordinates. - //TriangleMesh mesh = m_model_object.raw_mesh(); - //Transform3d transformation_matrix = m_model_object.instances[0]->get_matrix(true/*dont_translate*/); - //mesh.transform(transformation_matrix); - - // Check that the object is thick enough to produce any support points - BoundingBoxf3 bb = mesh.bounding_box(); - if (bb.size()(2) < m_config.minimal_z) - return; - - // All points that we curretly have must be transformed too, so distance to them is correcly calculated. - //for (Vec3f& point : m_model_object.sla_support_points) - // point = transformation_matrix.cast() * point; - - - // In order to calculate distance to already placed points, we must keep know which facet the point lies on. - std::vector facets_normals; - - // Only points belonging to islands were added so far - they all lie on horizontal surfaces: - for (unsigned int i=0; i aabb; - aabb.init(V, F); - for (unsigned int i=0; i dump; - Eigen::MatrixXf query_point = m_model_object.sla_support_points[i]; - aabb.squared_distance(V, F, query_point, facet_idx, dump); - Vec3f a1 = V.row(F(facet_idx,1)) - V.row(F(facet_idx,0)); - Vec3f a2 = V.row(F(facet_idx,2)) - V.row(F(facet_idx,0)); - Vec3f normal = a1.cross(a2); - normal.normalize(); - facets_normals.push_back(normal); - }*/ - - // New potential support point is randomly generated on the mesh and distance to all already placed points is calculated. - // In case it is never smaller than certain limit (depends on the new point's facet normal), the point is accepted. - // The process stops after certain number of points is refused in a row. - Vec3d point; - Vec3d normal; - int added_points = 0; - int refused_points = 0; - const int refused_limit = 30; - // Angle at which the density reaches zero: - const float threshold_angle = std::min(M_PI_2, M_PI_4 * acos(0.f/m_config.density_at_horizontal) / acos(m_config.density_at_45/m_config.density_at_horizontal)); - - size_t cancel_test_cntr = 0; - while (refused_points < refused_limit) { - if (++ cancel_test_cntr == 500) { - // Don't call the cancellation routine too often as the multi-core cache synchronization - // may be pretty expensive. - m_throw_on_cancel(); - cancel_test_cntr = 0; - } - // Place a random point on the mesh and calculate corresponding facet's normal: - Eigen::VectorXi FI; - Eigen::MatrixXd B; - igl::random_points_on_mesh(1, m_V, m_F, B, FI); - point = B(0,0)*m_V.row(m_F(FI(0),0)) + - B(0,1)*m_V.row(m_F(FI(0),1)) + - B(0,2)*m_V.row(m_F(FI(0),2)); - if (point(2) - bb.min(2) < m_config.minimal_z) - continue; - - Vec3d a1 = m_V.row(m_F(FI(0),1)) - m_V.row(m_F(FI(0),0)); - Vec3d a2 = m_V.row(m_F(FI(0),2)) - m_V.row(m_F(FI(0),0)); - normal = a1.cross(a2); - normal.normalize(); - - // calculate angle between the normal and vertical: - float angle = angle_from_normal(normal.cast()); - if (angle > threshold_angle) - continue; - - const float limit = distance_limit(angle); - bool add_it = true; - for (unsigned int i=0; i()); - facets_normals.push_back(normal); - ++added_points; - refused_points = 0; - } - } - - m_output.insert(m_output.end(), points.begin(), points.end()); - - // Now transform all support points to mesh coordinates: - //for (Vec3f& point : m_model_object.sla_support_points) - // point = transformation_matrix.inverse().cast() * point; -} - - - float SLAAutoSupports::get_required_density(float angle) const { // calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle @@ -179,10 +46,470 @@ float SLAAutoSupports::get_required_density(float angle) const float SLAAutoSupports::distance_limit(float angle) const { return 1./(2.4*get_required_density(angle)); +}*/ + +SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, + const Config& config, std::function throw_on_cancel) +: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel) +{ + process(slices, heights); + project_onto_mesh(m_output); +} + +void SLAAutoSupports::project_onto_mesh(std::vector& points) const +{ + // The function makes sure that all the points are really exactly placed on the mesh. + igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f}; + igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f}; + + // Use a reasonable granularity to account for the worker thread synchronization cost. + tbb::parallel_for(tbb::blocked_range(0, points.size(), 64), + [this, &points](const tbb::blocked_range& range) { + for (size_t point_id = range.begin(); point_id < range.end(); ++ point_id) { + if ((point_id % 16) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + m_throw_on_cancel(); + Vec3f& p = points[point_id].pos; + // Project the point upward and downward and choose the closer intersection with the mesh. + //bool up = igl::ray_mesh_intersect(p.cast(), Vec3f(0., 0., 1.), m_V, m_F, hit_up); + //bool down = igl::ray_mesh_intersect(p.cast(), Vec3f(0., 0., -1.), m_V, m_F, hit_down); + + sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); + sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); + + bool up = hit_up.face() != -1; + bool down = hit_down.face() != -1; + + if (!up && !down) + continue; + + sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; + //int fid = hit.face(); + //Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); + //p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast(); + + p = p + (hit.distance() * hit.direction()).cast(); + } + }); +} + +static std::vector make_layers( + const std::vector& slices, const std::vector& heights, + std::function throw_on_cancel) +{ + assert(slices.size() == heights.size()); + + // Allocate empty layers. + std::vector layers; + layers.reserve(slices.size()); + for (size_t i = 0; i < slices.size(); ++ i) + layers.emplace_back(i, heights[i]); + + // FIXME: calculate actual pixel area from printer config: + //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option("display_width") / wxGetApp().preset_bundle->project_config.option("display_pixels_x"), 2.f); // + const float pixel_area = pow(0.047f, 2.f); + + // Use a reasonable granularity to account for the worker thread synchronization cost. + tbb::parallel_for(tbb::blocked_range(0, layers.size(), 32), + [&layers, &slices, &heights, pixel_area, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + if ((layer_id % 8) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SLAAutoSupports::MyLayer &layer = layers[layer_id]; + const ExPolygons &islands = slices[layer_id]; + //FIXME WTF? + const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); + layer.islands.reserve(islands.size()); + for (const ExPolygon &island : islands) { + float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); + if (area >= pixel_area) + //FIXME this is not a correct centroid of a polygon with holes. + layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); + } + } + }); + + // Calculate overlap of successive layers. Link overlapping islands. + tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), + [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { + if ((layer_id % 2) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; + SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; + //FIXME WTF? + const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); + const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); + const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports + const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); + //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. + for (SLAAutoSupports::Structure &top : layer_above.islands) { + for (SLAAutoSupports::Structure &bottom : layer_below.islands) { + float overlap_area = top.overlap_area(bottom); + if (overlap_area > 0) { + top.islands_below.emplace_back(&bottom, overlap_area); + bottom.islands_above.emplace_back(&top, overlap_area); + } + } + if (! top.islands_below.empty()) { + Polygons top_polygons = to_polygons(*top.polygon); + Polygons bottom_polygons = top.polygons_below(); + top.overhangs = diff_ex(top_polygons, bottom_polygons); + if (! top.overhangs.empty()) { + top.overhangs_area = 0.f; + std::vector> expolys_with_areas; + for (ExPolygon &ex : top.overhangs) { + float area = float(ex.area()); + expolys_with_areas.emplace_back(&ex, area); + top.overhangs_area += area; + } + std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), + [](const std::pair &p1, const std::pair &p2) + { return p1.second > p2.second; }); + ExPolygons overhangs_sorted; + for (auto &p : expolys_with_areas) + overhangs_sorted.emplace_back(std::move(*p.first)); + top.overhangs = std::move(overhangs_sorted); + top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); + top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); + } + } + } + } + }); + + return layers; +} + +void SLAAutoSupports::process(const std::vector& slices, const std::vector& heights) +{ +#ifdef SLA_AUTOSUPPORTS_DEBUG + std::vector> islands; +#endif /* SLA_AUTOSUPPORTS_DEBUG */ + + std::vector layers = make_layers(slices, heights, m_throw_on_cancel); + + PointGrid3D point_grid; + point_grid.cell_size = Vec3f(10.f, 10.f, 10.f); + + for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) { + SLAAutoSupports::MyLayer *layer_top = &layers[layer_id]; + SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr; + std::vector support_force_bottom; + if (layer_bottom != nullptr) { + support_force_bottom.assign(layer_bottom->islands.size(), 0.f); + for (size_t i = 0; i < layer_bottom->islands.size(); ++ i) + support_force_bottom[i] = layer_bottom->islands[i].supports_force_total(); + } + for (Structure &top : layer_top->islands) + for (Structure::Link &bottom_link : top.islands_below) { + Structure &bottom = *bottom_link.island; + float centroids_dist = (bottom.centroid - top.centroid).norm(); + // Penalization resulting from centroid offset: +// bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area)); + float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; +//FIXME this condition does not reflect a bifurcation into a one large island and one tiny island well, it incorrectly resets the support force to zero. +// One should rather work with the overlap area vs overhang area. +// support_force *= std::min(1.f, 1.f - std::min(1.f, 0.1f * centroids_dist * centroids_dist / bottom.area)); + // Penalization resulting from increasing polygon area: + support_force *= std::min(1.f, 20.f * bottom.area / top.area); + } + // Let's assign proper support force to each of them: + if (layer_id > 0) { + for (Structure &below : layer_bottom->islands) { + float below_support_force = support_force_bottom[&below - layer_bottom->islands.data()]; + float above_overlap_area = 0.f; + for (Structure::Link &above_link : below.islands_above) + above_overlap_area += above_link.overlap_area; + for (Structure::Link &above_link : below.islands_above) + above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area; + } + } + // Now iterate over all polygons and append new points if needed. + for (Structure &s : layer_top->islands) { + // Penalization resulting from large diff from the last layer: +// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); + s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); + + float force_deficit = s.support_force_deficit(m_config.tear_pressure()); + if (s.islands_below.empty()) { // completely new island - needs support no doubt + uniformly_cover({ *s.polygon }, s, point_grid, true); + } else if (! s.dangling_areas.empty()) { + // Let's see if there's anything that overlaps enough to need supports: + // What we now have in polygons needs support, regardless of what the forces are, so we can add them. + //FIXME is it an island point or not? Vojtech thinks it is. + uniformly_cover(s.dangling_areas, s, point_grid); + } else if (! s.overhangs.empty()) { + //FIXME add the support force deficit as a parameter, only cover until the defficiency is covered. + uniformly_cover(s.overhangs, s, point_grid); + } + } + + m_throw_on_cancel(); + +#ifdef SLA_AUTOSUPPORTS_DEBUG + /*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i); + output_expolygons(expolys_top, "top" + layer_num_str + ".svg"); + output_expolygons(diff, "diff" + layer_num_str + ".svg"); + if (!islands.empty()) + output_expolygons(islands, "islands" + layer_num_str + ".svg");*/ +#endif /* SLA_AUTOSUPPORTS_DEBUG */ + } +} + +std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng) +{ + // Triangulate the polygon with holes into triplets of 3D points. + std::vector triangles = Slic3r::triangulate_expolygon_2f(expoly); + + std::vector out; + if (! triangles.empty()) + { + // Calculate area of each triangle. + std::vector areas; + areas.reserve(triangles.size() / 3); + for (size_t i = 0; i < triangles.size(); ) { + const Vec2f &a = triangles[i ++]; + const Vec2f v1 = triangles[i ++] - a; + const Vec2f v2 = triangles[i ++] - a; + areas.emplace_back(0.5f * std::abs(cross2(v1, v2))); + if (i != 3) + // Prefix sum of the areas. + areas.back() += areas[areas.size() - 2]; + } + + size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2)); + std::uniform_real_distribution<> random_triangle(0., double(areas.back())); + std::uniform_real_distribution<> random_float(0., 1.); + for (size_t i = 0; i < num_samples; ++ i) { + double r = random_triangle(rng); + size_t idx_triangle = std::min(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3; + // Select a random point on the triangle. + double u = float(sqrt(random_float(rng))); + double v = float(random_float(rng)); + const Vec2f &a = triangles[idx_triangle ++]; + const Vec2f &b = triangles[idx_triangle++]; + const Vec2f &c = triangles[idx_triangle]; + const Vec2f x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u); + out.emplace_back(x); + } + } + return out; +} + +std::vector sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) +{ + std::vector out = sample_expolygon(expoly, samples_per_mm2, rng); + double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary; + for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { + const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1]; + const Points pts = contour.equally_spaced_points(point_stepping_scaled); + for (size_t i = 0; i < pts.size(); ++ i) + out.emplace_back(unscale(pts[i].x()), unscale(pts[i].y())); + } + return out; +} + +std::vector sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) +{ + std::vector out; + for (const ExPolygon &expoly : expolys) + append(out, sample_expolygon_with_boundary(expoly, samples_per_mm2, samples_per_mm_boundary, rng)); + return out; +} + +template +static inline std::vector poisson_disk_from_samples(const std::vector &raw_samples, float radius, REFUSE_FUNCTION refuse_function) +{ + Vec2f corner_min(std::numeric_limits::max(), std::numeric_limits::max()); + for (const Vec2f &pt : raw_samples) { + corner_min.x() = std::min(corner_min.x(), pt.x()); + corner_min.y() = std::min(corner_min.y(), pt.y()); + } + + // Assign the raw samples to grid cells, sort the grid cells lexicographically. + struct RawSample { + Vec2f coord; + Vec2i cell_id; + }; + std::vector raw_samples_sorted; + RawSample sample; + for (const Vec2f &pt : raw_samples) { + sample.coord = pt; + sample.cell_id = ((pt - corner_min) / radius).cast(); + raw_samples_sorted.emplace_back(sample); + } + std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) + { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); }); + + struct PoissonDiskGridEntry { + // Resulting output sample points for this cell: + enum { + max_positions = 4 + }; + Vec2f poisson_samples[max_positions]; + int num_poisson_samples = 0; + + // Index into raw_samples: + int first_sample_idx; + int sample_cnt; + }; + + struct CellIDHash { + std::size_t operator()(const Vec2i &cell_id) const { + return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593); + } + }; + + // Map from cell IDs to hash_data. Each hash_data points to the range in raw_samples corresponding to that cell. + // (We could just store the samples in hash_data. This implementation is an artifact of the reference paper, which + // is optimizing for GPU acceleration that we haven't implemented currently.) + typedef std::unordered_map Cells; + Cells cells; + { + typename Cells::iterator last_cell_id_it; + Vec2i last_cell_id(-1, -1); + for (int i = 0; i < raw_samples_sorted.size(); ++ i) { + const RawSample &sample = raw_samples_sorted[i]; + if (sample.cell_id == last_cell_id) { + // This sample is in the same cell as the previous, so just increase the count. Cells are + // always contiguous, since we've sorted raw_samples_sorted by cell ID. + ++ last_cell_id_it->second.sample_cnt; + } else { + // This is a new cell. + PoissonDiskGridEntry data; + data.first_sample_idx = i; + data.sample_cnt = 1; + auto result = cells.insert({sample.cell_id, data}); + last_cell_id = sample.cell_id; + last_cell_id_it = result.first; + } + } + } + + const int max_trials = 5; + const float radius_squared = radius * radius; + for (int trial = 0; trial < max_trials; ++ trial) { + // Create sample points for each entry in cells. + for (auto &it : cells) { + const Vec2i &cell_id = it.first; + PoissonDiskGridEntry &cell_data = it.second; + // This cell's raw sample points start at first_sample_idx. On trial 0, try the first one. On trial 1, try first_sample_idx + 1. + int next_sample_idx = cell_data.first_sample_idx + trial; + if (trial >= cell_data.sample_cnt) + // There are no more points to try for this cell. + continue; + const RawSample &candidate = raw_samples_sorted[next_sample_idx]; + // See if this point conflicts with any other points in this cell, or with any points in + // neighboring cells. Note that it's possible to have more than one point in the same cell. + bool conflict = refuse_function(candidate.coord); + for (int i = -1; i < 2 && ! conflict; ++ i) { + for (int j = -1; j < 2; ++ j) { + const auto &it_neighbor = cells.find(cell_id + Vec2i(i, j)); + if (it_neighbor != cells.end()) { + const PoissonDiskGridEntry &neighbor = it_neighbor->second; + for (int i_sample = 0; i_sample < neighbor.num_poisson_samples; ++ i_sample) + if ((neighbor.poisson_samples[i_sample] - candidate.coord).squaredNorm() < radius_squared) { + conflict = true; + break; + } + } + } + } + if (! conflict) { + // Store the new sample. + assert(cell_data.num_poisson_samples < cell_data.max_positions); + if (cell_data.num_poisson_samples < cell_data.max_positions) + cell_data.poisson_samples[cell_data.num_poisson_samples ++] = candidate.coord; + } + } + } + + // Copy the results to the output. + std::vector out; + for (const auto& it : cells) + for (int i = 0; i < it.second.num_poisson_samples; ++ i) + out.emplace_back(it.second.poisson_samples[i]); + return out; +} + +void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one) +{ + //int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force)); + + const float support_force_deficit = structure.support_force_deficit(m_config.tear_pressure()); + if (support_force_deficit < 0) + return; + + // Number of newly added points. + const size_t poisson_samples_target = size_t(ceil(support_force_deficit / m_config.support_force())); + + const float density_horizontal = m_config.tear_pressure() / m_config.support_force(); + //FIXME why? + float poisson_radius = std::max(m_config.minimal_distance, 1.f / (5.f * density_horizontal)); +// const float poisson_radius = 1.f / (15.f * density_horizontal); + const float samples_per_mm2 = 30.f / (float(M_PI) * poisson_radius * poisson_radius); + // Minimum distance between samples, in 3D space. +// float min_spacing = poisson_radius / 3.f; + float min_spacing = poisson_radius; + + //FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon. + std::random_device rd; + std::mt19937 rng(rd()); + std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng); + std::vector poisson_samples; + for (size_t iter = 0; iter < 4; ++ iter) { + poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, + [&structure, &grid3d, min_spacing](const Vec2f &pos) { + return grid3d.collides_with(pos, &structure, min_spacing); + }); + if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON) + break; + float coeff = 0.5f; + if (poisson_samples.size() * 2 > poisson_samples_target) + coeff = float(poisson_samples.size()) / float(poisson_samples_target); + poisson_radius = std::max(m_config.minimal_distance, poisson_radius * coeff); + min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff); + } + +#ifdef SLA_AUTOSUPPORTS_DEBUG + { + static int irun = 0; + Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); + for (const ExPolygon &island : islands) + svg.draw(island); + for (const Vec2f &pt : raw_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red"); + for (const Vec2f &pt : poisson_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue"); + } +#endif /* NDEBUG */ + +// assert(! poisson_samples.empty()); + if (poisson_samples_target < poisson_samples.size()) { + std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng); + poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); + } + for (const Vec2f &pt : poisson_samples) { + m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island); + structure.supports_force_this_layer += m_config.support_force(); + grid3d.insert(pt, &structure); + } } #ifdef SLA_AUTOSUPPORTS_DEBUG -void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string filename) const +void SLAAutoSupports::output_structures(const std::vector& structures) +{ + for (unsigned int i=0 ; i{*structures[i].polygon}, ss.str()); + } +} + +void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename) { BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000)); Slic3r::SVG svg_cummulative(filename, bb); @@ -198,138 +525,6 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string f svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); } } -#endif /* SLA_AUTOSUPPORTS_DEBUG */ - -std::vector> SLAAutoSupports::find_islands(const std::vector& slices, const std::vector& heights) const -{ - std::vector> islands; - - struct PointAccessor { - const Point* operator()(const Point &pt) const { return &pt; } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - - for (unsigned int i = 0; i SLAAutoSupports::uniformly_cover(const std::pair& island) -{ - int num_of_points = std::max(1, (int)(island.first.area()*pow(SCALING_FACTOR, 2) * get_required_density(0))); - - // In case there is just one point to place, we'll place it into the polygon's centroid (unless it lies in a hole). - if (num_of_points == 1) { - Point out(island.first.contour.centroid()); - - for (const auto& hole : island.first.holes) - if (hole.contains(out)) - goto HOLE_HIT; - return std::vector{unscale(out(0), out(1), island.second)}; - } - -HOLE_HIT: - // In this case either the centroid lies in a hole, or there are multiple points - // to place. We will cover the island another way. - // For now we'll just place the points randomly not too close to the others. - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution<> dis(0., 1.); - - std::vector island_new_points; - const BoundingBox& bb = get_extents(island.first); - const int refused_limit = 30; - int refused_points = 0; - while (refused_points < refused_limit) { - Point out(bb.min(0) + bb.size()(0) * dis(gen), - bb.min(1) + bb.size()(1) * dis(gen)) ; - Vec3d unscaled_out = unscale(out(0), out(1), island.second); - bool add_it = true; - - if (!island.first.contour.contains(out)) - add_it = false; - else - for (const Polygon& hole : island.first.holes) - if (hole.contains(out)) - add_it = false; - - if (add_it) { - for (const Vec3d& p : island_new_points) { - if ((p - unscaled_out).squaredNorm() < distance_limit(0)) { - add_it = false; - ++refused_points; - break; - } - } - } - if (add_it) - island_new_points.emplace_back(unscaled_out); - } - return island_new_points; -} - -void SLAAutoSupports::project_upward_onto_mesh(std::vector& points) const -{ - Vec3f dir(0., 0., 1.); - igl::Hit hit{0, 0, 0.f, 0.f, 0.f}; - for (Vec3d& p : points) { - igl::ray_mesh_intersect(p.cast(), dir, m_V, m_F, hit); - int fid = hit.id; - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); - p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast(); - } -} - +#endif } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 311d7b0c7..0d5cc64f1 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -1,9 +1,12 @@ #ifndef SLAAUTOSUPPORTS_HPP_ #define SLAAUTOSUPPORTS_HPP_ +#include #include #include -#include +#include + +#include // #define SLA_AUTOSUPPORTS_DEBUG @@ -12,36 +15,184 @@ namespace Slic3r { class SLAAutoSupports { public: struct Config { - float density_at_horizontal; - float density_at_45; - float minimal_z; + float density_relative; + float minimal_distance; + /////////////// + inline float support_force() const { return 10.f / density_relative; } // a force one point can support (arbitrary force unit) + inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; - SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, - const std::vector& heights, const Config& config, std::function throw_on_cancel); - const std::vector& output() { return m_output; } + SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, + const std::vector& heights, const Config& config, std::function throw_on_cancel); + const std::vector& output() { return m_output; } -private: - std::vector m_output; - std::vector m_normals; - TriangleMesh mesh; - static float angle_from_normal(const stl_normal& normal) { return acos((-normal.normalized())(2)); } - float get_required_density(float angle) const; - float distance_limit(float angle) const; - static float approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2); - std::vector> find_islands(const std::vector& slices, const std::vector& heights) const; - void sprinkle_mesh(const TriangleMesh& mesh); - std::vector uniformly_cover(const std::pair& island); - void project_upward_onto_mesh(std::vector& points) const; + struct MyLayer; + struct Structure { + Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : + layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h) #ifdef SLA_AUTOSUPPORTS_DEBUG - void output_expolygons(const ExPolygons& expolys, std::string filename) const; + , unique_id(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) +#endif /* SLA_AUTOSUPPORTS_DEBUG */ + {} + MyLayer *layer; + const ExPolygon* polygon = nullptr; + const BoundingBox bbox; + const Vec2f centroid = Vec2f::Zero(); + const float area = 0.f; + float height = 0; + // How well is this ExPolygon held to the print base? + // Positive number, the higher the better. + float supports_force_this_layer = 0.f; + float supports_force_inherited = 0.f; + float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; } +#ifdef SLA_AUTOSUPPORTS_DEBUG + std::chrono::milliseconds unique_id; #endif /* SLA_AUTOSUPPORTS_DEBUG */ + struct Link { + Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {} + Structure *island; + float overlap_area; + }; + +#ifdef NDEBUG + // In release mode, use the optimized container. + boost::container::small_vector islands_above; + boost::container::small_vector islands_below; +#else + // In debug mode, use the standard vector, which is well handled by debugger visualizer. + std::vector islands_above; + std::vector islands_below; +#endif + ExPolygons dangling_areas; + ExPolygons overhangs; + float overhangs_area; + + bool overlaps(const Structure &rhs) const { + return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon)); + } + float overlap_area(const Structure &rhs) const { + double out = 0.; + if (this->bbox.overlap(rhs.bbox)) { + Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false); + for (const Polygon &poly : polys) + out += poly.area(); + } + return float(out); + } + float area_below() const { + float area = 0.f; + for (const Link &below : this->islands_below) + area += below.island->area; + return area; + } + Polygons polygons_below() const { + size_t cnt = 0; + for (const Link &below : this->islands_below) + cnt += 1 + below.island->polygon->holes.size(); + Polygons out; + out.reserve(cnt); + for (const Link &below : this->islands_below) { + out.emplace_back(below.island->polygon->contour); + append(out, below.island->polygon->holes); + } + return out; + } + ExPolygons expolygons_below() const { + ExPolygons out; + out.reserve(this->islands_below.size()); + for (const Link &below : this->islands_below) + out.emplace_back(*below.island->polygon); + return out; + } + // Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added. + float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); } + }; + + struct MyLayer { + MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {} + size_t layer_id; + coordf_t print_z; + std::vector islands; + }; + + struct RichSupportPoint { + Vec3f position; + Structure *island; + }; + + struct PointGrid3D { + struct GridHash { + std::size_t operator()(const Vec3i &cell_id) const { + return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593) ^ std::hash()(cell_id.z() * 7919); + } + }; + typedef std::unordered_multimap Grid; + + Vec3f cell_size; + Grid grid; + + Vec3i cell_id(const Vec3f &pos) { + return Vec3i(int(floor(pos.x() / cell_size.x())), + int(floor(pos.y() / cell_size.y())), + int(floor(pos.z() / cell_size.z()))); + } + + void insert(const Vec2f &pos, Structure *island) { + RichSupportPoint pt; + pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z)); + pt.island = island; + grid.emplace(cell_id(pt.position), pt); + } + + bool collides_with(const Vec2f &pos, Structure *island, float radius) { + Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z)); + Vec3i cell = cell_id(pos3d); + std::pair it_pair = grid.equal_range(cell); + if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) + return true; + for (int i = -1; i < 2; ++ i) + for (int j = -1; j < 2; ++ j) + for (int k = -1; k < 1; ++ k) { + if (i == 0 && j == 0 && k == 0) + continue; + it_pair = grid.equal_range(cell + Vec3i(i, j, k)); + if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) + return true; + } + return false; + } + + private: + bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) { + for (Grid::const_iterator it = it_begin; it != it_end; ++ it) { + float dist2 = (it->second.position - pos).squaredNorm(); + if (dist2 < radius * radius) + return true; + } + return false; + } + }; + +private: + std::vector m_output; + SLAAutoSupports::Config m_config; + + float m_supports_force_total = 0.f; + + void process(const std::vector& slices, const std::vector& heights); + void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false); + void project_onto_mesh(std::vector& points) const; + +#ifdef SLA_AUTOSUPPORTS_DEBUG + static void output_expolygons(const ExPolygons& expolys, const std::string &filename); + static void output_structures(const std::vector &structures); +#endif // SLA_AUTOSUPPORTS_DEBUG + std::function m_throw_on_cancel; - const Eigen::MatrixXd& m_V; - const Eigen::MatrixXi& m_F; + const sla::EigenMesh3D& m_emesh; }; diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp new file mode 100644 index 000000000..f7c0acf33 --- /dev/null +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -0,0 +1,137 @@ +#ifndef SLACOMMON_HPP +#define SLACOMMON_HPP + +#include + +// #define SLIC3R_SLA_NEEDS_WINDTREE + +namespace Slic3r { + +// Typedefs from Point.hpp +typedef Eigen::Matrix Vec3f; +typedef Eigen::Matrix Vec3d; + +class TriangleMesh; + +namespace sla { + +struct SupportPoint { + Vec3f pos; + float head_front_radius; + bool is_new_island; + + SupportPoint() : + pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {} + + SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) : + pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {} + + SupportPoint(Vec3f position, float head_radius, bool new_island) : + pos(position), head_front_radius(head_radius), is_new_island(new_island) {} + + SupportPoint(Eigen::Matrix data) : + pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {} + + bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } + bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } +}; + + +/// An index-triangle structure for libIGL functions. Also serves as an +/// alternative (raw) input format for the SLASupportTree +/*struct EigenMesh3D { + Eigen::MatrixXd V; + Eigen::MatrixXi F; + double ground_level = 0; +};*/ + +/// An index-triangle structure for libIGL functions. Also serves as an +/// alternative (raw) input format for the SLASupportTree +class EigenMesh3D { + class AABBImpl; + + Eigen::MatrixXd m_V; + Eigen::MatrixXi m_F; + double m_ground_level = 0; + + std::unique_ptr m_aabb; +public: + + EigenMesh3D(const TriangleMesh&); + EigenMesh3D(const EigenMesh3D& other); + EigenMesh3D& operator=(const EigenMesh3D&); + + ~EigenMesh3D(); + + inline double ground_level() const { return m_ground_level; } + + inline const Eigen::MatrixXd& V() const { return m_V; } + inline const Eigen::MatrixXi& F() const { return m_F; } + + // Result of a raycast + class hit_result { + double m_t = std::numeric_limits::infinity(); + int m_face_id = -1; + const EigenMesh3D& m_mesh; + Vec3d m_dir; + inline hit_result(const EigenMesh3D& em): m_mesh(em) {} + friend class EigenMesh3D; + public: + + inline double distance() const { return m_t; } + inline const Vec3d& direction() const { return m_dir; } + inline int face() const { return m_face_id; } + + inline Vec3d normal() const { + if(m_face_id < 0) return {}; + auto trindex = m_mesh.m_F.row(m_face_id); + const Vec3d& p1 = m_mesh.V().row(trindex(0)); + const Vec3d& p2 = m_mesh.V().row(trindex(1)); + const Vec3d& p3 = m_mesh.V().row(trindex(2)); + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + return U.cross(V).normalized(); + } + + inline bool is_inside() { + return m_face_id >= 0 && normal().dot(m_dir) > 0; + } + }; + + // Casting a ray on the mesh, returns the distance where the hit occures. + hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; + + class si_result { + double m_value; + int m_fidx; + Vec3d m_p; + si_result(double val, int i, const Vec3d& c): + m_value(val), m_fidx(i), m_p(c) {} + friend class EigenMesh3D; + public: + + si_result() = delete; + + double value() const { return m_value; } + operator double() const { return m_value; } + const Vec3d& point_on_mesh() const { return m_p; } + int F_idx() const { return m_fidx; } + }; + +#ifdef SLIC3R_SLA_NEEDS_WINDTREE + // The signed distance from a point to the mesh. Outputs the distance, + // the index of the triangle and the closest point in mesh coordinate space. + si_result signed_distance(const Vec3d& p) const; + + bool inside(const Vec3d& p) const; +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ +}; + + + + +} // namespace sla +} // namespace Slic3r + + +#endif // SLASUPPORTTREE_HPP \ No newline at end of file diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 5a5187093..3e36dfd85 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -550,10 +550,16 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -PointSet to_point_set(const std::vector &v) +PointSet to_point_set(const std::vector &v) { PointSet ret(v.size(), 3); - { long i = 0; for(const Vec3d& p : v) ret.row(i++) = p; } + long i = 0; + for(const SupportPoint& support_point : v) { + ret.row(i)(0) = support_point.pos(0); + ret.row(i)(1) = support_point.pos(1); + ret.row(i)(2) = support_point.pos(2); + ++i; + } return ret; } @@ -671,6 +677,7 @@ double pinhead_mesh_intersect(const Vec3d& s, return *mit; } + // Checking bridge (pillar and stick as well) intersection with the model. If // the function is used for headless sticks, the ins_check parameter have to be // true as the beginning of the stick might be inside the model geometry. diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 7d23a981f..c29b2a571 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -7,6 +7,9 @@ #include #include +#include "SLACommon.hpp" + + namespace Slic3r { // Needed types from Point.hpp @@ -105,86 +108,6 @@ struct Controller { std::function cancelfn = [](){}; }; -/// An index-triangle structure for libIGL functions. Also serves as an -/// alternative (raw) input format for the SLASupportTree -class EigenMesh3D { - class AABBImpl; - - Eigen::MatrixXd m_V; - Eigen::MatrixXi m_F; - double m_ground_level = 0; - - std::unique_ptr m_aabb; -public: - - EigenMesh3D(const TriangleMesh&); - EigenMesh3D(const EigenMesh3D& other); - EigenMesh3D& operator=(const EigenMesh3D&); - - ~EigenMesh3D(); - - inline double ground_level() const { return m_ground_level; } - - inline const Eigen::MatrixXd& V() const { return m_V; } - inline const Eigen::MatrixXi& F() const { return m_F; } - - // Result of a raycast - class hit_result { - double m_t = std::numeric_limits::infinity(); - int m_face_id = -1; - const EigenMesh3D& m_mesh; - Vec3d m_dir; - inline hit_result(const EigenMesh3D& em): m_mesh(em) {} - friend class EigenMesh3D; - public: - - inline double distance() const { return m_t; } - - inline int face() const { return m_face_id; } - - inline Vec3d normal() const { - if(m_face_id < 0) return {}; - auto trindex = m_mesh.m_F.row(m_face_id); - const Vec3d& p1 = m_mesh.V().row(trindex(0)); - const Vec3d& p2 = m_mesh.V().row(trindex(1)); - const Vec3d& p3 = m_mesh.V().row(trindex(2)); - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - return U.cross(V).normalized(); - } - - inline bool is_inside() { - return m_face_id >= 0 && normal().dot(m_dir) > 0; - } - }; - - // Casting a ray on the mesh, returns the distance where the hit occures. - hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; - - class si_result { - double m_value; - int m_fidx; - Vec3d m_p; - si_result(double val, int i, const Vec3d& c): - m_value(val), m_fidx(i), m_p(c) {} - friend class EigenMesh3D; - public: - - si_result() = delete; - - double value() const { return m_value; } - operator double() const { return m_value; } - const Vec3d& point_on_mesh() const { return m_p; } - int F_idx() const { return m_fidx; } - }; - - // The signed distance from a point to the mesh. Outputs the distance, - // the index of the triangle and the closest point in mesh coordinate space. - si_result signed_distance(const Vec3d& p) const; - - bool inside(const Vec3d& p) const; -}; - using PointSet = Eigen::MatrixXd; //EigenMesh3D to_eigenmesh(const TriangleMesh& m); @@ -193,7 +116,7 @@ using PointSet = Eigen::MatrixXd; //EigenMesh3D to_eigenmesh(const ModelObject& model); // Simple conversion of 'vector of points' to an Eigen matrix -PointSet to_point_set(const std::vector&); +PointSet to_point_set(const std::vector&); /* ************************************************************************** */ diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index d3af1eac8..6a3b71e7d 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -95,7 +95,9 @@ size_t SpatIndex::size() const class EigenMesh3D::AABBImpl: public igl::AABB { public: +#ifdef SLIC3R_SLA_NEEDS_WINDTREE igl::WindingNumberAABB windtree; +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ }; EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { @@ -136,7 +138,9 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { // Build the AABB accelaration tree m_aabb->init(m_V, m_F); +#ifdef SLIC3R_SLA_NEEDS_WINDTREE m_aabb->windtree.set_mesh(m_V, m_F); +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ } EigenMesh3D::~EigenMesh3D() {} @@ -168,6 +172,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const return ret; } +#ifdef SLIC3R_SLA_NEEDS_WINDTREE EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { double sign = 0; double sqdst = 0; int i = 0; Vec3d c; igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree, @@ -179,6 +184,7 @@ EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { bool EigenMesh3D::inside(const Vec3d &p) const { return m_aabb->windtree.inside(p); } +#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ /* **************************************************************************** * Misc functions diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 142428f1d..f25087cbe 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -25,7 +25,7 @@ using SupportTreePtr = std::unique_ptr; class SLAPrintObject::SupportData { public: sla::EigenMesh3D emesh; // index-triangle representation - sla::PointSet support_points; // all the support points (manual/auto) + std::vector support_points; // all the support points (manual/auto) SupportTreePtr support_tree_ptr; // the supports SlicedSupports support_slices; // sliced supports std::vector level_ids; @@ -354,14 +354,18 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf std::vector new_instances = sla_instances(model_object); if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) { // The SLAPrintObject is already there. - if (new_instances != it_print_object_status->print_object->instances()) { - // Instances changed. - it_print_object_status->print_object->set_instances(new_instances); - update_apply_status(this->invalidate_step(slapsRasterize)); - } - print_objects_new.emplace_back(it_print_object_status->print_object); - const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; - } else { + if (new_instances.empty()) { + const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; + } else { + if (new_instances != it_print_object_status->print_object->instances()) { + // Instances changed. + it_print_object_status->print_object->set_instances(new_instances); + update_apply_status(this->invalidate_step(slapsRasterize)); + } + print_objects_new.emplace_back(it_print_object_status->print_object); + const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; + } + } else if (! new_instances.empty()) { auto print_object = new SLAPrintObject(this, &model_object); // FIXME: this invalidates the transformed mesh in SLAPrintObject @@ -472,7 +476,7 @@ void SLAPrint::process() const size_t objcount = m_objects.size(); const unsigned min_objstatus = 0; // where the per object operations start - const unsigned max_objstatus = 80; // where the per object operations end + const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize]; // where the per object operations end // the coefficient that multiplies the per object status values which // are set up for <0, 100>. They need to be scaled into the whole process @@ -532,9 +536,8 @@ void SLAPrint::process() this->throw_if_canceled(); SLAAutoSupports::Config config; const SLAPrintObjectConfig& cfg = po.config(); - config.minimal_z = float(cfg.support_minimal_z); - config.density_at_45 = cfg.support_density_at_45 / 10000.f; - config.density_at_horizontal = cfg.support_density_at_horizontal / 10000.f; + config.density_relative = float(cfg.support_points_density_relative / 100.f); // the config value is in percents + config.minimal_distance = float(cfg.support_points_minimal_distance); // Construction of this object does the calculation. this->throw_if_canceled(); @@ -546,17 +549,19 @@ void SLAPrint::process() [this]() { throw_if_canceled(); }); // Now let's extract the result. - const std::vector& points = auto_supports.output(); + const std::vector& points = auto_supports.output(); this->throw_if_canceled(); - po.m_supportdata->support_points = sla::to_point_set(points); + po.m_supportdata->support_points = points; BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " - << po.m_supportdata->support_points.rows(); + << po.m_supportdata->support_points.size(); + + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports + report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { // There are some points on the front-end, no calculation will be done. - po.m_supportdata->support_points = - sla::to_point_set(po.transformed_support_points()); + po.m_supportdata->support_points = po.transformed_support_points(); } }; @@ -587,6 +592,8 @@ void SLAPrint::process() ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) { + //FIXME this status line scaling does not seem to be correct. + // How does it account for an increasing object index? report_status(*this, int(init + st*d), msg); }; @@ -594,7 +601,7 @@ void SLAPrint::process() ctl.cancelfn = [this]() { throw_if_canceled(); }; po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(po.m_supportdata->support_points, + new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points), po.m_supportdata->emesh, scfg, ctl)); // Create the unified mesh @@ -605,7 +612,7 @@ void SLAPrint::process() po.m_supportdata->support_tree_ptr->merged_mesh(); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " - << po.m_supportdata->support_points.rows(); + << po.m_supportdata->support_points.size(); // Check the mesh for later troubleshooting. if(po.support_mesh().empty()) @@ -883,16 +890,6 @@ void SLAPrint::process() using slaposFn = std::function; using slapsFn = std::function; - // This is the actual order of steps done on each PrintObject - std::array objectsteps = { - slaposObjectSlice, // SupportPoints will need this step - slaposSupportPoints, - slaposSupportTree, - slaposBasePool, - slaposSliceSupports, - slaposIndexSlices - }; - std::array pobj_program = { slice_model, @@ -916,28 +913,32 @@ void SLAPrint::process() // TODO: this loop could run in parallel but should not exhaust all the CPU // power available - for(SLAPrintObject * po : m_objects) { + // Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects. + std::vector step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount }; + for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) { + for(SLAPrintObject * po : m_objects) { - BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; + BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; - for(size_t s = 0; s < objectsteps.size(); ++s) { - auto currentstep = objectsteps[s]; + for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) { + auto currentstep = (SLAPrintObjectStep)s; - // Cancellation checking. Each step will check for cancellation - // on its own and return earlier gracefully. Just after it returns - // execution gets to this point and throws the canceled signal. - throw_if_canceled(); - - st += unsigned(incr * ostepd); - - if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { - report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); - pobj_program[currentstep](*po); + // Cancellation checking. Each step will check for cancellation + // on its own and return earlier gracefully. Just after it returns + // execution gets to this point and throws the canceled signal. throw_if_canceled(); - po->set_done(currentstep); - } - incr = OBJ_STEP_LEVELS[currentstep]; + st += unsigned(incr * ostepd); + + if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { + report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); + pobj_program[currentstep](*po); + throw_if_canceled(); + po->set_done(currentstep); + } + + incr = OBJ_STEP_LEVELS[currentstep]; + } } } @@ -1062,7 +1063,10 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; } -const Eigen::MatrixXd& SLAPrintObject::get_support_points() const +const std::vector& SLAPrintObject::get_support_points() const { return m_supportdata->support_points; } @@ -1244,15 +1248,19 @@ const TriangleMesh &SLAPrintObject::transformed_mesh() const { return m_transformed_rmesh.get(); } -std::vector SLAPrintObject::transformed_support_points() const +std::vector SLAPrintObject::transformed_support_points() const { assert(m_model_object != nullptr); - auto& spts = m_model_object->sla_support_points; + std::vector& spts = m_model_object->sla_support_points; // this could be cached as well - std::vector ret; ret.reserve(spts.size()); + std::vector ret; + ret.reserve(spts.size()); - for(auto& sp : spts) ret.emplace_back( trafo() * Vec3d(sp.cast())); + for(sla::SupportPoint& sp : spts) { + Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); + ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); + } return ret; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 21503c6f6..9aaba6a47 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -70,7 +70,7 @@ public: // This will return the transformed mesh which is cached const TriangleMesh& transformed_mesh() const; - std::vector transformed_support_points() const; + std::vector transformed_support_points() const; // Get the needed Z elevation for the model geometry if supports should be // displayed. This Z offset should also be applied to the support @@ -91,7 +91,7 @@ public: const std::vector& get_support_slices() const; // This method returns the support points of this SLAPrintObject. - const Eigen::MatrixXd& get_support_points() const; + const std::vector& get_support_points() const; // An index record referencing the slices // (get_model_slices(), get_support_slices()) where the keys are the height diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index b26534c54..31ae19f36 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -23,8 +23,6 @@ // Scene's GUI made using imgui library #define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1) #define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI) -// Modified Sla support gizmo -#define ENABLE_SLA_SUPPORT_GIZMO_MOD (1 && ENABLE_1_42_0_ALPHA1) // Use wxDataViewRender instead of wxDataViewCustomRenderer #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp index f4ce0703d..febb3d0e7 100644 --- a/src/libslic3r/Tesselate.cpp +++ b/src/libslic3r/Tesselate.cpp @@ -20,7 +20,7 @@ public: gluDeleteTess(m_tesselator); } - Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_) + std::vector tesselate3d(const ExPolygon &expoly, double z_, bool flipped_) { m_z = z_; m_flipped = flipped_; @@ -56,7 +56,7 @@ public: return std::move(m_output_triangles); } - Pointf3s tesselate(const ExPolygons &expolygons, double z_, bool flipped_) + std::vector tesselate3d(const ExPolygons &expolygons, double z_, bool flipped_) { m_z = z_; m_flipped = flipped_; @@ -189,16 +189,60 @@ private: bool m_flipped; }; -Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z, bool flip) +std::vector triangulate_expolygon_3d(const ExPolygon &poly, coordf_t z, bool flip) { GluTessWrapper tess; - return tess.tesselate(poly, z, flip); + return tess.tesselate3d(poly, z, flip); } -Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z, bool flip) +std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z, bool flip) { GluTessWrapper tess; - return tess.tesselate(polys, z, flip); + return tess.tesselate3d(polys, z, flip); +} + +std::vector triangulate_expolygon_2d(const ExPolygon &poly, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(poly, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(pt.x(), pt.y()); + return out; +} + +std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(polys, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(pt.x(), pt.y()); + return out; +} + +std::vector triangulate_expolygon_2f(const ExPolygon &poly, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(poly, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(float(pt.x()), float(pt.y())); + return out; +} + +std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip) +{ + GluTessWrapper tess; + std::vector triangles = tess.tesselate3d(polys, 0, flip); + std::vector out; + out.reserve(triangles.size()); + for (const Vec3d &pt : triangles) + out.emplace_back(float(pt.x()), float(pt.y())); + return out; } } // namespace Slic3r diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index ed12df888..02e86eb33 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -10,8 +10,12 @@ namespace Slic3r { class ExPolygon; typedef std::vector ExPolygons; -extern Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z = 0, bool flip = false); -extern Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z = 0, bool flip = false); +extern std::vector triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false); +extern std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false); +extern std::vector triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false); +extern std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false); +extern std::vector triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false); +extern std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false); } // namespace Slic3r diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b93ce12b5..0784b44f2 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1781,7 +1781,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3df(section, z, true); + Pointf3s triangles = triangulate_expolygons_3d(section, z, true); stl_facet facet; facet.normal = stl_normal(0, 0, -1.f); for (size_t i = 0; i < triangles.size(); ) { @@ -1795,7 +1795,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3df(section, z, false); + Pointf3s triangles = triangulate_expolygons_3d(section, z, false); stl_facet facet; facet.normal = stl_normal(0, 0, -1.f); for (size_t i = 0; i < triangles.size(); ) { diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 2621aac89..88815d9a6 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -789,7 +789,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glDisable(GL_BLEND)); } -void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface) const +void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, std::function filter_func) const { glsafe(glEnable(GL_BLEND)); glsafe(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -801,7 +801,7 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface) glsafe(glEnableClientState(GL_VERTEX_ARRAY)); glsafe(glEnableClientState(GL_NORMAL_ARRAY)); - GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, std::function()); + GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, filter_func); for (GLVolumeWithZ& volume : to_render) { volume.first->set_render_color(); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 9c4e0f687..46cb5b870 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -456,7 +456,7 @@ public: // Render the volumes by OpenGL. void render_VBOs(ERenderType type, bool disable_cullface, std::function filter_func = std::function()) const; - void render_legacy(ERenderType type, bool disable_cullface) const; + void render_legacy(ERenderType type, bool disable_cullface, std::function filter_func = std::function()) const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e1c5138b4..16a6dd682 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3074,6 +3074,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); float top_y = 0.5f * (cnv_h - height) + m_overlay_border; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) @@ -3356,42 +3357,28 @@ void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -#if ENABLE_SLA_SUPPORT_GIZMO_MOD void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) -#else -void GLCanvas3D::Gizmos::set_model_object_ptr(ModelObject* model_object) -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD { if (!m_enabled) return; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) -#if ENABLE_SLA_SUPPORT_GIZMO_MOD reinterpret_cast(it->second)->set_sla_support_data(model_object, selection); -#else - reinterpret_cast(it->second)->set_model_object_ptr(model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } -void GLCanvas3D::Gizmos::clicked_on_object(const Vec2d& mouse_position) + +// Returns true if the gizmo used the event to do something, false otherwise. +bool GLCanvas3D::Gizmos::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) { if (!m_enabled) - return; + return false; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->clicked_on_object(mouse_position); -} + return reinterpret_cast(it->second)->mouse_event(action, mouse_position, shift_down); -void GLCanvas3D::Gizmos::delete_current_grabber(bool delete_all) -{ - if (!m_enabled) - return; - - GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); - if (it != m_gizmos.end()) - reinterpret_cast(it->second)->delete_current_grabber(delete_all); + return false; } void GLCanvas3D::Gizmos::render_current_gizmo(const GLCanvas3D::Selection& selection) const @@ -4015,6 +4002,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_moving(false) , m_color_by("volume") , m_reload_delayed(false) + , m_render_sla_auxiliaries(true) #if !ENABLE_IMGUI , m_external_gizmo_widgets_parent(nullptr) #endif // not ENABLE_IMGUI @@ -4173,6 +4161,28 @@ int GLCanvas3D::check_volumes_outside_state() const return (int)state; } +void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible) +{ + for (GLVolume* vol : m_volumes.volumes) { + if (vol->composite_id.volume_id < 0) + vol->is_active = visible; + } + + m_render_sla_auxiliaries = visible; +} + +void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx) +{ + for (GLVolume* vol : m_volumes.volumes) { + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) + vol->is_active = visible; + } + if (visible && !mo) + toggle_sla_auxiliaries_visibility(true); +} + + void GLCanvas3D::set_config(const DynamicPrintConfig* config) { m_config = config; @@ -4536,9 +4546,9 @@ void GLCanvas3D::render() // this position is used later into on_mouse() to drag the objects m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); + _render_current_gizmo(); _render_selection_sidebar_hints(); - _render_current_gizmo(); #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); #endif // ENABLE_SHOW_CAMERA_TARGET @@ -5042,6 +5052,7 @@ void GLCanvas3D::bind_event_handlers() m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this); m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key_up, this); m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); @@ -5067,6 +5078,7 @@ void GLCanvas3D::unbind_event_handlers() m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this); m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this); m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this); + m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key_up, this); m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this); m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this); m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this); @@ -5110,7 +5122,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) switch (keyCode) { case 'a': case 'A': - case WXK_CONTROL_A: post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break; + case WXK_CONTROL_A: + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::SelectAll)) // Sla gizmo selects all support points + m_dirty = true; + else + post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); + break; #ifdef __APPLE__ case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. #else /* __APPLE__ */ @@ -5131,7 +5148,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #else /* __APPLE__ */ case WXK_DELETE: #endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::Delete)) + m_dirty = true; + else + post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); + break; + case '0': { select_view("iso"); break; } case '1': { select_view("top"); break; } case '2': { select_view("bottom"); break; } @@ -5168,6 +5190,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) } } +void GLCanvas3D::on_key_up(wxKeyEvent& evt) +{ + // see include/wx/defs.h enum wxKeyCode + int keyCode = evt.GetKeyCode(); + + // shift has been just released - SLA gizmo might want to close rectangular selection. + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && keyCode == WXK_SHIFT && m_gizmos.mouse_event(SLAGizmoEventType::ShiftUp)) + m_dirty = true; +} + void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) { // Ignore the wheel events if the middle button is pressed. @@ -5315,22 +5347,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } } -#if !ENABLE_IMGUI - else if ((m_gizmos.get_current_type() == Gizmos::SlaSupports) && gizmo_reset_rect_contains(*this, pos(0), pos(1))) - { - if (evt.LeftDown()) - { - m_gizmos.delete_current_grabber(true); - m_dirty = true; - } - } -#endif // not ENABLE_IMGUI else if (!m_selection.is_empty() && gizmos_overlay_contains_mouse) { m_gizmos.update_on_off_state(*this, m_mouse.position, m_selection); _update_gizmos_data(); m_dirty = true; } + else if (evt.LeftDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::LeftDown, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + { + // the gizmo got the event and took some action, there is no need to do anything more + } else if (evt.LeftDown() && !m_selection.is_empty() && m_gizmos.grabber_contains_mouse()) { _update_gizmos_data(); @@ -5346,9 +5372,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } - else if ((selected_object_idx != -1) && m_gizmos.grabber_contains_mouse() && evt.RightDown()) { - if (m_gizmos.get_current_type() == Gizmos::SlaSupports) - m_gizmos.delete_current_grabber(); + else if ((selected_object_idx != -1) && evt.RightDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::RightDown)) + { + // event was taken care of by the SlaSupports gizmo } else if (view_toolbar_contains_mouse != -1) { @@ -5453,7 +5479,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } } - else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.move_volume_idx != -1)) + else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) + && (m_mouse.drag.move_volume_idx != -1) && m_gizmos.get_current_type() != Gizmos::SlaSupports /* don't allow dragging objects with the Sla gizmo on */) { #if ENABLE_MOVE_MIN_THRESHOLD if (!m_mouse.drag.move_requires_threshold) @@ -5513,6 +5540,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } + else if (evt.Dragging() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::Dragging, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + { + // the gizmo got the event and took some action, no need to do anything more here + m_dirty = true; + } else if (evt.Dragging() && !gizmos_overlay_contains_mouse) { m_mouse.dragging = true; @@ -5568,6 +5600,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _stop_timer(); m_layers_editing.accept_changes(*this); } + else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && !m_gizmos.is_dragging() + && !m_mouse.dragging && m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + { + // the gizmo got the event and took some action, no need to do anything more + } else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; @@ -5577,16 +5614,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } - else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_hover_volume_id != -1) + else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() + && !is_layers_editing_enabled() && (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown()))) { - int id = m_selection.get_object_idx(); + // SLA gizmo cannot be deselected by clicking in canvas area to avoid inadvertent unselection and losing manual changes + // that's why the mouse_event function was called so that the gizmo can refuse the deselection in manual editing mode - if ((id != -1) && (m_model != nullptr)) { - m_gizmos.clicked_on_object(Vec2d(pos(0), pos(1))); - } - } - else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) - { // deselect and propagate event through callback if (!evt.ShiftDown() && m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) { @@ -5619,10 +5652,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) do_rotate(); break; } - case Gizmos::SlaSupports: - // End of mouse dragging, update the SLAPrint/SLAPrintObjects with the new support points. - post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - break; default: break; } @@ -6509,7 +6538,9 @@ void GLCanvas3D::_render_objects() const m_layers_editing.render_volumes(*this, this->m_volumes); } else { // do not cull backfaces to show broken geometry, if any - m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled); + m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); } m_volumes.render_VBOs(GLVolumeCollection::Transparent, false); m_shader.stop_using(); @@ -6525,7 +6556,9 @@ void GLCanvas3D::_render_objects() const } // do not cull backfaces to show broken geometry, if any - m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled); + m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, [this](const GLVolume& volume) { + return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); + }); m_volumes.render_legacy(GLVolumeCollection::Transparent, false); if (m_use_clipping_planes) @@ -6606,7 +6639,7 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const ::glColor4fv(vol->render_color); } - if (!fake_colors || !vol->disabled) + if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) vol->render(); ++volume_id; @@ -6757,20 +6790,20 @@ void GLCanvas3D::_render_sla_slices() const { // calculate model bottom cap if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) - bottom_obj_triangles = triangulate_expolygons_3df(model_slices[it_min_z->second.model_slices_idx], min_z, true); + bottom_obj_triangles = triangulate_expolygons_3d(model_slices[it_min_z->second.model_slices_idx], min_z, true); // calculate support bottom cap if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) - bottom_sup_triangles = triangulate_expolygons_3df(support_slices[it_min_z->second.support_slices_idx], min_z, true); + bottom_sup_triangles = triangulate_expolygons_3d(support_slices[it_min_z->second.support_slices_idx], min_z, true); } if (it_max_z != index.end()) { // calculate model top cap if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) - top_obj_triangles = triangulate_expolygons_3df(model_slices[it_max_z->second.model_slices_idx], max_z, false); + top_obj_triangles = triangulate_expolygons_3d(model_slices[it_max_z->second.model_slices_idx], max_z, false); // calculate support top cap if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) - top_sup_triangles = triangulate_expolygons_3df(support_slices[it_max_z->second.support_slices_idx], max_z, false); + top_sup_triangles = triangulate_expolygons_3d(support_slices[it_max_z->second.support_slices_idx], max_z, false); } } @@ -6883,11 +6916,7 @@ void GLCanvas3D::_update_gizmos_data() m_gizmos.set_rotation(Vec3d::Zero()); ModelObject* model_object = m_model->objects[m_selection.get_object_idx()]; m_gizmos.set_flattening_data(model_object); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_gizmos.set_sla_support_data(model_object, m_selection); -#else - m_gizmos.set_model_object_ptr(model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } else if (m_selection.is_single_volume() || m_selection.is_single_modifier()) { @@ -6895,22 +6924,14 @@ void GLCanvas3D::_update_gizmos_data() m_gizmos.set_scale(volume->get_volume_scaling_factor()); m_gizmos.set_rotation(Vec3d::Zero()); m_gizmos.set_flattening_data(nullptr); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_gizmos.set_sla_support_data(nullptr, m_selection); -#else - m_gizmos.set_model_object_ptr(nullptr); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } else { m_gizmos.set_scale(Vec3d::Ones()); m_gizmos.set_rotation(Vec3d::Zero()); m_gizmos.set_flattening_data(m_selection.is_from_single_object() ? m_model->objects[m_selection.get_object_idx()] : nullptr); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_gizmos.set_sla_support_data(nullptr, m_selection); -#else - m_gizmos.set_model_object_ptr(nullptr); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2136adca7..680761b37 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -142,6 +142,18 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); #endif // ENABLE_UNIQUE_BED +// this describes events being passed from GLCanvas3D to SlaSupport gizmo +enum class SLAGizmoEventType { + LeftDown = 1, + LeftUp, + RightDown, + Dragging, + Delete, + SelectAll, + ShiftUp +}; + + class GLCanvas3D { struct GCodePreviewVolumeIndex @@ -781,12 +793,8 @@ private: void set_flattening_data(const ModelObject* model_object); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); -#else - void set_model_object_ptr(ModelObject* model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD - void clicked_on_object(const Vec2d& mouse_position); + bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false); void delete_current_grabber(bool delete_all = false); void render_current_gizmo(const Selection& selection) const; @@ -916,6 +924,7 @@ private: bool m_multisample_allowed; bool m_regenerate_volumes; bool m_moving; + bool m_render_sla_auxiliaries; std::string m_color_by; @@ -951,6 +960,9 @@ public: void reset_volumes(); int check_volumes_outside_state() const; + void toggle_sla_auxiliaries_visibility(bool visible); + void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); + void set_config(const DynamicPrintConfig* config); void set_process(BackgroundSlicingProcess* process); void set_model(Model* model); @@ -1048,6 +1060,7 @@ public: void on_size(wxSizeEvent& evt); void on_idle(wxIdleEvent& evt); void on_char(wxKeyEvent& evt); + void on_key_up(wxKeyEvent& evt); void on_mouse_wheel(wxMouseEvent& evt); void on_timer(wxTimerEvent& evt); void on_mouse(wxMouseEvent& evt); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 1660976a1..e7b31e309 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -7,7 +7,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Geometry.hpp" #include "libslic3r/Utils.hpp" -#include "libslic3r/SLA/SLASupportTree.hpp" +#include "libslic3r/SLA/SLACommon.hpp" #include "libslic3r/SLAPrint.hpp" #include @@ -1741,28 +1741,20 @@ Vec3d GLGizmoFlatten::get_flattening_normal() const } GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent) -#if ENABLE_SLA_SUPPORT_GIZMO_MOD : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()), m_quadric(nullptr) -#else - : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()) -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD { -#if ENABLE_SLA_SUPPORT_GIZMO_MOD m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) // using GLU_FILL does not work when the instance's transformation // contains mirroring (normals are reverted) ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD } -#if ENABLE_SLA_SUPPORT_GIZMO_MOD GLGizmoSlaSupports::~GLGizmoSlaSupports() { if (m_quadric != nullptr) ::gluDeleteQuadric(m_quadric); } -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD bool GLGizmoSlaSupports::on_init() { @@ -1782,7 +1774,6 @@ bool GLGizmoSlaSupports::on_init() return true; } -#if ENABLE_SLA_SUPPORT_GIZMO_MOD void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) { m_starting_center = Vec3d::Zero(); @@ -1791,208 +1782,162 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const G if (selection.is_empty()) m_old_instance_id = -1; - if ((model_object != nullptr) && selection.is_from_single_instance()) + m_active_instance = selection.get_instance_idx(); + + if (model_object && selection.is_from_single_instance()) { if (is_mesh_update_necessary()) update_mesh(); // If there are no points, let's ask the backend if it calculated some. - if (model_object->sla_support_points.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints)) { - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == model_object->id()) { - const Eigen::MatrixXd& points = po->get_support_points(); - for (unsigned int i=0; isla_support_points.push_back(Vec3f(po->trafo().inverse().cast() * Vec3f(points(i,0), points(i,1), points(i,2)))); - break; - } - } + if (m_editing_mode_cache.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints)) + get_data_from_backend(); + + if (m_model_object != m_old_model_object) + m_editing_mode = false; + if (m_state == On) { + m_parent.toggle_model_objects_visibility(false); + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); } } } -#else -void GLGizmoSlaSupports::set_model_object_ptr(ModelObject* model_object) -{ - if (model_object != nullptr) { - m_starting_center = Vec3d::Zero(); - m_model_object = model_object; - - int selected_instance = m_parent.get_selection().get_instance_idx(); - assert(selected_instance < (int)model_object->instances.size()); - - m_instance_matrix = model_object->instances[selected_instance]->get_matrix(); - if (is_mesh_update_necessary()) - update_mesh(); - } -} -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const { ::glEnable(GL_BLEND); ::glEnable(GL_DEPTH_TEST); -#if !ENABLE_SLA_SUPPORT_GIZMO_MOD - // the dragged_offset is a vector measuring where was the object moved - // with the gizmo being on. This is reset in set_model_object_ptr and - // does not work correctly when there are multiple copies. - - if (m_starting_center == Vec3d::Zero()) - m_starting_center = selection.get_bounding_box().center(); - Vec3d dragged_offset = selection.get_bounding_box().center() - m_starting_center; -#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD - - - for (auto& g : m_grabbers) { - g.color[0] = 1.f; - g.color[1] = 0.f; - g.color[2] = 0.f; - } - -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - render_grabbers(selection, false); -#else - //::glTranslatef((GLfloat)dragged_offset(0), (GLfloat)dragged_offset(1), (GLfloat)dragged_offset(2)); - render_grabbers(false); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + render_points(selection, false); + render_selection_rectangle(); #if !ENABLE_IMGUI render_tooltip_texture(); #endif // not ENABLE_IMGUI + ::glDisable(GL_BLEND); } +void GLGizmoSlaSupports::render_selection_rectangle() const +{ + if (!m_selection_rectangle_active) + return; + + ::glLineWidth(1.5f); + float render_color[3] = {1.f, 0.f, 0.f}; + ::glColor3fv(render_color); + + ::glPushAttrib(GL_TRANSFORM_BIT); // remember current MatrixMode + + ::glMatrixMode(GL_MODELVIEW); // cache modelview matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glMatrixMode(GL_PROJECTION); // cache projection matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f); // set projection matrix so that world coords = window coords + + // render the selection rectangle (window coordinates): + ::glPushAttrib(GL_ENABLE_BIT); + ::glLineStipple(4, 0xAAAA); + ::glEnable(GL_LINE_STIPPLE); + + ::glBegin(GL_LINE_LOOP); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glEnd(); + ::glPopAttrib(); + + ::glPopMatrix(); // restore former projection matrix + ::glMatrixMode(GL_MODELVIEW); + ::glPopMatrix(); // restore former modelview matrix + ::glPopAttrib(); // restore former MatrixMode +} void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const { ::glEnable(GL_DEPTH_TEST); - for (unsigned int i=0; iget_sla_shift_z(); - - ::glPushMatrix(); - ::glTranslated(0.0, 0.0, z_shift); - - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - ::glMultMatrixd(m.data()); - if (!picking) ::glEnable(GL_LIGHTING); + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_shift = vol->get_sla_shift_z(); + const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); + + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, z_shift); + ::glMultMatrixd(instance_matrix.data()); + float render_color[3]; - for (int i = 0; i < (int)m_grabbers.size(); ++i) + for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) { - // first precalculate the grabber position in world coordinates, so that the grabber - // is not scaled with the object (as it would be if rendered with current gl matrix). - Eigen::Matrix glmatrix; - glGetFloatv (GL_MODELVIEW_MATRIX, glmatrix.data()); - Eigen::Matrix grabber_pos; - for (int j=0; j<3; ++j) - grabber_pos(j) = m_grabbers[i].center(j); - grabber_pos[3] = 1.f; - Eigen::Matrix grabber_world_position = glmatrix * grabber_pos; + const sla::SupportPoint& support_point = m_editing_mode_cache[i].first; + const bool& point_selected = m_editing_mode_cache[i].second; - if (!picking && (m_hover_id == i)) - { - render_color[0] = 1.0f - m_grabbers[i].color[0]; - render_color[1] = 1.0f - m_grabbers[i].color[1]; - render_color[2] = 1.0f - m_grabbers[i].color[2]; + // First decide about the color of the point. + if (picking) { + render_color[0] = 1.0f; + render_color[1] = 1.0f; + render_color[2] = picking_color_component(i); + } + else { + if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active + render_color[0] = 0.f; + render_color[1] = 1.0f; + render_color[2] = 1.0f; + } + else { // neigher hover nor picking + bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island; + if (m_editing_mode) { + render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); + render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); + render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f); + } + else + for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; + } } - else - ::memcpy((void*)render_color, (const void*)m_grabbers[i].color, 3 * sizeof(float)); - ::glColor3fv(render_color); + float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + + // Now render the sphere. Inverse matrix of the instance scaling is applied so that the + // sphere does not scale with the object. ::glPushMatrix(); - ::glLoadIdentity(); - ::glTranslated(grabber_world_position(0), grabber_world_position(1), grabber_world_position(2) + z_shift); - const float diameter = 0.8f; - ::gluSphere(m_quadric, diameter/2.f, 64, 36); + ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); + ::glMultMatrixd(instance_scaling_matrix_inverse.data()); + ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36); ::glPopMatrix(); } + { + // Reset emissive component to zero (the default value) + float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f }; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + } + if (!picking) ::glDisable(GL_LIGHTING); ::glPopMatrix(); } -#else -void GLGizmoSlaSupports::render_grabbers(bool picking) const -{ - if (m_parent.get_selection().is_empty()) - return; - - float z_shift = m_parent.get_selection().get_volume(0)->get_sla_shift_z(); - ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)z_shift); - - int selected_instance = m_parent.get_selection().get_instance_idx(); - assert(selected_instance < (int)m_model_object->instances.size()); - - float render_color_inactive[3] = { 0.5f, 0.5f, 0.5f }; - - for (const ModelInstance* inst : m_model_object->instances) { - bool active = inst == m_model_object->instances[selected_instance]; - if (picking && ! active) - continue; - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - if (!m_grabbers[i].enabled) - continue; - - float render_color[3]; - if (! picking && active && m_hover_id == i) { - render_color[0] = 1.0f - m_grabbers[i].color[0]; - render_color[1] = 1.0f - m_grabbers[i].color[1]; - render_color[2] = 1.0f - m_grabbers[i].color[2]; - } - else - ::memcpy((void*)render_color, active ? (const void*)m_grabbers[i].color : (const void*)render_color_inactive, 3 * sizeof(float)); - if (!picking) - ::glEnable(GL_LIGHTING); - ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]); - ::glPushMatrix(); - Vec3d center = inst->get_matrix() * m_grabbers[i].center; - ::glTranslatef((GLfloat)center(0), (GLfloat)center(1), (GLfloat)center(2)); - GLUquadricObj *quadric; - quadric = ::gluNewQuadric(); - ::gluQuadricDrawStyle(quadric, GLU_FILL ); - ::gluSphere( quadric , 0.4, 64 , 32 ); - ::gluDeleteQuadric(quadric); - ::glPopMatrix(); - if (!picking) - ::glDisable(GL_LIGHTING); - } - } - - ::glTranslatef((GLfloat)0, (GLfloat)0, (GLfloat)-z_shift); -} -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD bool GLGizmoSlaSupports::is_mesh_update_necessary() const { -#if ENABLE_SLA_SUPPORT_GIZMO_MOD return (m_state == On) && (m_model_object != nullptr) && (m_model_object != m_old_model_object) && !m_model_object->instances.empty(); -#else - return m_state == On && m_model_object && !m_model_object->instances.empty() && !m_instance_matrix.isApprox(m_source_data.matrix); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) // return false; @@ -2027,27 +1972,14 @@ void GLGizmoSlaSupports::update_mesh() m_AABB = igl::AABB(); m_AABB.init(m_V, m_F); -#if !ENABLE_SLA_SUPPORT_GIZMO_MOD - m_source_data.matrix = m_instance_matrix; -#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD - - // we'll now reload Grabbers (selection might have changed): - m_grabbers.clear(); - - for (const Vec3f& point : m_model_object->sla_support_points) { - m_grabbers.push_back(Grabber()); - m_grabbers.back().center = point.cast(); - } + // we'll now reload support points (selection might have changed): + editing_mode_reload_cache(); } Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: -#if ENABLE_SLA_SUPPORT_GIZMO_MOD if (m_V.size() == 0) -#else - if (m_V.size() == 0 || is_mesh_update_necessary()) -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD update_mesh(); Eigen::Matrix viewport; @@ -2064,20 +1996,15 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) igl::Hit hit; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD const GLCanvas3D::Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); double z_offset = volume->get_sla_shift_z(); -#else - double z_offset = m_parent.get_selection().get_volume(0)->get_sla_shift_z(); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + point1(2) -= z_offset; point2(2) -= z_offset; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD + Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); -#else - Transform3d inv = m_instance_matrix.inverse(); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + point1 = inv * point1; point2 = inv * point2; @@ -2089,68 +2016,174 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); } -void GLGizmoSlaSupports::clicked_on_object(const Vec2d& mouse_position) +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) { -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - int instance_id = m_parent.get_selection().get_instance_idx(); - if (m_old_instance_id != instance_id) - { - bool something_selected = (m_old_instance_id != -1); - m_old_instance_id = instance_id; - if (something_selected) - return; + if (!m_editing_mode) + return false; + + // left down - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && shift_down) { + if (m_hover_id == -1) { + m_selection_rectangle_active = true; + m_selection_rectangle_start_corner = mouse_position; + m_selection_rectangle_end_corner = mouse_position; + m_canvas_width = m_parent.get_canvas_size().get_width(); + m_canvas_height = m_parent.get_canvas_size().get_height(); + } + else + select_point(m_hover_id); + + return true; } - if (instance_id == -1) - return; -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD - Vec3f new_pos; - try { - new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new grabber in that case + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) { + m_selection_rectangle_end_corner = mouse_position; + return true; } - catch (...) { return; } - m_grabbers.push_back(Grabber()); - m_grabbers.back().center = new_pos.cast(); - m_model_object->sla_support_points.push_back(new_pos); + // mouse up without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) { + if (m_ignore_up_event) { + m_ignore_up_event = false; + return false; + } - // This should trigger the support generation - // wxGetApp().plater()->reslice(); + int instance_id = m_parent.get_selection().get_instance_idx(); + if (m_old_instance_id != instance_id) + { + bool something_selected = (m_old_instance_id != -1); + m_old_instance_id = instance_id; + if (something_selected) + return false; + } + if (instance_id == -1) + return false; - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + // Regardless of whether the user clicked the object or not, we will unselect all points: + select_point(NoPoints); + + Vec3f new_pos; + try { + new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case + m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), true)); + m_unsaved_changes = true; + } + catch (...) { // not clicked on object + return true; // prevents deselection of the gizmo by GLCanvas3D + } + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp) + && m_selection_rectangle_active) { + if (action == SLAGizmoEventType::ShiftUp) + m_ignore_up_event = true; + const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); + GLint viewport[4]; + ::glGetIntegerv(GL_VIEWPORT, viewport); + GLdouble modelview_matrix[16]; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); + GLdouble projection_matrix[16]; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); + + const GLCanvas3D::Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); + + // bounding box created from the rectangle corners - will take care of order of the corners + BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())}); + + const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); + // we'll recover current look direction from the modelview matrix (in world coords)... + Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); + // ...and transform it to model coords. + direction_to_camera = instance_matrix_no_translation.inverse().cast() * direction_to_camera.eval(); + + // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: + for (std::pair& point_and_selection : m_editing_mode_cache) { + const sla::SupportPoint& support_point = point_and_selection.first; + Vec3f pos = instance_matrix.cast() * support_point.pos; + pos(2) += z_offset; + GLdouble out_x, out_y, out_z; + ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); + out_y = m_canvas_height - out_y; + + if (rectangle.contains(Point(out_x, out_y))) { + bool is_obscured = false; + // Cast a ray in the direction of the camera and look for intersection with the mesh: + std::vector hits; + if (m_AABB.intersect_ray(m_V, m_F, support_point.pos, direction_to_camera, hits)) + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (hits.size() > 1 || hits.front().t > 0.001f) + is_obscured = true; + + if (!is_obscured) + point_and_selection.second = true; + } + } + m_selection_rectangle_active = false; + return true; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + + return false; } -void GLGizmoSlaSupports::delete_current_grabber(bool delete_all) +void GLGizmoSlaSupports::delete_selected_points() { - if (delete_all) { - m_grabbers.clear(); - m_model_object->sla_support_points.clear(); - - // This should trigger the support generation - // wxGetApp().plater()->reslice(); - } - else - if (m_hover_id != -1) { - m_grabbers.erase(m_grabbers.begin() + m_hover_id); - m_model_object->sla_support_points.erase(m_model_object->sla_support_points.begin() + m_hover_id); - m_hover_id = -1; + if (!m_editing_mode) + return; + for (unsigned int idx=0; idxreslice(); - } - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } + + //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) { - if (m_hover_id != -1 && data.mouse_pos) { + if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) { Vec3f new_pos; try { new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); } catch (...) { return; } - m_grabbers[m_hover_id].center = new_pos.cast(); - m_model_object->sla_support_points[m_hover_id] = new_pos; + m_editing_mode_cache[m_hover_id].first.pos = new_pos; + m_editing_mode_cache[m_hover_id].first.is_new_island = false; + m_unsaved_changes = true; // Do not update immediately, wait until the mouse is released. // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -2196,37 +2229,165 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove |/* ImGuiWindowFlags_NoResize | */ImGuiWindowFlags_NoCollapse); ImGui::PushItemWidth(100.0f); - m_imgui->text(_(L("Left mouse click - add point"))); - m_imgui->text(_(L("Right mouse click - remove point"))); - m_imgui->text(" "); - bool generate = m_imgui->button(_(L("Generate points automatically"))); - bool remove_all_clicked = m_imgui->button(_(L("Remove all points")) + (m_model_object == nullptr ? "" : " (" + std::to_string(m_model_object->sla_support_points.size())+")")); + bool force_refresh = false; + bool remove_selected = false; + bool old_editing_state = m_editing_mode; + + if (m_editing_mode) { + m_imgui->text(_(L("Left mouse click - add point"))); + m_imgui->text(_(L("Right mouse click - remove point"))); + m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); + m_imgui->text(" "); // vertical gap + + std::vector options = {"0.2", "0.4", "0.6", "0.8", "1.0"}; + std::stringstream ss; + ss << std::setprecision(1) << m_new_point_head_diameter; + wxString str = ss.str(); + + bool old_combo_state = m_combo_box_open; + m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options, str); + force_refresh |= (old_combo_state != m_combo_box_open); + + float current_number = atof(str); + if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change) + for (auto& point_and_selection : m_editing_mode_cache) + if (point_and_selection.second) { + point_and_selection.first.head_front_radius = current_number / 2.f; + m_unsaved_changes = true; + } + + if (std::abs(current_number - m_new_point_head_diameter) > 0.001) { + force_refresh = true; + m_new_point_head_diameter = current_number; + } + + bool changed = m_lock_unique_islands; + m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands); + force_refresh |= changed != m_lock_unique_islands; + + remove_selected = m_imgui->button(_(L("Remove selected points"))); + + m_imgui->text(" "); // vertical gap + + bool apply_changes = m_imgui->button(_(L("Apply changes"))); + if (apply_changes) { + editing_mode_apply_changes(); + force_refresh = true; + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } + ImGui::SameLine(); + bool discard_changes = m_imgui->button(_(L("Discard changes"))); + if (discard_changes) { + editing_mode_discard_changes(); + force_refresh = true; + } + } + else { + /* ImGui::PushItemWidth(50.0f); + m_imgui->text(_(L("Minimal points distance: "))); + ImGui::SameLine(); + bool value_changed = ImGui::InputDouble("mm", &m_minimal_point_distance, 0.0f, 0.0f, "%.2f"); + m_imgui->text(_(L("Support points density: "))); + ImGui::SameLine(); + value_changed |= ImGui::InputDouble("%", &m_density, 0.0f, 0.0f, "%.f");*/ + + bool generate = m_imgui->button(_(L("Auto-generate points"))); + + if (generate) { +#if SLAGIZMO_IMGUI_MODAL + ImGui::OpenPopup(_(L("Warning"))); + m_show_modal = true; + force_refresh = true; +#else + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L( + "Autogeneration will erase all manually edited points.\n\n" + "Are you sure you want to do it?\n" + )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); + if (m_model_object->sla_support_points.empty() || dlg.ShowModal() == wxID_YES) { + m_model_object->sla_support_points.clear(); + m_editing_mode_cache.clear(); + wxGetApp().plater()->reslice(); + } +#endif + } +#if SLAGIZMO_IMGUI_MODAL + if (m_show_modal) { + if (ImGui::BeginPopupModal(_(L("Warning")), &m_show_modal/*, ImGuiWindowFlags_NoDecoration*/)) + { + m_imgui->text(_(L("Autogeneration will erase all manually edited points."))); + m_imgui->text(""); + m_imgui->text(_(L("Are you sure you want to do it?"))); + + if (m_imgui->button(_(L("Continue")))) + { + ImGui::CloseCurrentPopup(); + m_show_modal = false; + + m_model_object->sla_support_points.clear(); + m_editing_mode_cache.clear(); + wxGetApp().plater()->reslice(); + } + ImGui::SameLine(); + if (m_imgui->button(_(L("Cancel")))) { + ImGui::CloseCurrentPopup(); + m_show_modal = false; + } + ImGui::EndPopup(); + } + + if (!m_show_modal) + force_refresh = true; + } +#endif + m_imgui->text(""); + m_imgui->text(""); + bool editing_clicked = m_imgui->button(_(L("Manual editing"))); + if (editing_clicked) { + editing_mode_reload_cache(); + m_editing_mode = true; + } + } m_imgui->end(); - if (remove_all_clicked) { - delete_current_grabber(true); + if (m_editing_mode != old_editing_state) { // user just toggled between editing/non-editing mode + m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode); + force_refresh = true; + } + + + if (remove_selected) { + force_refresh = false; + m_parent.reload_scene(true); + delete_selected_points(); if (first_run) { first_run = false; goto RENDER_AGAIN; } } - if (remove_all_clicked || generate) { + if (force_refresh) m_parent.reload_scene(true); - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - } } #endif // ENABLE_IMGUI bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const { - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) - && selection.is_from_single_instance(); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. + const GLCanvas3D::Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside) + return false; + + return true; } bool GLGizmoSlaSupports::on_is_selectable() const @@ -2239,6 +2400,105 @@ std::string GLGizmoSlaSupports::on_get_name() const return L("SLA Support Points [L]"); } +void GLGizmoSlaSupports::on_set_state() +{ + if (m_state == On) { + if (is_mesh_update_necessary()) + update_mesh(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + } + if (m_state == Off) { + if (m_old_state != Off && m_model_object) { // the gizmo was just turned Off + + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")), + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); + } + + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + +#if SLAGIZMO_IMGUI_MODAL + if (m_show_modal) { + m_show_modal = false; + on_render_input_window(0,0,m_parent.get_selection()); // this is necessary to allow ImGui to terminate the modal dialog correctly + } +#endif + } + } + m_old_state = m_state; +} + +void GLGizmoSlaSupports::on_start_dragging(const GLCanvas3D::Selection& selection) +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + } +} + +void GLGizmoSlaSupports::select_point(int i) +{ + if (i == AllPoints || i == NoPoints) { + for (auto& point_and_selection : m_editing_mode_cache) + point_and_selection.second = ( i == AllPoints ? true : false); + } + else + m_editing_mode_cache[i].second = true; +} + +void GLGizmoSlaSupports::editing_mode_discard_changes() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.push_back(std::make_pair(point, false)); + m_editing_mode = false; + m_unsaved_changes = false; +} + +void GLGizmoSlaSupports::editing_mode_apply_changes() +{ + // If there are no changes, don't touch the front-end. The data in the cache could have been + // taken from the backend and copying them to ModelObject would needlessly invalidate them. + if (m_unsaved_changes) { + m_model_object->sla_support_points.clear(); + for (const std::pair& point_and_selection : m_editing_mode_cache) + m_model_object->sla_support_points.push_back(point_and_selection.first); + } + m_editing_mode = false; + m_unsaved_changes = false; +} + +void GLGizmoSlaSupports::editing_mode_reload_cache() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.push_back(std::make_pair(point, false)); + m_unsaved_changes = false; +} + +void GLGizmoSlaSupports::get_data_from_backend() +{ + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == m_model_object->id()) { + const std::vector& points = po->get_support_points(); + auto mat = po->trafo().inverse().cast(); + for (unsigned int i=0; i m_AABB; struct SourceDataSummary { -#if !ENABLE_SLA_SUPPORT_GIZMO_MOD - BoundingBoxf3 bounding_box; - Transform3d matrix; -#endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD Vec3d mesh_first_point; }; @@ -472,14 +467,10 @@ private: public: explicit GLGizmoSlaSupports(GLCanvas3D& parent); -#if ENABLE_SLA_SUPPORT_GIZMO_MOD virtual ~GLGizmoSlaSupports(); void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); -#else - void set_model_object_ptr(ModelObject* model_object); -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD - void clicked_on_object(const Vec2d& mouse_position); - void delete_current_grabber(bool delete_all); + bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down); + void delete_selected_points(); private: bool on_init(); @@ -487,11 +478,8 @@ private: virtual void on_render(const GLCanvas3D::Selection& selection) const; virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; -#if ENABLE_SLA_SUPPORT_GIZMO_MOD - void render_grabbers(const GLCanvas3D::Selection& selection, bool picking = false) const; -#else - void render_grabbers(bool picking = false) const; -#endif // ENABLE_SLA_SUPPORT_GIZMO_MOD + void render_selection_rectangle() const; + void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; bool is_mesh_update_necessary() const; void update_mesh(); @@ -501,12 +489,41 @@ private: mutable GLTexture m_reset_texture; #endif // not ENABLE_IMGUI + bool m_lock_unique_islands = false; + bool m_editing_mode = false; + float m_new_point_head_diameter = 0.4f; + double m_minimal_point_distance = 20.; + double m_density = 100.; + std::vector> m_editing_mode_cache; // a support point and whether it is currently selected + + bool m_selection_rectangle_active = false; + Vec2d m_selection_rectangle_start_corner; + Vec2d m_selection_rectangle_end_corner; + bool m_ignore_up_event = false; + bool m_combo_box_open = false; + bool m_unsaved_changes = false; + EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) +#if SLAGIZMO_IMGUI_MODAL + bool m_show_modal = false; +#endif + int m_canvas_width; + int m_canvas_height; + + // Methods that do the model_object and editing cache synchronization, + // editing mode selection, etc: + enum { + AllPoints = -2, + NoPoints, + }; + void select_point(int i); + void editing_mode_apply_changes(); + void editing_mode_discard_changes(); + void editing_mode_reload_cache(); + void get_data_from_backend(); + protected: - void on_set_state() override { - if (m_state == On && is_mesh_update_necessary()) { - update_mesh(); - } - } + void on_set_state() override; + void on_start_dragging(const GLCanvas3D::Selection& selection) override; #if ENABLE_IMGUI virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0da858b2a..63fa09ae2 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -155,6 +155,12 @@ bool ImGuiWrapper::button(const wxString &label) return ImGui::Button(label_utf8.c_str()); } +bool ImGuiWrapper::radio_button(const wxString &label, bool active) +{ + auto label_utf8 = into_u8(label); + return ImGui::RadioButton(label_utf8.c_str(), active); +} + bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); @@ -191,6 +197,28 @@ void ImGuiWrapper::text(const wxString &label) ImGui::Text(label_utf8.c_str(), NULL); } + +bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, wxString& selection) +{ + std::string selection_u8 = into_u8(selection); + + // this is to force the label to the left of the widget: + text(label); + ImGui::SameLine(); + + if (ImGui::BeginCombo("", selection_u8.c_str())) { + for (const wxString& option : options) { + std::string option_u8 = into_u8(option); + bool is_selected = (selection_u8.empty()) ? false : (option_u8 == selection_u8); + if (ImGui::Selectable(option_u8.c_str(), is_selected)) + selection = option_u8; + } + ImGui::EndCombo(); + return true; + } + return false; +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 0f94f5d38..a4f5a8976 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -49,10 +49,12 @@ public: void end(); bool button(const wxString &label); + bool radio_button(const wxString &label, bool active); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); bool checkbox(const wxString &label, bool &value); void text(const wxString &label); + bool combo(const wxString& label, const std::vector& options, wxString& current_selection); void disabled_begin(bool disabled); void disabled_end(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f7d1e26d8..50ca682a4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -108,6 +108,7 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL Bind(wxEVT_ACTIVATE, [this](wxActivateEvent& event) { if (m_plater != nullptr && event.GetActive()) m_plater->on_activate(); + event.Skip(); }); wxGetApp().persist_window_geometry(this); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 55461a814..3e26a667a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2263,6 +2263,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) break; } } + if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS) { + // Update SLA gizmo (reload_scene calls update_gizmos_data) + q->canvas3D()->reload_scene(true); + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 19626847f..ab6d3275a 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -457,9 +457,8 @@ const std::vector& Preset::sla_print_options() "support_critical_angle", "support_max_bridge_length", "support_object_elevation", - "support_density_at_horizontal", - "support_density_at_45", - "support_minimal_z", + "support_points_density_relative", + "support_points_minimal_distance", "pad_enable", "pad_wall_thickness", "pad_wall_height", diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index fd8ab5f93..8f7c65d7e 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -3,6 +3,8 @@ #include "3DScene.hpp" #include "GUI.hpp" +#include + #include #include diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 28b7cd248..788276357 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3202,9 +3202,8 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_max_bridge_length"); optgroup = page->new_optgroup(_(L("Automatic generation"))); - optgroup->append_single_option_line("support_density_at_horizontal"); - optgroup->append_single_option_line("support_density_at_45"); - optgroup->append_single_option_line("support_minimal_z"); + optgroup->append_single_option_line("support_points_density_relative"); + optgroup->append_single_option_line("support_points_minimal_distance"); page = add_options_page(_(L("Pad")), "brick.png"); optgroup = page->new_optgroup(_(L("Pad")));