NEW: add snap connector

Jira:STUDIO-4227

most of the codes are referenced from PrusaSlicer,thanks YuSanka for the original submits

commit 7cd99d98f537198b78158feced2aa8977bfcbe08
Author: YuSanka <yusanka@gmail.com>
Date:   Wed Jul 12 18:06:51 2023 +0200
    WIP: Cut with Rivets
    * Code refactoring: get_connector_mesh() and apply_cut_connectors() moved from ModelObject to CutGizmo.
    * Allow to change values of space and bulges for snaps
7cd99d98f5
...

Change-Id: Id4c7991a0ed5bf9608823601775920a81a3dd1c4
This commit is contained in:
zhou.xu 2023-08-25 15:36:04 +08:00 committed by Lane.Wei
parent 5658d32633
commit 7e51bf41a1
6 changed files with 234 additions and 26 deletions

View File

@ -1696,7 +1696,7 @@ bool ModelObject::has_connectors() const
return false;
}
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes, CutConnectorParas para)
{
indexed_triangle_set connector_mesh;
@ -1718,7 +1718,9 @@ indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes conn
break;
}
if (connector_attributes.style == CutConnectorStyle::Prizm)
if (connector_attributes.type == CutConnectorType::Snap)
connector_mesh = its_make_snap(1.0, 1.0, para.snap_space_proportion, para.snap_bulge_proportion);
else if(connector_attributes.style == CutConnectorStyle::Prizm)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
@ -1737,7 +1739,7 @@ void ModelObject::apply_cut_connectors(const std::string &name)
size_t connector_id = cut_id.connectors_cnt();
for (const CutConnector &connector : cut_connectors) {
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs, connector.paras));
// Mesh will be centered when loading.
ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
@ -1865,7 +1867,18 @@ void ModelObject::process_connector_cut(
// This transformation is already there
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume *vol = upper->add_volume(*volume);
ModelVolume *vol = nullptr;
if (volume->cut_info.connector_type == CutConnectorType::Snap) {
TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
vol = upper->add_volume(std::move(mesh));
vol->set_transformation(volume->get_transformation());
vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
vol->cut_info = volume->cut_info;
vol->name = volume->name;
} else
vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix);
vol->apply_tolerance();
}
@ -2832,6 +2845,16 @@ void ModelVolume::apply_tolerance()
sf[Z] *= height_scale;
set_scaling_factor(sf);
// correct offset in respect to the new depth
Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
if (rot_norm.norm() != 0.0) rot_norm.normalize();
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
if (cut_info.connector_type == CutConnectorType::Plug || cut_info.connector_type == CutConnectorType::Snap)
z_offset -= 0.05; // add small Z offset to better preview
set_offset(get_offset() + rot_norm * z_offset);
}
// BBS

View File

@ -237,6 +237,7 @@ private:
enum class CutConnectorType : int {
Plug,
Dowel,
Snap,
Undef
};
@ -255,6 +256,11 @@ enum class CutConnectorShape : int {
Undef
//,D-shape
};
struct CutConnectorParas
{
float snap_space_proportion{0.3};
float snap_bulge_proportion{0.15};
};
struct CutConnectorAttributes
{
@ -290,7 +296,7 @@ struct CutConnector
float radius_tolerance; // [0.f : 1.f]
float height_tolerance; // [0.f : 1.f]
CutConnectorAttributes attribs;
CutConnectorParas paras;
CutConnector() : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {}
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
@ -468,7 +474,7 @@ public:
bool is_cut() const { return cut_id.id().valid(); }
bool has_connectors() const;
static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes, CutConnectorParas para);
void apply_cut_connectors(const std::string &name);
// invalidate cut state for this object and its connectors/volumes
void invalidate_cut();

View File

@ -1116,6 +1116,124 @@ indexed_triangle_set its_make_sphere(double radius, double fa)
return mesh;
}
indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion)
{
const float radius = (float) r;
const float height = (float) h;
const size_t sectors_cnt = 10; //(float)fa;
const float halfPI = 0.5f * (float) PI;
const float space_len = space_proportion * radius;
const float b_len = radius;
const float m_len = (1 + bulge_proportion) * radius;
const float t_len = 0.5f * radius;
const float b_height = 0.f;
const float m_height = 0.5f * height;
const float t_height = height;
const float b_angle = acos(space_len / b_len);
const float t_angle = acos(space_len / t_len);
const float b_angle_step = b_angle / (float) sectors_cnt;
const float t_angle_step = t_angle / (float) sectors_cnt;
const Vec2f b_vec = Eigen::Vector2f(0, b_len);
const Vec2f t_vec = Eigen::Vector2f(0, t_len);
auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector<stl_vertex> &vertices, float b_angle, float t_angle, const Vec2f &m_vec) {
Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec;
Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec;
Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec;
vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height));
vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height));
vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height));
};
auto add_side_facets = [](std::vector<stl_triangle_vertex_indices> &facets, int vertices_cnt, int frst_id, int scnd_id) {
int id = vertices_cnt - 1;
facets.emplace_back(frst_id, id - 2, id - 5);
facets.emplace_back(id - 2, id - 1, id - 5);
facets.emplace_back(id - 1, id - 4, id - 5);
facets.emplace_back(id - 4, id - 1, id);
facets.emplace_back(id, id - 3, id - 4);
facets.emplace_back(id, scnd_id, id - 3);
};
const float f = (b_len - m_len) / m_len; // Flattening
auto get_m_len = [b_len, f](float angle) {
const float rad_sqr = b_len * b_len;
const float sin_sqr = sin(angle) * sin(angle);
const float f_sqr = (1 - f) * (1 - f);
return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr));
};
auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len, b_height, t_height, b_angle, t_angle, b_angle_step,
t_angle_step](indexed_triangle_set &mesh, float center_x, float angle_rotation, int frst_vertex_id) {
auto &vertices = mesh.vertices;
auto &facets = mesh.indices;
// 2 special vertices, top and bottom center, rest are relative to this
vertices.emplace_back(Vec3f(center_x, 0.f, b_height));
vertices.emplace_back(Vec3f(center_x, 0.f, t_height));
float b_angle_start = angle_rotation - b_angle;
float t_angle_start = angle_rotation - t_angle;
const float b_angle_stop = angle_rotation + b_angle;
const int frst_id = frst_vertex_id;
const int scnd_id = frst_id + 1;
// add first side vertices and internal facets
{
const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
int id = (int) vertices.size() - 1;
facets.emplace_back(frst_id, id - 2, id - 1);
facets.emplace_back(frst_id, id - 1, id);
facets.emplace_back(frst_id, id, scnd_id);
}
// add d side vertices and facets
while (!is_approx(b_angle_start, b_angle_stop)) {
b_angle_start += b_angle_step;
t_angle_start += t_angle_step;
const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
add_side_facets(facets, (int) vertices.size(), frst_id, scnd_id);
}
// add last internal facets to close the mesh
{
int id = (int) vertices.size() - 1;
facets.emplace_back(frst_id, scnd_id, id);
facets.emplace_back(frst_id, id, id - 1);
facets.emplace_back(frst_id, id - 1, id - 2);
}
};
indexed_triangle_set mesh;
mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2));
mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6));
add_sub_mesh(mesh, -space_len, halfPI, 0);
add_sub_mesh(mesh, space_len, 3 * halfPI, (int) mesh.vertices.size());
return mesh;
}
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
{
std::vector<Vec3f> dst_vertices;

View File

@ -340,6 +340,7 @@ indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360));
indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount);
indexed_triangle_set its_make_pyramid(float base, float height);
indexed_triangle_set its_make_sphere(double radius, double fa);
indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f);
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts);
inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); }

View File

@ -1065,11 +1065,12 @@ void GLGizmoAdvancedCut::clear_selection()
void GLGizmoAdvancedCut::init_connector_shapes()
{
for (const CutConnectorType &type : {CutConnectorType::Dowel, CutConnectorType::Plug})
for (const CutConnectorType &type : {CutConnectorType::Snap, CutConnectorType::Dowel, CutConnectorType::Plug})
for (const CutConnectorStyle &style : {CutConnectorStyle::Frustum, CutConnectorStyle::Prizm})
for (const CutConnectorShape &shape : {CutConnectorShape::Circle, CutConnectorShape::Hexagon, CutConnectorShape::Square, CutConnectorShape::Triangle}) {
const CutConnectorAttributes attribs = {type, style, shape};
const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs);
CutConnectorAttributes attribs = {type, style, shape};
CutConnectorParas paras = {m_snap_space_proportion, m_snap_bulge_proportion};
const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs, paras);
m_shapes[attribs].init_from(its);
}
}
@ -1088,13 +1089,15 @@ void GLGizmoAdvancedCut::reset_connectors()
clear_selection();
}
void GLGizmoAdvancedCut::update_connector_shape()
void GLGizmoAdvancedCut::update_connector_shape()//update mesh
{
CutConnectorAttributes attribs = {m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id)};
const indexed_triangle_set its = ModelObject::get_connector_mesh(attribs);
m_connector_mesh.clear();
m_connector_mesh = TriangleMesh(its);
CutConnectorAttributes attribs = { m_connector_type, CutConnectorStyle(m_connector_style), CutConnectorShape(m_connector_shape_id)};
CutConnectorParas paras = {m_snap_space_proportion, m_snap_bulge_proportion};
if (m_connector_type == CutConnectorType::Snap) {
indexed_triangle_set its = ModelObject::get_connector_mesh(attribs, paras);
m_shapes[attribs].reset();
m_shapes[attribs].init_from(its);
}
}
void GLGizmoAdvancedCut::apply_selected_connectors(std::function<void(size_t idx)> apply_fn)
@ -1572,6 +1575,7 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.00f, 0.00f, 0.00f, 1.00f));
bool type_changed = render_connect_type_radio_button(CutConnectorType::Plug);
type_changed |= render_connect_type_radio_button(CutConnectorType::Dowel);
type_changed |= render_connect_type_radio_button(CutConnectorType::Snap);
if (type_changed)
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.type = CutConnectorType(m_connector_type); });
ImGui::PopStyleColor(1);
@ -1579,7 +1583,7 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float
std::vector<std::string> connector_styles = {_u8L("Prizm"), _u8L("Frustum")};
std::vector<std::string> connector_shapes = { _u8L("Triangle"), _u8L("Square"), _u8L("Hexagon"), _u8L("Circle") };
m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel);
m_imgui->disabled_begin(m_connector_type == CutConnectorType::Dowel || m_connector_type == CutConnectorType::Snap);
if (type_changed && m_connector_type == CutConnectorType::Dowel) {
m_connector_style = size_t(CutConnectorStyle::Prizm);
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
@ -1588,14 +1592,19 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float
ImGuiWrapper::push_combo_style(m_parent.get_scale());
if (render_combo(_u8L("Style"), connector_styles, m_connector_style))
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.style = CutConnectorStyle(m_connector_style); });
m_imgui->disabled_end();
ImGuiWrapper::pop_combo_style();
m_imgui->disabled_end();
m_imgui->disabled_begin(m_connector_type == CutConnectorType::Snap);
if (type_changed && m_connector_type == CutConnectorType::Snap) {
m_connector_shape_id = int(CutConnectorShape::Circle);
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
}
ImGuiWrapper::push_combo_style(m_parent.get_scale());
if (render_combo(_u8L("Shape"), connector_shapes, m_connector_shape_id))
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].attribs.shape = CutConnectorShape(m_connector_shape_id); });
ImGuiWrapper::pop_combo_style();
m_imgui->disabled_end();
if (render_slider_double_input(_u8L("Depth ratio"), m_connector_depth_ratio, m_connector_depth_ratio_tolerance))
apply_selected_connectors([this, &connectors](size_t idx) {
if (m_connector_depth_ratio > 0)
@ -1611,7 +1620,21 @@ void GLGizmoAdvancedCut::render_connectors_input_window(float x, float y, float
if (m_connector_size_tolerance >= 0)
connectors[idx].radius_tolerance = m_connector_size_tolerance;
});
if (m_connector_type == CutConnectorType::Snap) {
const std::string format = "%.0f %%";
bool is_changed = false;
if (render_slider_double_input_show_percentage(_u8L("Bulge"), m_snap_bulge_proportion, 5.f, 100.f * m_snap_space_proportion)) {
is_changed = true;
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].paras.snap_bulge_proportion = m_snap_bulge_proportion; });
}
if (render_slider_double_input_show_percentage(_u8L("Gap"), m_snap_space_proportion, 10.f, 50.f)) {
is_changed = true;
apply_selected_connectors([this, &connectors](size_t idx) { connectors[idx].paras.snap_space_proportion = m_snap_space_proportion; });
}
if (is_changed) {
update_connector_shape();
}
}
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
@ -1678,7 +1701,7 @@ bool GLGizmoAdvancedCut::render_reset_button(const std::string &label_id, const
bool GLGizmoAdvancedCut::render_connect_type_radio_button(CutConnectorType type)
{
ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : 2 * m_label_width);
ImGui::SameLine(type == CutConnectorType::Plug ? m_label_width : (type == CutConnectorType::Dowel ? 2 * m_label_width : 3 * m_label_width));
ImGui::PushItemWidth(m_control_width);
wxString radio_name;
@ -1689,13 +1712,15 @@ bool GLGizmoAdvancedCut::render_connect_type_radio_button(CutConnectorType type)
case CutConnectorType::Dowel:
radio_name = _L("Dowel");
break;
case CutConnectorType::Snap:
radio_name = _L("Snap");
break;
default:
break;
}
if (m_imgui->radio_button(radio_name, m_connector_type == type)) {
m_connector_type = type;
update_connector_shape();
return true;
}
return false;
@ -1728,8 +1753,6 @@ bool GLGizmoAdvancedCut::render_combo(const std::string &label, const std::vecto
bool is_changed = selection_idx != selection_out;
selection_idx = selection_out;
if (is_changed) update_connector_shape();
return is_changed;
}
@ -1787,17 +1810,51 @@ bool GLGizmoAdvancedCut::render_slider_double_input(const std::string &label, fl
float min_tolerance = tolerance_in < 0.f ? UndefMinVal : 0.f;
m_imgui->bbl_slider_float_style(("##tolerance_" + label).c_str(), &tolerance, min_tolerance, 2.f, format.c_str(), 1.f, true, _L("Tolerance"));
left_width += (slider_with + item_in_gap);
ImGui::SameLine(left_width);
ImGui::PushItemWidth(second_input_width);
ImGui::BBLDragFloat(("##tolerance_input_" + label).c_str(), &tolerance, 0.05f, min_tolerance, 2.f, format.c_str());
tolerance_in = tolerance * float(m_imperial_units ? units_in_to_mm : 1.0);
return !is_approx(old_val, value) || !is_approx(old_tolerance, tolerance);
}
bool GLGizmoAdvancedCut::render_slider_double_input_show_percentage(const std::string &label, float &value_in, float value_min, float value_max)
{
// -------- [ ]
// slider_with + item_in_gap + first_input_width + item_out_gap
double slider_with = 0.24 * m_editing_window_width; // m_control_width * 0.35;
double item_in_gap = 0.01 * m_editing_window_width;
double item_out_gap = 0.01 * m_editing_window_width;
double first_input_width = 0.29 * m_editing_window_width;
ImGui::AlignTextToFramePadding();
m_imgui->text(label);
ImGui::SameLine(m_label_width);
ImGui::PushItemWidth(slider_with);
double left_width = m_label_width + slider_with + item_in_gap;
float old_val = value_in;
float value = value_in *100;
const std::string format = "%.0f %%";
if (m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, value_min, value_max, format.c_str())) {
value_in = value * 0.01f;
}
ImGui::SameLine(left_width);
ImGui::PushItemWidth(first_input_width);
if (ImGui::BBLDragFloat(("##input_" + label).c_str(), &value, 0.05f, value_min, value_max, format.c_str())){
value_in = value * 0.01f;
}
return !is_approx(old_val, value_in);
}
bool GLGizmoAdvancedCut::cut_line_processing() const
{
return m_cut_line_begin != Vec3d::Zero();

View File

@ -81,6 +81,9 @@ private:
float m_connector_size{2.5f};
float m_connector_size_tolerance{0.f};
// Input params for cut with snaps
float m_snap_space_proportion{0.3f};
float m_snap_bulge_proportion{0.15f};
TriangleMesh m_connector_mesh;
bool m_has_invalid_connector{false};
@ -228,7 +231,7 @@ private:
bool render_combo(const std::string &label, const std::vector<std::string> &lines, size_t &selection_idx);
bool render_slider_double_input(const std::string &label, float &value_in, float &tolerance_in);
bool render_slider_double_input_show_percentage(const std::string &label, float &value_in, float value_min, float value_max);
bool cut_line_processing() const;
void discard_cut_line_processing();
bool process_cut_line(SLAGizmoEventType action, const Vec2d &mouse_position);