Merge branch 'master' into fs_emboss
This commit is contained in:
commit
57695056ed
17 changed files with 1398 additions and 1030 deletions
|
@ -14,21 +14,44 @@ namespace Measure {
|
|||
|
||||
constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it
|
||||
|
||||
static std::pair<Vec3d, double> get_center_and_radius(const std::vector<Vec3d>& border, int start_idx, int end_idx, const Transform3d& trafo)
|
||||
static std::pair<Vec3d, double> get_center_and_radius(const std::vector<Vec3d>& points, const Transform3d& trafo)
|
||||
{
|
||||
Vec2ds pts;
|
||||
Vec2ds out;
|
||||
double z = 0.;
|
||||
for (int i=start_idx; i<=end_idx; ++i) {
|
||||
Vec3d pt_transformed = trafo * border[i];
|
||||
for (const Vec3d& pt : points) {
|
||||
Vec3d pt_transformed = trafo * pt;
|
||||
z = pt_transformed.z();
|
||||
pts.emplace_back(pt_transformed.x(), pt_transformed.y());
|
||||
out.emplace_back(pt_transformed.x(), pt_transformed.y());
|
||||
}
|
||||
|
||||
auto circle = Geometry::circle_ransac(pts, 20); // FIXME: iterations?
|
||||
auto circle = Geometry::circle_ransac(out, 20); // FIXME: iterations?
|
||||
|
||||
return std::make_pair(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius);
|
||||
}
|
||||
|
||||
static bool circle_fit_is_ok(const std::vector<Vec3d>& pts, const Vec3d& center, double radius)
|
||||
{
|
||||
for (const Vec3d& pt : pts)
|
||||
if (std::abs((pt - center).norm() - radius) > 0.05)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::array<Vec3d, 3> orthonormal_basis(const Vec3d& v)
|
||||
{
|
||||
std::array<Vec3d, 3> ret;
|
||||
ret[2] = v.normalized();
|
||||
int index;
|
||||
ret[2].cwiseAbs().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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -47,6 +70,7 @@ public:
|
|||
std::optional<SurfaceFeature> get_feature(size_t face_idx, const Vec3d& point) const;
|
||||
std::vector<std::vector<int>> get_planes_triangle_indices() const;
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id) const;
|
||||
const TriangleMesh& get_mesh() const;
|
||||
|
||||
private:
|
||||
void update_planes();
|
||||
|
@ -54,7 +78,7 @@ private:
|
|||
|
||||
std::vector<PlaneData> m_planes;
|
||||
std::vector<size_t> m_face_to_plane;
|
||||
const indexed_triangle_set& m_its;
|
||||
TriangleMesh m_mesh;
|
||||
};
|
||||
|
||||
|
||||
|
@ -63,7 +87,7 @@ private:
|
|||
|
||||
|
||||
MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
|
||||
: m_its{its}
|
||||
: m_mesh(its)
|
||||
{
|
||||
update_planes();
|
||||
extract_features();
|
||||
|
@ -76,10 +100,10 @@ void MeasuringImpl::update_planes()
|
|||
|
||||
// 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();
|
||||
const size_t num_of_facets = m_mesh.its.indices.size();
|
||||
m_face_to_plane.resize(num_of_facets, size_t(-1));
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(m_its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(m_its);
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(m_mesh.its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(m_mesh.its);
|
||||
std::vector<int> facet_queue(num_of_facets, 0);
|
||||
int facet_queue_cnt = 0;
|
||||
const stl_normal* normal_ptr = nullptr;
|
||||
|
@ -128,7 +152,7 @@ void MeasuringImpl::update_planes()
|
|||
assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); }));
|
||||
|
||||
// Now we will walk around each of the planes and save vertices which form the border.
|
||||
SurfaceMesh sm(m_its);
|
||||
SurfaceMesh sm(m_mesh.its);
|
||||
for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) {
|
||||
const auto& facets = m_planes[plane_id].facets;
|
||||
m_planes[plane_id].borders.clear();
|
||||
|
@ -173,10 +197,16 @@ void MeasuringImpl::update_planes()
|
|||
he = sm.next_around_target(he);
|
||||
if (he.is_invalid())
|
||||
goto PLANE_FAILURE;
|
||||
|
||||
// For broken meshes, the iteration might never get back to he_orig.
|
||||
// Remember all halfedges we saw to break out of such infinite loops.
|
||||
boost::container::small_vector<Halfedge_index, 10> he_seen;
|
||||
|
||||
while ( (int)m_face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
|
||||
he_seen.emplace_back(he);
|
||||
he = sm.next_around_target(he);
|
||||
if (he.is_invalid())
|
||||
goto PLANE_FAILURE;
|
||||
if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end())
|
||||
goto PLANE_FAILURE;
|
||||
}
|
||||
he = sm.opposite(he);
|
||||
if (he.is_invalid())
|
||||
|
@ -194,12 +224,19 @@ void MeasuringImpl::update_planes()
|
|||
visited[face_it - facets.begin()][he.side()] = true;
|
||||
|
||||
last_border.emplace_back(sm.point(sm.source(he)).cast<double>());
|
||||
|
||||
// In case of broken meshes, this loop might be infinite. Break
|
||||
// out in case it is clearly going bad.
|
||||
if (last_border.size() > 3*facets.size()+1)
|
||||
goto PLANE_FAILURE;
|
||||
|
||||
} while (he != he_start);
|
||||
|
||||
if (last_border.size() == 1)
|
||||
m_planes[plane_id].borders.pop_back();
|
||||
else {
|
||||
assert(last_border.front() == last_border.back());
|
||||
last_border.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +254,7 @@ void MeasuringImpl::update_planes()
|
|||
|
||||
void MeasuringImpl::extract_features()
|
||||
{
|
||||
std::vector<double> angles;
|
||||
std::vector<double> angles; // placed in outer scope to prevent reallocations
|
||||
std::vector<double> lengths;
|
||||
|
||||
|
||||
|
@ -230,180 +267,201 @@ void MeasuringImpl::extract_features()
|
|||
q.setFromTwoVectors(plane.normal, Vec3d::UnitZ());
|
||||
Transform3d trafo = Transform3d::Identity();
|
||||
trafo.rotate(q);
|
||||
|
||||
|
||||
for (const std::vector<Vec3d>& border : plane.borders) {
|
||||
if (border.size() <= 1)
|
||||
continue;
|
||||
assert(border.front() == border.back());
|
||||
int start_idx = -1;
|
||||
|
||||
std::vector<SurfaceFeature> edges;
|
||||
bool done = false;
|
||||
|
||||
// First calculate angles at all the vertices.
|
||||
angles.clear();
|
||||
lengths.clear();
|
||||
for (int i=0; i<int(border.size()); ++i) { // front is the same as back, hence the weird indexing
|
||||
const Vec3d& v2 = (i == 0 ? border[0] - border[border.size()-2]
|
||||
: border[i] - border[i-1]);
|
||||
const Vec3d& v1 = i == (int)border.size()-1 ? border[1] - border.back()
|
||||
: border[i+1] - border[i];
|
||||
double angle = atan2(-normal.dot(v1.cross(v2)), -v1.dot(v2)) + M_PI;
|
||||
if (angle > M_PI)
|
||||
angle = 2*M_PI - angle;
|
||||
if (const auto& [center, radius] = get_center_and_radius(border, trafo);
|
||||
(border.size()>4) && circle_fit_is_ok(border, center, radius)) {
|
||||
// The whole border is one circle. Just add it into the list of features
|
||||
// and we are done.
|
||||
|
||||
angles.push_back(angle);
|
||||
lengths.push_back(v2.norm());
|
||||
}
|
||||
assert(border.size() == angles.size());
|
||||
assert(border.size() == lengths.size());
|
||||
bool is_polygon = border.size()>4 && border.size()<=8;
|
||||
bool lengths_match = std::all_of(border.begin()+2, border.end(), [is_polygon](const Vec3d& pt) {
|
||||
return Slic3r::is_approx((pt - *((&pt)-1)).squaredNorm(), (*((&pt)-1) - *((&pt)-2)).squaredNorm(), is_polygon ? 0.01 : 0.01);
|
||||
});
|
||||
|
||||
|
||||
// First go around the border and pick what might be circular segments.
|
||||
// Save pair of indices to where such potential segments start and end.
|
||||
// Also remember the length of these segments.
|
||||
bool circle = false;
|
||||
std::vector<SurfaceFeature> circles;
|
||||
std::vector<std::pair<int, int>> circles_idxs;
|
||||
std::vector<double> circles_lengths;
|
||||
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);
|
||||
if (lengths_match && (is_polygon || border.size() > 8)) {
|
||||
if (is_polygon) {
|
||||
// This is a polygon, add the separate edges with the center.
|
||||
for (int j=0; j<int(border.size()); ++j)
|
||||
plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge,
|
||||
border[j==0 ? border.size()-1 : j-1], border[j],
|
||||
std::make_optional(center)));
|
||||
} else {
|
||||
// The fit went well and it has more than 8 points - let's consider this a circle.
|
||||
plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
|
||||
}
|
||||
} else {
|
||||
if (circle) {
|
||||
const auto& [center, radius] = get_center_and_radius(border, start_idx, i, trafo);
|
||||
// Add the circle and remember indices into borders.
|
||||
circles_idxs.emplace_back(start_idx, i);
|
||||
circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
|
||||
circles_lengths.emplace_back(std::accumulate(lengths.begin() + start_idx + 1, lengths.begin() + i + 1, 0.));
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! done) {
|
||||
// In this case, the border is not a circle and may contain circular
|
||||
// segments. Try to find them and then add all remaining edges as edges.
|
||||
|
||||
auto are_angles_same = [](double a, double b) { return Slic3r::is_approx(a,b,0.01); };
|
||||
auto are_lengths_same = [](double a, double b) { return Slic3r::is_approx(a,b,0.01); };
|
||||
|
||||
|
||||
// Given an idx into border, return the index that is idx+offset position,
|
||||
// while taking into account the need for wrap-around and the fact that
|
||||
// the first and last point are the same.
|
||||
auto offset_to_index = [border_size = int(border.size())](int idx, int offset) -> int {
|
||||
assert(std::abs(offset) < border_size);
|
||||
int out = idx+offset;
|
||||
if (out >= border_size)
|
||||
out = out - border_size;
|
||||
else if (out < 0)
|
||||
out = border_size + out;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
// First calculate angles at all the vertices.
|
||||
angles.clear();
|
||||
lengths.clear();
|
||||
int first_different_angle_idx = 0;
|
||||
for (int i=0; i<int(border.size()); ++i) {
|
||||
const Vec3d& v2 = border[i] - (i == 0 ? border[border.size()-1] : border[i-1]);
|
||||
const Vec3d& v1 = (i == int(border.size()-1) ? border[0] : border[i+1]) - border[i];
|
||||
double angle = atan2(-normal.dot(v1.cross(v2)), -v1.dot(v2)) + M_PI;
|
||||
if (angle > M_PI)
|
||||
angle = 2*M_PI - angle;
|
||||
|
||||
angles.push_back(angle);
|
||||
lengths.push_back(v2.norm());
|
||||
if (first_different_angle_idx == 0 && angles.size() > 1) {
|
||||
if (! are_angles_same(angles.back(), angles[angles.size()-2]))
|
||||
first_different_angle_idx = angles.size()-1;
|
||||
}
|
||||
}
|
||||
assert(border.size() == angles.size());
|
||||
assert(border.size() == lengths.size());
|
||||
|
||||
// First go around the border and pick what might be circular segments.
|
||||
// Save pair of indices to where such potential segments start and end.
|
||||
// Also remember the length of these segments.
|
||||
int start_idx = -1;
|
||||
bool circle = false;
|
||||
bool first_iter = true;
|
||||
std::vector<SurfaceFeature> circles;
|
||||
std::vector<SurfaceFeature> edges;
|
||||
std::vector<std::pair<int, int>> circles_idxs;
|
||||
//std::vector<double> circles_lengths;
|
||||
std::vector<Vec3d> single_circle; // could be in loop-scope, but reallocations
|
||||
double single_circle_length = 0.;
|
||||
int first_pt_idx = offset_to_index(first_different_angle_idx, 1);
|
||||
int i = first_pt_idx;
|
||||
while (i != first_pt_idx || first_iter) {
|
||||
if (are_angles_same(angles[i], angles[offset_to_index(i,-1)])
|
||||
&& i != offset_to_index(first_pt_idx, -1) // not the last point
|
||||
&& i != start_idx ) {
|
||||
// circle
|
||||
if (! circle) {
|
||||
circle = true;
|
||||
single_circle.clear();
|
||||
single_circle_length = 0.;
|
||||
start_idx = offset_to_index(i, -2);
|
||||
single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] };
|
||||
single_circle_length += lengths[offset_to_index(i, -1)];
|
||||
}
|
||||
single_circle.emplace_back(border[i]);
|
||||
single_circle_length += lengths[i];
|
||||
} else {
|
||||
if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle.
|
||||
single_circle.emplace_back(border[i]);
|
||||
single_circle_length += lengths[i];
|
||||
|
||||
bool accept_circle = true;
|
||||
{
|
||||
// Check that lengths of internal (!!!) edges match.
|
||||
int j = offset_to_index(start_idx, 3);
|
||||
while (j != i) {
|
||||
if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) {
|
||||
accept_circle = false;
|
||||
break;
|
||||
}
|
||||
j = offset_to_index(j, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (accept_circle) {
|
||||
const auto& [center, radius] = get_center_and_radius(single_circle, trafo);
|
||||
|
||||
// Check that the fit went well. The tolerance is high, only to
|
||||
// reject complete failures.
|
||||
accept_circle &= circle_fit_is_ok(single_circle, center, radius);
|
||||
|
||||
// If the segment subtends less than 90 degrees, throw it away.
|
||||
accept_circle &= single_circle_length / radius > 0.9*M_PI/2.;
|
||||
|
||||
if (accept_circle) {
|
||||
// Add the circle and remember indices into borders.
|
||||
circles_idxs.emplace_back(start_idx, i);
|
||||
circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius));
|
||||
}
|
||||
}
|
||||
}
|
||||
circle = false;
|
||||
}
|
||||
// Take care of the wrap around.
|
||||
first_iter = false;
|
||||
i = offset_to_index(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we might need to merge the first and last segment, if the starting
|
||||
// point happened to be inside the segment. The discrimination of too small segments
|
||||
// will follow, so we need a complete picture before that.
|
||||
if (circles_idxs.size() > 1
|
||||
&& circles_idxs.back().second == angles.size()-1
|
||||
&& circles_idxs.front().first == 0) {
|
||||
// Possibly the same circle. Check that the angle and length criterion holds along the combined segment.
|
||||
bool same = true;
|
||||
double last_len = -1.;
|
||||
double last_angle = 0.;
|
||||
for (int i=circles_idxs.back().first + 1; i != circles_idxs.front().second; ++i) {
|
||||
if (i == angles.size())
|
||||
i = 1;
|
||||
if (last_len == -1.) {
|
||||
last_len = lengths[i];
|
||||
last_angle = angles[i];
|
||||
} else {
|
||||
if (! Slic3r::is_approx(lengths[i], last_len) || ! Slic3r::is_approx(angles[i], last_angle)) {
|
||||
same = false;
|
||||
break;
|
||||
// We have the circles. Now go around again and pick edges, while jumping over circles.
|
||||
if (circles_idxs.empty()) {
|
||||
// Just add all edges.
|
||||
for (int i=1; i<int(border.size()); ++i)
|
||||
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i-1], border[i]));
|
||||
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[0], border[border.size()-1]));
|
||||
} else if (circles_idxs.size() > 1 || circles_idxs.front().first != circles_idxs.front().second) {
|
||||
// There is at least one circular segment. Start at its end and add edges until the start of the next one.
|
||||
int i = circles_idxs.front().second;
|
||||
int circle_idx = 1;
|
||||
while (true) {
|
||||
i = offset_to_index(i, 1);
|
||||
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i]));
|
||||
if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) {
|
||||
i = circles_idxs[circle_idx].second;
|
||||
++circle_idx;
|
||||
}
|
||||
if (i == circles_idxs.front().first)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
// This seems to really be the same circle. Better apply ransac again. The parts can be small and inexact.
|
||||
std::vector<Vec3d> points(border.begin() + circles_idxs.back().first, border.end());
|
||||
points.insert(points.end(), border.begin(), border.begin() + circles_idxs.front().second+1);
|
||||
auto [c, radius] = get_center_and_radius(points, 0, points.size()-1, trafo);
|
||||
|
||||
// Now replace the first circle with the combined one, remove the last circle.
|
||||
// First index of the first circle is saved negative - we are going to pick edges
|
||||
// from the border later, we will need to know where the merged in segment was.
|
||||
// The sign simplifies the algorithm that picks the remaining edges - see below.
|
||||
circles.front() = SurfaceFeature(SurfaceFeatureType::Circle, c, plane.normal, std::nullopt, radius);
|
||||
circles_idxs.front().first = - circles_idxs.back().first;
|
||||
circles_lengths.front() += circles_lengths.back();
|
||||
circles.pop_back();
|
||||
circles_idxs.pop_back();
|
||||
circles_lengths.pop_back();
|
||||
// Merge adjacent edges where needed.
|
||||
assert(std::all_of(edges.begin(), edges.end(),
|
||||
[](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; }));
|
||||
for (int i=edges.size()-1; i>=0; --i) {
|
||||
const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge();
|
||||
const auto& [second_start, second_end] = edges[i].get_edge();
|
||||
|
||||
if (Slic3r::is_approx(first_end, second_start)
|
||||
&& Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) {
|
||||
// The edges have the same direction and share a point. Merge them.
|
||||
edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end);
|
||||
edges.erase(edges.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now throw away all circles that subtend less than 90 deg.
|
||||
assert(circles.size() == circles_lengths.size());
|
||||
for (int i=0; i<int(circles.size()); ++i) {
|
||||
double r = std::get<1>(circles[i].get_circle());
|
||||
if (circles_lengths[i] / r < 0.9*M_PI/2.) {
|
||||
circles_lengths.erase(circles_lengths.begin() + i);
|
||||
circles.erase(circles.begin() + i);
|
||||
circles_idxs.erase(circles_idxs.begin() + i);
|
||||
--i;
|
||||
}
|
||||
// Now move the circles and edges into the feature list for the plane.
|
||||
assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) {
|
||||
return f.get_type() == SurfaceFeatureType::Circle;
|
||||
}));
|
||||
assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) {
|
||||
return f.get_type() == SurfaceFeatureType::Edge;
|
||||
}));
|
||||
plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()),
|
||||
std::make_move_iterator(circles.end()));
|
||||
plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()),
|
||||
std::make_move_iterator(edges.end()));
|
||||
}
|
||||
circles_lengths.clear(); // no longer needed, make it obvious
|
||||
|
||||
// Anything under 5 vertices shall not be considered a circle.
|
||||
assert(circles_idxs.size() == circles.size());
|
||||
for (int i=int(circles_idxs.size())-1; i>=0; --i) {
|
||||
const auto& [start, end] = circles_idxs[i];
|
||||
int N = start >= 0
|
||||
? end - start + (start == 0 && end == border.size()-1 ? 0 : 1) // last point is the same as first
|
||||
: end + (border.size() + start);
|
||||
if (N < 5) {
|
||||
circles.erase(circles.begin() + i);
|
||||
circles_idxs.erase(circles_idxs.begin() + i);
|
||||
} else if (N <= 8 && start == 0 && end == border.size()-1) {
|
||||
// This is a regular 5-8 polygon. Add the edges as edges with a special
|
||||
// point and remove the circle. Leave the indices in circles_idxs, so
|
||||
// the edges are not picked up again later.
|
||||
const Vec3d center = std::get<0>(circles[i].get_circle());
|
||||
for (int j=1; j<=end; ++j)
|
||||
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge,
|
||||
border[j - 1], border[j], std::make_optional(center)));
|
||||
circles.erase(circles.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We have the circles. Now go around again and pick edges, while jumping over circles.
|
||||
// If the first index of the first circle is negative, it means that it was merged
|
||||
// with a segment that was originally at the back and is no longer there. Ressurect
|
||||
// its pair of indices so that edges are not picked again.
|
||||
if (! circles_idxs.empty() && circles_idxs.front().first < 0)
|
||||
circles_idxs.emplace_back(-circles_idxs.front().first, int(border.size()));
|
||||
int cidx = 0; // index of next circle to jump over
|
||||
for (int i=1; i<int(border.size()); ++i) {
|
||||
if (cidx < (int)circles_idxs.size() && i > (int)circles_idxs[cidx].first)
|
||||
i = circles_idxs[cidx++].second;
|
||||
else
|
||||
edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i - 1], border[i]));
|
||||
}
|
||||
|
||||
// Merge adjacent edges where needed.
|
||||
assert(std::all_of(edges.begin(), edges.end(),
|
||||
[](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; }));
|
||||
for (int i=edges.size()-1; i>=0; --i) {
|
||||
const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge();
|
||||
const auto& [second_start, second_end] = edges[i].get_edge();
|
||||
|
||||
if (Slic3r::is_approx(first_end, second_start)
|
||||
&& Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) {
|
||||
// The edges have the same direction and share a point. Merge them.
|
||||
edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end);
|
||||
edges.erase(edges.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Now move the circles and edges into the feature list for the plane.
|
||||
assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) {
|
||||
return f.get_type() == SurfaceFeatureType::Circle;
|
||||
}));
|
||||
assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) {
|
||||
return f.get_type() == SurfaceFeatureType::Edge;
|
||||
}));
|
||||
plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()),
|
||||
std::make_move_iterator(circles.end()));
|
||||
plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()),
|
||||
std::make_move_iterator(edges.end()));
|
||||
}
|
||||
|
||||
// The last surface feature is the plane itself.
|
||||
|
@ -510,6 +568,10 @@ const std::vector<SurfaceFeature>& MeasuringImpl::get_plane_features(unsigned in
|
|||
return m_planes[plane_id].surface_features;
|
||||
}
|
||||
|
||||
const TriangleMesh& MeasuringImpl::get_mesh() const
|
||||
{
|
||||
return this->m_mesh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -551,6 +613,11 @@ const std::vector<SurfaceFeature>& Measuring::get_plane_features(unsigned int pl
|
|||
return priv->get_plane_features(plane_id);
|
||||
}
|
||||
|
||||
const TriangleMesh& Measuring::get_mesh() const
|
||||
{
|
||||
return priv->get_mesh();
|
||||
}
|
||||
|
||||
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<Vec3d, Vec3d>& e1, const std::pair<Vec3d, Vec3d>& e2)
|
||||
|
@ -620,8 +687,8 @@ static AngleAndEdges angle_edge_edge(const std::pair<Vec3d, Vec3d>& e1, const st
|
|||
static AngleAndEdges angle_edge_plane(const std::pair<Vec3d, Vec3d>& e, const std::tuple<int, Vec3d, Vec3d>& 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))
|
||||
Vec3d e1e2_unit = edge_direction(e);
|
||||
if (are_perpendicular(e1e2_unit, normal))
|
||||
return AngleAndEdges::Dummy;
|
||||
|
||||
// ensure the edge is pointing away from the intersection
|
||||
|
@ -633,8 +700,22 @@ static AngleAndEdges angle_edge_plane(const std::pair<Vec3d, Vec3d>& e, const st
|
|||
// then verify edge direction and revert it, if needed
|
||||
Vec3d e1 = e.first;
|
||||
Vec3d e2 = e.second;
|
||||
if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm())
|
||||
if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm()) {
|
||||
std::swap(e1, e2);
|
||||
e1e2_unit = -e1e2_unit;
|
||||
}
|
||||
|
||||
if (are_parallel(e1e2_unit, normal)) {
|
||||
const std::array<Vec3d, 3> basis = orthonormal_basis(e1e2_unit);
|
||||
const double radius = (0.5 * (e1 + e2) - inters).norm();
|
||||
const Vec3d edge_on_plane_dir = (basis[1].dot(origin - inters) >= 0.0) ? basis[1] : -basis[1];
|
||||
std::pair<Vec3d, Vec3d> edge_on_plane = std::make_pair(inters, inters + radius * edge_on_plane_dir);
|
||||
if (!inters.isApprox(e1)) {
|
||||
edge_on_plane.first += radius * edge_on_plane_dir;
|
||||
edge_on_plane.second += radius * edge_on_plane_dir;
|
||||
}
|
||||
return AngleAndEdges(0.5 * double(PI), inters, std::make_pair(e1, e2), edge_on_plane, radius, inters.isApprox(e1));
|
||||
}
|
||||
|
||||
const Vec3d e1e2 = e2 - e1;
|
||||
const double e1e2_len = e1e2.norm();
|
||||
|
@ -763,7 +844,8 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature&
|
|||
///////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
} else if (f1.get_type() == SurfaceFeatureType::Edge) {
|
||||
}
|
||||
else if (f1.get_type() == SurfaceFeatureType::Edge) {
|
||||
if (f2.get_type() == SurfaceFeatureType::Edge) {
|
||||
std::vector<DistAndPoints> distances;
|
||||
|
||||
|
@ -890,21 +972,6 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature&
|
|||
const Vec3d D = c1 - c0;
|
||||
|
||||
if (!are_parallel(n0, n1)) {
|
||||
auto orthonormal_basis = [](const Vec3d& v) {
|
||||
std::array<Vec3d, 3> 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;
|
||||
|
@ -1149,27 +1216,6 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature&
|
|||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ struct indexed_triangle_set;
|
|||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace Measure {
|
||||
|
||||
|
||||
|
@ -87,8 +90,7 @@ 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.
|
||||
// Construct the measurement object on a given its.
|
||||
explicit Measuring(const indexed_triangle_set& its);
|
||||
~Measuring();
|
||||
|
||||
|
@ -108,6 +110,9 @@ public:
|
|||
// Returns the surface features of the plane with the given index
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id) const;
|
||||
|
||||
// Returns the mesh used for measuring
|
||||
const TriangleMesh& get_mesh() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<MeasuringImpl> priv;
|
||||
};
|
||||
|
@ -118,8 +123,6 @@ struct DistAndPoints {
|
|||
double dist;
|
||||
Vec3d from;
|
||||
Vec3d to;
|
||||
|
||||
void transform(const Transform3d& trafo);
|
||||
};
|
||||
|
||||
struct AngleAndEdges {
|
||||
|
@ -132,8 +135,6 @@ struct AngleAndEdges {
|
|||
double radius;
|
||||
bool coplanar;
|
||||
|
||||
void transform(const Transform3d& trafo);
|
||||
|
||||
static const AngleAndEdges Dummy;
|
||||
};
|
||||
|
||||
|
@ -150,17 +151,6 @@ struct MeasurementResult {
|
|||
bool has_any_data() const {
|
||||
return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value();
|
||||
}
|
||||
|
||||
void transform(const Transform3d& trafo) {
|
||||
if (angle.has_value())
|
||||
angle->transform(trafo);
|
||||
if (distance_infinite.has_value())
|
||||
distance_infinite->transform(trafo);
|
||||
if (distance_strict.has_value()) {
|
||||
distance_strict->transform(trafo);
|
||||
distance_xyz = (distance_strict->to - distance_strict->from).cwiseAbs();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns distance/angle between two SurfaceFeatures.
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <admesh/stl.h>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include "boost/container/small_vector.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
@ -115,11 +117,18 @@ public:
|
|||
|
||||
size_t degree(Vertex_index v) const
|
||||
{
|
||||
// In case the mesh is broken badly, the loop might end up to be infinite,
|
||||
// never getting back to the first halfedge. Remember list of all half-edges
|
||||
// and trip if any is encountered for the second time.
|
||||
Halfedge_index h_first = halfedge(v);
|
||||
boost::container::small_vector<Halfedge_index, 10> he_visited;
|
||||
Halfedge_index h = next_around_target(h_first);
|
||||
size_t degree = 2;
|
||||
while (! h.is_invalid() && h != h_first) {
|
||||
he_visited.emplace_back(h);
|
||||
h = next_around_target(h);
|
||||
if (std::find(he_visited.begin(), he_visited.end(), h) == he_visited.end())
|
||||
return 0;
|
||||
++degree;
|
||||
}
|
||||
return h.is_invalid() ? 0 : degree - 1;
|
||||
|
|
|
@ -37,12 +37,12 @@
|
|||
|
||||
|
||||
//====================
|
||||
// 2.5.0.alpha1 techs
|
||||
// 2.6.0.alpha1 techs
|
||||
//====================
|
||||
#define ENABLE_2_5_0_ALPHA1 1
|
||||
#define ENABLE_2_6_0_ALPHA1 1
|
||||
|
||||
// Enable removal of legacy OpenGL calls
|
||||
#define ENABLE_LEGACY_OPENGL_REMOVAL (1 && ENABLE_2_5_0_ALPHA1)
|
||||
#define ENABLE_LEGACY_OPENGL_REMOVAL (1 && ENABLE_2_6_0_ALPHA1)
|
||||
// Enable OpenGL ES
|
||||
#define ENABLE_OPENGL_ES (0 && ENABLE_LEGACY_OPENGL_REMOVAL)
|
||||
// Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows)
|
||||
|
@ -52,15 +52,15 @@
|
|||
// Shows an imgui dialog with GLModel statistics data
|
||||
#define ENABLE_GLMODEL_STATISTICS (0 && ENABLE_LEGACY_OPENGL_REMOVAL)
|
||||
// Enable rework of Reload from disk command
|
||||
#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1)
|
||||
#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_6_0_ALPHA1)
|
||||
// Enable editing volumes transformation in world coordinates and instances in local coordinates
|
||||
#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1)
|
||||
#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_6_0_ALPHA1)
|
||||
// Enable alternative version of file_wildcards()
|
||||
#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_5_0_ALPHA1)
|
||||
#define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_6_0_ALPHA1)
|
||||
// Enable processing of gcode G2 and G3 lines
|
||||
#define ENABLE_PROCESS_G2_G3_LINES (1 && ENABLE_2_5_0_ALPHA1)
|
||||
#define ENABLE_PROCESS_G2_G3_LINES (1 && ENABLE_2_6_0_ALPHA1)
|
||||
// Enable fix of used filament data exported to gcode file
|
||||
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
|
||||
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_6_0_ALPHA1)
|
||||
// Enable picking using raytracing
|
||||
#define ENABLE_RAYCAST_PICKING (1 && ENABLE_LEGACY_OPENGL_REMOVAL)
|
||||
#define ENABLE_RAYCAST_PICKING_DEBUG (0 && ENABLE_RAYCAST_PICKING)
|
||||
|
|
|
@ -1351,11 +1351,26 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
|
|||
|
||||
m_render_sla_auxiliaries = visible;
|
||||
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters_for_picking(SceneRaycaster::EType::Volume);
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
|
||||
for (GLVolume* vol : m_volumes.volumes) {
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
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)
|
||||
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
||||
&& vol->composite_id.volume_id < 0) {
|
||||
vol->is_active = visible;
|
||||
auto it = std::find_if(raycasters->begin(), raycasters->end(), [vol](std::shared_ptr<SceneRaycasterItem> item) { return item->get_raycaster() == vol->mesh_raycaster.get(); });
|
||||
if (it != raycasters->end())
|
||||
(*it)->set_active(vol->is_active);
|
||||
}
|
||||
#else
|
||||
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;
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5548,12 +5563,17 @@ void GLCanvas3D::_picking_pass()
|
|||
default: { break; }
|
||||
}
|
||||
|
||||
auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) {
|
||||
auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color,
|
||||
const std::string& col_3 = "", const ImVec4& col_3_color = ImGui::GetStyleColorVec4(ImGuiCol_Text)) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
imgui.text_colored(col_1_color, col_1.c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
imgui.text_colored(col_2_color, col_2.c_str());
|
||||
if (!col_3.empty()) {
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
imgui.text_colored(col_3_color, col_3.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
char buf[1024];
|
||||
|
@ -5582,6 +5602,21 @@ void GLCanvas3D::_picking_pass()
|
|||
add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* gizmo_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::Gizmo);
|
||||
if (gizmo_raycasters != nullptr && !gizmo_raycasters->empty()) {
|
||||
ImGui::Separator();
|
||||
imgui.text("Gizmo raycasters IDs:");
|
||||
if (ImGui::BeginTable("GizmoRaycasters", 3)) {
|
||||
for (size_t i = 0; i < gizmo_raycasters->size(); ++i) {
|
||||
add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT,
|
||||
std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, (*gizmo_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text),
|
||||
to_string(Geometry::Transformation((*gizmo_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text));
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
imgui.end();
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
}
|
||||
|
|
|
@ -900,6 +900,11 @@ RENDER_AGAIN:
|
|||
bool show_sups = m_c->instances_hider()->are_supports_shown();
|
||||
if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) {
|
||||
m_c->instances_hider()->show_supports(show_sups);
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
if (show_sups)
|
||||
// ensure supports and pad are disabled from picking even when they are visible
|
||||
set_sla_auxiliary_volumes_picking_state(false);
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
force_refresh = true;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,12 +4,12 @@
|
|||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||
#include "slic3r/GUI/MeshUtils.hpp"
|
||||
#include "libslic3r/Measure.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelVolume;
|
||||
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
namespace Measure { class Measuring; }
|
||||
|
@ -24,20 +24,19 @@ class GLGizmoMeasure : public GLGizmoBase
|
|||
enum class EMode : unsigned char
|
||||
{
|
||||
FeatureSelection,
|
||||
PointSelection,
|
||||
CenterSelection
|
||||
PointSelection
|
||||
};
|
||||
|
||||
struct SelectedFeatures
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
std::string source;
|
||||
bool is_center{ false };
|
||||
std::optional<Measure::SurfaceFeature> source;
|
||||
std::optional<Measure::SurfaceFeature> feature;
|
||||
|
||||
bool operator == (const Item& other) const {
|
||||
if (this->source != other.source) return false;
|
||||
return this->feature == other.feature;
|
||||
return this->is_center == other.is_center && this->source == other.source && this->feature == other.feature;
|
||||
}
|
||||
|
||||
bool operator != (const Item& other) const {
|
||||
|
@ -45,7 +44,8 @@ class GLGizmoMeasure : public GLGizmoBase
|
|||
}
|
||||
|
||||
void reset() {
|
||||
source.clear();
|
||||
is_center = false;
|
||||
source.reset();
|
||||
feature.reset();
|
||||
}
|
||||
};
|
||||
|
@ -68,6 +68,21 @@ class GLGizmoMeasure : public GLGizmoBase
|
|||
}
|
||||
};
|
||||
|
||||
struct VolumeCacheItem
|
||||
{
|
||||
const ModelObject* object{ nullptr };
|
||||
const ModelInstance* instance{ nullptr };
|
||||
const ModelVolume* volume{ nullptr };
|
||||
Transform3d world_trafo;
|
||||
|
||||
bool operator == (const VolumeCacheItem& other) const {
|
||||
return this->object == other.object && this->instance == other.instance && this->volume == other.volume &&
|
||||
this->world_trafo.isApprox(other.world_trafo);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<VolumeCacheItem> m_volumes_cache;
|
||||
|
||||
EMode m_mode{ EMode::FeatureSelection };
|
||||
Measure::MeasurementResult m_measurement_result;
|
||||
|
||||
|
@ -85,10 +100,14 @@ class GLGizmoMeasure : public GLGizmoBase
|
|||
};
|
||||
Dimensioning m_dimensioning;
|
||||
|
||||
Transform3d m_volume_matrix{ Transform3d::Identity() };
|
||||
// Uses a standalone raycaster and not the shared one because of the
|
||||
// difference in how the mesh is updated
|
||||
std::unique_ptr<MeshRaycaster> m_raycaster;
|
||||
|
||||
std::vector<GLModel> m_plane_models_cache;
|
||||
std::map<int, std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_selection_raycasters;
|
||||
// used to keep the raycasters for point/center spheres
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_selected_sphere_raycasters;
|
||||
std::optional<Measure::SurfaceFeature> m_curr_feature;
|
||||
std::optional<Vec3d> m_curr_point_on_feature_position;
|
||||
struct SceneRaycasterState
|
||||
|
@ -100,21 +119,14 @@ class GLGizmoMeasure : public GLGizmoBase
|
|||
std::vector<SceneRaycasterState> m_scene_raycasters;
|
||||
|
||||
// These hold information to decide whether recalculation is necessary:
|
||||
std::vector<Transform3d> m_volumes_matrices;
|
||||
std::vector<ModelVolumeType> m_volumes_types;
|
||||
Vec3d m_first_instance_scale{ Vec3d::Ones() };
|
||||
Vec3d m_first_instance_mirror{ Vec3d::Ones() };
|
||||
float m_last_inv_zoom{ 0.0f };
|
||||
std::optional<Measure::SurfaceFeature> m_last_circle;
|
||||
int m_last_plane_idx{ -1 };
|
||||
|
||||
bool m_mouse_left_down{ false }; // for detection left_up of this gizmo
|
||||
const ModelObject* m_old_model_object{ nullptr };
|
||||
const ModelVolume* m_old_model_volume{ nullptr };
|
||||
|
||||
Vec2d m_mouse_pos{ Vec2d::Zero() };
|
||||
|
||||
KeyAutoRepeatFilter m_ctrl_kar_filter;
|
||||
KeyAutoRepeatFilter m_shift_kar_filter;
|
||||
|
||||
SelectedFeatures m_selected_features;
|
||||
|
@ -147,17 +159,24 @@ public:
|
|||
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Measure gizmo"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Measure gizmo"); }
|
||||
std::string get_action_snapshot_name() override { return _u8L("Measure gizmo editing"); }
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
void on_render() override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
|
||||
void remove_selected_sphere_raycaster(int id);
|
||||
void update_measurement_result();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
|
|
@ -327,8 +327,6 @@ const TriangleMesh* HollowedMesh::get_hollowed_interior() const
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Raycaster::on_update()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
|
|
@ -29,6 +29,7 @@ enum class SLAGizmoEventType : unsigned char {
|
|||
ShiftDown,
|
||||
ShiftUp,
|
||||
AltUp,
|
||||
Escape,
|
||||
ApplyChanges,
|
||||
DiscardChanges,
|
||||
AutomaticGeneration,
|
||||
|
@ -137,7 +138,6 @@ protected:
|
|||
virtual void on_update() = 0;
|
||||
CommonGizmosDataPool* get_pool() const { return m_common; }
|
||||
|
||||
|
||||
private:
|
||||
bool m_is_valid = false;
|
||||
CommonGizmosDataPool* m_common = nullptr;
|
||||
|
|
|
@ -526,7 +526,10 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||
case WXK_ESCAPE:
|
||||
{
|
||||
if (m_current != Undefined) {
|
||||
if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges))
|
||||
if (m_current == Measure && gizmo_event(SLAGizmoEventType::Escape)) {
|
||||
// do nothing
|
||||
}
|
||||
else if (m_current != SlaSupports || !gizmo_event(SLAGizmoEventType::DiscardChanges))
|
||||
reset_all_states();
|
||||
|
||||
processed = true;
|
||||
|
@ -554,7 +557,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||
case WXK_BACK:
|
||||
case WXK_DELETE:
|
||||
{
|
||||
if ((m_current == SlaSupports || m_current == Hollow || m_current == Cut) && gizmo_event(SLAGizmoEventType::Delete))
|
||||
if ((m_current == SlaSupports || m_current == Hollow || m_current == Cut || m_current == Measure) && gizmo_event(SLAGizmoEventType::Delete))
|
||||
processed = true;
|
||||
|
||||
break;
|
||||
|
|
|
@ -429,10 +429,10 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
|||
// All hits are clipped.
|
||||
return false;
|
||||
}
|
||||
if ((hits.size()-i) % 2 != 0) {
|
||||
if (clipping_plane && (hits.size()-i) % 2 != 0) {
|
||||
// There is an odd number of unclipped hits - meaning the nearest must be from inside the mesh.
|
||||
// In that case, calculate intersection with the clipping place.
|
||||
if (clipping_plane && was_clipping_plane_hit) {
|
||||
if (was_clipping_plane_hit) {
|
||||
direction = direction + point;
|
||||
point = trafo * point; // transform to world coords
|
||||
direction = trafo * direction - point;
|
||||
|
|
|
@ -37,20 +37,20 @@ std::string gl_get_string_safe(GLenum param, const std::string& default_value)
|
|||
return std::string((value != nullptr) ? value : default_value);
|
||||
}
|
||||
|
||||
const std::string& OpenGLManager::GLInfo::get_version() const
|
||||
const std::string& OpenGLManager::GLInfo::get_version_string() const
|
||||
{
|
||||
if (!m_detected)
|
||||
detect();
|
||||
|
||||
return m_version;
|
||||
return m_version_string;
|
||||
}
|
||||
|
||||
const std::string& OpenGLManager::GLInfo::get_glsl_version() const
|
||||
const std::string& OpenGLManager::GLInfo::get_glsl_version_string() const
|
||||
{
|
||||
if (!m_detected)
|
||||
detect();
|
||||
|
||||
return m_glsl_version;
|
||||
return m_glsl_version_string;
|
||||
}
|
||||
|
||||
const std::string& OpenGLManager::GLInfo::get_vendor() const
|
||||
|
@ -71,7 +71,7 @@ const std::string& OpenGLManager::GLInfo::get_renderer() const
|
|||
|
||||
bool OpenGLManager::GLInfo::is_mesa() const
|
||||
{
|
||||
return boost::icontains(m_version, "mesa");
|
||||
return m_version_is_mesa;
|
||||
}
|
||||
|
||||
int OpenGLManager::GLInfo::get_max_tex_size() const
|
||||
|
@ -97,13 +97,19 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const
|
|||
return m_max_anisotropy;
|
||||
}
|
||||
|
||||
static Semver parse_version_string(const std::string& version);
|
||||
|
||||
void OpenGLManager::GLInfo::detect() const
|
||||
{
|
||||
*const_cast<std::string*>(&m_version) = gl_get_string_safe(GL_VERSION, "N/A");
|
||||
*const_cast<std::string*>(&m_glsl_version) = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A");
|
||||
*const_cast<std::string*>(&m_version_string) = gl_get_string_safe(GL_VERSION, "N/A");
|
||||
*const_cast<std::string*>(&m_glsl_version_string) = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A");
|
||||
*const_cast<std::string*>(&m_vendor) = gl_get_string_safe(GL_VENDOR, "N/A");
|
||||
*const_cast<std::string*>(&m_renderer) = gl_get_string_safe(GL_RENDERER, "N/A");
|
||||
|
||||
*const_cast<Semver*>(&m_version) = parse_version_string(m_version_string);
|
||||
*const_cast<bool*>(&m_version_is_mesa) = boost::icontains(m_version_string, "mesa");
|
||||
*const_cast<Semver*>(&m_glsl_version) = parse_version_string(m_glsl_version_string);
|
||||
|
||||
int* max_tex_size = const_cast<int*>(&m_max_tex_size);
|
||||
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, max_tex_size));
|
||||
|
||||
|
@ -119,16 +125,16 @@ void OpenGLManager::GLInfo::detect() const
|
|||
*const_cast<bool*>(&m_detected) = true;
|
||||
}
|
||||
|
||||
static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor)
|
||||
static Semver parse_version_string(const std::string& version)
|
||||
{
|
||||
if (version == "N/A")
|
||||
return false;
|
||||
return Semver::invalid();
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
|
||||
|
||||
if (tokens.empty())
|
||||
return false;
|
||||
return Semver::invalid();
|
||||
|
||||
#if ENABLE_OPENGL_ES
|
||||
const std::string version_container = (tokens.size() > 1 && boost::istarts_with(tokens[1], "ES")) ? tokens[2] : tokens[0];
|
||||
|
@ -150,20 +156,15 @@ static bool version_greater_or_equal_to(const std::string& version, unsigned int
|
|||
if (numbers.size() > 1)
|
||||
gl_minor = ::atoi(numbers[1].c_str());
|
||||
|
||||
if (gl_major < major)
|
||||
return false;
|
||||
else if (gl_major > major)
|
||||
return true;
|
||||
else
|
||||
return gl_minor >= minor;
|
||||
return Semver(gl_major, gl_minor, 0);
|
||||
}
|
||||
|
||||
bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
|
||||
{
|
||||
if (!m_detected)
|
||||
detect();
|
||||
|
||||
return version_greater_or_equal_to(m_version, major, minor);
|
||||
|
||||
return m_version >= Semver(major, minor, 0);
|
||||
}
|
||||
|
||||
bool OpenGLManager::GLInfo::is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
|
||||
|
@ -171,7 +172,7 @@ bool OpenGLManager::GLInfo::is_glsl_version_greater_or_equal_to(unsigned int maj
|
|||
if (!m_detected)
|
||||
detect();
|
||||
|
||||
return version_greater_or_equal_to(m_glsl_version, major, minor);
|
||||
return m_glsl_version >= Semver(major, minor, 0);
|
||||
}
|
||||
|
||||
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
|
||||
|
@ -378,13 +379,13 @@ bool OpenGLManager::init_gl()
|
|||
wxString message = from_u8((boost::format(
|
||||
#if ENABLE_OPENGL_ES
|
||||
_utf8(L("PrusaSlicer requires OpenGL ES 2.0 capable graphics driver to run correctly, \n"
|
||||
"while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
|
||||
"while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version_string() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
|
||||
#elif ENABLE_GL_CORE_PROFILE
|
||||
_utf8(L("PrusaSlicer requires OpenGL %s capable graphics driver to run correctly, \n"
|
||||
"while OpenGL version %s, render %s, vendor %s was detected."))) % (s_gl_info.is_core_profile() ? "3.3" : "2.0") % s_gl_info.get_version() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
|
||||
"while OpenGL version %s, render %s, vendor %s was detected."))) % (s_gl_info.is_core_profile() ? "3.3" : "2.0") % s_gl_info.get_version_string() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
|
||||
#else
|
||||
_utf8(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n"
|
||||
"while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
|
||||
"while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version_string() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
message += "\n";
|
||||
message += _L("You may need to update your graphics card driver.");
|
||||
|
@ -423,7 +424,7 @@ bool OpenGLManager::init_gl()
|
|||
// WHQL: 4.6.14800 Compatibility Profile Context 22.6.1 30.0.21023.1015
|
||||
// Non-WHQL: 4.6.0 Compatibility Profile Context 22.8.1.220810
|
||||
std::regex version_rgx(R"(Compatibility\sProfile\sContext\s(\d+)\.(\d+)\.(\d+))");
|
||||
if (std::smatch matches; std::regex_search(gl_info.get_version(), matches, version_rgx) && matches.size() == 4) {
|
||||
if (std::smatch matches; std::regex_search(gl_info.get_version_string(), matches, version_rgx) && matches.size() == 4) {
|
||||
int version_major = std::stoi(matches[1].str());
|
||||
int version_minor = std::stoi(matches[2].str());
|
||||
int version_patch = std::stoi(matches[3].str());
|
||||
|
|
|
@ -31,16 +31,21 @@ public:
|
|||
int m_max_tex_size{ 0 };
|
||||
float m_max_anisotropy{ 0.0f };
|
||||
|
||||
std::string m_version;
|
||||
std::string m_glsl_version;
|
||||
std::string m_version_string;
|
||||
Semver m_version = Semver::invalid();
|
||||
bool m_version_is_mesa = false;
|
||||
|
||||
std::string m_glsl_version_string;
|
||||
Semver m_glsl_version = Semver::invalid();
|
||||
|
||||
std::string m_vendor;
|
||||
std::string m_renderer;
|
||||
|
||||
public:
|
||||
GLInfo() = default;
|
||||
|
||||
const std::string& get_version() const;
|
||||
const std::string& get_glsl_version() const;
|
||||
const std::string& get_version_string() const;
|
||||
const std::string& get_glsl_version_string() const;
|
||||
const std::string& get_vendor() const;
|
||||
const std::string& get_renderer() const;
|
||||
|
||||
|
|
|
@ -613,6 +613,17 @@ bool Selection::contains_any_volume(const std::vector<unsigned int>& volume_idxs
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Selection::contains_sinking_volumes(bool ignore_modifiers) const
|
||||
{
|
||||
for (const GLVolume* v : *m_volumes) {
|
||||
if (!ignore_modifiers || !v->is_modifier) {
|
||||
if (v->is_sinking())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Selection::matches(const std::vector<unsigned int>& volume_idxs) const
|
||||
{
|
||||
unsigned int count = 0;
|
||||
|
|
|
@ -338,6 +338,8 @@ public:
|
|||
bool contains_all_volumes(const std::vector<unsigned int>& volume_idxs) const;
|
||||
// returns true if the selection contains at least one of the given indices
|
||||
bool contains_any_volume(const std::vector<unsigned int>& volume_idxs) const;
|
||||
// returns true if the selection contains any sinking volume
|
||||
bool contains_sinking_volumes(bool ignore_modifiers = true) const;
|
||||
// returns true if the selection contains all and only the given indices
|
||||
bool matches(const std::vector<unsigned int>& volume_idxs) const;
|
||||
|
||||
|
|
|
@ -504,8 +504,8 @@ static std::string generate_system_info_json()
|
|||
#endif // _WIN32
|
||||
|
||||
pt::ptree opengl_node;
|
||||
opengl_node.put("Version", OpenGLManager::get_gl_info().get_version());
|
||||
opengl_node.put("GLSLVersion", OpenGLManager::get_gl_info().get_glsl_version());
|
||||
opengl_node.put("Version", OpenGLManager::get_gl_info().get_version_string());
|
||||
opengl_node.put("GLSLVersion", OpenGLManager::get_gl_info().get_glsl_version_string());
|
||||
opengl_node.put("Vendor", OpenGLManager::get_gl_info().get_vendor());
|
||||
opengl_node.put("Renderer", OpenGLManager::get_gl_info().get_renderer());
|
||||
// Generate list of OpenGL extensions:
|
||||
|
|
Loading…
Add table
Reference in a new issue