diff --git a/resources/icons/measure.svg b/resources/icons/measure.svg
new file mode 100644
index 000000000..3ea137a1e
--- /dev/null
+++ b/resources/icons/measure.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 678c8fe20..6dabace5b 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -157,23 +157,25 @@ namespace ImGui
const wchar_t InfoMarker = 0x2603;
const wchar_t SliderFloatEditBtnIcon = 0x2604;
const wchar_t SliderFloatEditBtnPressedIcon = 0x2605;
- const wchar_t LegendTravel = 0x2606;
- const wchar_t LegendWipe = 0x2607;
- const wchar_t LegendRetract = 0x2608;
- const wchar_t LegendDeretract = 0x2609;
- const wchar_t LegendSeams = 0x2610;
- const wchar_t LegendToolChanges = 0x2611;
- const wchar_t LegendColorChanges = 0x2612;
- const wchar_t LegendPausePrints = 0x2613;
- const wchar_t LegendCustomGCodes = 0x2614;
- const wchar_t LegendCOG = 0x2615;
- const wchar_t LegendShells = 0x2616;
- const wchar_t LegendToolMarker = 0x2617;
- const wchar_t WarningMarkerSmall = 0x2618;
- const wchar_t ExpandBtn = 0x2619;
- const wchar_t CollapseBtn = 0x2620;
- const wchar_t InfoMarkerSmall = 0x2621;
+ const wchar_t ClipboardBtnIcon = 0x2606;
-// void MyFunction(const char* name, const MyMatrix44& v);
+ const wchar_t LegendTravel = 0x2701;
+ const wchar_t LegendWipe = 0x2702;
+ const wchar_t LegendRetract = 0x2703;
+ const wchar_t LegendDeretract = 0x2704;
+ const wchar_t LegendSeams = 0x2705;
+ const wchar_t LegendToolChanges = 0x2706;
+ const wchar_t LegendColorChanges = 0x2707;
+ const wchar_t LegendPausePrints = 0x2708;
+ const wchar_t LegendCustomGCodes = 0x2709;
+ const wchar_t LegendCOG = 0x2710;
+ const wchar_t LegendShells = 0x2711;
+ const wchar_t LegendToolMarker = 0x2712;
+ const wchar_t WarningMarkerSmall = 0x2713;
+ const wchar_t ExpandBtn = 0x2714;
+ const wchar_t CollapseBtn = 0x2715;
+ const wchar_t InfoMarkerSmall = 0x2716;
+
+ // void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index a118f6ef3..79e0e6355 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -180,6 +180,9 @@ set(SLIC3R_SOURCES
MultiMaterialSegmentation.hpp
MeshNormals.hpp
MeshNormals.cpp
+ Measure.hpp
+ Measure.cpp
+ MeasureUtils.hpp
CustomGCode.cpp
CustomGCode.hpp
Arrange.hpp
@@ -253,6 +256,7 @@ set(SLIC3R_SOURCES
Surface.hpp
SurfaceCollection.cpp
SurfaceCollection.hpp
+ SurfaceMesh.hpp
SVG.cpp
SVG.hpp
Technologies.hpp
diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp
new file mode 100644
index 000000000..f64b79e22
--- /dev/null
+++ b/src/libslic3r/Measure.cpp
@@ -0,0 +1,1082 @@
+#include "libslic3r/libslic3r.h"
+#include "Measure.hpp"
+#include "MeasureUtils.hpp"
+
+#include "libslic3r/Geometry/Circle.hpp"
+#include "libslic3r/SurfaceMesh.hpp"
+
+namespace Slic3r {
+namespace Measure {
+
+
+constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it
+constexpr double edge_endpoint_limit = 0.5; // how close to an edge endpoint the mouse ...
+
+static std::pair get_center_and_radius(const std::vector& border, int start_idx, int end_idx, const Transform3d& trafo)
+{
+ Vec2ds pts;
+ double z = 0.;
+ for (int i=start_idx; i<=end_idx; ++i) {
+ Vec3d pt_transformed = trafo * border[i];
+ z = pt_transformed.z();
+ pts.emplace_back(pt_transformed.x(), pt_transformed.y());
+ }
+
+ auto circle = Geometry::circle_ransac(pts, 20); // FIXME: iterations?
+
+ return std::make_pair(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius);
+}
+
+
+
+
+class MeasuringImpl {
+public:
+ explicit MeasuringImpl(const indexed_triangle_set& its);
+ struct PlaneData {
+ std::vector facets;
+ std::vector> borders; // FIXME: should be in fact local in update_planes()
+ std::vector surface_features;
+ Vec3d normal;
+ float area;
+ };
+
+ std::vector get_all_features() const;
+ std::optional get_feature(size_t face_idx, const Vec3d& point) const;
+ std::vector> get_planes_triangle_indices() const;
+ const std::vector& get_plane_features(unsigned int plane_id) const;
+
+private:
+ void update_planes();
+ void extract_features();
+
+ std::vector m_planes;
+ std::vector m_face_to_plane;
+ const indexed_triangle_set& m_its;
+};
+
+
+
+
+
+
+MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
+: m_its{its}
+{
+ update_planes();
+ extract_features();
+}
+
+
+void MeasuringImpl::update_planes()
+{
+ m_planes.clear();
+
+ // Now we'll go through all the facets and append Points of facets sharing the same normal.
+ // This part is still performed in mesh coordinate system.
+ const size_t num_of_facets = m_its.indices.size();
+ m_face_to_plane.resize(num_of_facets, size_t(-1));
+ const std::vector face_normals = its_face_normals(m_its);
+ const std::vector face_neighbors = its_face_neighbors(m_its);
+ std::vector facet_queue(num_of_facets, 0);
+ int facet_queue_cnt = 0;
+ const stl_normal* normal_ptr = nullptr;
+ size_t seed_facet_idx = 0;
+
+ auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool {
+ return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001);
+ };
+
+ while (1) {
+ // Find next unvisited triangle:
+ for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx)
+ if (m_face_to_plane[seed_facet_idx] == size_t(-1)) {
+ facet_queue[facet_queue_cnt ++] = seed_facet_idx;
+ normal_ptr = &face_normals[seed_facet_idx];
+ m_face_to_plane[seed_facet_idx] = m_planes.size();
+ m_planes.emplace_back();
+ break;
+ }
+ if (seed_facet_idx == num_of_facets)
+ break; // Everything was visited already
+
+ while (facet_queue_cnt > 0) {
+ int facet_idx = facet_queue[-- facet_queue_cnt];
+ const stl_normal& this_normal = face_normals[facet_idx];
+ if (is_same_normal(this_normal, *normal_ptr)) {
+// const Vec3i& face = m_its.indices[facet_idx];
+
+ m_face_to_plane[facet_idx] = m_planes.size() - 1;
+ m_planes.back().facets.emplace_back(facet_idx);
+ for (int j = 0; j < 3; ++ j)
+ if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && m_face_to_plane[neighbor_idx] == size_t(-1))
+ facet_queue[facet_queue_cnt ++] = neighbor_idx;
+ }
+ }
+
+ m_planes.back().normal = normal_ptr->cast();
+ std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end());
+ }
+
+ assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); }));
+
+ SurfaceMesh sm(m_its);
+ for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) {
+ //int plane_id = 5; {
+ const auto& facets = m_planes[plane_id].facets;
+ m_planes[plane_id].borders.clear();
+ std::vector> visited(facets.size(), {false, false, false});
+
+ for (int face_id=0; face_id& last_border = m_planes[plane_id].borders.back();
+ last_border.emplace_back(sm.point(sm.source(he)).cast());
+ //Vertex_index target = sm.target(he);
+ const Halfedge_index he_start = he;
+
+ Face_index fi = he.face();
+ auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi));
+ assert(face_it != facets.end());
+ assert(*face_it == int(fi));
+ visited[face_it - facets.begin()][he.side()] = true;
+
+ do {
+ const Halfedge_index he_orig = he;
+ he = sm.next_around_target(he);
+ while ( (int)m_face_to_plane[sm.face(he)] == plane_id && he != he_orig)
+ he = sm.next_around_target(he);
+ he = sm.opposite(he);
+
+ Face_index fi = he.face();
+ auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi));
+ assert(face_it != facets.end());
+ assert(*face_it == int(fi));
+ if (visited[face_it - facets.begin()][he.side()] && he != he_start) {
+ last_border.resize(1);
+ break;
+ }
+ visited[face_it - facets.begin()][he.side()] = true;
+
+ last_border.emplace_back(sm.point(sm.source(he)).cast());
+ } while (he != he_start);
+
+ if (last_border.size() == 1)
+ m_planes[plane_id].borders.pop_back();
+ }
+ }
+ }
+
+ m_planes.erase(std::remove_if(m_planes.begin(), m_planes.end(),
+ [](const PlaneData& p) { return p.borders.empty(); }),
+ m_planes.end());
+}
+
+
+
+
+
+
+void MeasuringImpl::extract_features()
+{
+ auto N_to_angle = [](double N) -> double { return 2.*M_PI / N; };
+ constexpr double polygon_upper_threshold = N_to_angle(4.5);
+ constexpr double polygon_lower_threshold = N_to_angle(8.5);
+ std::vector angles;
+ std::vector lengths;
+
+
+ for (int i=0; i<(int)m_planes.size(); ++i) {
+ PlaneData& plane = m_planes[i];
+ plane.surface_features.clear();
+ const Vec3d& normal = plane.normal;
+
+ Eigen::Quaterniond q;
+ q.setFromTwoVectors(plane.normal, Vec3d::UnitZ());
+ Transform3d trafo = Transform3d::Identity();
+ trafo.rotate(q);
+
+ for (const std::vector& border : plane.borders) {
+ assert(border.size() > 1);
+ int start_idx = -1;
+
+ // First calculate angles at all the vertices.
+ angles.clear();
+ lengths.clear();
+ for (int i=0; i M_PI)
+ angle = 2*M_PI - angle;
+
+ angles.push_back(angle);
+ lengths.push_back(v2.squaredNorm());
+ }
+ assert(border.size() == angles.size());
+ assert(border.size() == lengths.size());
+
+
+ bool circle = false;
+ std::vector circles;
+ std::vector> circles_idxs;
+ for (int i=1; i<(int)angles.size(); ++i) {
+ if (Slic3r::is_approx(lengths[i], lengths[i-1])
+ && Slic3r::is_approx(angles[i], angles[i-1])
+ && i != (int)angles.size()-1 ) {
+ // circle
+ if (! circle) {
+ circle = true;
+ start_idx = std::max(0, i-2);
+ }
+ } else {
+ if (circle) {
+ // Add the circle and remember indices into borders.
+ const auto& [center, radius] = get_center_and_radius(border, start_idx, i, trafo);
+ circles_idxs.emplace_back(start_idx, i);
+ circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
+ circle = false;
+ }
+ }
+ }
+
+ // Some of the "circles" may actually be polygons. We want them detected as
+ // edges, but also to remember the center and save it into those edges.
+ // We will add all such edges manually and delete the detected circles,
+ // leaving it in circles_idxs so they are not picked again:
+ assert(circles.size() == circles_idxs.size());
+ for (int i=circles.size()-1; i>=0; --i) {
+ assert(circles_idxs[i].first + 1 < angles.size() - 1); // Check that this is internal point of the circle, not the first, not the last.
+ double angle = angles[circles_idxs[i].first + 1];
+ if (angle > polygon_lower_threshold) {
+ if (angle < polygon_upper_threshold) {
+ const Vec3d center = std::get<0>(circles[i].get_circle());
+ for (int j=(int)circles_idxs[i].first + 1; j<=(int)circles_idxs[i].second; ++j)
+ plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge,
+ border[j - 1], border[j], std::make_optional(center)));
+ } else {
+ // This will be handled just like a regular edge.
+ circles_idxs.erase(circles_idxs.begin() + i);
+ }
+ circles.erase(circles.begin() + i);
+ }
+ }
+
+
+
+
+
+
+ // We have the circles. Now go around again and pick edges.
+ int cidx = 0; // index of next circle in the way
+ for (int i=1; i (int)circles_idxs[cidx].first)
+ i = circles_idxs[cidx++].second;
+ else
+ plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i - 1], border[i]));
+ }
+
+ // FIXME Throw away / do not create edges which are parts of circles or
+ // which lead to circle points (unless they belong to the same plane.)
+
+ // FIXME Check and merge first and last circle if needed.
+
+ // Now move the circles into the feature list.
+ assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) {
+ return f.get_type() == SurfaceFeatureType::Circle;
+ }));
+ plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()),
+ std::make_move_iterator(circles.end()));
+ }
+
+ // The last surface feature is the plane itself.
+ Vec3d cog = Vec3d::Zero();
+ size_t counter = 0;
+ for (const std::vector& b : plane.borders) {
+ for (size_t i = 1; i < b.size(); ++i) {
+ cog += b[i];
+ ++counter;
+ }
+ }
+ cog /= double(counter);
+ plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Plane,
+ plane.normal, cog, std::optional(), i + 0.0001));
+
+ plane.borders.clear();
+ plane.borders.shrink_to_fit();
+ }
+}
+
+
+
+std::vector MeasuringImpl::get_all_features() const
+{
+ std::vector features;
+ //PlaneData& plane = m_planes[0];
+ for (const PlaneData& plane : m_planes)
+ for (const SurfaceFeature& feature : plane.surface_features)
+ features.emplace_back(feature);
+ return features;
+}
+
+
+
+
+
+
+std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point) const
+{
+ if (face_idx >= m_face_to_plane.size())
+ return std::optional();
+
+ const PlaneData& plane = m_planes[m_face_to_plane[face_idx]];
+
+ size_t closest_feature_idx = size_t(-1);
+ double min_dist = std::numeric_limits::max();
+
+ MeasurementResult res;
+ SurfaceFeature point_sf(point);
+
+ for (size_t i=0; idist;
+ if (dist < feature_hover_limit && dist < min_dist) {
+ min_dist = std::min(dist, min_dist);
+ closest_feature_idx = i;
+ }
+ }
+ }
+
+ if (closest_feature_idx != size_t(-1)) {
+ const SurfaceFeature& f = plane.surface_features[closest_feature_idx];
+ if (f.get_type() == SurfaceFeatureType::Edge) {
+ // If this is an edge, check if we are not close to the endpoint. If so,
+ // we will include the endpoint as well.
+ constexpr double limit_sq = edge_endpoint_limit * edge_endpoint_limit;
+ const auto& [sp, ep] = f.get_edge();
+ if ((point-sp).squaredNorm() < limit_sq)
+ return std::make_optional(SurfaceFeature(sp));
+ if ((point-ep).squaredNorm() < limit_sq)
+ return std::make_optional(SurfaceFeature(ep));
+ }
+ return std::make_optional(f);
+ }
+
+ // Nothing detected, return the plane as a whole.
+ assert(plane.surface_features.back().get_type() == SurfaceFeatureType::Plane);
+ return std::make_optional(plane.surface_features.back());
+}
+
+
+
+
+
+std::vector> MeasuringImpl::get_planes_triangle_indices() const
+{
+ std::vector> out;
+ for (const PlaneData& plane : m_planes)
+ out.emplace_back(plane.facets);
+ return out;
+}
+
+const std::vector& MeasuringImpl::get_plane_features(unsigned int plane_id) const
+{
+ assert(plane_id < m_planes.size());
+ return m_planes[plane_id].surface_features;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+Measuring::Measuring(const indexed_triangle_set& its)
+: priv{std::make_unique(its)}
+{}
+
+Measuring::~Measuring() {}
+
+
+std::vector Measuring::get_all_features() const
+{
+ return priv->get_all_features();
+}
+
+
+std::optional Measuring::get_feature(size_t face_idx, const Vec3d& point) const
+{
+ return priv->get_feature(face_idx, point);
+}
+
+
+
+std::vector> Measuring::get_planes_triangle_indices() const
+{
+ return priv->get_planes_triangle_indices();
+}
+
+const std::vector& Measuring::get_plane_features(unsigned int plane_id) const
+{
+ return priv->get_plane_features(plane_id);
+}
+
+const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true };
+
+static AngleAndEdges angle_edge_edge(const std::pair& e1, const std::pair& e2)
+{
+ if (are_parallel(e1, e2))
+ return AngleAndEdges::Dummy;
+
+ Vec3d e1_unit = edge_direction(e1.first, e1.second);
+ Vec3d e2_unit = edge_direction(e2.first, e2.second);
+
+ // project edges on the plane defined by them
+ Vec3d normal = e1_unit.cross(e2_unit).normalized();
+ const Eigen::Hyperplane plane(normal, e1.first);
+ Vec3d e11_proj = plane.projection(e1.first);
+ Vec3d e12_proj = plane.projection(e1.second);
+ Vec3d e21_proj = plane.projection(e2.first);
+ Vec3d e22_proj = plane.projection(e2.second);
+
+ const bool coplanar = (e2.first - e21_proj).norm() < EPSILON && (e2.second - e22_proj).norm() < EPSILON;
+
+ // rotate the plane to become the XY plane
+ auto qp = Eigen::Quaternion::FromTwoVectors(normal, Vec3d::UnitZ());
+ auto qp_inverse = qp.inverse();
+ const Vec3d e11_rot = qp * e11_proj;
+ const Vec3d e12_rot = qp * e12_proj;
+ const Vec3d e21_rot = qp * e21_proj;
+ const Vec3d e22_rot = qp * e22_proj;
+
+ // discard Z
+ const Vec2d e11_rot_2d = Vec2d(e11_rot.x(), e11_rot.y());
+ const Vec2d e12_rot_2d = Vec2d(e12_rot.x(), e12_rot.y());
+ const Vec2d e21_rot_2d = Vec2d(e21_rot.x(), e21_rot.y());
+ const Vec2d e22_rot_2d = Vec2d(e22_rot.x(), e22_rot.y());
+
+ // find intersection (arc center) of edges in XY plane
+ const Eigen::Hyperplane e1_rot_2d_line = Eigen::Hyperplane::Through(e11_rot_2d, e12_rot_2d);
+ const Eigen::Hyperplane e2_rot_2d_line = Eigen::Hyperplane::Through(e21_rot_2d, e22_rot_2d);
+ const Vec2d center_rot_2d = e1_rot_2d_line.intersection(e2_rot_2d_line);
+
+ // arc center in original coordinate
+ const Vec3d center = qp_inverse * Vec3d(center_rot_2d.x(), center_rot_2d.y(), e11_rot.z());
+
+ // ensure the edges are pointing away from the center
+ std::pair out_e1 = e1;
+ std::pair out_e2 = e2;
+ if ((center_rot_2d - e11_rot_2d).squaredNorm() > (center_rot_2d - e12_rot_2d).squaredNorm()) {
+ std::swap(e11_proj, e12_proj);
+ std::swap(out_e1.first, out_e1.second);
+ e1_unit = -e1_unit;
+ }
+ if ((center_rot_2d - e21_rot_2d).squaredNorm() > (center_rot_2d - e22_rot_2d).squaredNorm()) {
+ std::swap(e21_proj, e22_proj);
+ std::swap(out_e2.first, out_e2.second);
+ e2_unit = -e2_unit;
+ }
+
+ // arc angle
+ const double angle = std::acos(std::clamp(e1_unit.dot(e2_unit), -1.0, 1.0));
+ // arc radius
+ const Vec3d e1_proj_mid = 0.5 * (e11_proj + e12_proj);
+ const Vec3d e2_proj_mid = 0.5 * (e21_proj + e22_proj);
+ const double radius = std::min((center - e1_proj_mid).norm(), (center - e2_proj_mid).norm());
+
+ return { angle, center, out_e1, out_e2, radius, coplanar };
+}
+
+static AngleAndEdges angle_edge_plane(const std::pair& e, const std::tuple& p)
+{
+ const auto& [idx, normal, origin] = p;
+ const Vec3d e1e2_unit = edge_direction(e);
+ if (are_parallel(e1e2_unit, normal) || are_perpendicular(e1e2_unit, normal))
+ return AngleAndEdges::Dummy;
+
+ // ensure the edge is pointing away from the intersection
+ // 1st calculate instersection between edge and plane
+ const Eigen::Hyperplane plane(normal, origin);
+ const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second);
+ const Vec3d inters = line.intersectionPoint(plane);
+
+ // then verify edge direction and revert it, if needed
+ Vec3d e1 = e.first;
+ Vec3d e2 = e.second;
+ if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm())
+ std::swap(e1, e2);
+
+ const Vec3d e1e2 = e2 - e1;
+ const double e1e2_len = e1e2.norm();
+
+ // calculate 2nd edge (on the plane)
+ const Vec3d temp = normal.cross(e1e2);
+ const Vec3d edge_on_plane_unit = normal.cross(temp).normalized();
+ std::pair edge_on_plane = { origin, origin + e1e2_len * edge_on_plane_unit };
+
+ // ensure the 2nd edge is pointing in the correct direction
+ const Vec3d test_edge = (edge_on_plane.second - edge_on_plane.first).cross(e1e2);
+ if (test_edge.dot(temp) < 0.0)
+ edge_on_plane = { origin, origin - e1e2_len * edge_on_plane_unit };
+
+ AngleAndEdges ret = angle_edge_edge({ e1, e2 }, edge_on_plane);
+ ret.radius = (inters - 0.5 * (e1 + e2)).norm();
+ return ret;
+}
+
+static AngleAndEdges angle_plane_plane(const std::tuple& p1, const std::tuple& p2)
+{
+ const auto& [idx1, normal1, origin1] = p1;
+ const auto& [idx2, normal2, origin2] = p2;
+
+ // are planes parallel ?
+ if (are_parallel(normal1, normal2))
+ return AngleAndEdges::Dummy;
+
+ auto intersection_plane_plane = [](const Vec3d& n1, const Vec3d& o1, const Vec3d& n2, const Vec3d& o2) {
+ Eigen::MatrixXd m(2, 3);
+ m << n1.x(), n1.y(), n1.z(), n2.x(), n2.y(), n2.z();
+ Eigen::VectorXd b(2);
+ b << o1.dot(n1), o2.dot(n2);
+ Eigen::VectorXd x = m.colPivHouseholderQr().solve(b);
+ return std::make_pair(n1.cross(n2).normalized(), Vec3d(x(0), x(1), x(2)));
+ };
+
+ // Calculate intersection line between planes
+ const auto [intersection_line_direction, intersection_line_origin] = intersection_plane_plane(normal1, origin1, normal2, origin2);
+
+ // Project planes' origin on intersection line
+ const Eigen::ParametrizedLine intersection_line = Eigen::ParametrizedLine(intersection_line_origin, intersection_line_direction);
+ const Vec3d origin1_proj = intersection_line.projection(origin1);
+ const Vec3d origin2_proj = intersection_line.projection(origin2);
+
+ // Calculate edges on planes
+ const Vec3d edge_on_plane1_unit = (origin1 - origin1_proj).normalized();
+ const Vec3d edge_on_plane2_unit = (origin2 - origin2_proj).normalized();
+ const double radius = std::max(10.0, std::max((origin1 - origin1_proj).norm(), (origin2 - origin2_proj).norm()));
+ const std::pair edge_on_plane1 = { origin1_proj + radius * edge_on_plane1_unit, origin1_proj + 2.0 * radius * edge_on_plane1_unit };
+ const std::pair edge_on_plane2 = { origin2_proj + radius * edge_on_plane2_unit, origin2_proj + 2.0 * radius * edge_on_plane2_unit };
+
+ AngleAndEdges ret = angle_edge_edge(edge_on_plane1, edge_on_plane2);
+ ret.radius = radius;
+ return ret;
+}
+
+
+
+
+
+
+
+MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring)
+{
+ assert(a.get_type() != SurfaceFeatureType::Undef && b.get_type() != SurfaceFeatureType::Undef);
+
+ const bool swap = int(a.get_type()) > int(b.get_type());
+ const SurfaceFeature& f1 = swap ? b : a;
+ const SurfaceFeature& f2 = swap ? a : b;
+
+ MeasurementResult result;
+
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ if (f1.get_type() == SurfaceFeatureType::Point) {
+ if (f2.get_type() == SurfaceFeatureType::Point) {
+ Vec3d diff = (f2.get_point() - f1.get_point());
+ result.distance_strict = std::make_optional(DistAndPoints{diff.norm(), f1.get_point(), f2.get_point()});
+ result.distance_xyz = diff;
+
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Edge) {
+ const auto [s,e] = f2.get_edge();
+ const Eigen::ParametrizedLine line(s, (e-s).normalized());
+ const double dist_inf = line.distance(f1.get_point());
+ const Vec3d proj = line.projection(f1.get_point());
+ const double len_sq = (e-s).squaredNorm();
+ const double dist_start_sq = (proj-s).squaredNorm();
+ const double dist_end_sq = (proj-e).squaredNorm();
+ if (dist_start_sq < len_sq && dist_end_sq < len_sq) {
+ // projection falls on the line - the strict distance is the same as infinite
+ result.distance_strict = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj});
+ } else { // the result is the closer of the endpoints
+ const bool s_is_closer = dist_start_sq < dist_end_sq;
+ result.distance_strict = std::make_optional(DistAndPoints{std::sqrt(std::min(dist_start_sq, dist_end_sq) + sqr(dist_inf)), f1.get_point(), s_is_closer ? s : e});
+ }
+ result.distance_infinite = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj});
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Circle) {
+ // Find a plane containing normal, center and the point.
+ const auto [c, radius, n] = f2.get_circle();
+ const Eigen::Hyperplane circle_plane(n, c);
+ const Vec3d proj = circle_plane.projection(f1.get_point());
+ if (proj.isApprox(c)) {
+ const Vec3d p_on_circle = c + radius * get_orthogonal(n, true);
+ result.distance_strict = std::make_optional(DistAndPoints{ radius, c, p_on_circle });
+ }
+ else {
+ const Eigen::Hyperplane circle_plane(n, c);
+ const Vec3d proj = circle_plane.projection(f1.get_point());
+ const double dist = std::sqrt(std::pow((proj - c).norm() - radius, 2.) +
+ (f1.get_point() - proj).squaredNorm());
+
+ const Vec3d p_on_circle = c + radius * (proj - c).normalized();
+ result.distance_strict = std::make_optional(DistAndPoints{ dist, f1.get_point(), p_on_circle }); // TODO
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ const auto [idx, normal, pt] = f2.get_plane();
+ Eigen::Hyperplane plane(normal, pt);
+ result.distance_infinite = std::make_optional(DistAndPoints{plane.absDistance(f1.get_point()), f1.get_point(), plane.projection(f1.get_point())}); // TODO
+ // TODO: result.distance_strict =
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Edge) {
+ if (f2.get_type() == SurfaceFeatureType::Edge) {
+ std::vector distances;
+
+// auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) {
+// const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second));
+// double distance = res.distance_strict->dist;
+// Vec3d v2 = res.distance_strict->to;
+//
+// const Vec3d e1e2 = e.second - e.first;
+// const Vec3d e1v2 = v2 - e.first;
+// if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm())
+// distances.emplace_back(distance, v, v2);
+// };
+
+ std::pair e1 = f1.get_edge();
+ std::pair e2 = f2.get_edge();
+
+ distances.emplace_back((e2.first - e1.first).norm(), e1.first, e2.first);
+ distances.emplace_back((e2.second - e1.first).norm(), e1.first, e2.second);
+ distances.emplace_back((e2.first - e1.second).norm(), e1.second, e2.first);
+ distances.emplace_back((e2.second - e1.second).norm(), e1.second, e2.second);
+// add_point_edge_distance(e1.first, e2);
+// add_point_edge_distance(e1.second, e2);
+// add_point_edge_distance(e2.first, e1);
+// add_point_edge_distance(e2.second, e1);
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(*it);
+
+ result.angle = angle_edge_edge(f1.get_edge(), f2.get_edge());
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Circle) {
+ const std::pair e = f1.get_edge();
+ const auto& [center, radius, normal] = f2.get_circle();
+ const Vec3d e1e2 = (e.second - e.first);
+ const Vec3d e1e2_unit = e1e2.normalized();
+
+ std::vector distances;
+ distances.emplace_back(*get_measurement(SurfaceFeature(e.first), f2).distance_strict);
+ distances.emplace_back(*get_measurement(SurfaceFeature(e.second), f2).distance_strict);
+
+ const Eigen::Hyperplane plane(e1e2_unit, center);
+ const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second);
+ const Vec3d inter = line.intersectionPoint(plane);
+ const Vec3d e1inter = inter - e.first;
+ if (e1inter.dot(e1e2) >= 0.0 && e1inter.norm() < e1e2.norm())
+ distances.emplace_back(*get_measurement(SurfaceFeature(inter), f2).distance_strict);
+
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{it->dist, it->from, it->to});
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ assert(measuring != nullptr);
+
+ const auto [from, to] = f1.get_edge();
+ const auto [idx, normal, origin] = f2.get_plane();
+
+ const Vec3d edge_unit = (to - from).normalized();
+ if (are_perpendicular(edge_unit, normal)) {
+ std::vector distances;
+ const Eigen::Hyperplane plane(normal, origin);
+ distances.push_back(DistAndPoints{ plane.absDistance(from), from, plane.projection(from) });
+ distances.push_back(DistAndPoints{ plane.absDistance(to), to, plane.projection(to) });
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ else {
+ const std::vector& plane_features = measuring->get_plane_features(idx);
+ std::vector distances;
+ for (const SurfaceFeature& sf : plane_features) {
+ if (sf.get_type() == SurfaceFeatureType::Edge) {
+ const auto m = get_measurement(sf, f1);
+ if (!m.distance_infinite.has_value()) {
+ distances.clear();
+ break;
+ }
+ else
+ distances.push_back(*m.distance_infinite);
+ }
+ }
+ if (!distances.empty()) {
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ }
+ result.angle = angle_edge_plane(f1.get_edge(), f2.get_plane());
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Circle) {
+ if (f2.get_type() == SurfaceFeatureType::Circle) {
+ const auto [c0, r0, n0] = f1.get_circle();
+ const auto [c1, r1, n1] = f2.get_circle();
+
+ // The following code is an adaptation of the algorithm found in:
+ // https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/DistCircle3Circle3.h
+ // and described in:
+ // https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf
+
+ struct ClosestInfo
+ {
+ double sqrDistance{ 0.0 };
+ Vec3d circle0Closest{ Vec3d::Zero() };
+ Vec3d circle1Closest{ Vec3d::Zero() };
+
+ inline bool operator < (const ClosestInfo& other) const { return sqrDistance < other.sqrDistance; }
+ };
+ std::array candidates{};
+
+ const double zero = 0.0;
+
+ const Vec3d D = c1 - c0;
+
+ if (!are_parallel(n0, n1)) {
+ auto orthonormal_basis = [](const Vec3d& v) {
+ std::array ret;
+ ret[2] = v.normalized();
+ int index;
+ ret[2].maxCoeff(&index);
+ switch (index)
+ {
+ case 0: { ret[0] = Vec3d(ret[2].y(), -ret[2].x(), 0.0).normalized(); break; }
+ case 1: { ret[0] = Vec3d(0.0, ret[2].z(), -ret[2].y()).normalized(); break; }
+ case 2: { ret[0] = Vec3d(-ret[2].z(), 0.0, ret[2].x()).normalized(); break; }
+ }
+ ret[1] = ret[2].cross(ret[0]).normalized();
+ return ret;
+ };
+
+ // Get parameters for constructing the degree-8 polynomial phi.
+ const double one = 1.0;
+ const double two = 2.0;
+ const double r0sqr = sqr(r0);
+ const double r1sqr = sqr(r1);
+
+ // Compute U1 and V1 for the plane of circle1.
+ const std::array basis = orthonormal_basis(n1);
+ const Vec3d U1 = basis[0];
+ const Vec3d V1 = basis[1];
+
+ // Construct the polynomial phi(cos(theta)).
+ const Vec3d N0xD = n0.cross(D);
+ const Vec3d N0xU1 = n0.cross(U1);
+ const Vec3d N0xV1 = n0.cross(V1);
+ const double a0 = r1 * D.dot(U1);
+ const double a1 = r1 * D.dot(V1);
+ const double a2 = N0xD.dot(N0xD);
+ const double a3 = r1 * N0xD.dot(N0xU1);
+ const double a4 = r1 * N0xD.dot(N0xV1);
+ const double a5 = r1sqr * N0xU1.dot(N0xU1);
+ const double a6 = r1sqr * N0xU1.dot(N0xV1);
+ const double a7 = r1sqr * N0xV1.dot(N0xV1);
+ Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 };
+ Polynomial1 p1{ two * a4, two * a6 };
+ Polynomial1 p2{ zero, a1 };
+ Polynomial1 p3{ -a0 };
+ Polynomial1 p4{ -a6, a4, two * a6 };
+ Polynomial1 p5{ -a3, a7 - a5 };
+ Polynomial1 tmp0{ one, zero, -one };
+ Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3;
+ Polynomial1 tmp2 = two * p2 * p3;
+ Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5;
+ Polynomial1 tmp4 = two * p4 * p5;
+ Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3;
+ Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4;
+
+ // Parameters for polynomial root finding. The roots[] array
+ // stores the roots. We need only the unique ones, which is
+ // the responsibility of the set uniqueRoots. The pairs[]
+ // array stores the (cosine,sine) information mentioned in the
+ // PDF. TODO: Choose the maximum number of iterations for root
+ // finding based on specific polynomial data?
+ const uint32_t maxIterations = 128;
+ int32_t degree = 0;
+ size_t numRoots = 0;
+ std::array roots{};
+ std::set uniqueRoots{};
+ size_t numPairs = 0;
+ std::array, 16> pairs{};
+ double temp = zero;
+ double sn = zero;
+
+ if (p7.GetDegree() > 0 || p7[0] != zero) {
+ // H(cs,sn) = p6(cs) + sn * p7(cs)
+ Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7;
+ degree = static_cast(phi.GetDegree());
+ assert(degree > 0);
+ numRoots = RootsPolynomial::Find(degree, &phi[0], maxIterations, roots.data());
+ for (size_t i = 0; i < numRoots; ++i) {
+ uniqueRoots.insert(roots[i]);
+ }
+
+ for (auto const& cs : uniqueRoots) {
+ if (std::fabs(cs) <= one) {
+ temp = p7(cs);
+ if (temp != zero) {
+ sn = -p6(cs) / temp;
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ }
+ else {
+ temp = std::max(one - sqr(cs), zero);
+ sn = std::sqrt(temp);
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ if (sn != zero)
+ pairs[numPairs++] = std::make_pair(cs, -sn);
+ }
+ }
+ }
+ }
+ else {
+ // H(cs,sn) = p6(cs)
+ degree = static_cast(p6.GetDegree());
+ assert(degree > 0);
+ numRoots = RootsPolynomial::Find(degree, &p6[0], maxIterations, roots.data());
+ for (size_t i = 0; i < numRoots; ++i) {
+ uniqueRoots.insert(roots[i]);
+ }
+
+ for (auto const& cs : uniqueRoots) {
+ if (std::fabs(cs) <= one) {
+ temp = std::max(one - sqr(cs), zero);
+ sn = std::sqrt(temp);
+ pairs[numPairs++] = std::make_pair(cs, sn);
+ if (sn != zero)
+ pairs[numPairs++] = std::make_pair(cs, -sn);
+ }
+ }
+ }
+
+ for (size_t i = 0; i < numPairs; ++i) {
+ ClosestInfo& info = candidates[i];
+ Vec3d delta = D + r1 * (pairs[i].first * U1 + pairs[i].second * V1);
+ info.circle1Closest = c0 + delta;
+ const double N0dDelta = n0.dot(delta);
+ const double lenN0xDelta = n0.cross(delta).norm();
+ if (lenN0xDelta > 0.0) {
+ const double diff = lenN0xDelta - r0;
+ info.sqrDistance = sqr(N0dDelta) + sqr(diff);
+ delta -= N0dDelta * n0;
+ delta.normalize();
+ info.circle0Closest = c0 + r0 * delta;
+ }
+ else {
+ const Vec3d r0U0 = r0 * get_orthogonal(n0, true);
+ const Vec3d diff = delta - r0U0;
+ info.sqrDistance = diff.dot(diff);
+ info.circle0Closest = c0 + r0U0;
+ }
+ }
+
+ std::sort(candidates.begin(), candidates.begin() + numPairs);
+ }
+ else {
+ ClosestInfo& info = candidates[0];
+
+ const double N0dD = n0.dot(D);
+ const Vec3d normProj = N0dD * n0;
+ const Vec3d compProj = D - normProj;
+ Vec3d U = compProj;
+ const double d = U.norm();
+ U.normalize();
+
+ // The configuration is determined by the relative location of the
+ // intervals of projection of the circles on to the D-line.
+ // Circle0 projects to [-r0,r0] and circle1 projects to
+ // [d-r1,d+r1].
+ const double dmr1 = d - r1;
+ double distance;
+ if (dmr1 >= r0) {
+ // d >= r0 + r1
+ // The circles are separated (d > r0 + r1) or tangent with one
+ // outside the other (d = r0 + r1).
+ distance = dmr1 - r0;
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 - r1 * U;
+ }
+ else {
+ // d < r0 + r1
+ // The cases implicitly use the knowledge that d >= 0.
+ const double dpr1 = d + r1;
+ if (dpr1 <= r0) {
+ // Circle1 is inside circle0.
+ distance = r0 - dpr1;
+ if (d > 0.0) {
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ else {
+ // The circles are concentric, so U = (0,0,0).
+ // Construct a vector perpendicular to N0 to use for
+ // closest points.
+ U = get_orthogonal(n0, true);
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ }
+ else if (dmr1 <= -r0) {
+ // Circle0 is inside circle1.
+ distance = -r0 - dmr1;
+ if (d > 0.0) {
+ info.circle0Closest = c0 - r0 * U;
+ info.circle1Closest = c1 - r1 * U;
+ }
+ else {
+ // The circles are concentric, so U = (0,0,0).
+ // Construct a vector perpendicular to N0 to use for
+ // closest points.
+ U = get_orthogonal(n0, true);
+ info.circle0Closest = c0 + r0 * U;
+ info.circle1Closest = c1 + r1 * U;
+ }
+ }
+ else {
+ distance = (c1 - c0).norm();
+ info.circle0Closest = c0;
+ info.circle1Closest = c1;
+ }
+ }
+
+ info.sqrDistance = distance * distance + N0dD * N0dD;
+ }
+
+ result.distance_infinite = std::make_optional(DistAndPoints{ std::sqrt(candidates[0].sqrDistance), candidates[0].circle0Closest, candidates[0].circle1Closest }); // TODO
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f2.get_type() == SurfaceFeatureType::Plane) {
+ assert(measuring != nullptr);
+
+ const auto [center, radius, normal1] = f1.get_circle();
+ const auto [idx2, normal2, origin2] = f2.get_plane();
+
+ const bool coplanar = are_parallel(normal1, normal2) && Eigen::Hyperplane(normal1, center).absDistance(origin2) < EPSILON;
+ if (!coplanar) {
+ const std::vector& plane_features = measuring->get_plane_features(idx2);
+ std::vector distances;
+ for (const SurfaceFeature& sf : plane_features) {
+ if (sf.get_type() == SurfaceFeatureType::Edge) {
+ const auto m = get_measurement(sf, f1);
+ if (!m.distance_infinite.has_value()) {
+ distances.clear();
+ break;
+ }
+ else
+ distances.push_back(*m.distance_infinite);
+ }
+ }
+ if (!distances.empty()) {
+ auto it = std::min_element(distances.begin(), distances.end(),
+ [](const DistAndPoints& item1, const DistAndPoints& item2) {
+ return item1.dist < item2.dist;
+ });
+ result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to });
+ }
+ }
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ } else if (f1.get_type() == SurfaceFeatureType::Plane) {
+ const auto [idx1, normal1, pt1] = f1.get_plane();
+ const auto [idx2, normal2, pt2] = f2.get_plane();
+
+ if (are_parallel(normal1, normal2)) {
+ // The planes are parallel, calculate distance.
+ const Eigen::Hyperplane plane(normal1, pt1);
+ result.distance_infinite = std::make_optional(DistAndPoints{ plane.absDistance(pt2), pt2, plane.projection(pt2) }); // TODO
+ }
+ else
+ result.angle = angle_plane_plane(f1.get_plane(), f2.get_plane());
+ }
+
+ // validation
+ if (result.distance_infinite.has_value() && result.distance_infinite->dist < EPSILON)
+ result.distance_infinite.reset();
+ if (result.distance_strict.has_value() && result.distance_strict->dist < EPSILON)
+ result.distance_strict.reset();
+ if (result.angle.has_value() && std::abs(result.angle->angle) < EPSILON)
+ result.angle.reset();
+
+ return result;
+}
+
+void DistAndPoints::transform(const Transform3d& trafo) {
+ from = trafo * from;
+ to = trafo * to;
+ dist = (to - from).norm();
+}
+
+void AngleAndEdges::transform(const Transform3d& trafo) {
+ const Vec3d old_e1 = e1.second - e1.first;
+ const Vec3d old_e2 = e2.second - e2.first;
+ center = trafo * center;
+ e1.first = trafo * e1.first;
+ e1.second = trafo * e1.second;
+ e2.first = trafo * e2.first;
+ e2.second = trafo * e2.second;
+ angle = std::acos(std::clamp(Measure::edge_direction(e1).dot(Measure::edge_direction(e2)), -1.0, 1.0));
+ const Vec3d new_e1 = e1.second - e1.first;
+ const Vec3d new_e2 = e2.second - e2.first;
+ const double average_scale = 0.5 * (new_e1.norm() / old_e1.norm() + new_e2.norm() / old_e2.norm());
+ radius = average_scale * radius;
+}
+
+
+
+
+
+
+
+
+} // namespace Measure
+} // namespace Slic3r
+
diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp
new file mode 100644
index 000000000..d1a0e3866
--- /dev/null
+++ b/src/libslic3r/Measure.hpp
@@ -0,0 +1,197 @@
+#ifndef Slic3r_Measure_hpp_
+#define Slic3r_Measure_hpp_
+
+#include
+#include
+
+#include "Point.hpp"
+
+
+struct indexed_triangle_set;
+
+
+
+namespace Slic3r {
+namespace Measure {
+
+
+enum class SurfaceFeatureType : int {
+ Undef = 0,
+ Point = 1 << 0,
+ Edge = 1 << 1,
+ Circle = 1 << 2,
+ Plane = 1 << 3
+};
+
+class SurfaceFeature {
+public:
+ SurfaceFeature(SurfaceFeatureType type, const Vec3d& pt1, const Vec3d& pt2, std::optional pt3 = std::nullopt, double value = 0.0)
+ : m_type(type), m_pt1(pt1), m_pt2(pt2), m_pt3(pt3), m_value(value) {}
+
+ explicit SurfaceFeature(const Vec3d& pt)
+ : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {}
+
+ // Get type of this feature.
+ SurfaceFeatureType get_type() const { return m_type; }
+
+ // For points, return the point.
+ Vec3d get_point() const { assert(m_type == SurfaceFeatureType::Point); return m_pt1; }
+
+ // For edges, return start and end.
+ std::pair get_edge() const { assert(m_type == SurfaceFeatureType::Edge); return std::make_pair(m_pt1, m_pt2); }
+
+ // For circles, return center, radius and normal.
+ std::tuple get_circle() const { assert(m_type == SurfaceFeatureType::Circle); return std::make_tuple(m_pt1, m_value, m_pt2); }
+
+ // For planes, return index into vector provided by Measuring::get_plane_triangle_indices, normal and point.
+ std::tuple get_plane() const { assert(m_type == SurfaceFeatureType::Plane); return std::make_tuple(int(m_value), m_pt1, m_pt2); }
+
+ // For anything, return an extra point that should also be considered a part of this.
+ std::optional get_extra_point() const { assert(m_type != SurfaceFeatureType::Undef); return m_pt3; }
+
+ bool operator == (const SurfaceFeature& other) const {
+ if (this->m_type != other.m_type) return false;
+ switch (this->m_type)
+ {
+ case SurfaceFeatureType::Undef: { break; }
+ case SurfaceFeatureType::Point: { return (this->m_pt1.isApprox(other.m_pt1)); }
+ case SurfaceFeatureType::Edge: {
+ return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2)) ||
+ (this->m_pt1.isApprox(other.m_pt2) && this->m_pt2.isApprox(other.m_pt1));
+ }
+ case SurfaceFeatureType::Plane:
+ case SurfaceFeatureType::Circle: {
+ return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2) && std::abs(this->m_value - other.m_value) < EPSILON);
+ }
+ }
+
+ return false;
+ }
+
+ bool operator != (const SurfaceFeature& other) const {
+ return !operator == (other);
+ }
+
+private:
+ SurfaceFeatureType m_type{ SurfaceFeatureType::Undef };
+ Vec3d m_pt1{ Vec3d::Zero() };
+ Vec3d m_pt2{ Vec3d::Zero() };
+ std::optional m_pt3;
+ double m_value{ 0.0 };
+};
+
+
+
+class MeasuringImpl;
+
+
+class Measuring {
+public:
+ // Construct the measurement object on a given its. The its must remain
+ // valid and unchanged during the whole lifetime of the object.
+ explicit Measuring(const indexed_triangle_set& its);
+ ~Measuring();
+
+ // Return a reference to a list of all features identified on the its.
+ // Use only for debugging. Expensive, do not call often.
+ std::vector get_all_features() const;
+
+ // Given a face_idx where the mouse cursor points, return a feature that
+ // should be highlighted (if any).
+ std::optional get_feature(size_t face_idx, const Vec3d& point) const;
+
+ // Returns a list of triangle indices for each identified plane. Each
+ // Plane object contains an index into this vector. Expensive, do not
+ // call too often.
+ std::vector> get_planes_triangle_indices() const;
+
+ // Returns the surface features of the plane with the given index
+ const std::vector& get_plane_features(unsigned int plane_id) const;
+
+private:
+ std::unique_ptr priv;
+};
+
+
+struct DistAndPoints {
+ DistAndPoints(double dist_, Vec3d from_, Vec3d to_) : dist(dist_), from(from_), to(to_) {}
+ double dist;
+ Vec3d from;
+ Vec3d to;
+
+ void transform(const Transform3d& trafo);
+};
+
+struct AngleAndEdges {
+ AngleAndEdges(double angle_, const Vec3d& center_, const std::pair& e1_, const std::pair& e2_, double radius_, bool coplanar_)
+ : angle(angle_), center(center_), e1(e1_), e2(e2_), radius(radius_), coplanar(coplanar_) {}
+ double angle;
+ Vec3d center;
+ std::pair e1;
+ std::pair e2;
+ double radius;
+ bool coplanar;
+
+ void transform(const Transform3d& trafo);
+
+ static const AngleAndEdges Dummy;
+};
+
+struct MeasurementResult {
+ std::optional angle;
+ std::optional distance_infinite;
+ std::optional distance_strict;
+ std::optional distance_xyz;
+
+ bool has_distance_data() const {
+ return distance_infinite.has_value() || distance_strict.has_value();
+ }
+
+ bool has_any_data() const {
+ return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value();
+ }
+};
+
+// Returns distance/angle between two SurfaceFeatures.
+MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring = nullptr);
+
+inline Vec3d edge_direction(const Vec3d& from, const Vec3d& to) { return (to - from).normalized(); }
+inline Vec3d edge_direction(const std::pair& e) { return edge_direction(e.first, e.second); }
+inline Vec3d edge_direction(const SurfaceFeature& edge) {
+ assert(edge.get_type() == SurfaceFeatureType::Edge);
+ return edge_direction(edge.get_edge());
+}
+
+inline Vec3d plane_normal(const SurfaceFeature& plane) {
+ assert(plane.get_type() == SurfaceFeatureType::Plane);
+ return std::get<1>(plane.get_plane());
+}
+
+inline bool are_parallel(const Vec3d& v1, const Vec3d& v2) { return std::abs(std::abs(v1.dot(v2)) - 1.0) < EPSILON; }
+inline bool are_perpendicular(const Vec3d& v1, const Vec3d& v2) { return std::abs(v1.dot(v2)) < EPSILON; }
+
+inline bool are_parallel(const std::pair& e1, const std::pair& e2) {
+ return are_parallel(e1.second - e1.first, e2.second - e2.first);
+}
+inline bool are_parallel(const SurfaceFeature& f1, const SurfaceFeature& f2) {
+ if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge)
+ return are_parallel(edge_direction(f1), edge_direction(f2));
+ else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane)
+ return are_perpendicular(edge_direction(f1), plane_normal(f2));
+ else
+ return false;
+}
+
+inline bool are_perpendicular(const SurfaceFeature& f1, const SurfaceFeature& f2) {
+ if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge)
+ return are_perpendicular(edge_direction(f1), edge_direction(f2));
+ else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane)
+ return are_parallel(edge_direction(f1), plane_normal(f2));
+ else
+ return false;
+}
+
+} // namespace Measure
+} // namespace Slic3r
+
+#endif // Slic3r_Measure_hpp_
diff --git a/src/libslic3r/MeasureUtils.hpp b/src/libslic3r/MeasureUtils.hpp
new file mode 100644
index 000000000..0ab4ac121
--- /dev/null
+++ b/src/libslic3r/MeasureUtils.hpp
@@ -0,0 +1,386 @@
+#ifndef Slic3r_MeasureUtils_hpp_
+#define Slic3r_MeasureUtils_hpp_
+
+#include
+
+namespace Slic3r {
+namespace Measure {
+
+// Utility class used to calculate distance circle-circle
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Polynomial1.h
+
+class Polynomial1
+{
+public:
+ Polynomial1(std::initializer_list values)
+ {
+ // C++ 11 will call the default constructor for
+ // Polynomial1 p{}, so it is guaranteed that
+ // values.size() > 0.
+ m_coefficient.resize(values.size());
+ std::copy(values.begin(), values.end(), m_coefficient.begin());
+ EliminateLeadingZeros();
+ }
+
+ // Construction and destruction. The first constructor creates a
+ // polynomial of the specified degree but sets all coefficients to
+ // zero (to ensure initialization). You are responsible for setting
+ // the coefficients, presumably with the degree-term set to a nonzero
+ // number. In the second constructor, the degree is the number of
+ // initializers plus 1, but then adjusted so that coefficient[degree]
+ // is not zero (unless all initializer values are zero).
+ explicit Polynomial1(uint32_t degree)
+ : m_coefficient(static_cast(degree) + 1, 0.0)
+ {}
+
+ // Eliminate any leading zeros in the polynomial, except in the case
+ // the degree is 0 and the coefficient is 0. The elimination is
+ // necessary when arithmetic operations cause a decrease in the degree
+ // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) =
+ // (2 + 3*x). The inputs both have degree 2, so the result is created
+ // with degree 2. After the addition we find that the degree is in
+ // fact 1 and resize the array of coefficients. This function is
+ // called internally by the arithmetic operators, but it is exposed in
+ // the public interface in case you need it for your own purposes.
+ void EliminateLeadingZeros()
+ {
+ const size_t size = m_coefficient.size();
+ if (size > 1) {
+ const double zero = 0.0;
+ int32_t leading;
+ for (leading = static_cast(size) - 1; leading > 0; --leading) {
+ if (m_coefficient[leading] != zero)
+ break;
+ }
+
+ m_coefficient.resize(++leading);
+ }
+ }
+
+ // Set all coefficients to the specified value.
+ void SetCoefficients(double value)
+ {
+ std::fill(m_coefficient.begin(), m_coefficient.end(), value);
+ }
+
+ inline uint32_t GetDegree() const
+ {
+ // By design, m_coefficient.size() > 0.
+ return static_cast(m_coefficient.size() - 1);
+ }
+
+ inline const double& operator[](uint32_t i) const { return m_coefficient[i]; }
+ inline double& operator[](uint32_t i) { return m_coefficient[i]; }
+
+ // Evaluate the polynomial. If the polynomial is invalid, the
+ // function returns zero.
+ double operator()(double t) const
+ {
+ int32_t i = static_cast(m_coefficient.size());
+ double result = m_coefficient[--i];
+ for (--i; i >= 0; --i) {
+ result *= t;
+ result += m_coefficient[i];
+ }
+ return result;
+ }
+
+protected:
+ // The class is designed so that m_coefficient.size() >= 1.
+ std::vector m_coefficient;
+};
+
+inline Polynomial1 operator * (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ Polynomial1 result(p0Degree + p1Degree);
+ result.SetCoefficients(0.0);
+ for (uint32_t i0 = 0; i0 <= p0Degree; ++i0) {
+ for (uint32_t i1 = 0; i1 <= p1Degree; ++i1) {
+ result[i0 + i1] += p0[i0] * p1[i1];
+ }
+ }
+ return result;
+}
+
+inline Polynomial1 operator + (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ uint32_t i;
+ if (p0Degree >= p1Degree) {
+ Polynomial1 result(p0Degree);
+ for (i = 0; i <= p1Degree; ++i) {
+ result[i] = p0[i] + p1[i];
+ }
+ for (/**/; i <= p0Degree; ++i) {
+ result[i] = p0[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+ else {
+ Polynomial1 result(p1Degree);
+ for (i = 0; i <= p0Degree; ++i) {
+ result[i] = p0[i] + p1[i];
+ }
+ for (/**/; i <= p1Degree; ++i) {
+ result[i] = p1[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+}
+
+inline Polynomial1 operator - (const Polynomial1& p0, const Polynomial1& p1)
+{
+ const uint32_t p0Degree = p0.GetDegree();
+ const uint32_t p1Degree = p1.GetDegree();
+ uint32_t i;
+ if (p0Degree >= p1Degree) {
+ Polynomial1 result(p0Degree);
+ for (i = 0; i <= p1Degree; ++i) {
+ result[i] = p0[i] - p1[i];
+ }
+ for (/**/; i <= p0Degree; ++i) {
+ result[i] = p0[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+ else {
+ Polynomial1 result(p1Degree);
+ for (i = 0; i <= p0Degree; ++i) {
+ result[i] = p0[i] - p1[i];
+ }
+ for (/**/; i <= p1Degree; ++i) {
+ result[i] = -p1[i];
+ }
+ result.EliminateLeadingZeros();
+ return result;
+ }
+}
+
+inline Polynomial1 operator * (double scalar, const Polynomial1& p)
+{
+ const uint32_t degree = p.GetDegree();
+ Polynomial1 result(degree);
+ for (uint32_t i = 0; i <= degree; ++i) {
+ result[i] = scalar * p[i];
+ }
+ return result;
+}
+
+// Utility class used to calculate distance circle-circle
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/RootsPolynomial.h
+
+class RootsPolynomial
+{
+public:
+ // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c'
+ // must have at least d+1 elements and the output array 'root' must
+ // have at least d elements.
+
+ // Find the roots on (-infinity,+infinity).
+ static int32_t Find(int32_t degree, const double* c, uint32_t maxIterations, double* roots)
+ {
+ if (degree >= 0 && c != nullptr) {
+ const double zero = 0.0;
+ while (degree >= 0 && c[degree] == zero) {
+ --degree;
+ }
+
+ if (degree > 0) {
+ // Compute the Cauchy bound.
+ const double one = 1.0;
+ const double invLeading = one / c[degree];
+ double maxValue = zero;
+ for (int32_t i = 0; i < degree; ++i) {
+ const double value = std::fabs(c[i] * invLeading);
+ if (value > maxValue)
+ maxValue = value;
+ }
+ const double bound = one + maxValue;
+
+ return FindRecursive(degree, c, -bound, bound, maxIterations, roots);
+ }
+ else if (degree == 0)
+ // The polynomial is a nonzero constant.
+ return 0;
+ else {
+ // The polynomial is identically zero.
+ roots[0] = zero;
+ return 1;
+ }
+ }
+ else
+ // Invalid degree or c.
+ return 0;
+ }
+
+ // If you know that p(tmin) * p(tmax) <= 0, then there must be at
+ // least one root in [tmin, tmax]. Compute it using bisection.
+ static bool Find(int32_t degree, const double* c, double tmin, double tmax, uint32_t maxIterations, double& root)
+ {
+ const double zero = 0.0;
+ double pmin = Evaluate(degree, c, tmin);
+ if (pmin == zero) {
+ root = tmin;
+ return true;
+ }
+ double pmax = Evaluate(degree, c, tmax);
+ if (pmax == zero) {
+ root = tmax;
+ return true;
+ }
+
+ if (pmin * pmax > zero)
+ // It is not known whether the interval bounds a root.
+ return false;
+
+ if (tmin >= tmax)
+ // Invalid ordering of interval endpoitns.
+ return false;
+
+ for (uint32_t i = 1; i <= maxIterations; ++i) {
+ root = 0.5 * (tmin + tmax);
+
+ // This test is designed for 'float' or 'double' when tmin
+ // and tmax are consecutive floating-point numbers.
+ if (root == tmin || root == tmax)
+ break;
+
+ const double p = Evaluate(degree, c, root);
+ const double product = p * pmin;
+ if (product < zero) {
+ tmax = root;
+ pmax = p;
+ }
+ else if (product > zero) {
+ tmin = root;
+ pmin = p;
+ }
+ else
+ break;
+ }
+
+ return true;
+ }
+
+ // Support for the Find functions.
+ static int32_t FindRecursive(int32_t degree, double const* c, double tmin, double tmax, uint32_t maxIterations, double* roots)
+ {
+ // The base of the recursion.
+ const double zero = 0.0;
+ double root = zero;
+ if (degree == 1) {
+ int32_t numRoots;
+ if (c[1] != zero) {
+ root = -c[0] / c[1];
+ numRoots = 1;
+ }
+ else if (c[0] == zero) {
+ root = zero;
+ numRoots = 1;
+ }
+ else
+ numRoots = 0;
+
+ if (numRoots > 0 && tmin <= root && root <= tmax) {
+ roots[0] = root;
+ return 1;
+ }
+ return 0;
+ }
+
+ // Find the roots of the derivative polynomial scaled by 1/degree.
+ // The scaling avoids the factorial growth in the coefficients;
+ // for example, without the scaling, the high-order term x^d
+ // becomes (d!)*x through multiple differentiations. With the
+ // scaling we instead get x. This leads to better numerical
+ // behavior of the root finder.
+ const int32_t derivDegree = degree - 1;
+ std::vector derivCoeff(static_cast(derivDegree) + 1);
+ std::vector derivRoots(derivDegree);
+ for (int32_t i = 0, ip1 = 1; i <= derivDegree; ++i, ++ip1) {
+ derivCoeff[i] = c[ip1] * (double)(ip1) / (double)degree;
+ }
+ const int32_t numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, maxIterations, &derivRoots[0]);
+
+ int32_t numRoots = 0;
+ if (numDerivRoots > 0) {
+ // Find root on [tmin,derivRoots[0]].
+ if (Find(degree, c, tmin, derivRoots[0], maxIterations, root))
+ roots[numRoots++] = root;
+
+ // Find root on [derivRoots[i],derivRoots[i+1]].
+ for (int32_t i = 0, ip1 = 1; i <= numDerivRoots - 2; ++i, ++ip1) {
+ if (Find(degree, c, derivRoots[i], derivRoots[ip1], maxIterations, root))
+ roots[numRoots++] = root;
+ }
+
+ // Find root on [derivRoots[numDerivRoots-1],tmax].
+ if (Find(degree, c, derivRoots[static_cast(numDerivRoots) - 1], tmax, maxIterations, root))
+ roots[numRoots++] = root;
+ }
+ else {
+ // The polynomial is monotone on [tmin,tmax], so has at most one root.
+ if (Find(degree, c, tmin, tmax, maxIterations, root))
+ roots[numRoots++] = root;
+ }
+ return numRoots;
+ }
+
+ static double Evaluate(int32_t degree, const double* c, double t)
+ {
+ int32_t i = degree;
+ double result = c[i];
+ while (--i >= 0) {
+ result = t * result + c[i];
+ }
+ return result;
+ }
+};
+
+// Adaptation of code found in:
+// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Vector.h
+
+// Construct a single vector orthogonal to the nonzero input vector. If
+// the maximum absolute component occurs at index i, then the orthogonal
+// vector U has u[i] = v[i+1], u[i+1] = -v[i], and all other components
+// zero. The index addition i+1 is computed modulo N.
+inline Vec3d get_orthogonal(const Vec3d& v, bool unitLength)
+{
+ double cmax = std::fabs(v[0]);
+ int32_t imax = 0;
+ for (int32_t i = 1; i < 3; ++i) {
+ double c = std::fabs(v[i]);
+ if (c > cmax) {
+ cmax = c;
+ imax = i;
+ }
+ }
+
+ Vec3d result = Vec3d::Zero();
+ int32_t inext = imax + 1;
+ if (inext == 3)
+ inext = 0;
+
+ result[imax] = v[inext];
+ result[inext] = -v[imax];
+ if (unitLength) {
+ const double sqrDistance = result[imax] * result[imax] + result[inext] * result[inext];
+ const double invLength = 1.0 / std::sqrt(sqrDistance);
+ result[imax] *= invLength;
+ result[inext] *= invLength;
+ }
+ return result;
+}
+
+} // namespace Slic3r
+} // namespace Measure
+
+#endif // Slic3r_MeasureUtils_hpp_
diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp
new file mode 100644
index 000000000..9e547eec4
--- /dev/null
+++ b/src/libslic3r/SurfaceMesh.hpp
@@ -0,0 +1,154 @@
+#ifndef slic3r_SurfaceMesh_hpp_
+#define slic3r_SurfaceMesh_hpp_
+
+#include
+#include
+
+namespace Slic3r {
+
+class TriangleMesh;
+
+
+
+enum Face_index : int;
+
+class Halfedge_index {
+ friend class SurfaceMesh;
+
+public:
+ Halfedge_index() : m_face(Face_index(-1)), m_side(0) {}
+ Face_index face() const { return m_face; }
+ unsigned char side() const { return m_side; }
+ bool is_invalid() const { return int(m_face) < 0; }
+ bool operator!=(const Halfedge_index& rhs) const { return ! ((*this) == rhs); }
+ bool operator==(const Halfedge_index& rhs) const { return m_face == rhs.m_face && m_side == rhs.m_side; }
+
+private:
+ Halfedge_index(int face_idx, unsigned char side_idx) : m_face(Face_index(face_idx)), m_side(side_idx) {}
+
+ Face_index m_face;
+ unsigned char m_side;
+};
+
+
+
+class Vertex_index {
+ friend class SurfaceMesh;
+
+public:
+ Vertex_index() : m_face(Face_index(-1)), m_vertex_idx(0) {}
+ bool is_invalid() const { return int(m_face) < 0; }
+ bool operator==(const Vertex_index& rhs) const = delete; // Use SurfaceMesh::is_same_vertex.
+
+private:
+ Vertex_index(int face_idx, unsigned char vertex_idx) : m_face(Face_index(face_idx)), m_vertex_idx(vertex_idx) {}
+
+ Face_index m_face;
+ unsigned char m_vertex_idx;
+};
+
+
+
+class SurfaceMesh {
+public:
+ explicit SurfaceMesh(const indexed_triangle_set& its)
+ : m_its(its),
+ m_face_neighbors(its_face_neighbors_par(its))
+ {}
+ SurfaceMesh(const SurfaceMesh&) = delete;
+ SurfaceMesh& operator=(const SurfaceMesh&) = delete;
+
+ Vertex_index source(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side); }
+ Vertex_index target(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side == 2 ? 0 : h.m_side + 1); }
+ Face_index face(Halfedge_index h) const { assert(! h.is_invalid()); return h.m_face; }
+
+ Halfedge_index next(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side + 1) % 3; return h; }
+ Halfedge_index prev(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side == 0 ? 2 : h.m_side - 1); return h; }
+ Halfedge_index halfedge(Vertex_index v) const { return Halfedge_index(v.m_face, (v.m_vertex_idx == 0 ? 2 : v.m_vertex_idx - 1)); }
+ Halfedge_index halfedge(Face_index f) const { return Halfedge_index(f, 0); }
+ Halfedge_index opposite(Halfedge_index h) const {
+ if (h.is_invalid())
+ return h;
+
+ int face_idx = m_face_neighbors[h.m_face][h.m_side];
+ Halfedge_index h_candidate = halfedge(Face_index(face_idx));
+
+ if (h_candidate.is_invalid())
+ return Halfedge_index(); // invalid
+
+ for (int i=0; i<3; ++i) {
+ if (is_same_vertex(source(h_candidate), target(h))) {
+ // Meshes in PrusaSlicer should be fixed enough for the following not to happen.
+ assert(is_same_vertex(target(h_candidate), source(h)));
+ return h_candidate;
+ }
+ h_candidate = next(h_candidate);
+ }
+ return Halfedge_index(); // invalid
+ }
+
+ Halfedge_index next_around_target(Halfedge_index h) const { return opposite(next(h)); }
+ Halfedge_index prev_around_target(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : prev(op)); }
+ Halfedge_index next_around_source(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : next(op)); }
+ Halfedge_index prev_around_source(Halfedge_index h) const { return opposite(prev(h)); }
+ Halfedge_index halfedge(Vertex_index source, Vertex_index target) const
+ {
+ Halfedge_index hi(source.m_face, source.m_vertex_idx);
+ assert(! hi.is_invalid());
+
+ const Vertex_index orig_target = this->target(hi);
+ Vertex_index current_target = orig_target;
+
+ while (! is_same_vertex(current_target, target)) {
+ hi = next_around_source(hi);
+ if (hi.is_invalid())
+ break;
+ current_target = this->target(hi);
+ if (is_same_vertex(current_target, orig_target))
+ return Halfedge_index(); // invalid
+ }
+
+ return hi;
+ }
+
+ const stl_vertex& point(Vertex_index v) const { return m_its.vertices[m_its.indices[v.m_face][v.m_vertex_idx]]; }
+
+ size_t degree(Vertex_index v) const
+ {
+ Halfedge_index h_first = halfedge(v);
+ Halfedge_index h = next_around_target(h_first);
+ size_t degree = 2;
+ while (! h.is_invalid() && h != h_first) {
+ h = next_around_target(h);
+ ++degree;
+ }
+ return h.is_invalid() ? 0 : degree - 1;
+ }
+
+ size_t degree(Face_index f) const {
+ size_t total = 0;
+ for (unsigned char i=0; i<3; ++i) {
+ size_t d = degree(Vertex_index(f, i));
+ if (d == 0)
+ return 0;
+ total += d;
+ }
+ assert(total - 6 >= 0);
+ return total - 6; // we counted 3 halfedges from f, and one more for each neighbor
+ }
+
+ bool is_border(Halfedge_index h) const { return m_face_neighbors[h.m_face][h.m_side] == -1; }
+
+ bool is_same_vertex(const Vertex_index& a, const Vertex_index& b) const { return m_its.indices[a.m_face][a.m_vertex_idx] == m_its.indices[b.m_face][b.m_vertex_idx]; }
+ Vec3i get_face_neighbors(Face_index face_id) const { assert(int(face_id) < int(m_face_neighbors.size())); return m_face_neighbors[face_id]; }
+
+
+
+private:
+ const std::vector m_face_neighbors;
+ const indexed_triangle_set& m_its;
+};
+
+} //namespace Slic3r
+
+#endif // slic3r_SurfaceMesh_hpp_
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 695c58abe..e390ff209 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -26,6 +26,8 @@
#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0
// Disable using instanced models to render options in gcode preview
#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1
+// Enable Measure Gizmo debug window
+#define ENABLE_MEASURE_GIZMO_DEBUG 0
// Enable rendering of objects using environment map
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index a25a63c46..d37221b7b 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -63,6 +63,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoSimplify.hpp
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
+ GUI/Gizmos/GLGizmoMeasure.cpp
+ GUI/Gizmos/GLGizmoMeasure.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
GUI/GLModel.hpp
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 619f09901..3fa5c24c6 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1,8008 +1,8007 @@
-#include "libslic3r/libslic3r.h"
-#include "GLCanvas3D.hpp"
-
-#include
-
-#include "libslic3r/BuildVolume.hpp"
-#include "libslic3r/ClipperUtils.hpp"
-#include "libslic3r/PrintConfig.hpp"
-#include "libslic3r/GCode/ThumbnailData.hpp"
-#include "libslic3r/Geometry/ConvexHull.hpp"
-#include "libslic3r/ExtrusionEntity.hpp"
-#include "libslic3r/Layer.hpp"
-#include "libslic3r/Utils.hpp"
-#include "libslic3r/LocalesUtils.hpp"
-#include "libslic3r/Technologies.hpp"
-#include "libslic3r/Tesselate.hpp"
-#include "libslic3r/PresetBundle.hpp"
-#include "3DBed.hpp"
-#include "3DScene.hpp"
-#include "BackgroundSlicingProcess.hpp"
-#include "GLShader.hpp"
-#include "GUI.hpp"
-#include "Tab.hpp"
-#include "GUI_Preview.hpp"
-#include "OpenGLManager.hpp"
-#include "Plater.hpp"
-#include "MainFrame.hpp"
-#include "GUI_App.hpp"
-#include "GUI_ObjectList.hpp"
-#include "GUI_ObjectManipulation.hpp"
-#include "Mouse3DController.hpp"
-#include "I18N.hpp"
-#include "NotificationManager.hpp"
-#include "format.hpp"
-
-#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
-#include "slic3r/Utils/UndoRedo.hpp"
-
-#if ENABLE_RETINA_GL
-#include "slic3r/Utils/RetinaHelper.hpp"
-#endif
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
-#include "libslic3r/Print.hpp"
-#include "libslic3r/SLAPrint.hpp"
-
-#include "wxExtensions.hpp"
-
-#include
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include "DoubleSlider.hpp"
-
-#include
-
-static constexpr const float TRACKBALLSIZE = 0.8f;
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-static const Slic3r::ColorRGBA DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f, 1.0f };
-static const Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f, 1.0f };
-static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f, 1.0f };
-static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f };
-#else
-static const Slic3r::ColorRGB DEFAULT_BG_DARK_COLOR = { 0.478f, 0.478f, 0.478f };
-static const Slic3r::ColorRGB DEFAULT_BG_LIGHT_COLOR = { 0.753f, 0.753f, 0.753f };
-static const Slic3r::ColorRGB ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f };
-static const Slic3r::ColorRGB ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f };
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-// Number of floats
-static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB
-// Reserve size in number of floats.
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
-static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB
-// Reserve size in number of floats, maximum sum of all preallocated buffers.
-//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
-namespace Slic3r {
-namespace GUI {
-
-#ifdef __WXGTK3__
-// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
-RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {}
-RetinaHelper::~RetinaHelper() {}
-float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); }
-#endif // __WXGTK3__
-
-// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0.
-#if defined(__linux__) && defined(Convex)
-#undef Convex
-#endif
-
-GLCanvas3D::LayersEditing::~LayersEditing()
-{
- if (m_z_texture_id != 0) {
- glsafe(::glDeleteTextures(1, &m_z_texture_id));
- m_z_texture_id = 0;
- }
- delete m_slicing_parameters;
-}
-
-const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f;
-
-void GLCanvas3D::LayersEditing::init()
-{
- glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id));
- glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
- if (!OpenGLManager::get_gl_info().is_core_profile() || !OpenGLManager::get_gl_info().is_mesa()) {
- glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
- glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
- }
- glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
- glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST));
- glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1));
- glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
-}
-
-void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
-{
- m_config = config;
- delete m_slicing_parameters;
- m_slicing_parameters = nullptr;
- m_layers_texture.valid = false;
-}
-
-void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
-{
- const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr;
- // Maximum height of an object changes when the object gets rotated or scaled.
- // Changing maximum height of an object will invalidate the layer heigth editing profile.
- // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently.
- const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z());
- if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
- (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
- m_layer_height_profile.clear();
- m_layer_height_profile_modified = false;
- delete m_slicing_parameters;
- m_slicing_parameters = nullptr;
- m_layers_texture.valid = false;
- this->last_object_id = object_id;
- m_model_object = model_object_new;
- m_object_max_z = new_max_z;
- }
-}
-
-bool GLCanvas3D::LayersEditing::is_allowed() const
-{
- return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0;
-}
-
-bool GLCanvas3D::LayersEditing::is_enabled() const
-{
- return m_enabled;
-}
-
-void GLCanvas3D::LayersEditing::set_enabled(bool enabled)
-{
- m_enabled = is_allowed() && enabled;
-}
-
-float GLCanvas3D::LayersEditing::s_overlay_window_width;
-
-void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas)
-{
- if (!m_enabled)
- return;
-
- const Size& cnv_size = canvas.get_canvas_size();
-
- ImGuiWrapper& imgui = *wxGetApp().imgui();
- imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH,
- static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f);
-
- imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
-
- imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Left mouse button:"));
- ImGui::SameLine();
- imgui.text(_L("Add detail"));
-
- imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Right mouse button:"));
- ImGui::SameLine();
- imgui.text(_L("Remove detail"));
-
- imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Left mouse button:"));
- ImGui::SameLine();
- imgui.text(_L("Reset to base"));
-
- imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Shift + Right mouse button:"));
- ImGui::SameLine();
- imgui.text(_L("Smoothing"));
-
- imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mouse wheel:"));
- ImGui::SameLine();
- imgui.text(_L("Increase/decrease edit area"));
-
- ImGui::Separator();
- if (imgui.button(_L("Adaptive")))
- wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality));
-
- ImGui::SameLine();
- float text_align = ImGui::GetCursorPosX();
- ImGui::AlignTextToFramePadding();
- imgui.text(_L("Quality / Speed"));
- if (ImGui::IsItemHovered()) {
- ImGui::BeginTooltip();
- ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8());
- ImGui::EndTooltip();
- }
-
- ImGui::SameLine();
- float widget_align = ImGui::GetCursorPosX();
- ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
- m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f);
- imgui.slider_float("", &m_adaptive_quality, 0.0f, 1.f, "%.2f");
-
- ImGui::Separator();
- if (imgui.button(_L("Smooth")))
- wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
-
- ImGui::SameLine();
- ImGui::SetCursorPosX(text_align);
- ImGui::AlignTextToFramePadding();
- imgui.text(_L("Radius"));
- ImGui::SameLine();
- ImGui::SetCursorPosX(widget_align);
- ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
- int radius = (int)m_smooth_params.radius;
- if (ImGui::SliderInt("##1", &radius, 1, 10)) {
- radius = std::clamp(radius, 1, 10);
- m_smooth_params.radius = (unsigned int)radius;
- }
-
- ImGui::SetCursorPosX(text_align);
- ImGui::AlignTextToFramePadding();
- imgui.text(_L("Keep min"));
- ImGui::SameLine();
- if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization
- ImGui::SetCursorPosX(widget_align);
-
- ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
- imgui.checkbox("##2", m_smooth_params.keep_min);
-
- ImGui::Separator();
- if (imgui.button(_L("Reset")))
- wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
-
- GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/;
- imgui.end();
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- render_active_object_annotations(canvas);
- render_profile(canvas);
-#else
- const Rect& bar_rect = get_bar_rect_viewport(canvas);
- render_active_object_annotations(canvas, bar_rect);
- render_profile(bar_rect);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
-{
- const Vec2d mouse_pos = canvas.get_local_mouse_position();
- const Rect& rect = get_bar_rect_screen(canvas);
- float x = (float)mouse_pos.x();
- float y = (float)mouse_pos.y();
- float t = rect.get_top();
- float b = rect.get_bottom();
-
- return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ?
- // Inside the bar.
- (b - y - 1.0f) / (b - t - 1.0f) :
- // Outside the bar.
- -1000.0f;
-}
-
-bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y)
-{
- const Rect& rect = get_bar_rect_screen(canvas);
- return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom();
-}
-
-Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
-{
- const Size& cnv_size = canvas.get_canvas_size();
- float w = (float)cnv_size.get_width();
- float h = (float)cnv_size.get_height();
-
- return { w - thickness_bar_width(canvas), 0.0f, w, h };
-}
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
-Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
-{
- const Size& cnv_size = canvas.get_canvas_size();
- float half_w = 0.5f * (float)cnv_size.get_width();
- float half_h = 0.5f * (float)cnv_size.get_height();
- float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
- return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom };
-}
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
-bool GLCanvas3D::LayersEditing::is_initialized() const
-{
- return wxGetApp().get_shader("variable_layer_height") != nullptr;
-}
-
-std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const
-{
- std::string ret;
- if (m_enabled && m_layer_height_profile.size() >= 4) {
- float z = get_cursor_z_relative(canvas);
- if (z != -1000.0f) {
- z *= m_object_max_z;
-
- float h = 0.0f;
- for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) {
- const float zi = static_cast(m_layer_height_profile[i]);
- const float zi_1 = static_cast(m_layer_height_profile[i - 2]);
- if (zi_1 <= z && z <= zi) {
- float dz = zi - zi_1;
- h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) :
- static_cast(m_layer_height_profile[i + 1]);
- break;
- }
- }
- if (h > 0.0f)
- ret = std::to_string(h);
- }
- }
- return ret;
-}
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas)
-{
- const Size cnv_size = canvas.get_canvas_size();
- const float cnv_width = (float)cnv_size.get_width();
- const float cnv_height = (float)cnv_size.get_height();
- if (cnv_width == 0.0f || cnv_height == 0.0f)
- return;
-
- const float cnv_inv_width = 1.0f / cnv_width;
-#else
-void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect)
-{
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height");
- if (shader == nullptr)
- return;
-
- shader->start_using();
-
- shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z));
- shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height);
- shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas));
- shader->set_uniform("z_cursor_band_width", band_width);
- shader->set_uniform("object_max_z", m_object_max_z);
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- shader->set_uniform("view_model_matrix", Transform3d::Identity());
- shader->set_uniform("projection_matrix", Transform3d::Identity());
- shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
- glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
-
- // Render the color bar
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- if (!m_profile.background.is_initialized() || m_profile.old_canvas_width != cnv_width) {
- m_profile.old_canvas_width = cnv_width;
- m_profile.background.reset();
-
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3T2 };
- init_data.reserve_vertices(4);
- init_data.reserve_indices(6);
-
- // vertices
- const float l = 1.0f - 2.0f * THICKNESS_BAR_WIDTH * cnv_inv_width;
- const float r = 1.0f;
- const float t = 1.0f;
- const float b = -1.0f;
- init_data.add_vertex(Vec3f(l, b, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 0.0f));
- init_data.add_vertex(Vec3f(r, b, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 0.0f));
- init_data.add_vertex(Vec3f(r, t, 0.0f), Vec3f::UnitZ(), Vec2f(1.0f, 1.0f));
- init_data.add_vertex(Vec3f(l, t, 0.0f), Vec3f::UnitZ(), Vec2f(0.0f, 1.0f));
-
- // indices
- init_data.add_triangle(0, 1, 2);
- init_data.add_triangle(2, 3, 0);
-
- m_profile.background.init_from(std::move(init_data));
- }
-
- m_profile.background.render();
-#else
- const float l = bar_rect.get_left();
- const float r = bar_rect.get_right();
- const float t = bar_rect.get_top();
- const float b = bar_rect.get_bottom();
-
- ::glBegin(GL_QUADS);
- ::glNormal3f(0.0f, 0.0f, 1.0f);
- ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b);
- ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b);
- ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t);
- ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t);
- glsafe(::glEnd());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
-
- shader->stop_using();
-}
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-void GLCanvas3D::LayersEditing::render_profile(const GLCanvas3D& canvas)
-#else
-void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect)
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-{
- //FIXME show some kind of legend.
-
- if (!m_slicing_parameters)
- return;
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- const Size cnv_size = canvas.get_canvas_size();
- const float cnv_width = (float)cnv_size.get_width();
- const float cnv_height = (float)cnv_size.get_height();
- if (cnv_width == 0.0f || cnv_height == 0.0f)
- return;
-
- // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
- const float scale_x = THICKNESS_BAR_WIDTH / float(1.12 * m_slicing_parameters->max_layer_height);
- const float scale_y = cnv_height / m_object_max_z;
-
- const float cnv_inv_width = 1.0f / cnv_width;
- const float cnv_inv_height = 1.0f / cnv_height;
-#else
- // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
- const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height);
- const float scale_y = bar_rect.get_height() / m_object_max_z;
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- // Baseline
- if (!m_profile.baseline.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) {
- m_profile.baseline.reset();
-
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P2 };
- init_data.color = ColorRGBA::BLACK();
- init_data.reserve_vertices(2);
- init_data.reserve_indices(2);
-
- // vertices
- const float axis_x = 2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_slicing_parameters->layer_height) * scale_x) * cnv_inv_width - 0.5f);
- init_data.add_vertex(Vec2f(axis_x, -1.0f));
- init_data.add_vertex(Vec2f(axis_x, 1.0f));
-
- // indices
- init_data.add_line(0, 1);
-
- m_profile.baseline.init_from(std::move(init_data));
- }
-
- if (!m_profile.profile.is_initialized() || m_profile.old_layer_height_profile != m_layer_height_profile) {
- m_profile.old_layer_height_profile = m_layer_height_profile;
- m_profile.profile.reset();
-
- GLModel::Geometry init_data;
- init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P2 };
- init_data.color = ColorRGBA::BLUE();
- init_data.reserve_vertices(m_layer_height_profile.size() / 2);
- init_data.reserve_indices(m_layer_height_profile.size() / 2);
-
- // vertices + indices
- for (unsigned int i = 0; i < (unsigned int)m_layer_height_profile.size(); i += 2) {
- init_data.add_vertex(Vec2f(2.0f * ((cnv_width - THICKNESS_BAR_WIDTH + float(m_layer_height_profile[i + 1]) * scale_x) * cnv_inv_width - 0.5f),
- 2.0f * (float(m_layer_height_profile[i]) * scale_y * cnv_inv_height - 0.5)));
- init_data.add_index(i / 2);
- }
-
- m_profile.profile.init_from(std::move(init_data));
- }
-
-#if ENABLE_GL_CORE_PROFILE
- GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
-#else
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
-#endif // ENABLE_GL_CORE_PROFILE
- if (shader != nullptr) {
- shader->start_using();
- shader->set_uniform("view_model_matrix", Transform3d::Identity());
- shader->set_uniform("projection_matrix", Transform3d::Identity());
-#if ENABLE_GL_CORE_PROFILE
- const std::array& viewport = wxGetApp().plater()->get_camera().get_viewport();
- shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
- shader->set_uniform("width", 0.25f);
- shader->set_uniform("gap_size", 0.0f);
-#endif // ENABLE_GL_CORE_PROFILE
- m_profile.baseline.render();
- m_profile.profile.render();
- shader->stop_using();
- }
-#else
- const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x;
-
- // Baseline
- glsafe(::glColor3f(0.0f, 0.0f, 0.0f));
- ::glBegin(GL_LINE_STRIP);
- ::glVertex2f(x, bar_rect.get_bottom());
- ::glVertex2f(x, bar_rect.get_top());
- glsafe(::glEnd());
-
- // Curve
- glsafe(::glColor3f(0.0f, 0.0f, 1.0f));
- ::glBegin(GL_LINE_STRIP);
- for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2)
- ::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y);
- glsafe(::glEnd());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const GLVolumeCollection& volumes)
-{
- assert(this->is_allowed());
- assert(this->last_object_id != -1);
-
- GLShaderProgram* current_shader = wxGetApp().get_current_shader();
- ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); });
- if (current_shader != nullptr)
- current_shader->stop_using();
-
- GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height");
- if (shader == nullptr)
- return;
-
- shader->start_using();
-
- generate_layer_height_texture();
-
- // Uniforms were resolved, go ahead using the layer editing shader.
- shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z)));
- shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height));
- shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas)));
- shader->set_uniform("z_cursor_band_width", float(this->band_width));
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- const Camera& camera = wxGetApp().plater()->get_camera();
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- // Initialize the layer height texture mapping.
- const GLsizei w = (GLsizei)m_layers_texture.width;
- const GLsizei h = (GLsizei)m_layers_texture.height;
- const GLsizei half_w = w / 2;
- const GLsizei half_h = h / 2;
- glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
- glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
- glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
- glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
- glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data()));
- glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4));
- for (GLVolume* glvolume : volumes.volumes) {
- // Render the object using the layer editing shader and texture.
- if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier)
- continue;
-
- shader->set_uniform("volume_world_matrix", glvolume->world_matrix());
- shader->set_uniform("object_max_z", 0.0f);
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- const Transform3d& view_matrix = camera.get_view_matrix();
- const Transform3d model_matrix = glvolume->world_matrix();
- shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
- const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
- shader->set_uniform("view_normal_matrix", view_normal_matrix);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glvolume->render();
- }
- // Revert back to the previous shader.
- glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
-}
-
-void GLCanvas3D::LayersEditing::adjust_layer_height_profile()
-{
- this->update_slicing_parameters();
- PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile);
- Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action);
- m_layer_height_profile_modified = true;
- m_layers_texture.valid = false;
-}
-
-void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas)
-{
- const_cast(m_model_object)->layer_height_profile.clear();
- m_layer_height_profile.clear();
- m_layers_texture.valid = false;
- canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
- wxGetApp().obj_list()->update_info_items(last_object_id);
-}
-
-void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor)
-{
- this->update_slicing_parameters();
- m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor);
- const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile);
- m_layers_texture.valid = false;
- canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
- wxGetApp().obj_list()->update_info_items(last_object_id);
-}
-
-void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params)
-{
- this->update_slicing_parameters();
- m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params);
- const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile);
- m_layers_texture.valid = false;
- canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
- wxGetApp().obj_list()->update_info_items(last_object_id);
-}
-
-void GLCanvas3D::LayersEditing::generate_layer_height_texture()
-{
- this->update_slicing_parameters();
- // Always try to update the layer height profile.
- bool update = ! m_layers_texture.valid;
- if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) {
- // Initialized to the default value.
- m_layer_height_profile_modified = false;
- update = true;
- }
- // Update if the layer height profile was changed, or when the texture is not valid.
- if (! update && ! m_layers_texture.data.empty() && m_layers_texture.cells > 0)
- // Texture is valid, don't update.
- return;
-
- if (m_layers_texture.data.empty()) {
- m_layers_texture.width = 1024;
- m_layers_texture.height = 1024;
- m_layers_texture.levels = 2;
- m_layers_texture.data.assign(m_layers_texture.width * m_layers_texture.height * 5, 0);
- }
-
- bool level_of_detail_2nd_level = true;
- m_layers_texture.cells = Slic3r::generate_layer_height_texture(
- *m_slicing_parameters,
- Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile),
- m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level);
- m_layers_texture.valid = true;
-}
-
-void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas)
-{
- if (last_object_id >= 0) {
- if (m_layer_height_profile_modified) {
- wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit"));
- const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile);
- canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
- wxGetApp().obj_list()->update_info_items(last_object_id);
- }
- }
- m_layer_height_profile_modified = false;
-}
-
-void GLCanvas3D::LayersEditing::update_slicing_parameters()
-{
- if (m_slicing_parameters == nullptr) {
- m_slicing_parameters = new SlicingParameters();
- *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z);
- }
-}
-
-float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas)
-{
- return
-#if ENABLE_RETINA_GL
- canvas.get_canvas_size().get_scale_factor()
-#else
- canvas.get_wxglcanvas()->GetContentScaleFactor()
-#endif
- * THICKNESS_BAR_WIDTH;
-}
-
-
-const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
-const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
-const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5;
-
-GLCanvas3D::Mouse::Drag::Drag()
- : start_position_2D(Invalid_2D_Point)
- , start_position_3D(Invalid_3D_Point)
- , move_volume_idx(-1)
- , move_requires_threshold(false)
- , move_start_threshold_position_2D(Invalid_2D_Point)
-{
-}
-
-GLCanvas3D::Mouse::Mouse()
- : dragging(false)
- , position(DBL_MAX, DBL_MAX)
- , scene_position(DBL_MAX, DBL_MAX, DBL_MAX)
- , ignore_left_up(false)
-{
-}
-
-void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const
-{
- if (!m_enabled || !is_shown())
- return;
-
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Model* model = m_canvas.get_model();
- if (model == nullptr)
- return;
-
- Transform3d world_to_eye = camera.get_view_matrix();
- Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye;
- const std::array& viewport = camera.get_viewport();
-
- struct Owner
- {
- int obj_idx;
- int inst_idx;
- size_t model_instance_id;
- BoundingBoxf3 world_box;
- double eye_center_z;
- std::string title;
- std::string label;
- std::string print_order;
- bool selected;
- };
-
- // collect owners world bounding boxes and data from volumes
- std::vector owners;
- const GLVolumeCollection& volumes = m_canvas.get_volumes();
- for (const GLVolume* volume : volumes.volumes) {
- int obj_idx = volume->object_idx();
- if (0 <= obj_idx && obj_idx < (int)model->objects.size()) {
- int inst_idx = volume->instance_idx();
- std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [obj_idx, inst_idx](const Owner& owner) {
- return (owner.obj_idx == obj_idx) && (owner.inst_idx == inst_idx);
- });
- if (it != owners.end()) {
- it->world_box.merge(volume->transformed_bounding_box());
- it->selected &= volume->selected;
- } else {
- const ModelObject* model_object = model->objects[obj_idx];
- Owner owner;
- owner.obj_idx = obj_idx;
- owner.inst_idx = inst_idx;
- owner.model_instance_id = model_object->instances[inst_idx]->id().id;
- owner.world_box = volume->transformed_bounding_box();
- owner.title = "object" + std::to_string(obj_idx) + "_inst##" + std::to_string(inst_idx);
- owner.label = model_object->name;
- if (model_object->instances.size() > 1)
- owner.label += " (" + std::to_string(inst_idx + 1) + ")";
- owner.selected = volume->selected;
- owners.emplace_back(owner);
- }
- }
- }
-
- // updates print order strings
- if (sorted_instances.size() > 1) {
- for (size_t i = 0; i < sorted_instances.size(); ++i) {
- size_t id = sorted_instances[i]->id().id;
- std::vector::iterator it = std::find_if(owners.begin(), owners.end(), [id](const Owner& owner) {
- return owner.model_instance_id == id;
- });
- if (it != owners.end())
- it->print_order = std::string((_(L("Seq."))).ToUTF8()) + "#: " + std::to_string(i + 1);
- }
- }
-
- // calculate eye bounding boxes center zs
- for (Owner& owner : owners) {
- owner.eye_center_z = (world_to_eye * owner.world_box.center())(2);
- }
-
- // sort owners by center eye zs and selection
- std::sort(owners.begin(), owners.end(), [](const Owner& owner1, const Owner& owner2) {
- if (!owner1.selected && owner2.selected)
- return true;
- else if (owner1.selected && !owner2.selected)
- return false;
- else
- return (owner1.eye_center_z < owner2.eye_center_z);
- });
-
- ImGuiWrapper& imgui = *wxGetApp().imgui();
-
- // render info windows
- for (const Owner& owner : owners) {
- Vec3d screen_box_center = world_to_screen * owner.world_box.center();
- float x = 0.0f;
- float y = 0.0f;
- if (camera.get_type() == Camera::EType::Perspective) {
- x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2];
- y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3];
- } else {
- x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2];
- y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3];
- }
-
- if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y)
- continue;
-
- ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f);
- ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
- ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f));
- imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f);
- imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
- ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
- float win_w = ImGui::GetWindowWidth();
- float label_len = ImGui::CalcTextSize(owner.label.c_str()).x;
- ImGui::SetCursorPosX(0.5f * (win_w - label_len));
- ImGui::AlignTextToFramePadding();
- imgui.text(owner.label);
-
- if (!owner.print_order.empty()) {
- ImGui::Separator();
- float po_len = ImGui::CalcTextSize(owner.print_order.c_str()).x;
- ImGui::SetCursorPosX(0.5f * (win_w - po_len));
- ImGui::AlignTextToFramePadding();
- imgui.text(owner.print_order);
- }
-
- // force re-render while the windows gets to its final size (it takes several frames)
- if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
- imgui.set_requires_extra_frame();
-
- imgui.end();
- ImGui::PopStyleColor();
- ImGui::PopStyleVar(2);
- }
-}
-
-void GLCanvas3D::Tooltip::set_text(const std::string& text)
-{
- // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
- m_text = m_in_imgui ? std::string() : text;
-}
-
-void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas)
-{
- static ImVec2 size(0.0f, 0.0f);
-
- auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) {
- auto calc_cursor_height = []() {
- float ret = 16.0f;
-#ifdef _WIN32
- // see: https://forums.codeguru.com/showthread.php?449040-get-the-system-current-cursor-size
- // this code is not perfect because it returns a maximum height equal to 31 even if the cursor bitmap shown on screen is bigger
- // but at least it gives the same result as wxWidgets in the settings tabs
- ICONINFO ii;
- if (::GetIconInfo((HICON)GetCursor(), &ii) != 0) {
- BITMAP bitmap;
- ::GetObject(ii.hbmMask, sizeof(BITMAP), &bitmap);
- int width = bitmap.bmWidth;
- int height = (ii.hbmColor == nullptr) ? bitmap.bmHeight / 2 : bitmap.bmHeight;
- HDC dc = ::CreateCompatibleDC(nullptr);
- if (dc != nullptr) {
- if (::SelectObject(dc, ii.hbmMask) != nullptr) {
- for (int i = 0; i < width; ++i) {
- for (int j = 0; j < height; ++j) {
- if (::GetPixel(dc, i, j) != RGB(255, 255, 255)) {
- if (ret < float(j))
- ret = float(j);
- }
- }
- }
- ::DeleteDC(dc);
- }
- }
- ::DeleteObject(ii.hbmColor);
- ::DeleteObject(ii.hbmMask);
- }
-#endif // _WIN32
- return ret;
- };
-
- const Size cnv_size = canvas.get_canvas_size();
- const float x = std::clamp(float(position.x()), 0.0f, float(cnv_size.get_width()) - wnd_size.x);
- const float y = std::clamp(float(position.y()) + calc_cursor_height(), 0.0f, float(cnv_size.get_height()) - wnd_size.y);
- return Vec2f(x, y);
- };
-
- if (m_text.empty()) {
- m_start_time = std::chrono::steady_clock::now();
- return;
- }
-
- // draw the tooltip as hidden until the delay is expired
- // use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f
- const float alpha = (std::chrono::duration_cast(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f;
-
- const Vec2f position = validate_position(mouse_position, canvas, size);
-
- ImGuiWrapper& imgui = *wxGetApp().imgui();
- ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
- ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
- imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f);
-
- imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
- ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
- ImGui::TextUnformatted(m_text.c_str());
-
- // force re-render while the windows gets to its final size (it may take several frames) or while hidden
- if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
- imgui.set_requires_extra_frame();
-
- size = ImGui::GetWindowSize();
-
- imgui.end();
- ImGui::PopStyleVar(2);
-}
-
-void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons)
-{
- m_perimeter.reset();
- m_fill.reset();
- if (polygons.empty())
- return;
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- size_t triangles_count = 0;
- for (const Polygon& poly : polygons) {
- triangles_count += poly.points.size() - 2;
- }
- const size_t vertices_count = 3 * triangles_count;
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
- if (m_render_fill) {
- GLModel::Geometry fill_data;
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- fill_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
- fill_data.color = { 0.3333f, 0.0f, 0.0f, 0.5f };
-
- // vertices + indices
- const ExPolygons polygons_union = union_ex(polygons);
- unsigned int vertices_counter = 0;
- for (const ExPolygon& poly : polygons_union) {
- const std::vector triangulation = triangulate_expolygon_3d(poly);
- fill_data.reserve_vertices(fill_data.vertices_count() + triangulation.size());
- fill_data.reserve_indices(fill_data.indices_count() + triangulation.size());
- for (const Vec3d& v : triangulation) {
- fill_data.add_vertex((Vec3f)(v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting
- ++vertices_counter;
- if (vertices_counter % 3 == 0)
- fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
- }
- }
-
- m_fill.init_from(std::move(fill_data));
-#else
- GLModel::Geometry::Entity entity;
- entity.type = GLModel::EPrimitiveType::Triangles;
- entity.color = { 0.3333f, 0.0f, 0.0f, 0.5f };
- entity.positions.reserve(vertices_count);
- entity.normals.reserve(vertices_count);
- entity.indices.reserve(vertices_count);
-
- const ExPolygons polygons_union = union_ex(polygons);
- for (const ExPolygon& poly : polygons_union) {
- const std::vector triangulation = triangulate_expolygon_3d(poly);
- for (const Vec3d& v : triangulation) {
- entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting
- entity.normals.emplace_back(Vec3f::UnitZ());
- const size_t positions_count = entity.positions.size();
- if (positions_count % 3 == 0) {
- entity.indices.emplace_back(positions_count - 3);
- entity.indices.emplace_back(positions_count - 2);
- entity.indices.emplace_back(positions_count - 1);
- }
- }
- }
-
- fill_data.entities.emplace_back(entity);
- m_fill.init_from(fill_data);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_perimeter.init_from(polygons, 0.025f); // add a small positive z to avoid z-fighting
-#else
- GLModel::Geometry perimeter_data;
- for (const Polygon& poly : polygons) {
- GLModel::Geometry::Entity ent;
- ent.type = GLModel::EPrimitiveType::LineLoop;
- ent.positions.reserve(poly.points.size());
- ent.indices.reserve(poly.points.size());
- unsigned int id_count = 0;
- for (const Point& p : poly.points) {
- ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting
- ent.normals.emplace_back(Vec3f::UnitZ());
- ent.indices.emplace_back(id_count++);
- }
-
- perimeter_data.entities.emplace_back(ent);
- }
-
- m_perimeter.init_from(perimeter_data);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-void GLCanvas3D::SequentialPrintClearance::render()
-{
- const ColorRGBA FILL_COLOR = { 1.0f, 0.0f, 0.0f, 0.5f };
- const ColorRGBA NO_FILL_COLOR = { 1.0f, 1.0f, 1.0f, 0.75f };
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- GLShaderProgram* shader = wxGetApp().get_shader("flat");
-#else
- GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- if (shader == nullptr)
- return;
-
- shader->start_using();
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- const Camera& camera = wxGetApp().plater()->get_camera();
- shader->set_uniform("view_model_matrix", camera.get_view_matrix());
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- glsafe(::glEnable(GL_DEPTH_TEST));
- glsafe(::glDisable(GL_CULL_FACE));
- glsafe(::glEnable(GL_BLEND));
- glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR);
-#else
- m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- m_perimeter.render();
- m_fill.render();
-
- glsafe(::glDisable(GL_BLEND));
- glsafe(::glEnable(GL_CULL_FACE));
- glsafe(::glDisable(GL_DEPTH_TEST));
-
- shader->stop_using();
-}
-
-wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event);
-wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
-#if ENABLE_WORLD_COORDINATE
-wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent);
-#endif // ENABLE_WORLD_COORDINATE
-wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event);
-wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
-wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event);
-wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/);
-wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent);
-wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent);
-
-const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
-
-void GLCanvas3D::load_arrange_settings()
-{
- std::string dist_fff_str =
- wxGetApp().app_config->get("arrange", "min_object_distance_fff");
-
- std::string dist_bed_fff_str =
- wxGetApp().app_config->get("arrange", "min_bed_distance_fff");
-
- std::string dist_fff_seq_print_str =
- wxGetApp().app_config->get("arrange", "min_object_distance_fff_seq_print");
-
- std::string dist_bed_fff_seq_print_str =
- wxGetApp().app_config->get("arrange", "min_bed_distance_fff_seq_print");
-
- std::string dist_sla_str =
- wxGetApp().app_config->get("arrange", "min_object_distance_sla");
-
- std::string dist_bed_sla_str =
- wxGetApp().app_config->get("arrange", "min_bed_distance_sla");
-
- std::string en_rot_fff_str =
- wxGetApp().app_config->get("arrange", "enable_rotation_fff");
-
- std::string en_rot_fff_seqp_str =
- wxGetApp().app_config->get("arrange", "enable_rotation_fff_seq_print");
-
- std::string en_rot_sla_str =
- wxGetApp().app_config->get("arrange", "enable_rotation_sla");
-
- if (!dist_fff_str.empty())
- m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str);
-
- if (!dist_bed_fff_str.empty())
- m_arrange_settings_fff.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_str);
-
- if (!dist_fff_seq_print_str.empty())
- m_arrange_settings_fff_seq_print.distance = string_to_float_decimal_point(dist_fff_seq_print_str);
-
- if (!dist_bed_fff_seq_print_str.empty())
- m_arrange_settings_fff_seq_print.distance_from_bed = string_to_float_decimal_point(dist_bed_fff_seq_print_str);
-
- if (!dist_sla_str.empty())
- m_arrange_settings_sla.distance = string_to_float_decimal_point(dist_sla_str);
-
- if (!dist_bed_sla_str.empty())
- m_arrange_settings_sla.distance_from_bed = string_to_float_decimal_point(dist_bed_sla_str);
-
- if (!en_rot_fff_str.empty())
- m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes");
-
- if (!en_rot_fff_seqp_str.empty())
- m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes");
-
- if (!en_rot_sla_str.empty())
- m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes");
-}
-
-PrinterTechnology GLCanvas3D::current_printer_technology() const
-{
- return m_process->current_printer_technology();
-}
-
-GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
- : m_canvas(canvas)
- , m_context(nullptr)
- , m_bed(bed)
-#if ENABLE_RETINA_GL
- , m_retina_helper(nullptr)
-#endif
- , m_in_render(false)
- , m_main_toolbar(GLToolbar::Normal, "Main")
- , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo")
- , m_gizmos(*this)
- , m_use_clipping_planes(false)
- , m_sidebar_field("")
- , m_extra_frame_requested(false)
- , m_config(nullptr)
- , m_process(nullptr)
- , m_model(nullptr)
- , m_dirty(true)
- , m_initialized(false)
- , m_apply_zoom_to_volumes_filter(false)
- , m_picking_enabled(false)
- , m_moving_enabled(false)
- , m_dynamic_background_enabled(false)
- , m_multisample_allowed(false)
- , m_moving(false)
- , m_tab_down(false)
- , m_cursor_type(Standard)
- , m_reload_delayed(false)
- , m_render_sla_auxiliaries(true)
- , m_labels(*this)
- , m_slope(m_volumes)
-{
- if (m_canvas != nullptr) {
- m_timer.SetOwner(m_canvas);
- m_render_timer.SetOwner(m_canvas);
-#if ENABLE_RETINA_GL
- m_retina_helper.reset(new RetinaHelper(canvas));
-#endif // ENABLE_RETINA_GL
- }
-
- load_arrange_settings();
-
- m_selection.set_volumes(&m_volumes.volumes);
-}
-
-GLCanvas3D::~GLCanvas3D()
-{
- reset_volumes();
-}
-
-void GLCanvas3D::post_event(wxEvent &&event)
-{
- event.SetEventObject(m_canvas);
- wxPostEvent(m_canvas, event);
-}
-
-bool GLCanvas3D::init()
-{
- if (m_initialized)
- return true;
-
- if (m_canvas == nullptr || m_context == nullptr)
- return false;
-
- glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
-#if ENABLE_OPENGL_ES
- glsafe(::glClearDepthf(1.0f));
-#else
- glsafe(::glClearDepth(1.0f));
-#endif // ENABLE_OPENGL_ES
-
- glsafe(::glDepthFunc(GL_LESS));
-
- glsafe(::glEnable(GL_DEPTH_TEST));
- glsafe(::glEnable(GL_CULL_FACE));
- glsafe(::glEnable(GL_BLEND));
- glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- // Set antialiasing / multisampling
- glsafe(::glDisable(GL_LINE_SMOOTH));
- glsafe(::glDisable(GL_POLYGON_SMOOTH));
-
- // ambient lighting
- GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
- glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient));
-
- glsafe(::glEnable(GL_LIGHT0));
- glsafe(::glEnable(GL_LIGHT1));
-
- // light from camera
- GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
- glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam));
- GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
- glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam));
-
- // light from above
- GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
- glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top));
- GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
- glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top));
-
- // Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
- glsafe(::glShadeModel(GL_SMOOTH));
-
- // A handy trick -- have surface material mirror the color.
- glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE));
- glsafe(::glEnable(GL_COLOR_MATERIAL));
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
- if (m_multisample_allowed)
- glsafe(::glEnable(GL_MULTISAMPLE));
-
- if (m_main_toolbar.is_enabled())
- m_layers_editing.init();
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- // on linux the gl context is not valid until the canvas is not shown on screen
- // we defer the geometry finalization of volumes until the first call to render()
- m_volumes.finalize_geometry(true);
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
- if (m_gizmos.is_enabled() && !m_gizmos.init())
- std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl;
-
- if (!_init_toolbars())
- return false;
-
- if (m_selection.is_enabled() && !m_selection.init())
- return false;
-
- m_initialized = true;
-
- return true;
-}
-
-void GLCanvas3D::set_as_dirty()
-{
- m_dirty = true;
-}
-
-unsigned int GLCanvas3D::get_volumes_count() const
-{
- return (unsigned int)m_volumes.volumes.size();
-}
-
-void GLCanvas3D::reset_volumes()
-{
- if (!m_initialized)
- return;
-
- if (m_volumes.empty())
- return;
-
- _set_current();
-
- m_selection.clear();
- m_volumes.clear();
- m_dirty = true;
-
- _set_warning_notification(EWarning::ObjectOutside, false);
-}
-
-ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const
-{
- ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside;
- if (m_initialized)
- m_volumes.check_outside_state(m_bed.build_volume(), &state);
- return state;
-}
-
-void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
-{
- if (current_printer_technology() != ptSLA)
- return;
-
- m_render_sla_auxiliaries = visible;
-
- 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->composite_id.volume_id < 0)
- vol->is_active = visible;
- }
-}
-
-void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv)
-{
-#if ENABLE_RAYCAST_PICKING
- std::vector>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume);
-#endif // ENABLE_RAYCAST_PICKING
- for (GLVolume* vol : m_volumes.volumes) {
- if (vol->is_wipe_tower)
- vol->is_active = (visible && mo == nullptr);
- else {
- if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
- && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
- && (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) {
- vol->is_active = visible;
- if (!vol->is_modifier)
- vol->color.a(1.f);
-
- if (instance_idx == -1) {
- vol->force_native_color = false;
- vol->force_neutral_color = false;
- } else {
- const GLGizmosManager& gm = get_gizmos_manager();
- auto gizmo_type = gm.get_current_type();
- if ( (gizmo_type == GLGizmosManager::FdmSupports
- || gizmo_type == GLGizmosManager::Seam
- || gizmo_type == GLGizmosManager::Cut)
- && ! vol->is_modifier) {
- vol->force_neutral_color = true;
- if (gizmo_type == GLGizmosManager::Cut)
- vol->color.a(0.95f);
- }
- else if (gizmo_type == GLGizmosManager::MmuSegmentation)
- vol->is_active = false;
- else
- vol->force_native_color = true;
- }
- }
- }
-#if ENABLE_RAYCAST_PICKING
- auto it = std::find_if(raycasters->begin(), raycasters->end(), [vol](std::shared_ptr item) { return item->get_raycaster() == vol->mesh_raycaster.get(); });
- if (it != raycasters->end())
- (*it)->set_active(vol->is_active);
-#endif // ENABLE_RAYCAST_PICKING
- }
-
- if (visible && !mo)
- toggle_sla_auxiliaries_visibility(true, mo, instance_idx);
-
- if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1))
- _set_warning_notification(EWarning::SomethingNotShown, true);
-
- if (!mo && visible)
- _set_warning_notification(EWarning::SomethingNotShown, false);
-}
-
-void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx)
-{
- ModelObject* model_object = m_model->objects[obj_idx];
- for (int inst_idx = 0; inst_idx < (int)model_object->instances.size(); ++inst_idx) {
- ModelInstance* instance = model_object->instances[inst_idx];
-
- for (GLVolume* volume : m_volumes.volumes) {
- if (volume->object_idx() == (int)obj_idx && volume->instance_idx() == inst_idx)
- volume->printable = instance->printable;
- }
- }
-}
-
-void GLCanvas3D::update_instance_printable_state_for_objects(const std::vector& object_idxs)
-{
- for (size_t obj_idx : object_idxs)
- update_instance_printable_state_for_object(obj_idx);
-}
-
-void GLCanvas3D::set_config(const DynamicPrintConfig* config)
-{
- m_config = config;
- m_layers_editing.set_config(config);
-}
-
-void GLCanvas3D::set_process(BackgroundSlicingProcess *process)
-{
- m_process = process;
-}
-
-void GLCanvas3D::set_model(Model* model)
-{
- m_model = model;
- m_selection.set_model(m_model);
-}
-
-void GLCanvas3D::bed_shape_changed()
-{
- refresh_camera_scene_box();
- wxGetApp().plater()->get_camera().requires_zoom_to_bed = true;
- m_dirty = true;
-}
-
-void GLCanvas3D::refresh_camera_scene_box()
-{
- wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box());
-}
-
-BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
-{
- BoundingBoxf3 bb;
- for (const GLVolume* volume : m_volumes.volumes) {
- if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes))
- bb.merge(volume->transformed_bounding_box());
- }
- return bb;
-}
-
-BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
-{
- BoundingBoxf3 bb = volumes_bounding_box();
- bb.merge(m_bed.extended_bounding_box());
- double h = m_bed.build_volume().max_print_height();
- //FIXME why -h?
- bb.min.z() = std::min(bb.min.z(), -h);
- bb.max.z() = std::max(bb.max.z(), h);
- return bb;
-}
-
-bool GLCanvas3D::is_layers_editing_enabled() const
-{
- return m_layers_editing.is_enabled();
-}
-
-bool GLCanvas3D::is_layers_editing_allowed() const
-{
- return m_layers_editing.is_allowed();
-}
-
-void GLCanvas3D::reset_layer_height_profile()
-{
- wxGetApp().plater()->take_snapshot(_L("Variable layer height - Reset"));
- m_layers_editing.reset_layer_height_profile(*this);
- m_layers_editing.state = LayersEditing::Completed;
- m_dirty = true;
-}
-
-void GLCanvas3D::adaptive_layer_height_profile(float quality_factor)
-{
- wxGetApp().plater()->take_snapshot(_L("Variable layer height - Adaptive"));
- m_layers_editing.adaptive_layer_height_profile(*this, quality_factor);
- m_layers_editing.state = LayersEditing::Completed;
- m_dirty = true;
-}
-
-void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params)
-{
- wxGetApp().plater()->take_snapshot(_L("Variable layer height - Smooth all"));
- m_layers_editing.smooth_layer_height_profile(*this, smoothing_params);
- m_layers_editing.state = LayersEditing::Completed;
- m_dirty = true;
-}
-
-bool GLCanvas3D::is_reload_delayed() const
-{
- return m_reload_delayed;
-}
-
-void GLCanvas3D::enable_layers_editing(bool enable)
-{
- m_layers_editing.set_enabled(enable);
- set_as_dirty();
-}
-
-void GLCanvas3D::enable_legend_texture(bool enable)
-{
- m_gcode_viewer.enable_legend(enable);
-}
-
-void GLCanvas3D::enable_picking(bool enable)
-{
- m_picking_enabled = enable;
- m_selection.set_mode(Selection::Instance);
-}
-
-void GLCanvas3D::enable_moving(bool enable)
-{
- m_moving_enabled = enable;
-}
-
-void GLCanvas3D::enable_gizmos(bool enable)
-{
- m_gizmos.set_enabled(enable);
-}
-
-void GLCanvas3D::enable_selection(bool enable)
-{
- m_selection.set_enabled(enable);
-}
-
-void GLCanvas3D::enable_main_toolbar(bool enable)
-{
- m_main_toolbar.set_enabled(enable);
-}
-
-void GLCanvas3D::enable_undoredo_toolbar(bool enable)
-{
- m_undoredo_toolbar.set_enabled(enable);
-}
-
-void GLCanvas3D::enable_dynamic_background(bool enable)
-{
- m_dynamic_background_enabled = enable;
-}
-
-void GLCanvas3D::allow_multisample(bool allow)
-{
- m_multisample_allowed = allow;
-}
-
-void GLCanvas3D::zoom_to_bed()
-{
- BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
- box.min.z() = 0.0;
- box.max.z() = 0.0;
- _zoom_to_box(box);
-}
-
-void GLCanvas3D::zoom_to_volumes()
-{
- m_apply_zoom_to_volumes_filter = true;
- _zoom_to_box(volumes_bounding_box());
- m_apply_zoom_to_volumes_filter = false;
-}
-
-void GLCanvas3D::zoom_to_selection()
-{
- if (!m_selection.is_empty())
- _zoom_to_box(m_selection.get_bounding_box());
-}
-
-void GLCanvas3D::zoom_to_gcode()
-{
- _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05);
-}
-
-void GLCanvas3D::select_view(const std::string& direction)
-{
- wxGetApp().plater()->get_camera().select_view(direction);
- if (m_canvas != nullptr)
- m_canvas->Refresh();
-}
-
-void GLCanvas3D::update_volumes_colors_by_extruder()
-{
- if (m_config != nullptr)
- m_volumes.update_colors_by_extruder(m_config);
-}
-
-void GLCanvas3D::render()
-{
- if (m_in_render) {
- // if called recursively, return
- m_dirty = true;
- return;
- }
-
- m_in_render = true;
- Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; });
- (void)in_render_guard;
-
- if (m_canvas == nullptr)
- return;
-
- // ensures this canvas is current and initialized
- if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl())
- return;
-
- if (!is_initialized() && !init())
- return;
-
- if (!m_main_toolbar.is_enabled())
- m_gcode_viewer.init();
-
- if (! m_bed.build_volume().valid()) {
- // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
- post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE));
- return;
- }
-
-#if ENABLE_ENVIRONMENT_MAP
- if (wxGetApp().is_editor())
- wxGetApp().plater()->init_environment_texture();
-#endif // ENABLE_ENVIRONMENT_MAP
-
-#if ENABLE_GLMODEL_STATISTICS
- GLModel::reset_statistics_counters();
-#endif // ENABLE_GLMODEL_STATISTICS
-
- const Size& cnv_size = get_canvas_size();
- // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
- // to preview, this was called before canvas had its final size. It reported zero width
- // and the viewport was set incorrectly, leading to tripping glAsserts further down
- // the road (in apply_projection). That's why the minimum size is forced to 10.
- Camera& camera = wxGetApp().plater()->get_camera();
-#if ENABLE_RAYCAST_PICKING
- camera.set_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
- camera.apply_viewport();
-#else
- camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
-#endif // ENABLE_RAYCAST_PICKING
-
- if (camera.requires_zoom_to_bed) {
- zoom_to_bed();
- _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
- camera.requires_zoom_to_bed = false;
- }
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- camera.apply_view_matrix();
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
- camera.apply_projection(_max_bounding_box(true, true));
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f };
- glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam));
- GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f };
- glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top));
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
- wxGetApp().imgui()->new_frame();
-
- if (m_picking_enabled) {
- if (m_rectangle_selection.is_dragging() && !m_rectangle_selection.is_empty())
- // picking pass using rectangle selection
- _rectangular_selection_picking_pass();
- else if (!m_volumes.empty())
- // regular picking pass
- _picking_pass();
-#if ENABLE_RAYCAST_PICKING_DEBUG
- else {
- ImGuiWrapper& imgui = *wxGetApp().imgui();
- imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize);
- imgui.text("Picking disabled");
- imgui.end();
- }
-#endif // ENABLE_RAYCAST_PICKING_DEBUG
- }
-
- const bool is_looking_downward = camera.is_looking_downward();
-
- // draw scene
- glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
- _render_background();
-
- _render_objects(GLVolumeCollection::ERenderType::Opaque);
- if (!m_main_toolbar.is_enabled())
- _render_gcode();
- _render_sla_slices();
- _render_selection();
- if (is_looking_downward)
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true);
-#else
- _render_bed(false, true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- _render_objects(GLVolumeCollection::ERenderType::Transparent);
-
- _render_sequential_clearance();
-#if ENABLE_RENDER_SELECTION_CENTER
- _render_selection_center();
-#endif // ENABLE_RENDER_SELECTION_CENTER
- if (!m_main_toolbar.is_enabled())
- _render_gcode_cog();
-
- // we need to set the mouse's scene position here because the depth buffer
- // could be invalidated by the following gizmo render methods
- // this position is used later into on_mouse() to drag the objects
- if (m_picking_enabled)
- m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast());
-
- // sidebar hints need to be rendered before the gizmos because the depth buffer
- // could be invalidated by the following gizmo render methods
- _render_selection_sidebar_hints();
- _render_current_gizmo();
- if (!is_looking_downward)
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true);
-#else
- _render_bed(true, true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
-#if ENABLE_RAYCAST_PICKING_DEBUG
- if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging())
- m_scene_raycaster.render_hit(camera);
-#endif // ENABLE_RAYCAST_PICKING_DEBUG
-
-#if ENABLE_SHOW_CAMERA_TARGET
- _render_camera_target();
-#endif // ENABLE_SHOW_CAMERA_TARGET
-
- if (m_picking_enabled && m_rectangle_selection.is_dragging())
- m_rectangle_selection.render(*this);
-
- // draw overlays
- _render_overlays();
-
- if (wxGetApp().plater()->is_render_statistic_dialog_visible()) {
- ImGuiWrapper& imgui = *wxGetApp().imgui();
- imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
- imgui.text("FPS (SwapBuffers() calls per second):");
- ImGui::SameLine();
- imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed()));
- ImGui::Separator();
- imgui.text("Compressed textures:");
- ImGui::SameLine();
- imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported");
- imgui.text("Max texture size:");
- ImGui::SameLine();
- imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size()));
- imgui.end();
- }
-
-#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
- if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown())
- wxGetApp().plater()->render_project_state_debug_window();
-#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
-
-#if ENABLE_CAMERA_STATISTICS
- camera.debug_render();
-#endif // ENABLE_CAMERA_STATISTICS
-#if ENABLE_GLMODEL_STATISTICS
- GLModel::render_statistics();
-#endif // ENABLE_GLMODEL_STATISTICS
-
- std::string tooltip;
-
- // Negative coordinate means out of the window, likely because the window was deactivated.
- // In that case the tooltip should be hidden.
- if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
- if (tooltip.empty())
- tooltip = m_layers_editing.get_tooltip(*this);
-
- if (tooltip.empty())
- tooltip = m_gizmos.get_tooltip();
-
- if (tooltip.empty())
- tooltip = m_main_toolbar.get_tooltip();
-
- if (tooltip.empty())
- tooltip = m_undoredo_toolbar.get_tooltip();
-
- if (tooltip.empty())
- tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip();
-
- if (tooltip.empty())
- tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip();
- }
-
- set_tooltip(tooltip);
-
- if (m_tooltip_enabled)
- m_tooltip.render(m_mouse.position, *this);
-
- wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
-
- wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width());
-
- wxGetApp().imgui()->render();
-
- m_canvas->SwapBuffers();
- m_render_stats.increment_fps_counter();
-}
-
-void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type)
-{
- render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type);
-}
-
-void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
-{
- switch (OpenGLManager::get_framebuffers_type())
- {
- case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
- case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
- default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
- }
-}
-
-void GLCanvas3D::select_all()
-{
- m_selection.add_all();
- m_dirty = true;
- wxGetApp().obj_manipul()->set_dirty();
- m_gizmos.reset_all_states();
- m_gizmos.update_data();
- post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
-}
-
-void GLCanvas3D::deselect_all()
-{
- if (m_selection.is_empty())
- return;
-
- m_selection.remove_all();
- wxGetApp().obj_manipul()->set_dirty();
- m_gizmos.reset_all_states();
- m_gizmos.update_data();
- post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
-}
-
-void GLCanvas3D::delete_selected()
-{
- m_selection.erase();
-}
-
-void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z)
-{
- if (allow_negative_z)
- return;
-
- typedef std::map, double> InstancesToZMap;
- InstancesToZMap instances_min_z;
-
- for (GLVolume* volume : m_volumes.volumes) {
- if (volume->object_idx() == (int)object_idx && !volume->is_modifier) {
- double min_z = volume->transformed_convex_hull_bounding_box().min.z();
- std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx());
- InstancesToZMap::iterator it = instances_min_z.find(instance);
- if (it == instances_min_z.end())
- it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first;
-
- it->second = std::min(it->second, min_z);
- }
- }
-
- for (GLVolume* volume : m_volumes.volumes) {
- std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx());
- InstancesToZMap::iterator it = instances_min_z.find(instance);
- if (it != instances_min_z.end())
- volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second);
- }
-}
-
-
-const std::vector& GLCanvas3D::get_gcode_layers_zs() const
-{
- return m_gcode_viewer.get_layers_zs();
-}
-
-std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const
-{
- return m_volumes.get_current_print_zs(active_only);
-}
-
-void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags)
-{
- m_gcode_viewer.set_options_visibility_from_flags(flags);
-}
-
-void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags)
-{
- m_gcode_viewer.set_toolpath_role_visibility_flags(flags);
-}
-
-void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type)
-{
- m_gcode_viewer.set_view_type(type);
-}
-
-void GLCanvas3D::set_volumes_z_range(const std::array& range)
-{
- m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6);
-}
-
-void GLCanvas3D::set_toolpaths_z_range(const std::array& range)
-{
- if (m_gcode_viewer.has_data())
- m_gcode_viewer.set_layers_z_range(range);
-}
-
-std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs)
-{
- if (instance_idxs.empty()) {
- for (unsigned int i = 0; i < model_object.instances.size(); ++i) {
- instance_idxs.emplace_back(i);
- }
- }
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- return m_volumes.load_object(&model_object, obj_idx, instance_idxs);
-#else
- return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_initialized);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-}
-
-std::vector GLCanvas3D::load_object(const Model& model, int obj_idx)
-{
- if (0 <= obj_idx && obj_idx < (int)model.objects.size()) {
- const ModelObject* model_object = model.objects[obj_idx];
- if (model_object != nullptr)
- return load_object(*model_object, obj_idx, std::vector());
- }
-
- return std::vector();
-}
-
-void GLCanvas3D::mirror_selection(Axis axis)
-{
- m_selection.mirror(axis);
- do_mirror(L("Mirror Object"));
- wxGetApp().obj_manipul()->set_dirty();
-}
-
-// Reload the 3D scene of
-// 1) Model / ModelObjects / ModelInstances / ModelVolumes
-// 2) Print bed
-// 3) SLA support meshes for their respective ModelObjects / ModelInstances
-// 4) Wipe tower preview
-// 5) Out of bed collision status & message overlay (texture)
-void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh)
-{
- if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr)
- return;
-
- if (!m_initialized)
- return;
-
- _set_current();
-
- m_hover_volume_idxs.clear();
-
- struct ModelVolumeState {
- ModelVolumeState(const GLVolume* volume) :
- model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
- ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) :
- model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
- ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) :
- model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
- bool new_geometry() const { return this->volume_idx == size_t(-1); }
- const ModelVolume* model_volume;
- // ObjectID of ModelVolume + ObjectID of ModelInstance
- // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
- std::pair geometry_id;
- GLVolume::CompositeID composite_id;
- // Volume index in the new GLVolume vector.
- size_t volume_idx;
- };
- std::vector model_volume_state;
- std::vector aux_volume_state;
-
- struct GLVolumeState {
- GLVolumeState() :
- volume_idx(size_t(-1)) {}
- GLVolumeState(const GLVolume* volume, unsigned int volume_idx) :
- composite_id(volume->composite_id), volume_idx(volume_idx) {}
- GLVolumeState(const GLVolume::CompositeID &composite_id) :
- composite_id(composite_id), volume_idx(size_t(-1)) {}
-
- GLVolume::CompositeID composite_id;
- // Volume index in the old GLVolume vector.
- size_t volume_idx;
- };
-
- // SLA steps to pull the preview meshes for.
- typedef std::array SLASteps;
- SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad };
- struct SLASupportState {
- std::array::value> step;
- };
- // State of the sla_steps for all SLAPrintObjects.
- std::vector sla_support_state;
-
- std::vector instance_ids_selected;
- std::vector map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
- std::vector deleted_volumes;
- std::vector glvolumes_new;
- glvolumes_new.reserve(m_volumes.volumes.size());
- auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; };
-
- m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh;
-
- PrinterTechnology printer_technology = current_printer_technology();
- int volume_idx_wipe_tower_old = -1;
-
- // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
- // First initialize model_volumes_new_sorted & model_instances_new_sorted.
- for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) {
- const ModelObject* model_object = m_model->objects[object_idx];
- for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) {
- const ModelInstance* model_instance = model_object->instances[instance_idx];
- for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) {
- const ModelVolume* model_volume = model_object->volumes[volume_idx];
- model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
- }
- }
- }
- if (printer_technology == ptSLA) {
- const SLAPrint* sla_print = this->sla_print();
-#ifndef NDEBUG
- // Verify that the SLAPrint object is synchronized with m_model.
- check_model_ids_equal(*m_model, sla_print->model());
-#endif /* NDEBUG */
- sla_support_state.reserve(sla_print->objects().size());
- for (const SLAPrintObject* print_object : sla_print->objects()) {
- SLASupportState state;
- for (size_t istep = 0; istep < sla_steps.size(); ++istep) {
- state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
- if (state.step[istep].state == PrintStateBase::DONE) {
- if (!print_object->has_mesh(sla_steps[istep]))
- // Consider the DONE step without a valid mesh as invalid for the purpose
- // of mesh visualization.
- state.step[istep].state = PrintStateBase::INVALID;
- else if (sla_steps[istep] != slaposDrillHoles)
- for (const ModelInstance* model_instance : print_object->model_object()->instances)
- // Only the instances, which are currently printable, will have the SLA support structures kept.
- // The instances outside the print bed will have the GLVolumes of their support structures released.
- if (model_instance->is_printable())
- aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
- }
- }
- sla_support_state.emplace_back(state);
- }
- }
- std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
- std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower);
- // Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh.
- for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) {
- GLVolume* volume = m_volumes.volumes[volume_id];
- ModelVolumeState key(volume);
- ModelVolumeState* mvs = nullptr;
- if (volume->volume_idx() < 0) {
- auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
- if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id)
- // This can be an SLA support structure that should not be rendered (in case someone used undo
- // to revert to before it was generated). We only reuse the volume if that's not the case.
- if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints)
- mvs = &(*it);
- }
- else {
- auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
- if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
- mvs = &(*it);
- }
- // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID.
- // The wipe tower has its own wipe_tower_instance_id().
- if (m_selection.contains_volume(volume_id))
- instance_ids_selected.emplace_back(volume->geometry_id.second);
- if (mvs == nullptr || force_full_scene_refresh) {
- // This GLVolume will be released.
- if (volume->is_wipe_tower) {
- // There is only one wipe tower.
- assert(volume_idx_wipe_tower_old == -1);
-#if ENABLE_OPENGL_ES
- m_wipe_tower_mesh.clear();
-#endif // ENABLE_OPENGL_ES
- volume_idx_wipe_tower_old = (int)volume_id;
- }
- if (!m_reload_delayed) {
- deleted_volumes.emplace_back(volume, volume_id);
- delete volume;
- }
- }
- else {
- // This GLVolume will be reused.
- volume->set_sla_shift_z(0.0);
- map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
- mvs->volume_idx = glvolumes_new.size();
- glvolumes_new.emplace_back(volume);
- // Update color of the volume based on the current extruder.
- if (mvs->model_volume != nullptr) {
- int extruder_id = mvs->model_volume->extruder_id();
- if (extruder_id != -1)
- volume->extruder_id = extruder_id;
-
- volume->is_modifier = !mvs->model_volume->is_model_part();
- volume->set_color(color_from_model_volume(*mvs->model_volume));
- // force update of render_color alpha channel
- volume->set_render_color(volume->color.is_transparent());
-
- // updates volumes transformations
- volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation());
- volume->set_volume_transformation(mvs->model_volume->get_transformation());
-
- // updates volumes convex hull
- if (mvs->model_volume->is_model_part() && ! volume->convex_hull())
- // Model volume was likely changed from modifier or support blocker / enforcer to a model part.
- // Only model parts require convex hulls.
- volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr());
- }
- }
- }
- sort_remove_duplicates(instance_ids_selected);
- auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; };
- std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower);
-
- if (m_reload_delayed)
- return;
-
- bool update_object_list = false;
- if (m_volumes.volumes != glvolumes_new)
- update_object_list = true;
- m_volumes.volumes = std::move(glvolumes_new);
- for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) {
- const ModelObject &model_object = *m_model->objects[obj_idx];
- for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) {
- const ModelVolume &model_volume = *model_object.volumes[volume_idx];
- for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
- const ModelInstance &model_instance = *model_object.instances[instance_idx];
- ModelVolumeState key(model_volume.id(), model_instance.id());
- auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
- assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
- if (it->new_geometry()) {
- // New volume.
- auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower);
- if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id)
- // If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection.
- map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size();
- // Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh
- // later in this function.
- it->volume_idx = m_volumes.volumes.size();
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx);
-#else
- m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_initialized);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- m_volumes.volumes.back()->geometry_id = key.geometry_id;
- update_object_list = true;
- } else {
- // Recycling an old GLVolume.
- GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
- assert(existing_volume.geometry_id == key.geometry_id);
- // Update the Object/Volume/Instance indices into the current Model.
- if (existing_volume.composite_id != it->composite_id) {
- existing_volume.composite_id = it->composite_id;
- update_object_list = true;
- }
- }
- }
- }
- }
- if (printer_technology == ptSLA) {
- size_t idx = 0;
- const SLAPrint *sla_print = this->sla_print();
- std::vector shift_zs(m_model->objects.size(), 0);
- double relative_correction_z = sla_print->relative_correction().z();
- if (relative_correction_z <= EPSILON)
- relative_correction_z = 1.;
- for (const SLAPrintObject *print_object : sla_print->objects()) {
- SLASupportState &state = sla_support_state[idx ++];
- const ModelObject *model_object = print_object->model_object();
- // Find an index of the ModelObject
- int object_idx;
- // There may be new SLA volumes added to the scene for this print_object.
- // Find the object index of this print_object in the Model::objects list.
- auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object);
- assert(it != sla_print->model().objects.end());
- object_idx = it - sla_print->model().objects.begin();
- // Cache the Z offset to be applied to all volumes with this object_idx.
- shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z;
- // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene.
- // pairs of
- std::vector> instances[std::tuple_size::value];
- for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) {
- const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx];
- // Find index of ModelInstance corresponding to this SLAPrintObject::Instance.
- auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
- [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; });
- assert(it != model_object->instances.end());
- int instance_idx = it - model_object->instances.begin();
- for (size_t istep = 0; istep < sla_steps.size(); ++ istep)
- if (sla_steps[istep] == slaposDrillHoles) {
- // Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance,
- // not into its own GLVolume.
- // There shall always be such a GLVolume allocated.
- ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id);
- auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
- assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
- assert(!it->new_geometry());
- GLVolume &volume = *m_volumes.volumes[it->volume_idx];
- if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) {
- // The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen.
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- volume.model.reset();
-#else
- volume.indexed_vertex_array.release_geometry();
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- if (state.step[istep].state == PrintStateBase::DONE) {
- TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles);
- assert(! mesh.empty());
-
- // sla_trafo does not contain volume trafo. To get a mesh to create
- // a new volume from, we have to apply vol trafo inverse separately.
- const ModelObject& mo = *m_model->objects[volume.object_idx()];
- Transform3d trafo = sla_print->sla_trafo(mo)
- * mo.volumes.front()->get_transformation().get_matrix();
- mesh.transform(trafo.inverse());
-#if ENABLE_SMOOTH_NORMALS
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- volume.model.init_from(mesh, true);
-#else
- volume.indexed_vertex_array.load_mesh(mesh, true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#else
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- volume.model.init_from(mesh);
-#if ENABLE_RAYCAST_PICKING
- volume.mesh_raycaster = std::make_unique(std::make_shared(mesh));
-#endif // ENABLE_RAYCAST_PICKING
-#else
- volume.indexed_vertex_array.load_mesh(mesh);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#endif // ENABLE_SMOOTH_NORMALS
- }
- else {
- // Reload the original volume.
-#if ENABLE_SMOOTH_NORMALS
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true);
-#else
- volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#else
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_RAYCAST_PICKING
- const TriangleMesh& new_mesh = m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh();
- volume.model.init_from(new_mesh);
- volume.mesh_raycaster = std::make_unique(std::make_shared(new_mesh));
-#else
- volume.model.init_from(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh());
-#endif // ENABLE_RAYCAST_PICKING
-#else
- volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh());
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-#endif // ENABLE_SMOOTH_NORMALS
- }
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
- volume.finalize_geometry(true);
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
- }
- //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable
- // to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables
- // of various concenrs (model vs. 3D print path).
- volume.offsets = { state.step[istep].timestamp };
- }
- else if (state.step[istep].state == PrintStateBase::DONE) {
- // Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created.
- ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id);
- auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
- assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id);
- if (it->new_geometry()) {
- // This can be an SLA support structure that should not be rendered (in case someone used undo
- // to revert to before it was generated). If that's the case, we should not generate anything.
- if (model_object->sla_points_status != sla::PointsStatus::NoPoints)
- instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx));
- else
- shift_zs[object_idx] = 0.;
- }
- else {
- // Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
- m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
- m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation());
- }
- }
- }
-
- for (size_t istep = 0; istep < sla_steps.size(); ++istep)
- if (!instances[istep].empty())
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp);
-#else
- m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- }
-
- // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
- for (GLVolume* volume : m_volumes.volumes)
- if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable())
- volume->set_sla_shift_z(shift_zs[volume->object_idx()]);
- }
-
- if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) {
- // Should the wipe tower be visualized ?
- unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size();
-
- bool wt = dynamic_cast(m_config->option("wipe_tower"))->value;
- bool co = dynamic_cast(m_config->option("complete_objects"))->value;
-
- if (extruders_count > 1 && wt && !co) {
- // Height of a print (Show at least a slab)
- double height = std::max(m_model->bounding_box().max(2), 10.0);
-
- float x = dynamic_cast(m_config->option("wipe_tower_x"))->value;
- float y = dynamic_cast(m_config->option("wipe_tower_y"))->value;
- float w = dynamic_cast(m_config->option("wipe_tower_width"))->value;
- float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value;
-
- const Print *print = m_process->fff_print();
-
- float depth = print->wipe_tower_data(extruders_count).depth;
- float brim_width = print->wipe_tower_data(extruders_count).brim_width;
-
-#if ENABLE_LEGACY_OPENGL_REMOVAL
-#if ENABLE_OPENGL_ES
- int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
- x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
- brim_width, &m_wipe_tower_mesh);
-#else
- int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
- x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
- brim_width);
-#endif // ENABLE_OPENGL_ES
-#else
- int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
- x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower),
- brim_width, m_initialized);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
- if (volume_idx_wipe_tower_old != -1)
- map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
- }
- }
-
- update_volumes_colors_by_extruder();
- // Update selection indices based on the old/new GLVolumeCollection.
- if (m_selection.get_mode() == Selection::Instance)
- m_selection.instances_changed(instance_ids_selected);
- else
- m_selection.volumes_changed(map_glvolume_old_to_new);
-
- m_gizmos.update_data();
- m_gizmos.refresh_on_off_state();
-
- // Update the toolbar
- if (update_object_list)
- post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
-
- // checks for geometry outside the print volume to render it accordingly
- if (!m_volumes.empty()) {
- ModelInstanceEPrintVolumeState state;
- const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state);
- const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside);
- const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside);
-
- if (printer_technology != ptSLA) {
- _set_warning_notification(EWarning::ObjectClashed, partlyOut);
- _set_warning_notification(EWarning::ObjectOutside, fullyOut);
- _set_warning_notification(EWarning::SlaSupportsOutside, false);
- }
- else {
- const auto [res, volume] = _is_any_volume_outside();
- const bool is_support = volume != nullptr && volume->is_sla_support();
- if (is_support) {
- _set_warning_notification(EWarning::ObjectClashed, false);
- _set_warning_notification(EWarning::ObjectOutside, false);
- _set_warning_notification(EWarning::SlaSupportsOutside, partlyOut || fullyOut);
- }
- else {
- _set_warning_notification(EWarning::ObjectClashed, partlyOut);
- _set_warning_notification(EWarning::ObjectOutside, fullyOut);
- _set_warning_notification(EWarning::SlaSupportsOutside, false);
- }
- }
-
- post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,
- contained_min_one && !m_model->objects.empty() && !partlyOut));
- }
- else {
- _set_warning_notification(EWarning::ObjectOutside, false);
- _set_warning_notification(EWarning::ObjectClashed, false);
- _set_warning_notification(EWarning::SlaSupportsOutside, false);
- post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
- }
-
- refresh_camera_scene_box();
-
- if (m_selection.is_empty()) {
- // If no object is selected, deactivate the active gizmo, if any
- // Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted)
- m_gizmos.reset_all_states();
-
- // If no object is selected, reset the objects manipulator on the sidebar
- // to force a reset of its cache
- auto manip = wxGetApp().obj_manipul();
- if (manip != nullptr)
- manip->set_dirty();
- }
-
-#if ENABLE_RAYCAST_PICKING
- // refresh volume raycasters for picking
- m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume);
- for (size_t i = 0; i < m_volumes.volumes.size(); ++i) {
- const GLVolume* v = m_volumes.volumes[i];
- assert(v->mesh_raycaster != nullptr);
- std::shared_ptr raycaster = add_raycaster_for_picking(SceneRaycaster::EType::Volume, i, *v->mesh_raycaster, v->world_matrix());
- raycaster->set_active(v->is_active);
- }
-
- // refresh gizmo elements raycasters for picking
- GLGizmoBase* curr_gizmo = m_gizmos.get_current();
- if (curr_gizmo != nullptr)
- curr_gizmo->unregister_raycasters_for_picking();
- m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo);
- if (curr_gizmo != nullptr && !m_selection.is_empty())
- curr_gizmo->register_raycasters_for_picking();
-#endif // ENABLE_RAYCAST_PICKING
-
- // and force this canvas to be redrawn.
- m_dirty = true;
-}
-
-#if !ENABLE_LEGACY_OPENGL_REMOVAL
-static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE)
-{
- // Assign the large pre-allocated buffers to the new GLVolume.
- vol_new.indexed_vertex_array = std::move(vol_old.indexed_vertex_array);
- // Copy the content back to the old GLVolume.
- vol_old.indexed_vertex_array = vol_new.indexed_vertex_array;
- // Clear the buffers, but keep them pre-allocated.
- vol_new.indexed_vertex_array.clear();
- // Just make sure that clear did not clear the reserved memory.
- // Reserving number of vertices (3x position + 3x color)
- vol_new.indexed_vertex_array.reserve(prealloc_size / 6);
- // Finalize the old geometry, possibly move data to the graphics card.
- vol_old.finalize_geometry(gl_initialized);
-}
-#endif // !ENABLE_LEGACY_OPENGL_REMOVAL
-
-void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors)
-{
-#if ENABLE_LEGACY_OPENGL_REMOVAL
- m_gcode_viewer.load(gcode_result, *this->fff_print());
-#else
- m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized);
-#endif // ENABLE_LEGACY_OPENGL_REMOVAL
-
- if (wxGetApp().is_editor()) {
- m_gcode_viewer.update_shells_color_by_extruder(m_config);
- _set_warning_notification_if_needed(EWarning::ToolpathOutside);
- }
-
- m_gcode_viewer.refresh(gcode_result, str_tool_colors);
- set_as_dirty();
- request_extra_frame();
-}
-
-void GLCanvas3D::refresh_gcode_preview_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last)
-{
- m_gcode_viewer.refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last);
- set_as_dirty();
- request_extra_frame();
-}
-
-void GLCanvas3D::load_sla_preview()
-{
- const SLAPrint* print = sla_print();
- if (m_canvas != nullptr && print != nullptr) {
- _set_current();
- // Release OpenGL data before generating new data.
- reset_volumes();
- _load_sla_shells();
- _update_sla_shells_outside_state();
- _set_warning_notification_if_needed(EWarning::ObjectClashed);
- _set_warning_notification_if_needed(EWarning::SlaSupportsOutside);
- }
-}
-
-void GLCanvas3D::load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values)
-{
- const Print *print = this->fff_print();
- if (print == nullptr)
- return;
-
- _set_current();
-
- // Release OpenGL data before generating new data.
- this->reset_volumes();
-
- const BuildVolume &build_volume = m_bed.build_volume();
- _load_print_toolpaths(build_volume);
- _load_wipe_tower_toolpaths(build_volume, str_tool_colors);
- for (const PrintObject* object : print->objects())
- _load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values);
-
- _set_warning_notification_if_needed(EWarning::ToolpathOutside);
-}
-
-void GLCanvas3D::bind_event_handlers()
-{
- if (m_canvas != nullptr) {
- 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_DOWN, &GLCanvas3D::on_key, this);
- m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this);
- m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
- m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
- m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
- m_toolbar_highlighter.set_timer_owner(m_canvas, 0);
- m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); });
- m_gizmo_highlighter.set_timer_owner(m_canvas, 0);
- m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); });
- m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
- m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
- m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
-
- m_event_handlers_bound = true;
- }
-}
-
-void GLCanvas3D::unbind_event_handlers()
-{
- if (m_canvas != nullptr && m_event_handlers_bound) {
- 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_DOWN, &GLCanvas3D::on_key, this);
- m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this);
- m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
- m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
- m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
- m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
- m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
- m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
-
- m_event_handlers_bound = false;
- }
-}
-
-void GLCanvas3D::on_size(wxSizeEvent& evt)
-{
- m_dirty = true;
-}
-
-void GLCanvas3D::on_idle(wxIdleEvent& evt)
-{
- if (!m_initialized)
- return;
-
- m_dirty |= m_main_toolbar.update_items_state();
- m_dirty |= m_undoredo_toolbar.update_items_state();
- m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
- m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
- bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
- m_dirty |= mouse3d_controller_applied;
- m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
- auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current();
- if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
- // ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism
- bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame();
- m_dirty |= imgui_requires_extra_frame;
-
- if (!m_dirty)
- return;
-
- // this needs to be done here.
- // during the render launched by the refresh the value may be set again
- wxGetApp().imgui()->reset_requires_extra_frame();
-
- _refresh_if_shown_on_screen();
-
- if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
- m_extra_frame_requested = false;
- evt.RequestMore();
- }
- else
- m_dirty = false;
-}
-
-void GLCanvas3D::on_char(wxKeyEvent& evt)
-{
- if (!m_initialized)
- return;
-
- // see include/wx/defs.h enum wxKeyCode
- int keyCode = evt.GetKeyCode();
- int ctrlMask = wxMOD_CONTROL;
- int shiftMask = wxMOD_SHIFT;
-
- auto imgui = wxGetApp().imgui();
- if (imgui->update_key_data(evt)) {
- render();
- return;
- }
-
- if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()))
- return;
-
- if (m_gizmos.on_char(evt))
- return;
-
- if ((evt.GetModifiers() & ctrlMask) != 0) {
- // CTRL is pressed
- switch (keyCode) {
-#ifdef __APPLE__
- case 'a':
- case 'A':
-#else /* __APPLE__ */
- case WXK_CONTROL_A:
-#endif /* __APPLE__ */
- post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
- break;
-#ifdef __APPLE__
- case 'c':
- case 'C':
-#else /* __APPLE__ */
- case WXK_CONTROL_C:
-#endif /* __APPLE__ */
- post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
- break;
-#ifdef __APPLE__
- case 'm':
- case 'M':
-#else /* __APPLE__ */
- case WXK_CONTROL_M:
-#endif /* __APPLE__ */
- {
-#ifdef _WIN32
- if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") {
-#endif //_WIN32
-#ifdef __APPLE__
- // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog"
- if ((evt.GetModifiers() & shiftMask) != 0) {
-#endif // __APPLE__
- Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
- controller.show_settings_dialog(!controller.is_settings_dialog_shown());
- m_dirty = true;
-#ifdef __APPLE__
- }
- else
- // and Cmd+M to minimize application
- wxGetApp().mainframe->Iconize();
-#endif // __APPLE__
-#ifdef _WIN32
- }
-#endif //_WIN32
- break;
- }
-#ifdef __APPLE__
- case 'v':
- case 'V':
-#else /* __APPLE__ */
- case WXK_CONTROL_V:
-#endif /* __APPLE__ */
- post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE));
- break;
-
-
-#ifdef __APPLE__
- case 'f':
- case 'F':
-#else /* __APPLE__ */
- case WXK_CONTROL_F:
-#endif /* __APPLE__ */
- _activate_search_toolbar_item();
- break;
-
-
-#ifdef __APPLE__
- case 'y':
- case 'Y':
-#else /* __APPLE__ */
- case WXK_CONTROL_Y:
-#endif /* __APPLE__ */
- post_event(SimpleEvent(EVT_GLCANVAS_REDO));
- break;
-#ifdef __APPLE__
- case 'z':
- case 'Z':
-#else /* __APPLE__ */
- case WXK_CONTROL_Z:
-#endif /* __APPLE__ */
- post_event(SimpleEvent(EVT_GLCANVAS_UNDO));
- break;
-
- case WXK_BACK:
- case WXK_DELETE:
- post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break;
- default: evt.Skip();
- }
- } else {
- switch (keyCode)
- {
- case WXK_BACK:
- case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
- case WXK_ESCAPE: { deselect_all(); break; }
- case WXK_F5: {
- if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) ||
- (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty()))
- post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK));
- break;
- }
- case '0': { select_view("iso"); break; }
- case '1': { select_view("top"); break; }
- case '2': { select_view("bottom"); break; }
- case '3': { select_view("front"); break; }
- case '4': { select_view("rear"); break; }
- case '5': { select_view("left"); break; }
- case '6': { select_view("right"); break; }
- case '+': {
- if (dynamic_cast(m_canvas->GetParent()) != nullptr)
- post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
- else
- post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1));
- break;
- }
- case '-': {
- if (dynamic_cast(m_canvas->GetParent()) != nullptr)
- post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
- else
- post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1));
- break;
- }
- case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
- case 'A':
- case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; }
- case 'B':
- case 'b': { zoom_to_bed(); break; }
- case 'C':
- case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; }
- case 'E':
- case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
- case 'G':
- case 'g': {
- if ((evt.GetModifiers() & shiftMask) != 0) {
- if (dynamic_cast(m_canvas->GetParent()) != nullptr)
- post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt));
- }
- break;
- }
- case 'I':
- case 'i': { _update_camera_zoom(1.0); break; }
- case 'K':
- case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
- case 'L':
- case 'l': {
- if (!m_main_toolbar.is_enabled())
- show_legend(!is_legend_shown());
- break;
- }
- case 'O':
- case 'o': { _update_camera_zoom(-1.0); break; }
- case 'Z':
- case 'z': {
- if (!m_selection.is_empty())
- zoom_to_selection();
- else {
- if (!m_volumes.empty())
- zoom_to_volumes();
- else
- _zoom_to_box(m_gcode_viewer.get_paths_bounding_box());
- }
- break;
- }
- default: { evt.Skip(); break; }
- }
- }
-}
-
-class TranslationProcessor
-{
- using UpAction = std::function;
- using DownAction = std::function;
-
- UpAction m_up_action{ nullptr };
- DownAction m_down_action{ nullptr };
-
- bool m_running{ false };
- Vec3d m_direction{ Vec3d::UnitX() };
-
-public:
- TranslationProcessor(UpAction up_action, DownAction down_action)
- : m_up_action(up_action), m_down_action(down_action)
- {
- }
-
- void process(wxKeyEvent& evt)
- {
- const int keyCode = evt.GetKeyCode();
- wxEventType type = evt.GetEventType();
- if (type == wxEVT_KEY_UP) {
- switch (keyCode)
- {
- case WXK_NUMPAD_LEFT: case WXK_LEFT:
- case WXK_NUMPAD_RIGHT: case WXK_RIGHT:
- case WXK_NUMPAD_UP: case WXK_UP:
- case WXK_NUMPAD_DOWN: case WXK_DOWN:
- {
- m_running = false;
- m_up_action();
- break;
- }
- default: { break; }
- }
- }
- else if (type == wxEVT_KEY_DOWN) {
- bool apply = false;
-
- switch (keyCode)
- {
- case WXK_SHIFT:
- {
- if (m_running)
- apply = true;
-
- break;
- }
- case WXK_NUMPAD_LEFT:
- case WXK_LEFT:
- {
- m_direction = -Vec3d::UnitX();
- apply = true;
- break;
- }
- case WXK_NUMPAD_RIGHT:
- case WXK_RIGHT:
- {
- m_direction = Vec3d::UnitX();
- apply = true;
- break;
- }
- case WXK_NUMPAD_UP:
- case WXK_UP:
- {
- m_direction = Vec3d::UnitY();
- apply = true;
- break;
- }
- case WXK_NUMPAD_DOWN:
- case WXK_DOWN:
- {
- m_direction = -Vec3d::UnitY();
- apply = true;
- break;
- }
- default: { break; }
- }
-
- if (apply) {
- m_running = true;
- m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown());
- }
- }
- }
-};
-
-void GLCanvas3D::on_key(wxKeyEvent& evt)
-{
- static TranslationProcessor translationProcessor(
- [this]() {
- do_move(L("Gizmo-Move"));
- m_gizmos.update_data();
-
- wxGetApp().obj_manipul()->set_dirty();
- // Let the plater know that the dragging finished, so a delayed refresh
- // of the scene with the background processing data should be performed.
- post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
- // updates camera target constraints
- refresh_camera_scene_box();
- m_dirty = true;
- },
- [this](const Vec3d& direction, bool slow, bool camera_space) {
- m_selection.setup_cache();
- double multiplier = slow ? 1.0 : 10.0;
-
- Vec3d displacement;
- if (camera_space) {
- Eigen::Matrix inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
- displacement = multiplier * (inv_view_3x3 * direction);
- displacement.z() = 0.0;
- }
- else
- displacement = multiplier * direction;
-
-#if ENABLE_WORLD_COORDINATE
- TransformationType trafo_type;
- trafo_type.set_relative();
- m_selection.translate(displacement, trafo_type);
-#else
- m_selection.translate(displacement);
-#endif // ENABLE_WORLD_COORDINATE
- m_dirty = true;
- }
- );
-
- const int keyCode = evt.GetKeyCode();
-
- auto imgui = wxGetApp().imgui();
- if (imgui->update_key_data(evt)) {
- render();
- }
- else {
- if (!m_gizmos.on_key(evt)) {
- if (evt.GetEventType() == wxEVT_KEY_UP) {
- if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) {
- wxGetApp().plater()->toggle_render_statistic_dialog();
- m_dirty = true;
- }
- if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) {
- // Enable switching between 3D and Preview with Tab
- // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
- post_event(SimpleEvent(EVT_GLCANVAS_TAB));
- }
- else if (keyCode == WXK_TAB && evt.ShiftDown() && ! wxGetApp().is_gcode_viewer()) {
- // Collapse side-panel with Shift+Tab
- post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
- }
- else if (keyCode == WXK_SHIFT) {
- translationProcessor.process(evt);
-
- if (m_picking_enabled && m_rectangle_selection.is_dragging()) {
- _update_selection_from_hover();
- m_rectangle_selection.stop_dragging();
- m_mouse.ignore_left_up = true;
- }
- m_shift_kar_filter.reset_count();
- m_dirty = true;
-// set_cursor(Standard);
- }
- else if (keyCode == WXK_ALT) {
- if (m_picking_enabled && m_rectangle_selection.is_dragging()) {
- _update_selection_from_hover();
- m_rectangle_selection.stop_dragging();
- m_mouse.ignore_left_up = true;
- m_dirty = true;
- }
-// set_cursor(Standard);
- }
- else if (keyCode == WXK_CONTROL) {
-#if ENABLE_NEW_CAMERA_MOVEMENTS
-#if ENABLE_RAYCAST_PICKING
- if (m_mouse.dragging && !m_moving) {
-#else
- if (m_mouse.dragging) {
-#endif // ENABLE_RAYCAST_PICKING
- // if the user releases CTRL while rotating the 3D scene
- // prevent from moving the selected volume
- m_mouse.drag.move_volume_idx = -1;
- m_mouse.set_start_position_3D_as_invalid();
- }
-#endif // ENABLE_NEW_CAMERA_MOVEMENTS
- m_ctrl_kar_filter.reset_count();
- m_dirty = true;
- }
- else if (m_gizmos.is_enabled() && !m_selection.is_empty()) {
- translationProcessor.process(evt);
-
- switch (keyCode)
- {
- case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP:
- case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN:
- {
- do_rotate(L("Gizmo-Rotate"));
- m_gizmos.update_data();
-
- wxGetApp().obj_manipul()->set_dirty();
- // Let the plater know that the dragging finished, so a delayed refresh
- // of the scene with the background processing data should be performed.
- post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
- // updates camera target constraints
- refresh_camera_scene_box();
- m_dirty = true;
-
- break;
- }
- default: { break; }
- }
- }
- }
- else if (evt.GetEventType() == wxEVT_KEY_DOWN) {
- m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers();
- if (keyCode == WXK_SHIFT) {
- translationProcessor.process(evt);
-
- if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) {
- m_mouse.ignore_left_up = false;
-// set_cursor(Cross);
- }
- if (m_shift_kar_filter.is_first())
- m_dirty = true;
-
- m_shift_kar_filter.increase_count();
- }
- else if (keyCode == WXK_ALT) {
- if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)) {
- m_mouse.ignore_left_up = false;
-// set_cursor(Cross);
- }
- }
- else if (keyCode == WXK_CONTROL) {
- if (m_ctrl_kar_filter.is_first())
- m_dirty = true;
-
- m_ctrl_kar_filter.increase_count();
- }
- else if (m_gizmos.is_enabled() && !m_selection.is_empty()) {
- auto do_rotate = [this](double angle_z_rad) {
- m_selection.setup_cache();
- m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint));
- m_dirty = true;
-// wxGetApp().obj_manipul()->set_dirty();
- };
-
- translationProcessor.process(evt);
-
- switch (keyCode)
- {
- case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { do_rotate(0.25 * M_PI); break; }
- case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { do_rotate(-0.25 * M_PI); break; }
- default: { break; }
- }
- }
- else if (!m_gizmos.is_enabled()) {
- // DoubleSlider navigation in Preview
- if (keyCode == WXK_LEFT ||
- keyCode == WXK_RIGHT ||
- keyCode == WXK_UP ||
- keyCode == WXK_DOWN) {
- if (dynamic_cast(m_canvas->GetParent()) != nullptr)
- post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_SLIDERS, evt));
- }
- }
- }
- }
- }
-
- if (keyCode != WXK_TAB
- && keyCode != WXK_LEFT
- && keyCode != WXK_UP
- && keyCode != WXK_RIGHT
- && keyCode != WXK_DOWN) {
- evt.Skip(); // Needed to have EVT_CHAR generated as well
- }
-}
-
-void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
-{
-#ifdef WIN32
- // Try to filter out spurious mouse wheel events comming from 3D mouse.
- if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel())
- return;
-#endif
-
- if (!m_initialized)
- return;
-
- // Ignore the wheel events if the middle button is pressed.
- if (evt.MiddleIsDown())
- return;
-
-#if ENABLE_RETINA_GL
- const float scale = m_retina_helper->get_scale_factor();
- evt.SetX(evt.GetX() * scale);
- evt.SetY(evt.GetY() * scale);
-#endif
-
- if (wxGetApp().imgui()->update_mouse_data(evt)) {
- m_dirty = true;
- return;
- }
-
-#ifdef __WXMSW__
- // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
- // if the event is not allowed to be passed further.
- // https://github.com/prusa3d/PrusaSlicer/issues/2750
- // evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now.
- wxWakeUpIdle();
-#endif /* __WXMSW__ */
-
- // Performs layers editing updates, if enabled
- if (is_layers_editing_enabled()) {
- int object_idx_selected = m_selection.get_object_idx();
- if (object_idx_selected != -1) {
- // A volume is selected. Test, whether hovering over a layer thickness bar.
- if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) {
- // Adjust the width of the selection.
- m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f);
- if (m_canvas != nullptr)
- m_canvas->Refresh();
-
- return;
- }
- }
- }
-
- // If the Search window or Undo/Redo list is opened,
- // update them according to the event
- if (m_main_toolbar.is_item_pressed("search") ||
- m_undoredo_toolbar.is_item_pressed("undo") ||
- m_undoredo_toolbar.is_item_pressed("redo")) {
- m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
- return;
- }
-
- // Inform gizmos about the event so they have the opportunity to react.
- if (m_gizmos.on_mouse_wheel(evt))
- return;
-
- // Calculate the zoom delta and apply it to the current zoom factor
- double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0;
- _update_camera_zoom(direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
-}
-
-void GLCanvas3D::on_timer(wxTimerEvent& evt)
-{
- if (m_layers_editing.state == LayersEditing::Editing)
- _perform_layer_editing_action();
-}
-
-void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
-{
- // no need to wake up idle
- // right after this event, idle event is fired
- // m_dirty = true;
- // wxWakeUpIdle();
-}
-
-
-void GLCanvas3D::schedule_extra_frame(int miliseconds)
-{
- // Schedule idle event right now
- if (miliseconds == 0)
- {
- // We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait
- if (m_in_render)
- miliseconds = 33;
- else {
- m_dirty = true;
- wxWakeUpIdle();
- return;
- }
- }
- int remaining_time = m_render_timer.GetInterval();
- // Timer is not running
- if (!m_render_timer.IsRunning()) {
- m_render_timer.StartOnce(miliseconds);
- // Timer is running - restart only if new period is shorter than remaning period
- } else {
- if (miliseconds + 20 < remaining_time) {
- m_render_timer.Stop();
- m_render_timer.StartOnce(miliseconds);
- }
- }
-}
-
-#ifndef NDEBUG
-// #define SLIC3R_DEBUG_MOUSE_EVENTS
-#endif
-
-#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
-std::string format_mouse_event_debug_message(const wxMouseEvent &evt)
-{
- static int idx = 0;
- char buf[2048];
- std::string out;
- sprintf(buf, "Mouse Event %d - ", idx ++);
- out = buf;
-
- if (evt.Entering())
- out += "Entering ";
- if (evt.Leaving())
- out += "Leaving ";
- if (evt.Dragging())
- out += "Dragging ";
- if (evt.Moving())
- out += "Moving ";
- if (evt.Magnify())
- out += "Magnify ";
- if (evt.LeftDown())
- out += "LeftDown ";
- if (evt.LeftUp())
- out += "LeftUp ";
- if (evt.LeftDClick())
- out += "LeftDClick ";
- if (evt.MiddleDown())
- out += "MiddleDown ";
- if (evt.MiddleUp())
- out += "MiddleUp ";
- if (evt.MiddleDClick())
- out += "MiddleDClick ";
- if (evt.RightDown())
- out += "RightDown ";
- if (evt.RightUp())
- out += "RightUp ";
- if (evt.RightDClick())
- out += "RightDClick ";
-
- sprintf(buf, "(%d, %d)", evt.GetX(), evt.GetY());
- out += buf;
- return out;
-}
-#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
-
-void GLCanvas3D::on_mouse(wxMouseEvent& evt)
-{
- if (!m_initialized || !_set_current())
- return;
-
-#if ENABLE_RETINA_GL
- const float scale = m_retina_helper->get_scale_factor();
- evt.SetX(evt.GetX() * scale);
- evt.SetY(evt.GetY() * scale);
-#endif
-
- Point pos(evt.GetX(), evt.GetY());
-
- ImGuiWrapper* imgui = wxGetApp().imgui();
- if (m_tooltip.is_in_imgui() && evt.LeftUp())
- // ignore left up events coming from imgui windows and not processed by them
- m_mouse.ignore_left_up = true;
- m_tooltip.set_in_imgui(false);
- if (imgui->update_mouse_data(evt)) {
- m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast();
- m_tooltip.set_in_imgui(true);
- render();
-#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
- printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
-#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
- m_dirty = true;
- // do not return if dragging or tooltip not empty to allow for tooltip update
- // also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
- if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving()))
- return;
- }
-
-#ifdef __WXMSW__
- bool on_enter_workaround = false;
- if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) {
- // Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering()
- m_mouse.position = pos.cast();
- render();
-#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
- printf((format_mouse_event_debug_message(evt) + " - OnEnter workaround\n").c_str());
-#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
- on_enter_workaround = true;
- } else
-#endif /* __WXMSW__ */
- {
-#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
- printf((format_mouse_event_debug_message(evt) + " - other\n").c_str());
-#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
- }
-
- if (m_main_toolbar.on_mouse(evt, *this)) {
- if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
- mouse_up_cleanup();
- m_mouse.set_start_position_3D_as_invalid();
- return;
- }
-
- if (m_undoredo_toolbar.on_mouse(evt, *this)) {
- if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
- mouse_up_cleanup();
- m_mouse.set_start_position_3D_as_invalid();
- return;
- }
-
- if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) {
- if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
- mouse_up_cleanup();
- m_mouse.set_start_position_3D_as_invalid();
- return;
- }
-
- if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) {
- if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
- mouse_up_cleanup();
- m_mouse.set_start_position_3D_as_invalid();
- return;
- }
-
- for (GLVolume* volume : m_volumes.volumes) {
- volume->force_sinking_contours = false;
- }
-
- auto show_sinking_contours = [this]() {
- const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
- for (unsigned int idx : idxs) {
- m_volumes.volumes[idx]->force_sinking_contours = true;
- }
- m_dirty = true;
- };
-
- if (m_gizmos.on_mouse(evt)) {
- if (wxWindow::FindFocus() != m_canvas)
- // Grab keyboard focus for input in gizmo dialogs.
- m_canvas->SetFocus();
-
- if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
- mouse_up_cleanup();
-
- m_mouse.set_start_position_3D_as_invalid();
- m_mouse.position = pos.cast();
-
- // It should be detection of volume change
- // Not only detection of some modifiers !!!
- if (evt.Dragging()) {
- GLGizmosManager::EType c = m_gizmos.get_current_type();
- if (current_printer_technology() == ptFFF &&
- fff_print()->config().complete_objects){
- if (c == GLGizmosManager::EType::Move ||
- c == GLGizmosManager::EType::Scale ||
- c == GLGizmosManager::EType::Rotate )
- update_sequential_clearance();
- } else {
- if (c == GLGizmosManager::EType::Move ||
- c == GLGizmosManager::EType::Scale ||
- c == GLGizmosManager::EType::Rotate)
- show_sinking_contours();
- }
- }
- else if (evt.LeftUp() &&
- m_gizmos.get_current_type() == GLGizmosManager::EType::Scale &&
- m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) {
- wxGetApp().obj_list()->selection_changed();
- }
-
- return;
- }
-
- bool any_gizmo_active = m_gizmos.get_current() != nullptr;
-
- int selected_object_idx = m_selection.get_object_idx();
- int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
- m_layers_editing.select_object(*m_model, layer_editing_object_idx);
-
- if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) {
- m_mouse.drag.move_requires_threshold = false;
- m_mouse.set_move_start_threshold_position_2D_as_invalid();
- }
-
- if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas)
- // Grab keyboard focus on any mouse click event.
- m_canvas->SetFocus();
-
- if (evt.Entering()) {
-//#if defined(__WXMSW__) || defined(__linux__)
-// // On Windows and Linux needs focus in order to catch key events
- // Set focus in order to remove it from object list
- if (m_canvas != nullptr) {
- // Only set focus, if the top level window of this canvas is active
- // and ObjectList has a focus
- auto p = dynamic_cast(evt.GetEventObject());
- while (p->GetParent())
- p = p->GetParent();
-#ifdef __WIN32__
- wxWindow* const obj_list = wxGetApp().obj_list()->GetMainWindow();
-#else
- wxWindow* const obj_list = wxGetApp().obj_list();
-#endif
- if (obj_list == p->FindFocus())
- m_canvas->SetFocus();
- m_mouse.position = pos.cast();
- m_tooltip_enabled = false;
- // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
- // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to
- // change the volume hover state if any is under the mouse
- // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible,
- // so forces a resize to avoid multiple renders with different sizes (seen as flickering)
- _refresh_if_shown_on_screen();
- m_tooltip_enabled = true;
- }
- m_mouse.set_start_position_2D_as_invalid();
-//#endif
- }
- else if (evt.Leaving()) {
- _deactivate_undo_redo_toolbar_items();
-
- // to remove hover on objects when the mouse goes out of this canvas
- m_mouse.position = Vec2d(-1.0, -1.0);
- m_dirty = true;
- }
- else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
- if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())
- return;
-
- // If user pressed left or right button we first check whether this happened
- // on a volume or not.
- m_layers_editing.state = LayersEditing::Unknown;
- if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) {
- // A volume is selected and the mouse is inside the layer thickness bar.
- // Start editing the layer height.
- m_layers_editing.state = LayersEditing::Editing;
- _perform_layer_editing_action(&evt);
- }
- else {
- const bool rectangle_selection_dragging = m_rectangle_selection.is_dragging();
- if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) {
- if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports &&
- m_gizmos.get_current_type() != GLGizmosManager::FdmSupports &&
- m_gizmos.get_current_type() != GLGizmosManager::Seam &&
- m_gizmos.get_current_type() != GLGizmosManager::Cut &&
- m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
- m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
- m_dirty = true;
- }
- }
-
- // Select volume in this 3D canvas.
- // Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected
- // during the scene manipulation.
-
- if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) {
- if (evt.LeftDown() && !m_hover_volume_idxs.empty()) {
- int volume_idx = get_first_hover_volume_idx();
- bool already_selected = m_selection.contains_volume(volume_idx);
- bool shift_down = evt.ShiftDown();
-
- Selection::IndicesList curr_idxs = m_selection.get_volume_idxs();
-
- if (already_selected && shift_down)
- m_selection.remove(volume_idx);
- else {
- m_selection.add(volume_idx, !shift_down, true);
- m_mouse.drag.move_requires_threshold = !already_selected;
- if (already_selected)
- m_mouse.set_move_start_threshold_position_2D_as_invalid();
- else
- m_mouse.drag.move_start_threshold_position_2D = pos;
- }
-
- // propagate event through callback
- if (curr_idxs != m_selection.get_volume_idxs()) {
- if (m_selection.is_empty())
- m_gizmos.reset_all_states();
- else
- m_gizmos.refresh_on_off_state();
-
- m_gizmos.update_data();
- post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
- m_dirty = true;
- }
- }
- }
-
- if (!m_hover_volume_idxs.empty() && !m_rectangle_selection.is_dragging()) {
- if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) {
- // Only accept the initial position, if it is inside the volume bounding box.
- const int volume_idx = get_first_hover_volume_idx();
- BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
- volume_bbox.offset(1.0);
- const bool is_cut_connector_selected = m_selection.is_any_connector();
- if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) {
- m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None;
- // The dragging operation is initiated.
- m_mouse.drag.move_volume_idx = volume_idx;
-#if ENABLE_NEW_CAMERA_MOVEMENTS
- m_selection.setup_cache();
- if (!evt.CmdDown())
-#endif // ENABLE_NEW_CAMERA_MOVEMENTS
- m_mouse.drag.start_position_3D = m_mouse.scene_position;
- m_sequential_print_clearance_first_displacement = true;
-#if !ENABLE_RAYCAST_PICKING
- m_moving = true;
-#endif // !ENABLE_RAYCAST_PICKING
- }
- }
- }
- }
- }
-#if ENABLE_NEW_CAMERA_MOVEMENTS
- else if (evt.Dragging() && evt.LeftIsDown() && !evt.CmdDown() && m_layers_editing.state == LayersEditing::Unknown &&
- m_mouse.drag.move_volume_idx != -1 && m_mouse.is_start_position_3D_defined()) {
-#else
- else if (evt.Dragging() && evt.LeftIsDown() && m_layers_editing.state == LayersEditing::Unknown && m_mouse.drag.move_volume_idx != -1) {
-#endif // ENABLE_NEW_CAMERA_MOVEMENTS
- if (!m_mouse.drag.move_requires_threshold) {
- m_mouse.dragging = true;
- Vec3d cur_pos = m_mouse.drag.start_position_3D;
- // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
- if (m_selection.contains_volume(get_first_hover_volume_idx())) {
- const Camera& camera = wxGetApp().plater()->get_camera();
- if (std::abs(camera.get_dir_forward().z()) < EPSILON) {
- // side view -> move selected volumes orthogonally to camera view direction
- const Linef3 ray = mouse_ray(pos);
- const Vec3d dir = ray.unit_vector();
- // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
- // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
- // in our case plane normal and ray direction are the same (orthogonal view)
- // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
- const Vec3d inters = ray.a + (m_mouse.drag.start_position_3D - ray.a).dot(dir) / dir.squaredNorm() * dir;
- // vector from the starting position to the found intersection
- const Vec3d inters_vec = inters - m_mouse.drag.start_position_3D;
-
- const Vec3d camera_right = camera.get_dir_right();
- const Vec3d camera_up = camera.get_dir_up();
-
- // finds projection of the vector along the camera axes
- const double projection_x = inters_vec.dot(camera_right);
- const double projection_z = inters_vec.dot(camera_up);
-
- // apply offset
- cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up;
- }
- else {
- // Generic view
- // Get new position at the same Z of the initial click point.
- cur_pos = mouse_ray(pos).intersect_plane(m_mouse.drag.start_position_3D.z());
- }
- }
-
-#if ENABLE_WORLD_COORDINATE
-#if ENABLE_RAYCAST_PICKING
- m_moving = true;
-#endif // ENABLE_RAYCAST_PICKING
- TransformationType trafo_type;
- trafo_type.set_relative();
- m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type);
-#else
- m_selection.translate(cur_pos - m_mouse.drag.start_position_3D);
-#endif // ENABLE_WORLD_COORDINATE
- if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects)
- update_sequential_clearance();
- wxGetApp().obj_manipul()->set_dirty();
- m_dirty = true;
- }
- }
- else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
- // keeps the mouse position updated while dragging the selection rectangle
- m_mouse.position = pos.cast();
- m_rectangle_selection.dragging(m_mouse.position);
- m_dirty = true;
- }
- else if (evt.Dragging()) {
- m_mouse.dragging = true;
-
- if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
- if (m_layers_editing.state == LayersEditing::Editing) {
- _perform_layer_editing_action(&evt);
- m_mouse.position = pos.cast();
- }
- }
- // do not process the dragging if the left mouse was set down in another canvas
-#if ENABLE_NEW_CAMERA_MOVEMENTS
- else if (evt.LeftIsDown()) {
- // if dragging over blank area with left button, rotate
-#if ENABLE_RAYCAST_PICKING
- if (!m_moving) {
-#endif // ENABLE_RAYCAST_PICKING
- if ((any_gizmo_active || evt.CmdDown() || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
-#else
- // if dragging over blank area with left button, rotate
- else if (evt.LeftIsDown()) {
- if ((any_gizmo_active || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
-#endif // ENABLE_NEW_CAMERA_MOVEMENTS
- const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.0) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.0);
- if (wxGetApp().app_config->get("use_free_camera") == "1")
- // Virtual track ball (similar to the 3DConnexion mouse).
- wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.0));
- else {
- // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
- // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
- // which checks an atomics (flushes CPU caches).
- // See GH issue #3816.
- Camera& camera = wxGetApp().plater()->get_camera();
- camera.recover_from_free_camera();
- camera.rotate_on_sphere(rot.x(), rot.y(), current_printer_technology() != ptSLA);
- }
-
- m_dirty = true;
- }
- m_mouse.drag.start_position_3D = Vec3d((double)pos.x(), (double)pos.y(), 0.0);
-#if ENABLE_RAYCAST_PICKING
- }
-#endif // ENABLE_RAYCAST_PICKING
- }
- else if (evt.MiddleIsDown() || evt.RightIsDown()) {
- // If dragging over blank area with right/middle button, pan.
- if (m_mouse.is_start_position_2D_defined()) {
- // get point in model space at Z = 0
- float z = 0.0f;
- const Vec3d cur_pos = _mouse_to_3d(pos, &z);
- const Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
- Camera& camera = wxGetApp().plater()->get_camera();
- if (wxGetApp().app_config->get("use_free_camera") != "1")
- // Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
- // It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
- // which checks an atomics (flushes CPU caches).
- // See GH issue #3816.
- camera.recover_from_free_camera();
-
- camera.set_target(camera.get_target() + orig - cur_pos);
- m_dirty = true;
- }
-
- m_mouse.drag.start_position_2D = pos;
- }
- }
- else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) {
-#if ENABLE_RAYCAST_PICKING
- m_mouse.position = pos.cast();
-#endif // ENABLE_RAYCAST_PICKING
-
- if (m_layers_editing.state != LayersEditing::Unknown) {
- m_layers_editing.state = LayersEditing::Unknown;
- _stop_timer();
- m_layers_editing.accept_changes(*this);
- }
- else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) {
- do_move(L("Move Object"));
- wxGetApp().obj_manipul()->set_dirty();
- // Let the plater know that the dragging finished, so a delayed refresh
- // of the scene with the background processing data should be performed.
- post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
- }
- else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
- if (evt.ShiftDown() || evt.AltDown())
- _update_selection_from_hover();
-
- m_rectangle_selection.stop_dragging();
- }
- else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && !is_layers_editing_enabled()) {
- // deselect and propagate event through callback
- if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled)
- deselect_all();
- }
- else if (evt.RightUp()) {
-#if !ENABLE_RAYCAST_PICKING
- m_mouse.position = pos.cast();
-#endif // !ENABLE_RAYCAST_PICKING
- // forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
- // the context menu is already shown
- render();
- if (!m_hover_volume_idxs.empty()) {
- // if right clicking on volume, propagate event through callback (shows context menu)
- int volume_idx = get_first_hover_volume_idx();
- if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower
- && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open
- {
- // forces the selection of the volume
- /* m_selection.add(volume_idx); // #et_FIXME_if_needed
- * To avoid extra "Add-Selection" snapshots,
- * call add() with check_for_already_contained=true
- * */
- m_selection.add(volume_idx, true, true);
- m_gizmos.refresh_on_off_state();
- post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
- m_gizmos.update_data();
- wxGetApp().obj_manipul()->set_dirty();
- // forces a frame render to update the view before the context menu is shown
- render();
- }
- }
- Vec2d logical_pos = pos.cast();
-#if ENABLE_RETINA_GL
- const float factor = m_retina_helper->get_scale_factor();
- logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor));
-#endif // ENABLE_RETINA_GL
- if (!m_mouse.dragging) {
- // do not post the event if the user is panning the scene
- // or if right click was done over the wipe tower
- const bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower;
- if (post_right_click_event)
- post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() }));
- }
- }
-
- mouse_up_cleanup();
- }
- else if (evt.Moving()) {
- m_mouse.position = pos.cast();
-
- // updates gizmos overlay
- if (m_selection.is_empty())
- m_gizmos.reset_all_states();
-
- m_dirty = true;
- }
- else
- evt.Skip();
-
- if (m_moving)
- show_sinking_contours();
-
-#ifdef __WXMSW__
- if (on_enter_workaround)
- m_mouse.position = Vec2d(-1., -1.);
-#endif /* __WXMSW__ */
-}
-
-void GLCanvas3D::on_paint(wxPaintEvent& evt)
-{
- if (m_initialized)
- m_dirty = true;
- else
- // Call render directly, so it gets initialized immediately, not from On Idle handler.
- this->render();
-}
-
-void GLCanvas3D::on_set_focus(wxFocusEvent& evt)
-{
- m_tooltip_enabled = false;
- _refresh_if_shown_on_screen();
- m_tooltip_enabled = true;
-}
-
-Size GLCanvas3D::get_canvas_size() const
-{
- int w = 0;
- int h = 0;
-
- if (m_canvas != nullptr)
- m_canvas->GetSize(&w, &h);
-
-#if ENABLE_RETINA_GL
- const float factor = m_retina_helper->get_scale_factor();
- w *= factor;
- h *= factor;
-#else
- const float factor = 1.0f;
-#endif
-
- return Size(w, h, factor);
-}
-
-Vec2d GLCanvas3D::get_local_mouse_position() const
-{
- if (m_canvas == nullptr)
- return Vec2d::Zero();
-
- wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition());
- const double factor =
-#if ENABLE_RETINA_GL
- m_retina_helper->get_scale_factor();
-#else
- 1.0;
-#endif
- return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y);
-}
-
-void GLCanvas3D::set_tooltip(const std::string& tooltip)
-{
- if (m_canvas != nullptr)
- m_tooltip.set_text(tooltip);
-}
-
-void GLCanvas3D::do_move(const std::string& snapshot_type)
-{
- if (m_model == nullptr)
- return;
-
- if (!snapshot_type.empty())
- wxGetApp().plater()->take_snapshot(_(snapshot_type));
-
- std::set> done; // keeps track of modified instances
- bool object_moved = false;
- Vec3d wipe_tower_origin = Vec3d::Zero();
-
- Selection::EMode selection_mode = m_selection.get_mode();
-
- for (const GLVolume* v : m_volumes.volumes) {
- int object_idx = v->object_idx();
- int instance_idx = v->instance_idx();
- int volume_idx = v->volume_idx();
-
- std::pair done_id(object_idx, instance_idx);
-
- if (0 <= object_idx && object_idx < (int)m_model->objects.size()) {
- done.insert(done_id);
-
- // Move instances/volumes
- ModelObject* model_object = m_model->objects[object_idx];
- if (model_object != nullptr) {
- if (selection_mode == Selection::Instance)
-#if ENABLE_WORLD_COORDINATE
- model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation());
-#else
- model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
-#endif // ENABLE_WORLD_COORDINATE
- else if (selection_mode == Selection::Volume)
-#if ENABLE_WORLD_COORDINATE
- model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation());
-#else
- model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
-#endif // ENABLE_WORLD_COORDINATE
-
- object_moved = true;
- model_object->invalidate_bounding_box();
- }
- }
- else if (v->is_wipe_tower)
- // Move a wipe tower proxy.
- wipe_tower_origin = v->get_volume_offset();
- }
-
- // Fixes flying instances
- for (const std::pair& i : done) {
- ModelObject* m = m_model->objects[i.first];
- const double shift_z = m->get_instance_min_z(i.second);
- if (current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) {
- const Vec3d shift(0.0, 0.0, -shift_z);
- m_selection.translate(i.first, i.second, shift);
- m->translate_instance(i.second, shift);
- }
- wxGetApp().obj_list()->update_info_items(static_cast(i.first));
- }
-
- // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running
- // similar to void Plater::priv::selection_changed()
- if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled())
- post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
-
- if (object_moved)
- post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
-
- if (wipe_tower_origin != Vec3d::Zero())
- post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin)));
-
- reset_sequential_print_clearance();
-
- m_dirty = true;
-}
-
-void GLCanvas3D::do_rotate(const std::string& snapshot_type)
-{
- if (m_model == nullptr)
- return;
-
- if (!snapshot_type.empty())
- wxGetApp().plater()->take_snapshot(_(snapshot_type));
-
- // stores current min_z of instances
- std::map, double> min_zs;
- for (int i = 0; i < static_cast(m_model->objects.size()); ++i) {
- const ModelObject* obj = m_model->objects[i];
- for (int j = 0; j < static_cast(obj->instances.size()); ++j) {
- if (snapshot_type == L("Gizmo-Place on Face") && m_selection.get_object_idx() == i) {
- // This means we are flattening this object. In that case pretend
- // that it is not sinking (even if it is), so it is placed on bed
- // later on (whatever is sinking will be left sinking).
- min_zs[{ i, j }] = SINKING_Z_THRESHOLD;
- }
- else
- min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
-
- }
- }
-
- std::set