Cut WIP: First implementation for detection of the invalid connectors position

Implemented cases:
* overlap of some connectors
* check if some connector position is outside of clipper
This commit is contained in:
YuSanka 2022-10-24 16:57:02 +02:00
parent 98d7fe335b
commit ae21667786
7 changed files with 158 additions and 55 deletions

View File

@ -262,9 +262,9 @@ constexpr inline T lerp(const T& a, const T& b, Number t)
} }
template <typename Number> template <typename Number>
constexpr inline bool is_approx(Number value, Number test_value) constexpr inline bool is_approx(Number value, Number test_value, Number precision = EPSILON)
{ {
return std::fabs(double(value) - double(test_value)) < double(EPSILON); return std::fabs(double(value) - double(test_value)) < double(precision);
} }
// A meta-predicate which is true for integers wider than or equal to coord_t // A meta-predicate which is true for integers wider than or equal to coord_t

View File

@ -1845,6 +1845,32 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c
return assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix; return assemble_transform(offset, Vec3d::Zero(), Vec3d::Ones() - border_scale, Vec3d::Ones()) * vol_matrix;
} }
bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos)
{
// check if connector pos is out of clipping plane
if (m_c->object_clipper() && !m_c->object_clipper()->containes(cur_pos))
return true;
const CutConnector& cur_connector = connectors[idx];
const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m *
scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast<double>());
const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix);
if (!bounding_box().contains(cur_tbb))
return true;
for (size_t i = 0; i < connectors.size(); ++i) {
if (i == idx)
continue;
const CutConnector& connector = connectors[i];
if ((connector.pos - cur_connector.pos).norm() < double(connector.radius + cur_connector.radius))
return true;
}
return false;
}
void GLGizmoCut3D::render_connectors() void GLGizmoCut3D::render_connectors()
{ {
::glEnable(GL_DEPTH_TEST); ::glEnable(GL_DEPTH_TEST);
@ -1872,8 +1898,6 @@ void GLGizmoCut3D::render_connectors()
const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane();
const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal;
const Transform3d instance_trafo = assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix();
m_has_invalid_connector = false; m_has_invalid_connector = false;
for (size_t i = 0; i < connectors.size(); ++i) { for (size_t i = 0; i < connectors.size(); ++i) {
@ -1881,16 +1905,14 @@ void GLGizmoCut3D::render_connectors()
float height = connector.height; float height = connector.height;
// recalculate connector position to world position // recalculate connector position to world position
Vec3d pos = connector.pos + instance_offset; Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ();
if (connector.attribs.type == CutConnectorType::Dowel &&
connector.attribs.style == CutConnectorStyle::Prizm) {
pos -= height * normal;
height *= 2;
}
pos += sla_shift * Vec3d::UnitZ();
// First decide about the color of the point. // First decide about the color of the point.
if (!m_connectors_editing) if (is_conflict_for_connector(i, connectors, pos)) {
m_has_invalid_connector = true;
render_color = CONNECTOR_ERR_COLOR;
}
else if (!m_connectors_editing)
render_color = CONNECTOR_ERR_COLOR; render_color = CONNECTOR_ERR_COLOR;
else if (size_t(m_hover_id - m_connectors_group_id) == i) else if (size_t(m_hover_id - m_connectors_group_id) == i)
render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR;
@ -1898,26 +1920,13 @@ void GLGizmoCut3D::render_connectors()
render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR;
else // neither hover nor picking else // neither hover nor picking
render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR;
// ! #ysFIXME rework get_volume_transformation
if (0) { // else { // neither hover nor picking
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
++mesh_id;
if (!mv->is_model_part())
continue;
const Transform3d volume_trafo = get_volume_transformation(mv);
if (m_c->raycaster()->raycasters()[mesh_id]->is_valid_intersection(pos, -normal, instance_trafo * volume_trafo)) {
render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : CONNECTOR_ERR_COLOR;
break;
}
render_color = CONNECTOR_ERR_COLOR;
m_has_invalid_connector = true;
}
}
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
if (connector.attribs.type == CutConnectorType::Dowel &&
connector.attribs.style == CutConnectorStyle::Prizm) {
pos -= height * normal;
height *= 2;
}
const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m *
scale_transform(Vec3f(connector.radius, connector.radius, height).cast<double>()); scale_transform(Vec3f(connector.radius, connector.radius, height).cast<double>());
@ -1930,8 +1939,8 @@ bool GLGizmoCut3D::can_perform_cut() const
if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing) if (m_has_invalid_connector || (!m_keep_upper && !m_keep_lower) || m_connectors_editing)
return false; return false;
const BoundingBoxf3 tbb = transformed_bounding_box(m_plane_center, true); const auto clipper = m_c->object_clipper();
return tbb.contains(m_plane_center); return clipper && clipper->has_valid_contour();
} }
void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object) void GLGizmoCut3D::apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object)
@ -1982,7 +1991,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
Vec3d cut_center_offset = m_plane_center - instance_offset; Vec3d cut_center_offset = m_plane_center - instance_offset;
cut_center_offset[Z] -= sla_shift_z; cut_center_offset[Z] -= sla_shift_z;
if (0.0 < object_cut_z && can_perform_cut()) { if (0.0 < object_cut_z) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane")); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Cut by Plane"));
bool create_dowels_as_separate_object = false; bool create_dowels_as_separate_object = false;
@ -2014,7 +2023,7 @@ bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair
const ModelObject* mo = m_c->selection_info()->model_object(); const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()];
const Transform3d instance_trafo = sla_shift > 0.0 ? const Transform3d instance_trafo = sla_shift > 0.f ?
assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); assemble_transform(Vec3d(0.0, 0.0, sla_shift)) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix();
const Camera& camera = wxGetApp().plater()->get_camera(); const Camera& camera = wxGetApp().plater()->get_camera();
@ -2138,33 +2147,28 @@ bool GLGizmoCut3D::process_cut_line(SLAGizmoEventType action, const Vec2d& mouse
bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_position) bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_position)
{ {
if (!m_connectors_editing)
return false;
std::pair<Vec3d, Vec3d> pos_and_normal; std::pair<Vec3d, Vec3d> pos_and_normal;
Vec3d pos_world; Vec3d pos_world;
if (unproject_on_cut_plane(mouse_position.cast<double>(), pos_and_normal, pos_world)) { if (unproject_on_cut_plane(mouse_position.cast<double>(), pos_and_normal, pos_world)) {
const Vec3d& hit = pos_and_normal.first; const Vec3d& hit = pos_and_normal.first;
if (m_connectors_editing) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction);
unselect_all_connectors();
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); connectors.emplace_back(hit, m_rotation_m,
unselect_all_connectors(); m_connector_size * 0.5f, m_connector_depth_ratio,
m_connector_size_tolerance, m_connector_depth_ratio_tolerance,
connectors.emplace_back(hit, m_rotation_m, CutConnectorAttributes( CutConnectorType(m_connector_type),
m_connector_size * 0.5f, m_connector_depth_ratio, CutConnectorStyle(m_connector_style),
m_connector_size_tolerance, m_connector_depth_ratio_tolerance, CutConnectorShape(m_connector_shape_id)));
CutConnectorAttributes( CutConnectorType(m_connector_type), m_selected.push_back(true);
CutConnectorStyle(m_connector_style), m_selected_count = 1;
CutConnectorShape(m_connector_shape_id))); assert(m_selected.size() == connectors.size());
m_selected.push_back(true); update_raycasters_for_picking();
m_selected_count = 1; m_parent.set_as_dirty();
assert(m_selected.size() == connectors.size());
update_raycasters_for_picking();
m_parent.set_as_dirty();
}
else {
// Following would inform the clipper about the mouse click, so it can
// toggle the respective contour as disabled.
//m_c->object_clipper()->pass_mouse_click(pos_world);
}
return true; return true;
} }

View File

@ -223,6 +223,7 @@ private:
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
bool render_connect_type_radio_button(CutConnectorType type); bool render_connect_type_radio_button(CutConnectorType type);
Transform3d get_volume_transformation(const ModelVolume* volume) const; Transform3d get_volume_transformation(const ModelVolume* volume) const;
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
void render_connectors(); void render_connectors();
bool can_perform_cut() const; bool can_perform_cut() const;

View File

@ -456,6 +456,64 @@ void ObjectClipper::render_cut() const
} }
} }
bool ObjectClipper::containes(Vec3d point) const
{
if (m_clp_ratio == 0.)
return false;
const SelectionInfo* sel_info = get_pool()->selection_info();
int sel_instance_idx = sel_info->get_active_instance();
if (sel_instance_idx < 0)
return false;
const ModelObject* mo = sel_info->model_object();
const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation();
size_t clipper_id = 0;
for (const ModelVolume* mv : mo->volumes) {
const Geometry::Transformation vol_trafo = mv->get_transformation();
Geometry::Transformation trafo = inst_trafo * vol_trafo;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
auto& clipper = m_clippers[clipper_id];
clipper->set_plane(*m_clp);
clipper->set_transformation(trafo);
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
if (clipper->contains(point))
return true;
++clipper_id;
}
return false;
}
bool ObjectClipper::has_valid_contour() const
{
if (m_clp_ratio == 0.)
return false;
const SelectionInfo* sel_info = get_pool()->selection_info();
int sel_instance_idx = sel_info->get_active_instance();
if (sel_instance_idx < 0)
return false;
const ModelObject* mo = sel_info->model_object();
const Geometry::Transformation inst_trafo = mo->instances[sel_instance_idx]->get_transformation();
size_t clipper_id = 0;
for (const ModelVolume* mv : mo->volumes) {
const Geometry::Transformation vol_trafo = mv->get_transformation();
Geometry::Transformation trafo = inst_trafo * vol_trafo;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
auto& clipper = m_clippers[clipper_id];
clipper->set_plane(*m_clp);
clipper->set_transformation(trafo);
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
if (clipper->has_valid_contour())
return true;
++clipper_id;
}
return false;
}
void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal) void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal)
{ {
const ModelObject* mo = get_pool()->selection_info()->model_object(); const ModelObject* mo = get_pool()->selection_info()->model_object();

View File

@ -266,6 +266,8 @@ public:
void pass_mouse_click(const Vec3d& pt); void pass_mouse_click(const Vec3d& pt);
std::vector<Vec3d> get_disabled_contours() const; std::vector<Vec3d> get_disabled_contours() const;
bool containes(Vec3d point) const;
bool has_valid_contour() const;
protected: protected:

View File

@ -146,6 +146,42 @@ void MeshClipper::render_contour()
#endif // ENABLE_LEGACY_OPENGL_REMOVAL #endif // ENABLE_LEGACY_OPENGL_REMOVAL
} }
bool MeshClipper::contains(Vec3d point)
{
if (!m_result)
recalculate_triangles();
for (CutIsland& isl : m_result->cut_islands) {
BoundingBoxf3 bb = isl.model_expanded.get_bounding_box();
// instead of using of standard bb.contains(point)
// because of precision (Note, that model_expanded is pretranslate(0.003 * normal.normalized()))
constexpr double pres = 0.01;
bool ret = (point.x() > bb.min.x() || is_approx(point.x(), bb.min.x(), pres)) && (point.x() < bb.max.x() || is_approx(point.x(), bb.max.x(), pres))
&& (point.y() > bb.min.y() || is_approx(point.y(), bb.min.y(), pres)) && (point.y() < bb.max.y() || is_approx(point.y(), bb.max.y(), pres))
&& (point.z() > bb.min.z() || is_approx(point.z(), bb.min.z(), pres)) && (point.z() < bb.max.z() || is_approx(point.z(), bb.max.z(), pres));
if (ret) {
// when we detected, that model_expanded's bb contains a point, then check if its polygon contains this point
Vec3d point_inv = m_result->trafo.inverse() * point;
Point pt = Point(scale_(point_inv.x()), scale_(point_inv.y()));
if (isl.expoly.contains(pt))
return true;
}
}
return false;
}
bool MeshClipper::has_valid_contour()
{
if (!m_result)
recalculate_triangles();
for (CutIsland& isl : m_result->cut_islands)
if (isl.model_expanded.get_bounding_box().defined)
return true;
return false;
}
void MeshClipper::pass_mouse_click(const Vec3d& point_in) void MeshClipper::pass_mouse_click(const Vec3d& point_in)

View File

@ -115,6 +115,8 @@ public:
void pass_mouse_click(const Vec3d& pt); void pass_mouse_click(const Vec3d& pt);
bool contains(Vec3d point);
bool has_valid_contour();
private: private:
void recalculate_triangles(); void recalculate_triangles();