NEW:add groove cut function

Jira:STUDIO-4227
Approximately 70% of the code comes from Prusa,thanks for PrusaSlcer and YuSanka

commit 492e356a21734b3503caae115fbb280da5fbaa22
Author: YuSanka <yusanka@gmail.com>
Date:   Thu Aug 3 16:09:28 2023 +0200
    CutGizmo: Fixed and improved Undo/Redo.
...

Change-Id: I63abb69180aec2ab0ce9bc8f30477d9e9a25a5fb
This commit is contained in:
zhou.xu
2023-09-21 10:39:13 +08:00
committed by Lane.Wei
parent 88e27d84c2
commit f9f44d0785
13 changed files with 1451 additions and 689 deletions

View File

@@ -6,7 +6,10 @@
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/CameraUtils.hpp"
#include <GL/glew.h>
@@ -14,34 +17,35 @@
#include "CameraUtils.hpp"
namespace Slic3r {
namespace GUI {
namespace Slic3r { namespace GUI {
void MeshClipper::set_behaviour(bool fill_cut, double contour_width)
{
if (fill_cut != m_fill_cut || !is_approx(contour_width, m_contour_width)) m_result.reset();
m_fill_cut = fill_cut;
m_contour_width = contour_width;
}
void MeshClipper::set_plane(const ClippingPlane& plane)
void MeshClipper::set_plane(const ClippingPlane &plane)
{
if (m_plane != plane) {
m_plane = plane;
m_triangles_valid = false;
m_result.reset();
}
}
void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
{
if (m_limiting_plane != plane) {
m_limiting_plane = plane;
m_triangles_valid = false;
m_result.reset();
}
}
void MeshClipper::set_mesh(const TriangleMesh& mesh)
{
if (m_mesh != &mesh) {
m_mesh = &mesh;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_result.reset();
}
}
@@ -49,8 +53,7 @@ void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
{
if (m_negative_mesh != &mesh) {
m_negative_mesh = &mesh;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_result.reset();
}
}
@@ -60,34 +63,75 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
{
if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
m_trafo = trafo;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_result.reset();
}
}
void MeshClipper::render_cut()
void MeshClipper::render_cut(const ColorRGBA &color, const std::vector<size_t> *ignore_idxs)
{
if (! m_triangles_valid)
recalculate_triangles();
if (!m_result) recalculate_triangles();
GLShaderProgram *curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr) curr_shader->stop_using();
if (m_vertex_array.has_VBOs())
m_vertex_array.render();
GLShaderProgram *shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
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());
for (size_t i = 0; i < m_result->cut_islands.size(); ++i) {
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) continue;
CutIsland &isl = m_result->cut_islands[i];
ColorRGBA gray{0.5f, 0.5f, 0.5f, 1.f};
isl.model.set_color(-1, isl.disabled ? gray : color);
isl.model.render();
}
shader->stop_using();
}
if (curr_shader != nullptr) curr_shader->start_using();
}
bool MeshClipper::is_projection_inside_cut(const Vec3d &point_in) const
void MeshClipper::render_contour(const ColorRGBA &color, const std::vector<size_t> *ignore_idxs)
{
if (!m_result) recalculate_triangles();
GLShaderProgram *curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr) curr_shader->stop_using();
GLShaderProgram *shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
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());
for (size_t i = 0; i < m_result->cut_islands.size(); ++i) {
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) continue;
CutIsland &isl = m_result->cut_islands[i];
ColorRGBA red{1.0f, 0.f, 0.f, 1.f};
isl.model_expanded.set_color(-1, isl.disabled ? red : color);
isl.model_expanded.render();
}
shader->stop_using();
}
if (curr_shader != nullptr)
curr_shader->start_using();
}
int MeshClipper::is_projection_inside_cut(const Vec3d &point_in) const
{
if (!m_result || m_result->cut_islands.empty())
return false;
return -1;
Vec3d point = m_result->trafo.inverse() * point_in;
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
for (const CutIsland &isl : m_result->cut_islands) {
for (int i = 0; i < int(m_result->cut_islands.size()); ++i) {
const CutIsland &isl = m_result->cut_islands[i];
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
return true;
return i; // TODO: handle intersecting contours
}
return false;
return -1;
}
bool MeshClipper::has_valid_contour() const
@@ -138,22 +182,25 @@ std::vector<Vec3d> MeshClipper::point_per_contour() const {
void MeshClipper::recalculate_triangles()
{
const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
// Calculate clipping plane normal in mesh coordinates.
const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
const Vec3d up = up_noscale.cast<double>().cwiseProduct(m_trafo.get_scaling_factor());
// Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
m_result = ClipResult();
auto plane_mesh = Eigen::Hyperplane<double, 3>(m_plane.get_normal(), -m_plane.distance(Vec3d::Zero())).transform(m_trafo.get_matrix().inverse());
const Vec3d up = plane_mesh.normal();
const float height_mesh = -plane_mesh.offset();
// Now do the cutting
MeshSlicingParams slicing_params;
slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
ExPolygons expolys;
// if (m_csgmesh.empty()) {
if (m_mesh) {
expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
}
if (m_negative_mesh && !m_negative_mesh->empty()) {
const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
expolys = diff_ex(expolys, neg_expolys);
expolys = diff_ex(expolys, neg_expolys);
}
// Triangulate and rotate the cut into world coords:
@@ -163,41 +210,37 @@ void MeshClipper::recalculate_triangles()
tr.rotate(q);
tr = m_trafo.get_matrix() * tr;
m_result = ClipResult();
m_result->trafo = tr;
if (m_limiting_plane != ClippingPlane::ClipsNothing())
{
if (m_limiting_plane != ClippingPlane::ClipsNothing()) {
// Now remove whatever ended up below the limiting plane (e.g. sinking objects).
// First transform the limiting plane from world to mesh coords.
// Note that inverse of tr transforms the plane from world to horizontal.
const Vec3d normal_old = m_limiting_plane.get_normal().normalized();
const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
const Vec3d normal_new = (tr.matrix().block<3, 3>(0, 0).transpose() * normal_old).normalized();
// normal_new should now be the plane normal in mesh coords. To find the offset,
// transform a point and set offset so it belongs to the transformed plane.
Vec3d pt = Vec3d::Zero();
Vec3d pt = Vec3d::Zero();
const double plane_offset = m_limiting_plane.get_data()[3];
if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
pt.z() = - plane_offset / normal_old.z();
pt.z() = -plane_offset / normal_old.z();
else if (std::abs(normal_old.y()) > 0.5)
pt.y() = - plane_offset / normal_old.y();
pt.y() = -plane_offset / normal_old.y();
else
pt.x() = - plane_offset / normal_old.x();
pt = tr.inverse() * pt;
pt.x() = -plane_offset / normal_old.x();
pt = tr.inverse() * pt;
const double offset = -(normal_new.dot(pt));
if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
// The cuts are parallel, show all or nothing.
if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh)
expolys.clear();
if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh) expolys.clear();
} else {
// The cut is a horizontal plane defined by z=height_mesh.
// ax+by+e=0 is the line of intersection with the limiting plane.
// Normalized so a^2 + b^2 = 1.
const double len = std::hypot(normal_new.x(), normal_new.y());
if (len == 0.)
return;
if (len == 0.) return;
const double a = normal_new.x() / len;
const double b = normal_new.y() / len;
const double e = (normal_new.z() * height_mesh + offset) / len;
@@ -211,36 +254,140 @@ void MeshClipper::recalculate_triangles()
// it so it lies on our line. This will be the figure to subtract
// from the cut. The coordinates must not overflow after the transform,
// make the rectangle a bit smaller.
const coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
const coord_t size = (std::numeric_limits<coord_t>::max() / 2 - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4;
Polygons ep{Polygon({Point(-size, 0), Point(size, 0), Point(size, 2 * size), Point(-size, 2 * size)})};
ep.front().rotate(angle);
ep.front().translate(scale_(-e * a), scale_(-e * b));
expolys = diff_ex(expolys, ep);
}
}
for (const ExPolygon &exp : expolys) {
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
Transform3d tr2 = tr;
tr2.pretranslate(0.002 * m_plane.get_normal().normalized());
std::vector<Vec2f> triangles2d;
for (const ExPolygon &exp : expolys) {
triangles2d.clear();
m_result->cut_islands.push_back(CutIsland());
CutIsland &isl = m_result->cut_islands.back();
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(exp);
if (m_fill_cut) {
triangles2d = triangulate_expolygon_2f(exp, m_trafo.get_matrix().matrix().determinant() < 0.);
GLModel::InitializationData init_data; // GLModel::Geometry init_data;
init_data.entities.push_back(GLModel::InitializationData::Entity());
init_data.entities.back().type = GLModel::PrimitiveType::Triangles;
init_data.entities.back().positions.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().normals.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().indices.reserve(triangles2d.size());
/*init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3};
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());*/
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
/*init_data.add_vertex((Vec3f) (tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f) up.cast<float>());
init_data.add_vertex((Vec3f) (tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f) up.cast<float>());
init_data.add_vertex((Vec3f) (tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f) up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.add_triangle((unsigned int) idx, (unsigned int) idx + 1, (unsigned int) idx + 2);*/
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.entities.back().indices.push_back((unsigned int) idx);
init_data.entities.back().indices.push_back((unsigned int) idx + 1);
init_data.entities.back().indices.push_back((unsigned int) idx + 2);
}
if (init_data.entities.back().indices.size() != 0) isl.model.init_from(std::move(init_data));
}
if (m_contour_width != 0. && !exp.contour.empty()) {
triangles2d.clear();
// The contours must not scale with the object. Check the scale factor
// in the respective directions, create a scaled copy of the ExPolygon
// offset it and then unscale the result again.
Transform3d t = tr;
t.translation() = Vec3d::Zero();
double scale_x = (t * Vec3d::UnitX()).norm();
double scale_y = (t * Vec3d::UnitY()).norm();
// To prevent overflow after scaling, downscale the input if needed:
double extra_scale = 1.;
int32_t limit = int32_t(
std::min(std::numeric_limits<coord_t>::max() / (2. * std::max(1., scale_x)), std::numeric_limits<coord_t>::max() / (2. * std::max(1., scale_y))));
int32_t max_coord = 0;
for (const Point &pt : exp.contour) max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y())));
if (max_coord + m_contour_width >= limit) extra_scale = 0.9 * double(limit) / max_coord;
ExPolygon exp_copy = exp;
if (extra_scale != 1.) exp_copy.scale(extra_scale);
exp_copy.scale(scale_x, scale_y);
ExPolygons expolys_exp = offset_ex(exp_copy, scale_(m_contour_width));
expolys_exp = diff_ex(expolys_exp, ExPolygons({exp_copy}));
for (ExPolygon &e : expolys_exp) {
e.scale(1. / scale_x, 1. / scale_y);
if (extra_scale != 1.) e.scale(1. / extra_scale);
}
triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.);
GLModel::InitializationData init_data; // GLModel::Geometry init_data;
init_data.entities.push_back(GLModel::InitializationData::Entity());
init_data.entities.back().type = GLModel::PrimitiveType::Triangles;
init_data.entities.back().positions.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().normals.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().indices.reserve(triangles2d.size());
/*GLModel::Geometry init_data = GLModel::Geometry();
init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3};
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());*/
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.entities.back().indices.push_back((unsigned int) idx);
init_data.entities.back().indices.push_back((unsigned int) idx + 1);
init_data.entities.back().indices.push_back((unsigned int) idx + 2);
}
if (init_data.entities.back().indices.size() != 0) isl.model_expanded.init_from(std::move(init_data));
}
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(isl.expoly);
Point centroid_scaled = isl.expoly.contour.centroid();
Vec3d centroid_world = m_result->trafo * Vec3d(unscale(centroid_scaled).x(), unscale(centroid_scaled).y(), 0.);
isl.hash = isl.expoly.contour.size() + size_t(std::abs(100. * centroid_world.x())) + size_t(std::abs(100. * centroid_world.y())) +
size_t(std::abs(100. * centroid_world.z()));
}
m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
m_vertex_array.release_geometry();
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
const size_t idx = it - m_triangles2d.cbegin();
m_vertex_array.push_triangle(idx, idx+1, idx+2);
}
m_vertex_array.finalize_geometry(true);
m_triangles_valid = true;
// Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes
// flips the normal of the cut, in which case the contours stay the same but their order may change.
std::sort(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland &a, const CutIsland &b) { return a.hash < b.hash; });
}