diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index d50156ed2..f1305f8a1 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -491,7 +491,7 @@ void MeasuringImpl::extract_features(int plane_idx) Vec3d cog = Vec3d::Zero(); size_t counter = 0; for (const std::vector& b : plane.borders) { - for (size_t i = 1; i < b.size(); ++i) { + for (size_t i = 0; i < b.size(); ++i) { cog += b[i]; ++counter; } @@ -931,8 +931,6 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& result.distance_infinite = std::make_optional(DistAndPoints{it->dist, it->from, it->to}); /////////////////////////////////////////////////////////////////////////// } else if (f2.get_type() == SurfaceFeatureType::Plane) { - assert(measuring != nullptr); - const auto [from, to] = f1.get_edge(); const auto [idx, normal, origin] = f2.get_plane(); @@ -1190,14 +1188,12 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& } } - info.sqrDistance = distance * distance + N0dD * N0dD; + info.sqrDistance = distance * distance; } result.distance_infinite = std::make_optional(DistAndPoints{ std::sqrt(candidates[0].sqrDistance), candidates[0].circle0Closest, candidates[0].circle1Closest }); // TODO /////////////////////////////////////////////////////////////////////////// } else if (f2.get_type() == SurfaceFeatureType::Plane) { - assert(measuring != nullptr); - const auto [center, radius, normal1] = f1.get_circle(); const auto [idx2, normal2, origin2] = f2.get_plane(); @@ -1289,12 +1285,46 @@ void SurfaceFeature::translate(const Transform3d &tran) } case Measure::SurfaceFeatureType::Plane: { // m_pt1 is normal; - m_pt2 = tran * m_pt2; + Vec3d temp_pt1 = m_pt2 + m_pt1; + temp_pt1 = tran * temp_pt1; + m_pt2 = tran * m_pt2; + m_pt1 = (temp_pt1 - m_pt2).normalized(); break; } case Measure::SurfaceFeatureType::Circle: { - m_pt1 = tran * m_pt1; + // m_pt1 is center; // m_pt2 is normal; + auto local_normal = m_pt2; + auto local_center = m_pt1; + Vec3d temp_pt2 = local_normal + local_center; + temp_pt2 = tran * temp_pt2; + m_pt1 = tran * m_pt1; + auto world_center = m_pt1; + m_pt2 = (temp_pt2 - m_pt1).normalized(); + auto get_point_projection_to_plane = [](const Vec3d& pt, const Vec3d& plane_origin, const Vec3d& plane_normal,Vec3d& intersection_pt )->bool { + auto normal = plane_normal.normalized(); + auto BA=plane_origin-pt; + auto length = BA.dot(normal); + intersection_pt = pt + length * normal; + return true; + }; + auto calc_world_radius = [&local_center, &local_normal, &tran, &world_center, &get_point_projection_to_plane](const Vec3d &pt, double &value) { + Vec3d intersection_pt; + get_point_projection_to_plane(pt, local_center, local_normal, intersection_pt); + auto local_radius_pt = (intersection_pt - local_center).normalized() * value + local_center; + auto radius_pt = tran * local_radius_pt; + value = (radius_pt - world_center).norm(); + }; + //m_value is radius + float eps = 1e-2; + if ((local_normal-Vec3d(1,0,0)).norm()<1e-2) { + Vec3d new_pt = local_center + Vec3d(0, 1, 0); + calc_world_radius(new_pt, m_value); + } + else { + Vec3d new_pt= local_center + Vec3d(1, 0, 0); + calc_world_radius(new_pt,m_value); + } break; } default: break; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 9f9482a5e..81590dec8 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -137,6 +137,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoMmuSegmentation.hpp GUI/Gizmos/GLGizmoFaceDetector.cpp GUI/Gizmos/GLGizmoFaceDetector.hpp + GUI/Gizmos/GLGizmoMeasure.cpp + GUI/Gizmos/GLGizmoMeasure.hpp GUI/Gizmos/GLGizmoSeam.cpp GUI/Gizmos/GLGizmoSeam.hpp GUI/Gizmos/GLGizmoText.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 8eaff1263..81f916235 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -200,7 +200,7 @@ public: void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } virtual bool apply_clipping_plane() { return true; } - + virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } unsigned int get_sprite_id() const { return m_sprite_id; } int get_hover_id() const { return m_hover_id; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp new file mode 100644 index 000000000..0f4d1c8d2 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -0,0 +1,2344 @@ +#include "GLGizmoMeasure.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" + + +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/MeasureUtils.hpp" + +#include + +#include + +#include + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +static const Slic3r::ColorRGBA SELECTED_1ST_COLOR = { 0.25f, 0.75f, 0.75f, 1.0f }; +static const Slic3r::ColorRGBA SELECTED_2ND_COLOR = { 0.75f, 0.25f, 0.75f, 1.0f }; +static const Slic3r::ColorRGBA NEUTRAL_COLOR = {0.5f, 0.5f, 0.5f, 1.0f}; +static const Slic3r::ColorRGBA HOVER_COLOR = {0.0f, 1.0f, 0.0f, 1.0f};//Green + +static const int POINT_ID = 100; +static const int EDGE_ID = 200; +static const int CIRCLE_ID = 300; +static const int PLANE_ID = 400; +static const int SEL_SPHERE_1_ID = 501; +static const int SEL_SPHERE_2_ID = 502; + +static const float TRIANGLE_BASE = 10.0f; +static const float TRIANGLE_HEIGHT = TRIANGLE_BASE * 1.618033f; + +static const std::string CTRL_STR = +#ifdef __APPLE__ +"⌘" +#else +"Ctrl" +#endif //__APPLE__ +; + +static std::string format_double(double value) +{ + char buf[1024]; + sprintf(buf, "%.3f", value); + return std::string(buf); +} + +static std::string format_vec3(const Vec3d& v) +{ + char buf[1024]; + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", v.x(), v.y(), v.z()); + return std::string(buf); +} + +static std::string surface_feature_type_as_string(Measure::SurfaceFeatureType type) +{ + switch (type) + { + default: + case Measure::SurfaceFeatureType::Undef: { return ("No feature"); } + case Measure::SurfaceFeatureType::Point: { return _u8L("Vertex"); } + case Measure::SurfaceFeatureType::Edge: { return _u8L("Edge"); } + case Measure::SurfaceFeatureType::Circle: { return _u8L("Circle"); } + case Measure::SurfaceFeatureType::Plane: { return _u8L("Plane"); } + } +} + +static std::string point_on_feature_type_as_string(Measure::SurfaceFeatureType type, int hover_id) +{ + std::string ret; + switch (type) { + case Measure::SurfaceFeatureType::Point: { ret = _u8L("Vertex"); break; } + case Measure::SurfaceFeatureType::Edge: { ret = _u8L("Point on edge"); break; } + case Measure::SurfaceFeatureType::Circle: { ret = _u8L("Point on circle"); break; } + case Measure::SurfaceFeatureType::Plane: { ret = _u8L("Point on plane"); break; } + default: { assert(false); break; } + } + return ret; +} + +static std::string center_on_feature_type_as_string(Measure::SurfaceFeatureType type) +{ + std::string ret; + switch (type) { + case Measure::SurfaceFeatureType::Edge: { ret = _u8L("Center of edge"); break; } + case Measure::SurfaceFeatureType::Circle: { ret = _u8L("Center of circle"); break; } + default: { assert(false); break; } + } + return ret; +} + +static std::shared_ptr init_plane_data(const indexed_triangle_set &its, const std::vector &triangle_indices) +{ + GLModel::Geometry init_data; + init_data.format = {GUI::GLModel::PrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3}; + init_data.reserve_indices(3 * triangle_indices.size()); + init_data.reserve_vertices(3 * triangle_indices.size()); + unsigned int i = 0; + for (int idx : triangle_indices) { + const Vec3f &v0 = its.vertices[its.indices[idx][0]]; + const Vec3f &v1 = its.vertices[its.indices[idx][1]]; + const Vec3f &v2 = its.vertices[its.indices[idx][2]]; + + const Vec3f n = (v1 - v0).cross(v2 - v0).normalized(); + init_data.add_vertex(v0, n); + init_data.add_vertex(v1, n); + init_data.add_vertex(v2, n); + init_data.add_triangle(i, i + 1, i + 2); + i += 3; + } + std::shared_ptr gl_data = std::make_shared(); + gl_data->init_from(std::move(init_data),true); + return gl_data; +} + + +static std::shared_ptr init_torus_data(unsigned int primary_resolution, + unsigned int secondary_resolution, + const Vec3f & center, + float radius, + float thickness, + const Vec3f& model_axis, + const Transform3f& world_trafo) +{ + const unsigned int torus_sector_count = std::max(4, primary_resolution); + const unsigned int section_sector_count = std::max(4, secondary_resolution); + const float torus_sector_step = 2.0f * float(M_PI) / float(torus_sector_count); + const float section_sector_step = 2.0f * float(M_PI) / float(section_sector_count); + + GLModel::Geometry data; + data.format = {GLModel::PrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3}; + data.reserve_vertices(torus_sector_count * section_sector_count); + data.reserve_indices(torus_sector_count * section_sector_count * 2 * 3); + + // vertices + const Transform3f local_to_world_matrix = world_trafo * Geometry::translation_transform(center.cast()).cast() * + Eigen::Quaternion::FromTwoVectors(Vec3f::UnitZ(), model_axis); + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const float section_angle = torus_sector_step * i; + const Vec3f radius_dir(std::cos(section_angle), std::sin(section_angle), 0.0f); + const Vec3f local_section_center = radius * radius_dir; + const Vec3f world_section_center = local_to_world_matrix * local_section_center; + const Vec3f local_section_normal = local_section_center.normalized().cross(Vec3f::UnitZ()).normalized(); + const Vec3f world_section_normal = (Vec3f) (local_to_world_matrix.matrix().block(0, 0, 3, 3) * local_section_normal).normalized(); + const Vec3f base_v = thickness * radius_dir; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const Vec3f v = Eigen::AngleAxisf(section_sector_step * j, world_section_normal) * base_v; + data.add_vertex(world_section_center + v, (Vec3f) v.normalized()); + } + } + + // triangles + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const unsigned int ii = i * section_sector_count; + const unsigned int ii_next = ((i + 1) % torus_sector_count) * section_sector_count; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const unsigned int j_next = (j + 1) % section_sector_count; + const unsigned int i0 = ii + j; + const unsigned int i1 = ii_next + j; + const unsigned int i2 = ii_next + j_next; + const unsigned int i3 = ii + j_next; + data.add_triangle(i0, i1, i2); + data.add_triangle(i0, i2, i3); + } + } + std::shared_ptr gl_data = std::make_shared(); + gl_data->init_from(std::move(data), true); + return gl_data; +} + +static bool is_feature_with_center(const Measure::SurfaceFeature& feature) +{ + const Measure::SurfaceFeatureType type = feature.get_type(); + return (type == Measure::SurfaceFeatureType::Circle || (type == Measure::SurfaceFeatureType::Edge && feature.get_extra_point().has_value())); +} + +static Vec3d get_feature_offset(const Measure::SurfaceFeature& feature) +{ + Vec3d ret; + switch (feature.get_type()) + { + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = feature.get_circle(); + ret = center; + break; + } + case Measure::SurfaceFeatureType::Edge: + { + std::optional p = feature.get_extra_point(); + assert(p.has_value()); + ret = *p; + break; + } + case Measure::SurfaceFeatureType::Point: + { + ret = feature.get_point(); + break; + } + default: { assert(false); } + } + return ret; +} + +class TransformHelper +{ + struct Cache + { + std::array viewport; + Matrix4d ndc_to_ss_matrix; + Transform3d ndc_to_ss_matrix_inverse; + }; + + static Cache s_cache; + +public: + static Vec3d model_to_world(const Vec3d& model, const Transform3d& world_matrix) { + return world_matrix * model; + } + + static Vec4d world_to_clip(const Vec3d& world, const Matrix4d& projection_view_matrix) { + return projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0); + } + + static Vec3d clip_to_ndc(const Vec4d& clip) { + return Vec3d(clip.x(), clip.y(), clip.z()) / clip.w(); + } + + static Vec2d ndc_to_ss(const Vec3d& ndc, const std::array& viewport) { + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + return { half_w * ndc.x() + double(viewport[0]) + half_w, half_h * ndc.y() + double(viewport[1]) + half_h }; + }; + + static Vec4d model_to_clip(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix) { + return world_to_clip(model_to_world(model, world_matrix), projection_view_matrix); + } + + static Vec3d model_to_ndc(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix) { + return clip_to_ndc(world_to_clip(model_to_world(model, world_matrix), projection_view_matrix)); + } + + static Vec2d model_to_ss(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(model_to_world(model, world_matrix), projection_view_matrix)), viewport); + } + + static Vec2d world_to_ss(const Vec3d& world, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(world, projection_view_matrix)), viewport); + } + + static const Matrix4d& ndc_to_ss_matrix(const std::array& viewport) { + update(viewport); + return s_cache.ndc_to_ss_matrix; + } + + static const Transform3d ndc_to_ss_matrix_inverse(const std::array& viewport) { + update(viewport); + return s_cache.ndc_to_ss_matrix_inverse; + } + +private: + static void update(const std::array& viewport) { + if (s_cache.viewport == viewport) + return; + + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + s_cache.ndc_to_ss_matrix << half_w, 0.0, 0.0, double(viewport[0]) + half_w, + 0.0, half_h, 0.0, double(viewport[1]) + half_h, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0; + + s_cache.ndc_to_ss_matrix_inverse = s_cache.ndc_to_ss_matrix.inverse(); + s_cache.viewport = viewport; + } +}; + +TransformHelper::Cache TransformHelper::s_cache = { { 0, 0, 0, 0 }, Matrix4d::Identity(), Transform3d::Identity() }; + +GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) +: GLGizmoBase(parent, icon_filename, sprite_id) +{ + auto sphere_geometry = smooth_sphere(16, 7.5f); + m_sphere = std::make_shared(); + m_sphere->init_from(std::move(sphere_geometry),true); + m_gripper_id_raycast_map[GripperType::POINT] = std::make_shared(m_sphere->mesh, POINT_ID); + + auto cylinder_geometry = smooth_cylinder(16, 5.0f, 1.0f); + + m_cylinder = std::make_shared(); + m_cylinder->init_from(std::move(cylinder_geometry), true); + m_gripper_id_raycast_map[GripperType::EDGE] = std::make_shared(m_cylinder->mesh,EDGE_ID); +} + +bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) { + // only for sure + m_mouse_left_down = false; + return false; + } + else if (mouse_event.Dragging()) { + // Enable/Disable panning/rotating the 3D scene + // Ctrl is pressed or the mouse is not hovering a selected volume + bool unlock_dragging = mouse_event.CmdDown() || (m_hover_id == -1 && !m_parent.get_selection().contains_volume(m_parent.get_first_hover_volume_idx())); + // mode is not center selection or mouse is not hovering a center + unlock_dragging &= !mouse_event.ShiftDown() || (m_hover_id != SEL_SPHERE_1_ID && m_hover_id != SEL_SPHERE_2_ID && m_hover_id != POINT_ID); + return !unlock_dragging; + } + else if (mouse_event.LeftDown()) { + // let the event pass through to allow panning/rotating the 3D scene + if (mouse_event.CmdDown()) + return false; + + if (m_hover_id != -1) { + m_mouse_left_down = true; + m_mouse_left_down_mesh_deal = true; + auto detect_current_item = [this]() { + SelectedFeatures::Item item; + if (m_hover_id == SEL_SPHERE_1_ID) { + if (m_selected_features.first.is_center) + // mouse is hovering over a selected center + item = { true, m_selected_features.first.source, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.first.source)) } }; + else if (is_feature_with_center(*m_selected_features.first.feature)) + // mouse is hovering over a unselected center + item = { true, m_selected_features.first.feature, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.first.feature)) } }; + else + // mouse is hovering over a point + item = m_selected_features.first; + } + else if (m_hover_id == SEL_SPHERE_2_ID) { + if (m_selected_features.second.is_center) + // mouse is hovering over a selected center + item = { true, m_selected_features.second.source, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.second.source)) } }; + else if (is_feature_with_center(*m_selected_features.second.feature)) + // mouse is hovering over a center + item = { true, m_selected_features.second.feature, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.second.feature)) } }; + else + // mouse is hovering over a point + item = m_selected_features.second; + } + else { + switch (m_mode) + { + case EMode::FeatureSelection: { item = { false, m_curr_feature, m_curr_feature }; break; } + case EMode::PointSelection: { item = { false, m_curr_feature, Measure::SurfaceFeature(*m_curr_point_on_feature_position) }; break; } + } + } + return item; + }; + + auto requires_sphere_raycaster_for_picking = [this](const SelectedFeatures::Item& item) { + if (m_mode == EMode::PointSelection || item.feature->get_type() == Measure::SurfaceFeatureType::Point) + return true; + else if (m_mode == EMode::FeatureSelection) { + if (is_feature_with_center(*item.feature)) + return true; + } + return false; + }; + + if (m_selected_features.first.feature.has_value()) { + const SelectedFeatures::Item item = detect_current_item(); + if (m_selected_features.first != item) { + bool processed = false; + if (item.is_center) { + if (item.source == m_selected_features.first.feature) { + // switch 1st selection from feature to its center + m_selected_features.first = item; + processed = true; + } + else if (item.source == m_selected_features.second.feature) { + // switch 2nd selection from feature to its center + m_selected_features.second = item; + processed = true; + } + } + else if (is_feature_with_center(*item.feature)) { + if (m_selected_features.first.is_center && m_selected_features.first.source == item.feature) { + // switch 1st selection from center to its feature + m_selected_features.first = item; + processed = true; + } + else if (m_selected_features.second.is_center && m_selected_features.second.source == item.feature) { + // switch 2nd selection from center to its feature + m_selected_features.second = item; + processed = true; + } + } + + if (!processed) { + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + if (m_selected_features.second == item) { + // 2nd feature deselection + // m_selected_features.second.reset(); + reset_feature2(); + } + else { + // 2nd feature selection + m_selected_features.second = item; + if (requires_sphere_raycaster_for_picking(item)) { + auto pick = std::make_shared(m_sphere->mesh, SEL_SPHERE_2_ID); + m_gripper_id_raycast_map[GripperType::SPHERE_2] = pick; + } + } + } + } + else { + remove_selected_sphere_raycaster(SEL_SPHERE_1_ID); + if (m_selected_features.second.feature.has_value()) { + // promote 2nd feature to 1st feature + reset_feature1(); + if (requires_sphere_raycaster_for_picking(m_selected_features.first)) { + auto pick = std::make_shared(m_sphere->mesh, SEL_SPHERE_1_ID); + m_gripper_id_raycast_map[GripperType::SPHERE_1] = pick; + } + } else { + // 1st feature deselection + reset_feature1(); + } + } + } + else { + // 1st feature selection + const SelectedFeatures::Item item = detect_current_item(); + m_selected_features.first = item; + if (requires_sphere_raycaster_for_picking(item)) { + auto pick = std::make_shared(m_sphere->mesh, SEL_SPHERE_1_ID); + m_gripper_id_raycast_map[GripperType::SPHERE_1] = pick; + } + } + + update_measurement_result(); + + m_imgui->set_requires_extra_frame(); + + const Measure::MeasurementResult &measure = m_measurement_result; + if (measure.distance_xyz.has_value() && measure.distance_xyz->norm() > EPSILON) { + Vec3d distance = *measure.distance_xyz; + if (wxGetApp().app_config->get("use_inches") == "1") + distance = GizmoObjectManipulation::mm_to_in * distance; + m_buffered_distance = distance; + } + return true; + } + else + // if the mouse pointer is on any volume, filter out the event to prevent the user to move it + // equivalent tp: return (m_parent.get_first_hover_volume_idx() != -1); + return m_curr_feature.has_value(); + + // fix: prevent restart gizmo when reselect object + // take responsibility for left up + if (m_parent.get_first_hover_volume_idx() >= 0) + m_mouse_left_down = true; + } + else if (mouse_event.LeftUp()) { + if (m_mouse_left_down) { + // responsible for mouse left up after selecting plane + m_mouse_left_down = false; + return true; + } + if (m_hover_id == -1 && !m_parent.is_mouse_dragging()) + // avoid closing the gizmo if the user clicks outside of any volume + return true; + } + else if (mouse_event.RightDown()) { + // let the event pass through to allow panning/rotating the 3D scene + if (mouse_event.CmdDown()) + return false; + } + else if (mouse_event.Leaving()) + m_mouse_left_down = false; + + return false; +} +void func() {} + +void GLGizmoMeasure::data_changed(bool is_serializing) +{ + wxBusyCursor wait; + + if (m_pending_scale) { + update_if_needed(); + register_single_mesh_pick(); + update_measurement_result(); + m_pending_scale = false; + } + else { + m_parent.toggle_selected_volume_visibility(true); + reset_all_pick(); + update_if_needed(); + register_single_mesh_pick(); + reset_all_feature(); + } + m_last_inv_zoom = 0.0f; + m_last_plane_idx = -1; + m_editing_distance = false; + m_is_editing_distance_first_frame = true; +} + +bool GLGizmoMeasure::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (action == SLAGizmoEventType::ShiftDown) { + if (m_shift_kar_filter.is_first()) { + m_mode = EMode::PointSelection; + disable_scene_raycasters(); + } + m_shift_kar_filter.increase_count(); + } + else if (action == SLAGizmoEventType::ShiftUp) { + m_shift_kar_filter.reset_count(); + m_mode = EMode::FeatureSelection; + restore_scene_raycasters_state(); + } + else if (action == SLAGizmoEventType::Delete) { + reset_all_feature(); + m_parent.request_extra_frame(); + } + else if (action == SLAGizmoEventType::Escape) { + if (!m_selected_features.first.feature.has_value()) { + update_measurement_result(); + return false; + } + else { + if (m_selected_features.second.feature.has_value()) { + reset_feature2(); + } + else { + reset_feature1(); + } + } + } + return true; +} + +bool GLGizmoMeasure::on_init() +{ + m_shortcut_key = WXK_CONTROL_U; + + m_desc["feature_selection"] = _L("Select feature"); + m_desc["point_selection_caption"] = _L("Shift + Left mouse button"); + m_desc["point_selection"] = _L("Select point"); + m_desc["reset_caption"] = _L("Delete"); + m_desc["reset"] = _L("Restart selection"); + m_desc["unselect_caption"] = _L("Esc"); + m_desc["unselect"] = _L("Cancel a feature until exit"); + + return true; +} + +void GLGizmoMeasure::on_set_state() +{ + if (m_state == Off) { + m_parent.toggle_selected_volume_visibility(false); + m_shift_kar_filter.reset_count(); + m_curr_feature.reset(); + m_curr_point_on_feature_position.reset(); + restore_scene_raycasters_state(); + m_editing_distance = false; + m_is_editing_distance_first_frame = true; + std::map>().swap(m_mesh_measure_map); + } + else { + m_mode = EMode::FeatureSelection; + m_hover_id = -1; + m_show_reset_first_tip = false; + } +} + +std::string GLGizmoMeasure::on_get_name() const +{ + return _u8L("Measure"); +} + +bool GLGizmoMeasure::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + return selection.volumes_count()>0; +} + +void GLGizmoMeasure::init_circle_glmodel(GripperType gripper_type, const Measure::SurfaceFeature &feature, CircleGLModel &circle_gl_model,float inv_zoom) +{ + const auto [center, radius, normal] = feature.get_circle(); + auto cur_feature =const_cast(&feature); + if (circle_gl_model.inv_zoom != inv_zoom) { + if (circle_gl_model.last_circle_feature) { + const auto [last_center, last_radius, last_normal] = circle_gl_model.last_circle_feature->get_circle(); + float eps = 1e-2; + if ((last_center - center).norm() < eps && (last_normal - normal).norm() < eps && abs(last_radius - radius) < eps){ + return; + } + } + reset_gripper_pick(gripper_type); + circle_gl_model.circle = init_torus_data(64, 16, center.cast(), float(radius), 5.0f * inv_zoom, normal.cast(), Transform3f::Identity()); + if (circle_gl_model.circle) { + if (auto mesh = circle_gl_model.circle->mesh) { + m_gripper_id_raycast_map[gripper_type] = std::make_shared(m_curr_circle.circle->mesh, CIRCLE_ID); + } + } + circle_gl_model.last_circle_feature = cur_feature; + circle_gl_model.inv_zoom = inv_zoom; + } +} + +void GLGizmoMeasure::init_plane_glmodel(GripperType gripper_type, const Measure::SurfaceFeature &feature, PlaneGLModel &plane_gl_model) +{ + const auto &[idx, normal, pt] = feature.get_plane(); + if (plane_gl_model.plane_idx != idx) { + plane_gl_model.plane.reset(); + } + if (!plane_gl_model.plane) { + plane_gl_model.plane_idx = idx; + reset_gripper_pick(gripper_type); + plane_gl_model.plane = init_plane_data(*feature.mesh, *feature.plane_indices); + if (plane_gl_model.plane) { + if (auto mesh = plane_gl_model.plane->mesh) { + m_gripper_id_raycast_map[gripper_type] = std::make_shared(mesh, PLANE_ID); + m_gripper_id_raycast_map[gripper_type]->set_transform(feature.world_tran); + } + } + } +} + +void GLGizmoMeasure::on_render() +{ +#if ENABLE_MEASURE_GIZMO_DEBUG + render_debug_dialog(); +#endif // ENABLE_MEASURE_GIZMO_DEBUG + + Vec2d mouse_position = m_parent.get_local_mouse_position(); + update_if_needed(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const float inv_zoom = (float)camera.get_inv_zoom(); + bool mouse_on_gripper = false; + bool mouse_on_object =false; + { + if (!m_editing_distance) { + Vec3f hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + for (auto item : m_gripper_id_raycast_map) { + if (!item.second->is_active()) { + continue; + } + auto world_tran = item.second->world_tran; + Vec3f normal_on_gripper; + if (item.second->mesh_raycaster->closest_hit(mouse_position, item.second->world_tran.get_matrix(), camera, hit, normal_on_gripper)) { + double hit_squared_distance = (camera.get_position() - world_tran.get_matrix() * hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + if (item.second->get_id() > 0) { + m_hover_id = item.second->get_id(); + mouse_on_gripper = true; + } + } + } + } + } + } + Vec3d position_on_model; + Vec3d direction_on_model; + size_t model_facet_idx = -1; + double closest_hit_distance = std::numeric_limits::max(); + { + if (!m_editing_distance) { + for (auto item : m_mesh_raycaster_map) { + auto raycaster = item.second->mesh_raycaster; + auto world_tran = item.second->world_tran; + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + if (raycaster->unproject_on_mesh(mouse_position, world_tran.get_matrix(), camera, hit, normal, nullptr, &facet)) { + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position() - world_tran.get_matrix() * hit.cast()).norm(); + if (hit_squared_distance < closest_hit_distance) { + closest_hit_distance = hit_squared_distance; + mouse_on_object = true; + model_facet_idx = facet; + position_on_model = hit.cast(); + m_last_hit_volume = item.first; + m_curr_measuring = m_mesh_measure_map[m_last_hit_volume]; + } + } + } + } + + if (!(mouse_on_gripper || mouse_on_object)) { + m_hover_id = -1; + m_last_plane_idx = -1; + reset_gripper_pick(GripperType::PLANE); + reset_gripper_pick(GripperType::CIRCLE); + } + if (m_mouse_left_down_mesh_deal && m_hover_id >= 0) { + m_mouse_left_down_mesh_deal = false; + if (m_selected_features.first.feature.has_value()) { + if (m_hit_order_volumes.size() == 0) { + m_hit_order_volumes.push_back(m_last_hit_volume); + } else { + m_hit_order_volumes[0] = m_last_hit_volume; + } + } + if (m_selected_features.second.feature.has_value()) { + if (m_hit_order_volumes.size() == 1) { + m_hit_order_volumes.push_back(m_last_hit_volume); + } else { + m_hit_order_volumes[1] = m_last_hit_volume; + } + } + + auto is_two_volume_pick = [this, &camera]() { + if (m_mesh_raycaster_map.size() < 2) { + return false; + } + auto hit_volume = m_last_hit_volume; + if (hit_volume) { + if (m_hit_different_volumes.size() == 0) { + m_hit_different_volumes.push_back(hit_volume); + return true; + } + if (m_hit_different_volumes.size() == 1 && hit_volume == m_hit_different_volumes[0]) { + return false; + } + m_hit_different_volumes.push_back(hit_volume); + return true; + } + return false; + }; + + is_two_volume_pick(); + } + } + //const bool mouse_on_object = m_raycaster->unproject_on_mesh(mouse_position, Transform3d::Identity(), camera, position_on_model, normal_on_model, nullptr, &model_facet_idx); + const bool is_hovering_on_feature = m_mode == EMode::PointSelection && m_hover_id != -1; + + if (m_mode == EMode::FeatureSelection || m_mode == EMode::PointSelection) { + if (m_hover_id == SEL_SPHERE_1_ID || m_hover_id == SEL_SPHERE_2_ID) { + // Skip feature detection if hovering on a selected point/center + //reset_gripper_pick(GripperType::UNDEFINE, true); + m_curr_feature.reset(); + m_curr_point_on_feature_position.reset(); + } + else { + std::optional curr_feature = std::nullopt; + if (m_curr_measuring) { + curr_feature = wxGetMouseState().LeftIsDown() ? m_curr_feature : + mouse_on_object ? m_curr_measuring->get_feature(model_facet_idx, position_on_model, m_mesh_raycaster_map[m_last_hit_volume]->world_tran.get_matrix()) : + std::nullopt; + } + + if (m_curr_feature != curr_feature || + (curr_feature.has_value() && curr_feature->get_type() == Measure::SurfaceFeatureType::Circle && (m_curr_feature != curr_feature || m_last_inv_zoom != inv_zoom))) { + reset_gripper_pick(GripperType::UNDEFINE, true); + + m_curr_feature = curr_feature; + auto mesh = const_cast(m_last_hit_volume->ori_mesh); + m_curr_feature->mesh = &mesh->its; + m_curr_feature->world_tran = m_mesh_raycaster_map[m_last_hit_volume]->world_tran.get_matrix(); + if (!m_curr_feature.has_value()) + return; + + switch (m_curr_feature->get_type()) { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + if (m_sphere) { + if (auto mesh = m_sphere->mesh) { + m_gripper_id_raycast_map[GripperType::POINT] = std::make_shared(mesh, POINT_ID); + } + } + break; + } + case Measure::SurfaceFeatureType::Edge: + { + if (m_cylinder) { + if (auto mesh = m_cylinder->mesh) { + m_gripper_id_raycast_map[GripperType::EDGE] = std::make_shared(mesh, EDGE_ID); + } + } + break; + } + case Measure::SurfaceFeatureType::Circle: { + m_curr_circle.last_circle_feature = nullptr; + m_curr_circle.inv_zoom = 0; + init_circle_glmodel(GripperType::CIRCLE, *m_curr_feature, m_curr_circle,inv_zoom); + break; + } + case Measure::SurfaceFeatureType::Plane: { + const auto &[idx, normal, pt] = m_curr_feature->get_plane(); + m_curr_feature->plane_indices = const_cast*>(&m_curr_measuring->get_plane_triangle_indices(idx)); + auto cur_plane_features = const_cast *>(&m_curr_measuring->get_plane_features(idx)); + if (cur_plane_features) { + if (!m_curr_feature->world_plane_features) { + m_curr_feature->world_plane_features = new std::vector(); + } + m_curr_feature->world_plane_features->clear(); // resize(cur_plane_features->size()); + for (size_t i = 0; i < cur_plane_features->size(); i++) { + Measure::SurfaceFeature temp(cur_plane_features->at(i)); + temp.translate(m_curr_feature->world_tran); + m_curr_feature->world_plane_features->push_back(std::move(temp)); + } + } + m_curr_plane.plane_idx = -1; + init_plane_glmodel(GripperType::PLANE, *m_curr_feature, m_curr_plane); + break; + } + } + } + } + } + + if (m_mode != EMode::PointSelection) { + m_curr_point_on_feature_position.reset(); + } + else if (is_hovering_on_feature) { + auto position_on_feature = [this, &mouse_position](int feature_type_id, const Camera &camera, std::function callback = nullptr) -> Vec3d { + auto it = m_gripper_id_raycast_map.find((GripperType) feature_type_id); // m_raycasters.find(feature_type_id); + if (it != m_gripper_id_raycast_map.end() && it->second != nullptr) { + Vec3f p; + Vec3f n; + const Transform3d& trafo = it->second->world_tran.get_matrix(); + bool res = it->second->mesh_raycaster->closest_hit(mouse_position, trafo, camera, p, n); + if (res) { + if (callback) + p = callback(p); + return trafo * p.cast(); + } + } + return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + }; + if (m_curr_feature.has_value()) { + switch (m_curr_feature->get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + m_curr_point_on_feature_position = m_curr_feature->get_point(); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const std::optional extra = m_curr_feature->get_extra_point(); + if (extra.has_value() && m_hover_id == GripperType::POINT) + m_curr_point_on_feature_position = *extra; + else { + const Vec3d pos = position_on_feature(GripperType::EDGE, camera, [](const Vec3f &v) { return Vec3f(0.0f, 0.0f, v.z()); }); + if (!pos.isApprox(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX))) + m_curr_point_on_feature_position = pos; + } + break; + } + case Measure::SurfaceFeatureType::Plane: + { + m_curr_point_on_feature_position = position_on_feature(GripperType::PLANE, camera); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = m_curr_feature->get_circle(); + if (m_hover_id == POINT_ID) + m_curr_point_on_feature_position = center; + else { + const Vec3d world_pof = position_on_feature(GripperType::CIRCLE, camera, [](const Vec3f &v) { return v; }); + const Eigen::Hyperplane plane(normal, center); + const Transform3d local_to_model_matrix = Geometry::translation_transform(center) * Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), normal); + const Vec3d local_proj = local_to_model_matrix.inverse() * plane.projection(world_pof); + double angle = std::atan2(local_proj.y(), local_proj.x()); + if (angle < 0.0) + angle += 2.0 * double(M_PI); + + const Vec3d local_pos = radius * Vec3d(std::cos(angle), std::sin(angle), 0.0); + m_curr_point_on_feature_position = local_to_model_matrix * local_pos; + } + break; + } + } + } + } + else { + m_curr_point_on_feature_position.reset(); + } + + if (!m_curr_feature.has_value() && !m_selected_features.first.feature.has_value()) { + return; + } + + const bool old_cullface = ::glIsEnabled(GL_CULL_FACE); + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + + auto render_feature = [this](const Measure::SurfaceFeature& feature, const std::vector& colors, + float inv_zoom, bool hover, bool update_raycasters_transform,int featura_index = -1) { + switch (feature.get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + const Transform3d feature_matrix = Geometry::translation_transform(feature.get_point()) * Geometry::scale_transform(inv_zoom); + Geometry::Transformation tran(feature_matrix); + render_glmodel(*m_sphere, colors.front().get_data(), tran.get_matrix(), false, hover ? 0.5f : 0.25f); + if (update_raycasters_transform) { + auto it = m_gripper_id_raycast_map.find(GripperType::POINT); + if (it != m_gripper_id_raycast_map.end() && it->second != nullptr) + it->second->set_transform(feature_matrix); + } + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto& [center, radius, normal] = feature.get_circle(); + // render circle + const Transform3d circle_matrix = Transform3d::Identity(); + //set_matrix_uniforms(circle_matrix); + Geometry::Transformation tran(circle_matrix); + if (featura_index == -1) { + init_circle_glmodel(GripperType::CIRCLE, feature, m_curr_circle, inv_zoom); + render_glmodel(*m_curr_circle.circle, colors.front().get_data(), tran.get_matrix(), false, hover ? 0.5f : 0.25f); + } + // render plane feature1 or feature2 + if (featura_index == 0) { // feature1 + init_circle_glmodel(GripperType::CIRCLE_1, feature, m_feature_circle_first, inv_zoom); + render_glmodel(*m_feature_circle_first.circle, colors.front().get_data(), tran.get_matrix(), false, hover ? 0.5f : 0.25f); + } else if (featura_index == 1) { // feature2 + init_circle_glmodel(GripperType::CIRCLE_2, feature, m_feature_circle_second, inv_zoom); + render_glmodel(*m_feature_circle_second.circle, colors.front().get_data(), tran.get_matrix(), false, hover ? 0.5f : 0.25f); + } + // render center + if (colors.size() > 1) { + const Transform3d center_matrix = Geometry::translation_transform(center) * Geometry::scale_transform(inv_zoom); + render_glmodel(*m_sphere, colors.back().get_data(), center_matrix, false, hover ? 0.5f : 0.25f); + + Geometry::Transformation tran(center_matrix); + auto it = m_gripper_id_raycast_map.find(GripperType::POINT); + if (it != m_gripper_id_raycast_map.end() && it->second != nullptr) + it->second->set_transform(center_matrix); + } + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const auto& [from, to] = feature.get_edge(); + // render edge + const Transform3d edge_matrix = Geometry::translation_transform(from) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), to - from) * + Geometry::scale_transform({ (double)inv_zoom, (double)inv_zoom, (to - from).norm() }); + + Geometry::Transformation tran(edge_matrix); + render_glmodel(*m_cylinder, colors.front().get_data(), tran.get_matrix(), false, hover ? 0.5f : 0.25f); + if (update_raycasters_transform) { + auto it = m_gripper_id_raycast_map.find(GripperType::EDGE); + if (it != m_gripper_id_raycast_map.end() && it->second != nullptr){ + it->second->set_transform(edge_matrix); + } + } + // render extra point + if (colors.size() > 1) { + const std::optional extra = feature.get_extra_point(); + if (extra.has_value()) { + const Transform3d point_matrix = Geometry::translation_transform(*extra) * Geometry::scale_transform(inv_zoom); + Geometry::Transformation tran(point_matrix); + render_glmodel(*m_sphere, colors.back().get_data(), tran.get_matrix(), false, hover ? 0.5f : 0.25f); + + auto it = m_gripper_id_raycast_map.find(GripperType::POINT); + if (it != m_gripper_id_raycast_map.end() && it->second != nullptr) { + it->second->set_transform(point_matrix); + } + } + } + break; + } + case Measure::SurfaceFeatureType::Plane: { + if (featura_index == -1) { + render_glmodel(*m_curr_plane.plane, colors.back().get_data(), feature.world_tran, false, hover ? 0.5f : 0.25f); + break; + } + //render plane feature1 or feature2 + if (featura_index == 0) {//feature1 + init_plane_glmodel(GripperType::PLANE_1, feature, m_feature_plane_first); + render_glmodel(*m_feature_plane_first.plane, colors.back().get_data(), feature.world_tran, false, hover ? 0.5f : 0.25f); + } else if (featura_index == 1) {//feature2 + init_plane_glmodel(GripperType::PLANE_2, feature, m_feature_plane_second); + render_glmodel(*m_feature_plane_second.plane, colors.back().get_data(), feature.world_tran, false, hover ? 0.5f : 0.25f); + } + break; + } + } + }; + + auto hover_selection_color = [this]() { + return ((m_mode == EMode::PointSelection && !m_selected_features.first.feature.has_value()) || + (m_mode != EMode::PointSelection && (!m_selected_features.first.feature.has_value() || *m_curr_feature == *m_selected_features.first.feature))) ? + SELECTED_1ST_COLOR : SELECTED_2ND_COLOR; + }; + + auto hovering_color = [this, hover_selection_color]() { + return (m_mode == EMode::PointSelection) ? HOVER_COLOR : hover_selection_color(); + }; + + if (m_curr_feature.has_value()) { + // render hovered feature + std::vector colors; + if (m_selected_features.first.feature.has_value() && *m_curr_feature == *m_selected_features.first.feature) { + // hovering over the 1st selected feature + if (m_selected_features.first.is_center) + // hovering over a center + colors = { NEUTRAL_COLOR, hovering_color() }; + else if (is_feature_with_center(*m_selected_features.first.feature)) + // hovering over a feature with center + colors = { hovering_color(), NEUTRAL_COLOR }; + else + colors = { hovering_color() }; + } + else if (m_selected_features.second.feature.has_value() && *m_curr_feature == *m_selected_features.second.feature) { + // hovering over the 2nd selected feature + if (m_selected_features.second.is_center) + // hovering over a center + colors = { NEUTRAL_COLOR, hovering_color() }; + else if (is_feature_with_center(*m_selected_features.second.feature)) + // hovering over a feature with center + colors = { hovering_color(), NEUTRAL_COLOR }; + else + colors = { hovering_color() }; + } + else { + switch (m_curr_feature->get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + colors.emplace_back(hover_selection_color()); + break; + } + case Measure::SurfaceFeatureType::Edge: + case Measure::SurfaceFeatureType::Circle: + { + if (m_selected_features.first.is_center && m_curr_feature == m_selected_features.first.source) + colors = { SELECTED_1ST_COLOR, NEUTRAL_COLOR }; + else if (m_selected_features.second.is_center && m_curr_feature == m_selected_features.second.source) + colors = { SELECTED_2ND_COLOR, NEUTRAL_COLOR }; + else + colors = { hovering_color(), hovering_color() }; + break; + } + case Measure::SurfaceFeatureType::Plane: + { + colors.emplace_back(hovering_color()); + break; + } + } + } + + render_feature(*m_curr_feature, colors, inv_zoom, true, true); + } + + if (m_selected_features.first.feature.has_value() && (!m_curr_feature.has_value() || *m_curr_feature != *m_selected_features.first.feature)) { + // render 1st selected feature + + std::optional feature_to_render; + std::vector colors; + bool requires_raycaster_update = false; + if (m_hover_id == SEL_SPHERE_1_ID && (m_selected_features.first.is_center || is_feature_with_center(*m_selected_features.first.feature))) { + // hovering over a center + feature_to_render = m_selected_features.first.source; + colors = { NEUTRAL_COLOR, SELECTED_1ST_COLOR }; + requires_raycaster_update = true; + } + else if (is_feature_with_center(*m_selected_features.first.feature)) { + // hovering over a feature with center + feature_to_render = m_selected_features.first.feature; + colors = { SELECTED_1ST_COLOR, NEUTRAL_COLOR }; + requires_raycaster_update = true; + } + else { + feature_to_render = m_selected_features.first.feature; + colors = { SELECTED_1ST_COLOR }; + requires_raycaster_update = m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Point; + } + + render_feature(*feature_to_render, colors, inv_zoom, m_hover_id == SEL_SPHERE_1_ID, false, 0); + + if (requires_raycaster_update) { + if (m_gripper_id_raycast_map.find(GripperType::SPHERE_1) != m_gripper_id_raycast_map.end()) { + m_gripper_id_raycast_map[GripperType::SPHERE_1]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) * + Geometry::scale_transform(inv_zoom)); + } + } + } + + if (m_selected_features.second.feature.has_value() && (!m_curr_feature.has_value() || *m_curr_feature != *m_selected_features.second.feature)) { + // render 2nd selected feature + + std::optional feature_to_render; + std::vector colors; + bool requires_raycaster_update = false; + if (m_hover_id == SEL_SPHERE_2_ID && (m_selected_features.second.is_center || is_feature_with_center(*m_selected_features.second.feature))) { + // hovering over a center + feature_to_render = m_selected_features.second.source; + colors = { NEUTRAL_COLOR, SELECTED_2ND_COLOR }; + requires_raycaster_update = true; + } + else if (is_feature_with_center(*m_selected_features.second.feature)) { + // hovering over a feature with center + feature_to_render = m_selected_features.second.feature; + colors = { SELECTED_2ND_COLOR, NEUTRAL_COLOR }; + requires_raycaster_update = true; + } + else { + feature_to_render = m_selected_features.second.feature; + colors = { SELECTED_2ND_COLOR }; + requires_raycaster_update = m_selected_features.second.feature->get_type() == Measure::SurfaceFeatureType::Point; + } + + render_feature(*feature_to_render, colors, inv_zoom, m_hover_id == SEL_SPHERE_2_ID, false, 1); + + if (requires_raycaster_update) { + if (m_gripper_id_raycast_map.find(GripperType::SPHERE_2) != m_gripper_id_raycast_map.end()) { + m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) * + Geometry::scale_transform(inv_zoom)); + } + } + } + + if (is_hovering_on_feature && m_curr_point_on_feature_position.has_value()) { + if (m_hover_id != POINT_ID) { + // render point on feature while SHIFT is pressed + const Transform3d matrix = Geometry::translation_transform(*m_curr_point_on_feature_position) * Geometry::scale_transform(inv_zoom); + const ColorRGBA color = hover_selection_color(); + + Geometry::Transformation tran(matrix); + render_glmodel(*m_sphere, color.get_data(), tran.get_matrix(), false, 0.5f); + } + } + + if (old_cullface) + glsafe(::glEnable(GL_CULL_FACE)); + + render_dimensioning(); +} + +void GLGizmoMeasure::update_if_needed() +{ + const Selection& selection = m_parent.get_selection(); + if (selection.is_empty()) + return; + + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + std::vector volumes_cache; + volumes_cache.reserve(idxs.size()); + for (unsigned int idx : idxs) { + const GLVolume* v = selection.get_volume(idx); + const int volume_idx = v->volume_idx(); + if (volume_idx < 0) + continue; + + const ModelObject* obj = selection.get_model()->objects[v->object_idx()]; + const ModelInstance* inst = obj->instances[v->instance_idx()]; + const ModelVolume* vol = obj->volumes[volume_idx]; + const VolumeCacheItem item = { + obj, inst, vol, + Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * inst->get_matrix() * vol->get_matrix() + }; + volumes_cache.emplace_back(item); + } + + if (m_state != On || volumes_cache.empty()) + return; + + if (m_volumes_cache != volumes_cache) { + m_volumes_cache = volumes_cache; + } +} + +void GLGizmoMeasure::disable_scene_raycasters() +{ + for(auto iter:m_gripper_id_raycast_map) + { + iter.second->set_active(false); + } +} + +void GLGizmoMeasure::restore_scene_raycasters_state() +{ + for(auto iter:m_gripper_id_raycast_map) + { + iter.second->set_active(true); + } +} + + +void GLGizmoMeasure::render_dimensioning() +{ + static SelectedFeatures last_selected_features; + + if (!m_selected_features.first.feature.has_value()) + return; + + if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() != Measure::SurfaceFeatureType::Circle) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + auto point_point = [this, &shader](const Vec3d& v1, const Vec3d& v2, float distance) { + if ((v2 - v1).squaredNorm() < 0.000001 || distance < 0.001f) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + + // screen coordinates + const Vec2d v1ss = TransformHelper::world_to_ss(v1, projection_view_matrix, viewport); + const Vec2d v2ss = TransformHelper::world_to_ss(v2, projection_view_matrix, viewport); + + if (v1ss.isApprox(v2ss)) + return; + + const Vec2d v12ss = v2ss - v1ss; + const double v12ss_len = v12ss.norm(); + + const bool overlap = v12ss_len - 2.0 * TRIANGLE_HEIGHT < 0.0; + + const auto q12ss = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(v12ss.x(), v12ss.y(), 0.0)); + const auto q21ss = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(-v12ss.x(), -v12ss.y(), 0.0)); + + shader->set_uniform("projection_matrix", Transform3d::Identity()); + + const Vec3d v1ss_3 = { v1ss.x(), v1ss.y(), 0.0 }; + const Vec3d v2ss_3 = { v2ss.x(), v2ss.y(), 0.0 }; + + const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + // stem + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss * Geometry::translation_transform(-2.0 * TRIANGLE_HEIGHT * Vec3d::UnitX()) * Geometry::scale_transform({ v12ss_len + 4.0 * TRIANGLE_HEIGHT, 1.0f, 1.0f }) : + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss * Geometry::scale_transform({ v12ss_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(-1,ColorRGBA::WHITE().get_data()); + m_dimensioning.line.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(1.0f)); + + // arrow 1 + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss : + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q21ss); + m_dimensioning.triangle.render(); + + // arrow 2 + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v2ss_3) * q21ss : + ss_to_ndc_matrix * Geometry::translation_transform(v2ss_3) * q12ss); + m_dimensioning.triangle.render(); + + const bool use_inches = wxGetApp().app_config->get("use_inches") == "1"; + const double curr_value = use_inches ? GizmoObjectManipulation::mm_to_in * distance : distance; + const std::string curr_value_str = format_double(curr_value); + const std::string units = use_inches ? _u8L("in") : _u8L("mm"); + const float value_str_width = 20.0f + ImGui::CalcTextSize(curr_value_str.c_str()).x; + static double edit_value = 0.0; + + ImGuiWrapper::push_common_window_style(m_parent.get_scale()); + const Vec2d label_position = 0.5 * (v1ss + v2ss); + m_imgui->set_next_window_pos(label_position.x(), viewport[3] - label_position.y(), ImGuiCond_Always, 0.0f, 1.0f); + m_imgui->set_next_window_bg_alpha(0.0f); + + if (!m_editing_distance) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); + m_imgui->begin(std::string("distance"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::AlignTextToFramePadding(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const std::string txt = curr_value_str + " " + units; + ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); + const ImGuiStyle& style = ImGui::GetStyle(); + draw_list->AddRectFilled({pos.x - style.FramePadding.x, pos.y + style.FramePadding.y}, + {pos.x + txt_size.x + 2.0f * style.FramePadding.x, pos.y + txt_size.y + 2.0f * style.FramePadding.y}, + ImGuiWrapper::to_ImU32({1.0f, 1.0f, 1.0f, 0.5f})); + ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); + m_imgui->text(txt); + if (m_hit_different_volumes.size() < 2) { + ImGui::SameLine(); + if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _L("Edit to scale"))) { + m_editing_distance = true; + edit_value = curr_value; + m_imgui->requires_extra_frame(); + } + } + m_imgui->end(); + ImGui::PopStyleVar(3); + } + + if (m_editing_distance && !ImGui::IsPopupOpen("distance_popup")) + ImGui::OpenPopup("distance_popup"); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 4.0f, 0.0f }); + if (ImGui::BeginPopupModal("distance_popup", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration)) { + auto perform_scale = [this](double new_value, double old_value,bool scale_single_volume) { + if (new_value == old_value || new_value <= 0.0) + return; + + const double ratio = new_value / old_value; + wxGetApp().plater()->take_snapshot(_u8L("Scale")); + + struct TrafoData + { + double ratio; + Vec3d old_pivot; + Vec3d new_pivot; + Transform3d scale_matrix; + + TrafoData(double ratio, const Vec3d& old_pivot, const Vec3d& new_pivot) { + this->ratio = ratio; + this->scale_matrix = Geometry::scale_transform(ratio); + this->old_pivot = old_pivot; + this->new_pivot = new_pivot; + } + + Vec3d transform(const Vec3d& point) const { return this->scale_matrix * (point - this->old_pivot) + this->new_pivot; } + }; + + auto scale_feature = [](Measure::SurfaceFeature& feature, const TrafoData& trafo_data) { + switch (feature.get_type()) + { + case Measure::SurfaceFeatureType::Point: + { + feature = Measure::SurfaceFeature(trafo_data.transform(feature.get_point())); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const auto [from, to] = feature.get_edge(); + const std::optional extra = feature.get_extra_point(); + const std::optional new_extra = extra.has_value() ? trafo_data.transform(*extra) : extra; + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, trafo_data.transform(from), trafo_data.transform(to), new_extra); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = feature.get_circle(); + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Circle, trafo_data.transform(center), normal, std::nullopt, trafo_data.ratio * radius); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto [idx, normal, origin] = feature.get_plane(); + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Plane, normal, trafo_data.transform(origin), std::nullopt, idx); + break; + } + default: { break; } + } + }; + + // apply scale + TransformationType type; + type.set_world(); + type.set_relative(); + type.set_joint(); + + // scale selection + Selection& selection = m_parent.get_selection(); + selection.setup_cache(); + Vec3d old_center, new_center; + if (scale_single_volume && m_hit_different_volumes.size()==1) { + //todo + } else { + old_center = selection.get_bounding_box().center(); + selection.scale(ratio * Vec3d::Ones(), type); + wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot + new_center = selection.get_bounding_box().center(); + } + wxGetApp().obj_manipul()->set_dirty(); + // scale dimensioning + const TrafoData trafo_data(ratio, old_center, new_center); + scale_feature(*m_selected_features.first.feature, trafo_data); + if (m_selected_features.second.feature.has_value()) + scale_feature(*m_selected_features.second.feature, trafo_data); + // update measure on next call to data_changed() + m_pending_scale = true; + + }; + auto action_exit = [this]() { + m_editing_distance = false; + m_is_editing_distance_first_frame = true; + ImGui::CloseCurrentPopup(); + }; + auto action_scale = [perform_scale, action_exit](double new_value, double old_value, bool scale_single_volume) { + perform_scale(new_value, old_value, scale_single_volume); + action_exit(); + }; + + m_imgui->disable_background_fadeout_animation(); + ImGui::PushItemWidth(value_str_width); + if (ImGui::InputDouble("##distance", &edit_value, 0.0f, 0.0f, "%.3f")) { + } + + // trick to auto-select text in the input widgets on 1st frame + if (m_is_editing_distance_first_frame) { + ImGui::SetKeyboardFocusHere(0); + m_is_editing_distance_first_frame = false; + m_imgui->set_requires_extra_frame(); + } + + // handle keys input + if (ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) + action_scale(edit_value, curr_value, false); + else if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) + action_exit(); + + ImGui::SameLine(); + ImGuiWrapper::push_confirm_button_style(); + //if (m_imgui->button(_CTX(L_CONTEXT("Scale part", "Verb"), "Verb"))) + //action_scale(edit_value, curr_value, true); + //ImGui::SameLine(); + if (m_imgui->button(_CTX(L_CONTEXT("Scale all", "Verb"), "Verb"))) + action_scale(edit_value, curr_value, false); + ImGuiWrapper::pop_confirm_button_style(); + ImGui::SameLine(); + ImGuiWrapper::push_cancel_button_style(); + if (m_imgui->button(_L("Cancel"))) + action_exit(); + ImGuiWrapper::pop_cancel_button_style(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(4); + ImGuiWrapper::pop_common_window_style(); + }; + + auto point_edge = [this, shader](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Point && f2.get_type() == Measure::SurfaceFeatureType::Edge); + std::pair e = f2.get_edge(); + const Vec3d v_proj = m_measurement_result.distance_infinite->to; + const Vec3d e1e2 = e.second - e.first; + const Vec3d v_proje1 = v_proj - e.first; + const bool on_e1_side = v_proje1.dot(e1e2) < -EPSILON; + const bool on_e2_side = !on_e1_side && v_proje1.norm() > e1e2.norm(); + if (on_e1_side || on_e2_side) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); + + const Vec2d v_projss = TransformHelper::world_to_ss(v_proj, projection_view_matrix, viewport); + auto render_extension = [this, &v_projss, &projection_view_matrix, &viewport, &ss_to_ndc_matrix, shader](const Vec3d& p) { + const Vec2d pss = TransformHelper::world_to_ss(p, projection_view_matrix, viewport); + if (!pss.isApprox(v_projss)) { + const Vec2d pv_projss = v_projss - pss; + const double pv_projss_len = pv_projss.norm(); + + const auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(pv_projss.x(), pv_projss.y(), 0.0)); + + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("view_model_matrix", ss_to_ndc_matrix * Geometry::translation_transform({ pss.x(), pss.y(), 0.0 }) * q * + Geometry::scale_transform({ pv_projss_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(-1, ColorRGBA::LIGHT_GRAY().get_data()); + m_dimensioning.line.render(); + } + }; + + render_extension(on_e1_side ? e.first : e.second); + } + }; + + auto arc_edge_edge = [this, &shader](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2, double radius = 0.0) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Edge && f2.get_type() == Measure::SurfaceFeatureType::Edge); + if (!m_measurement_result.angle.has_value()) + return; + + const double angle = m_measurement_result.angle->angle; + const Vec3d center = m_measurement_result.angle->center; + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + const bool coplanar = m_measurement_result.angle->coplanar; + + if (std::abs(angle) < EPSILON || std::abs(calc_radius) < EPSILON) + return; + + const double draw_radius = (radius > 0.0) ? radius : calc_radius; + + const Vec3d e1_unit = Measure::edge_direction(e1); + const Vec3d e2_unit = Measure::edge_direction(e2); + + const unsigned int resolution = std::max(2, 64 * angle / double(PI)); + const double step = angle / double(resolution); + const Vec3d normal = e1_unit.cross(e2_unit).normalized(); + + if (!m_dimensioning.arc.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3}; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(resolution + 1); + init_data.reserve_indices(resolution + 1); + + // vertices + indices + for (unsigned int i = 0; i <= resolution; ++i) { + const double a = step * double(i); + const Vec3d v = draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(a, normal)) * e1_unit); + init_data.add_vertex((Vec3f) v.cast()); + init_data.add_index(i); + } + + m_dimensioning.arc.init_from(std::move(init_data)); + } + + const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + + // arc + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center)); + m_dimensioning.arc.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(1.0f)); + + // arrows + auto render_arrow = [this, shader, &camera, &normal, ¢er, &e1_unit, draw_radius, step, resolution](unsigned int endpoint_id) { + const double angle = (endpoint_id == 1) ? 0.0 : step * double(resolution); + const Vec3d position_model = Geometry::translation_transform(center) * (draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(angle, normal)) * e1_unit)); + const Vec3d direction_model = (endpoint_id == 1) ? -normal.cross(position_model - center).normalized() : normal.cross(position_model - center).normalized(); + const auto qz = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), (endpoint_id == 1) ? normal : -normal); + const auto qx = Eigen::Quaternion::FromTwoVectors(qz * Vec3d::UnitX(), direction_model); + const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(position_model) * + qx * qz * Geometry::scale_transform(camera.get_inv_zoom()); + shader->set_uniform("view_model_matrix", view_model_matrix); + m_dimensioning.triangle.render(); + }; + + glsafe(::glDisable(GL_CULL_FACE)); + render_arrow(1); + render_arrow(2); + glsafe(::glEnable(GL_CULL_FACE)); + + // edge 1 extension + const Vec3d e11e12 = e1.second - e1.first; + const Vec3d e11center = center - e1.first; + const double e11center_len = e11center.norm(); + + if (e11center_len > EPSILON && e11center.dot(e11e12) < 0.0) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Measure::edge_direction(e1.first, e1.second)) * + Geometry::scale_transform({ e11center_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(-1, ColorRGBA::LIGHT_GRAY().get_data()); + m_dimensioning.line.render(); + } + + // edge 2 extension + const Vec3d e21center = center - e2.first; + const double e21center_len = e21center.norm(); + if (e21center_len > EPSILON) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Measure::edge_direction(e2.first, e2.second)) * + Geometry::scale_transform({ (coplanar && radius > 0.0) ? e21center_len : draw_radius, 1.0f, 1.0f })); + m_dimensioning.line.set_color(-1, ColorRGBA::LIGHT_GRAY().get_data()); + m_dimensioning.line.render(); + } + + // label + // label world coordinates + const Vec3d label_position_world = Geometry::translation_transform(center) * (draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(step * 0.5 * double(resolution), normal)) * e1_unit)); + + // label screen coordinates + const std::array& viewport = camera.get_viewport(); + const Vec2d label_position_ss = TransformHelper::world_to_ss(label_position_world, + camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(), viewport); + + ImGuiWrapper::push_common_window_style(m_parent.get_scale()); + m_imgui->set_next_window_pos(label_position_ss.x(), viewport[3] - label_position_ss.y(), ImGuiCond_Always, 0.0f, 1.0f); + m_imgui->set_next_window_bg_alpha(0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + m_imgui->begin(wxString("##angle"), ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::AlignTextToFramePadding(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const std::string txt = format_double(Geometry::rad2deg(angle)) + "°"; + ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); + const ImGuiStyle& style = ImGui::GetStyle(); + ColorRGBA color{1.0f, 1.0f, 1.0f, 0.5f}; + draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x, + pos.y + txt_size.y + 2.0f * style.FramePadding.y}, ImGuiWrapper::to_ImU32(color)); + ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); + m_imgui->text(txt); + m_imgui->end(); + ImGui::PopStyleVar(); + ImGuiWrapper::pop_common_window_style(); + }; + + auto arc_edge_plane = [this, arc_edge_edge](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Edge && f2.get_type() == Measure::SurfaceFeatureType::Plane); + if (!m_measurement_result.angle.has_value()) + return; + + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + + if (calc_radius == 0.0) + return; + + arc_edge_edge(Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e1.first, e1.second), + Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e2.first, e2.second), calc_radius); + }; + + auto arc_plane_plane = [this, arc_edge_edge](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Plane && f2.get_type() == Measure::SurfaceFeatureType::Plane); + if (!m_measurement_result.angle.has_value()) + return; + + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + + if (calc_radius == 0.0) + return; + + arc_edge_edge(Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e1.first, e1.second), + Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e2.first, e2.second), calc_radius); + }; + + shader->start_using(); + + if (!m_dimensioning.line.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(1.0f, 0.0f, 0.0f)); + + // indices + init_data.add_line(0, 1); + + m_dimensioning.line.init_from(std::move(init_data)); + } + + if (!m_dimensioning.triangle.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3}; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(3); + init_data.reserve_indices(3); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(-TRIANGLE_HEIGHT, 0.5f * TRIANGLE_BASE, 0.0f)); + init_data.add_vertex(Vec3f(-TRIANGLE_HEIGHT, -0.5f * TRIANGLE_BASE, 0.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + + m_dimensioning.triangle.init_from(std::move(init_data),true); + } + + if (last_selected_features != m_selected_features) + m_dimensioning.arc.reset(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + const bool has_distance = m_measurement_result.has_distance_data(); + + const Measure::SurfaceFeature* f1 = &(*m_selected_features.first.feature); + const Measure::SurfaceFeature* f2 = nullptr; + std::unique_ptr temp_feature; + if (m_selected_features.second.feature.has_value()) + f2 = &(*m_selected_features.second.feature); + else { + assert(m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle); + temp_feature = std::make_unique(std::get<0>(m_selected_features.first.feature->get_circle())); + f2 = temp_feature.get(); + } + + if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() != Measure::SurfaceFeatureType::Circle) + return; + + Measure::SurfaceFeatureType ft1 = f1->get_type(); + Measure::SurfaceFeatureType ft2 = f2->get_type(); + + // Order features by type so following conditions are simple. + if (ft1 > ft2) { + std::swap(ft1, ft2); + std::swap(f1, f2); + } + + // If there is an angle to show, draw the arc: + if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Edge) + arc_edge_edge(*f1, *f2); + else if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Plane) + arc_edge_plane(*f1, *f2); + else if (ft1 == Measure::SurfaceFeatureType::Plane && ft2 == Measure::SurfaceFeatureType::Plane) + arc_plane_plane(*f1, *f2); + + if (has_distance){ + // Where needed, draw the extension of the edge to where the dist is measured: + if (ft1 == Measure::SurfaceFeatureType::Point && ft2 == Measure::SurfaceFeatureType::Edge) + point_edge(*f1, *f2); + + // Render the arrow between the points that the backend passed: + const Measure::DistAndPoints& dap = m_measurement_result.distance_infinite.has_value() + ? *m_measurement_result.distance_infinite + : *m_measurement_result.distance_strict; + point_point(dap.from, dap.to, dap.dist); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader->stop_using(); +} + +static void add_row_to_table(std::function col_1 = nullptr, std::function col_2 = nullptr) +{ + assert(col_1 != nullptr && col_2 != nullptr); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + col_1(); + ImGui::TableSetColumnIndex(1); + col_2(); +} + +static void add_strings_row_to_table(ImGuiWrapper& imgui, const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) +{ + add_row_to_table([&]() { imgui.text_colored(col_1_color, col_1); }, [&]() { imgui.text_colored(col_2_color, col_2); }); +}; + +#if ENABLE_MEASURE_GIZMO_DEBUG +void GLGizmoMeasure::render_debug_dialog() +{ + auto add_feature_data = [this](const SelectedFeatures::Item& item) { + const std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); + add_strings_row_to_table(*m_imgui, "Type", ImGuiWrapper::COL_BAMBU, text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + switch (item.feature->get_type()) + { + case Measure::SurfaceFeatureType::Point: + { + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BAMBU, format_vec3(item.feature->get_point()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + auto [from, to] = item.feature->get_edge(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BAMBU, format_vec3(from), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_BAMBU, format_vec3(to), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + auto [idx, normal, origin] = item.feature->get_plane(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BAMBU, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_BAMBU, format_vec3(origin), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_BAMBU, format_double(idx), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + auto [center, radius, normal] = item.feature->get_circle(); + const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); + radius = (on_circle - center).norm(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_BAMBU, format_vec3(center), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_BAMBU, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_BAMBU, format_double(radius), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + } + std::optional extra_point = item.feature->get_extra_point(); + if (extra_point.has_value()) + add_strings_row_to_table(*m_imgui, "m_pt3", ImGuiWrapper::COL_BAMBU, format_vec3(*extra_point), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + }; + + m_imgui->begin("Measure tool debug", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + if (ImGui::BeginTable("Mode", 2)) { + std::string txt; + switch (m_mode) + { + case EMode::FeatureSelection: { txt = "Feature selection"; break; } + case EMode::PointSelection: { txt = "Point selection"; break; } + default: { assert(false); break; } + } + add_strings_row_to_table(*m_imgui, "Mode", ImGuiWrapper::COL_BAMBU, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + ImGui::Separator(); + if (ImGui::BeginTable("Hover", 2)) { + add_strings_row_to_table(*m_imgui, "Hover id", ImGuiWrapper::COL_BAMBU, std::to_string(m_hover_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + const std::string txt = m_curr_feature.has_value() ? surface_feature_type_as_string(m_curr_feature->get_type()) : "None"; + add_strings_row_to_table(*m_imgui, "Current feature", ImGuiWrapper::COL_BAMBU, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + ImGui::Separator(); + if (!m_selected_features.first.feature.has_value() && !m_selected_features.second.feature.has_value()) + m_imgui->text("Empty selection"); + else { + const ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersH; + if (m_selected_features.first.feature.has_value()) { + m_imgui->text_colored(ImGuiWrapper::COL_BAMBU, "Selection 1"); + if (ImGui::BeginTable("Selection 1", 2, flags)) { + add_feature_data(m_selected_features.first); + ImGui::EndTable(); + } + } + if (m_selected_features.second.feature.has_value()) { + m_imgui->text_colored(ImGuiWrapper::COL_BAMBU, "Selection 2"); + if (ImGui::BeginTable("Selection 2", 2, flags)) { + add_feature_data(m_selected_features.second); + ImGui::EndTable(); + } + } + } + m_imgui->end(); +} +#endif // ENABLE_MEASURE_GIZMO_DEBUG +void GLGizmoMeasure::on_render_for_picking() +{ + +} + +void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit) +{ + static std::optional last_feature; + static EMode last_mode = EMode::FeatureSelection; + static SelectedFeatures last_selected_features; + + static float last_y = 0.0f; + static float last_h = 0.0f; + + if (m_editing_distance) + return; + unsigned int current_active_id = ImGui::GetActiveID(); + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + // Orca + ImGuiWrapper::push_toolbar_style(m_parent.get_scale()); + + GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + float caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{ "point_selection", "reset", "unselect"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + } + + const bool use_inches = wxGetApp().app_config->get("use_inches") == "1"; + const std::string units = use_inches ? " " + _u8L("in") : " " + _u8L("mm"); + + // Show selection + { + auto format_item_text = [this, use_inches, &units](const SelectedFeatures::Item& item) { + if (!item.feature.has_value()) + return _u8L("None"); + + std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : + item.is_center ? center_on_feature_type_as_string(item.source->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); + if (item.feature.has_value() && item.feature->get_type() == Measure::SurfaceFeatureType::Circle) { + auto [center, radius, normal] = item.feature->get_circle(); + const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); + radius = (on_circle - center).norm(); + if (use_inches) + radius = GizmoObjectManipulation::mm_to_in * radius; + text += " (" + _u8L("Diameter") + ": " + format_double(2.0 * radius) + units + ")"; + } + else if (item.feature.has_value() && item.feature->get_type() == Measure::SurfaceFeatureType::Edge) { + auto [start, end] = item.feature->get_edge(); + double length = (end - start).norm(); + if (use_inches) + length = GizmoObjectManipulation::mm_to_in * length; + text += " (" + _u8L("Length") + ": " + format_double(length) + units + ")"; + } + return text; + }; + + const float space_size = ImGui::CalcTextSize(" ").x * 2; + const float selection_cap_length = ImGui::CalcTextSize((_u8L("Selection") + " 1").c_str()).x * 1.2; + auto feature_first_text= format_item_text(m_selected_features.first); + const float feature_first_text_length = ImGui::CalcTextSize((_u8L(feature_first_text)).c_str()).x; + ImGui::AlignTextToFramePadding(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(SELECTED_1ST_COLOR)); + + m_imgui->text(_u8L("Selection") + " 1"); + ImGui::SameLine(selection_cap_length + space_size); + ImGui::PushItemWidth(feature_first_text_length); + m_imgui->text(feature_first_text); + if (m_selected_features.first.feature.has_value()) { + ImGui::SameLine(selection_cap_length + feature_first_text_length + space_size * 2); + ImGui::PushItemWidth(space_size * 2); + ImGui::PushID("Reset1");//for image_button + if (m_imgui->image_button(m_is_dark_mode ? ImGui::RevertBtn : ImGui::RevertBtn, _L("Reset"))) { + reset_feature1(); + } + ImGui::PopID(); + } + ImGui::PopStyleColor(); + + auto feature_second_text = format_item_text(m_selected_features.second); + const float feature_second_text_length = ImGui::CalcTextSize((_u8L(feature_second_text)).c_str()).x; + ImGui::AlignTextToFramePadding(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::to_ImVec4(SELECTED_2ND_COLOR)); + m_imgui->text(_u8L("Selection") + " 2"); + ImGui::SameLine(selection_cap_length + space_size); + ImGui::PushItemWidth(feature_second_text_length); + m_imgui->text(feature_second_text); + if (m_selected_features.first.feature.has_value() && m_selected_features.second.feature.has_value()) { + ImGui::SameLine(selection_cap_length + feature_second_text_length + space_size * 2); + ImGui::PushItemWidth(space_size * 2); + ImGui::PushID("Reset2"); + if (m_imgui->image_button(m_is_dark_mode ? ImGui::RevertBtn : ImGui::RevertBtn, _L("Reset"))) { + reset_feature2(); + } + ImGui::PopID(); + } + ImGui::PopStyleColor(); + } + + m_imgui->disabled_begin(!m_selected_features.first.feature.has_value()); + if (m_imgui->button(_L("Restart selection"))) { + reset_all_feature(); + m_imgui->set_requires_extra_frame(); + } + m_imgui->disabled_end(); + + if (m_show_reset_first_tip) { + m_imgui->text(_L("Feature 1 has been reset, \nfeature 2 has been feature 1")); + } + + auto add_measure_row_to_table = [this](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + m_imgui->text_colored(col_1_color, col_1); + ImGui::TableSetColumnIndex(1); + m_imgui->text_colored(col_2_color, col_2); + ImGui::TableSetColumnIndex(2); + if (m_imgui->image_button(m_is_dark_mode ? ImGui::ClipboardBtnDarkIcon : ImGui::ClipboardBtnIcon, _L("Copy to clipboard"))) { + wxTheClipboard->Open(); + wxTheClipboard->SetData(new wxTextDataObject(wxString((col_1 + ": " + col_2).c_str(), wxConvUTF8))); + wxTheClipboard->Close(); + } + }; + auto add_edit_distance_xyz_box = [this, ¤t_active_id](Vec3d distance) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + m_imgui->text_colored(ImGuiWrapper::COL_BAMBU, "X:"); + ImGui::TableSetColumnIndex(1); + ImGui::BBLInputDouble("##measure_distance_x", &m_buffered_distance[0], 0.0f, 0.0f, "%.2f"); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + m_imgui->text_colored(ImGuiWrapper::COL_BAMBU, "Y:"); + ImGui::TableSetColumnIndex(1); + ImGui::BBLInputDouble("##measure_distance_y", &m_buffered_distance[1], 0.0f, 0.0f, "%.2f"); + bool same_model_object = is_two_volume_in_same_model_object(); + m_imgui->disabled_begin(same_model_object); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + m_imgui->text_colored(ImGuiWrapper::COL_BAMBU, "Z:"); + ImGui::TableSetColumnIndex(1); + ImGui::BBLInputDouble("##measure_distance_z", &m_buffered_distance[2], 0.0f, 0.0f, "%.2f"); + + ImGui::TableNextRow(); + if (m_imgui->button(_L("Adsorbed onto the surface"))) { + std::cout << ""; + } + m_imgui->disabled_end(); + if (m_last_active_item_imgui != current_active_id && m_hit_different_volumes.size() == 2) { + auto selection = const_cast(&m_parent.get_selection()); + Vec3d displacement = Vec3d::Zero(); + auto v = m_hit_different_volumes[1]; + if (std::abs(m_buffered_distance[0] - distance[0]) > EPSILON) { + if (same_model_object == false) { + wxGetApp().plater()->take_snapshot(_u8L("modify x distance between objects")); + displacement[0] = m_buffered_distance[0] - distance[0]; + auto world_tran = v->get_instance_transformation() * v->get_volume_transformation(); + selection->translate(v->object_idx(), v->instance_idx(), displacement); + distance[0] = m_buffered_distance[0]; + } + } else if (std::abs(m_buffered_distance[1] - distance[1]) > EPSILON) { + if (same_model_object == false) { + wxGetApp().plater()->take_snapshot(_u8L("modify y distance between objects")); + displacement[1] = m_buffered_distance[1] - distance[1]; + selection->translate(v->object_idx(), v->instance_idx(), displacement); + distance[1] = m_buffered_distance[1]; + } + } else if (std::abs(m_buffered_distance[2] - distance[2]) > EPSILON) { + if (same_model_object == false) { + wxGetApp().plater()->take_snapshot(_u8L("modify y distance between objects")); + displacement[2] = m_buffered_distance[2] - distance[2]; + selection->translate(v->object_idx(), v->instance_idx(), displacement); + distance[2] = m_buffered_distance[2]; + } + } + //m_measuring.reset(); + update_if_needed(); + std::future resultFuture = std::async(std::launch::async, [this]() { this->register_single_mesh_pick(); }); + //todo and undo:remember 2 featura to solve + m_selected_features.second.feature->translate(displacement); + update_measurement_result(); + } + }; + ImGui::Separator(); + m_imgui->text(_u8L("Measure")); + + const unsigned int max_measure_row_count = 2; + unsigned int measure_row_count = 0; + if (ImGui::BeginTable("Measure", 4)) { + if (m_selected_features.second.feature.has_value()) { + const Measure::MeasurementResult& measure = m_measurement_result; + if (measure.angle.has_value()) { + ImGui::PushID("ClipboardAngle"); + add_measure_row_to_table(_u8L("Angle"), ImGuiWrapper::COL_BAMBU, format_double(Geometry::rad2deg(measure.angle->angle)) + "°", + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + + const bool show_strict = measure.distance_strict.has_value() && + (!measure.distance_infinite.has_value() || std::abs(measure.distance_strict->dist - measure.distance_infinite->dist) > EPSILON); + + if (measure.distance_infinite.has_value()) { + double distance = measure.distance_infinite->dist; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceInfinite"); + add_measure_row_to_table(show_strict ? _u8L("Perpendicular distance") : _u8L("Distance"), ImGuiWrapper::COL_BAMBU, format_double(distance) + units, + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + if (show_strict) { + double distance = measure.distance_strict->dist; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceStrict"); + add_measure_row_to_table(_u8L("Direct distance"), ImGuiWrapper::COL_BAMBU, format_double(distance) + units, + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + if (measure.distance_xyz.has_value()) { + Vec3d distance = *measure.distance_xyz; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + if (measure.distance_xyz->norm() > EPSILON) { + ImGui::PushID("ClipboardDistanceXYZ"); + add_measure_row_to_table(_u8L("Distance XYZ"), ImGuiWrapper::COL_BAMBU, format_vec3(distance), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + } + } + // add dummy rows to keep dialog size fixed + /*for (unsigned int i = measure_row_count; i < max_measure_row_count; ++i) { + add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_BAMBU, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); + }*/ + ImGui::EndTable(); + } + + ImGui::Separator(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(caption_max, x, get_cur_y); + + float f_scale =m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); + + ImGui::PopStyleVar(2); + + if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { + // the dialog may have changed its size, ask for an extra frame to render it properly + last_feature = m_curr_feature; + last_mode = m_mode; + last_selected_features = m_selected_features; + m_imgui->set_requires_extra_frame(); + } + m_last_active_item_imgui = current_active_id; + GizmoImguiEnd(); + + // Orca + ImGuiWrapper::pop_toolbar_style(); +} + +void GLGizmoMeasure::remove_selected_sphere_raycaster(int id) +{ + reset_gripper_pick(id == SEL_SPHERE_1_ID ? GripperType::SPHERE_1 : GripperType::SPHERE_2); +} + +void GLGizmoMeasure::update_measurement_result() +{ + if (!m_selected_features.first.feature.has_value()) + m_measurement_result = Measure::MeasurementResult(); + else if (m_selected_features.second.feature.has_value()) + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature); + else if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle) + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, Measure::SurfaceFeature(std::get<0>(m_selected_features.first.feature->get_circle()))); +} + +void GLGizmoMeasure::show_tooltip_information(float caption_max, float x, float y) +{ + ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP); + ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER); + + caption_max += m_imgui->calc_text_size(": ").x + 35.f; + + float font_size = ImGui::GetFontSize(); + ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, ImGui::GetStyle().FramePadding.y }); + ImGui::ImageButton3(normal_id, hover_id, button_size); + + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip2(ImVec2(x, y)); + auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { + m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption); + ImGui::SameLine(caption_max); + m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text); + }; + + for (const auto &t : std::array{"point_selection", "reset", "unselect"}) + draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + +void GLGizmoMeasure::reset_all_pick() +{ + std::map>().swap(m_mesh_raycaster_map); + reset_gripper_pick(GripperType::UNDEFINE,true); +} + +void GLGizmoMeasure::reset_gripper_pick(GripperType id, bool is_all) +{ + if (m_gripper_id_raycast_map.find(id) != m_gripper_id_raycast_map.end()) { + m_gripper_id_raycast_map[id].reset(); + m_gripper_id_raycast_map.erase(id); + } + if (id == GripperType ::UNDEFINE && is_all) { + reset_gripper_pick(GripperType::POINT); + reset_gripper_pick(GripperType::EDGE); + reset_gripper_pick(GripperType::PLANE); + reset_gripper_pick(GripperType::PLANE_1); + reset_gripper_pick(GripperType::PLANE_2); + reset_gripper_pick(GripperType::CIRCLE); + reset_gripper_pick(GripperType::CIRCLE_1); + reset_gripper_pick(GripperType::CIRCLE_2); + reset_gripper_pick(GripperType::SPHERE_1); + reset_gripper_pick(GripperType::SPHERE_1); + } +} + +void GLGizmoMeasure::register_single_mesh_pick() +{ + Selection& selection = m_parent.get_selection(); + const Selection::IndicesList &idxs = selection.get_volume_idxs(); + if (idxs.size() > 0) { + for (unsigned int idx : idxs) { + GLVolume *v = const_cast(selection.get_volume(idx)); + auto world_tran = v->get_instance_transformation() * v->get_volume_transformation(); + auto mesh = const_cast(v->ori_mesh); + if (m_mesh_raycaster_map.find(v) != m_mesh_raycaster_map.end()) { + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + } else { + m_mesh_raycaster_map[v] = std::make_shared(mesh, -1); + m_mesh_raycaster_map[v]->world_tran.set_from_transform(world_tran.get_matrix()); + m_mesh_measure_map[v] = std::make_shared(mesh->its); + } + } + } +} + + void GLGizmoMeasure::reset_all_feature() { + reset_feature2(); + reset_feature1(); + m_show_reset_first_tip = false; + m_hit_different_volumes.clear(); + m_hit_different_volumes.reserve(2); + m_hit_order_volumes.clear(); + m_hit_order_volumes.clear(); + } + +void GLGizmoMeasure::reset_feature1() + { + if (m_selected_features.second.feature.has_value()) { + if (m_hit_different_volumes.size() == 2) { + m_hit_different_volumes[0] = m_hit_different_volumes[1]; + } + if (m_hit_order_volumes.size() == 2) { + m_hit_order_volumes[0] = m_hit_order_volumes[1]; + } + m_selected_features.first = m_selected_features.second; + reset_feature2(); + m_show_reset_first_tip = true; + } else { + remove_selected_sphere_raycaster(SEL_SPHERE_1_ID); + m_selected_features.first.feature.reset(); + m_show_reset_first_tip = false; + if (m_hit_different_volumes.size() == 1) { + m_hit_different_volumes[0] = m_hit_different_volumes[1]; + m_hit_different_volumes.erase(m_hit_different_volumes.begin() + 0); + } + if (m_hit_order_volumes.size() == 1) { + m_hit_order_volumes[0] = m_hit_order_volumes[1]; + m_hit_order_volumes.erase(m_hit_order_volumes.begin() + 0); + } + if (m_feature_plane_first.plane) { + m_feature_plane_first.plane->reset(); + m_feature_plane_first.plane_idx = -1; + } + if (m_feature_circle_first.circle) { + m_feature_circle_first.circle->reset(); + m_feature_circle_first.last_circle_feature=nullptr; + m_feature_circle_first.inv_zoom = 0; + } + reset_gripper_pick(GripperType::PLANE_1); + reset_gripper_pick(GripperType::CIRCLE_1); + reset_gripper_pick(GripperType::SPHERE_1); + } + update_measurement_result(); + } + +void GLGizmoMeasure::reset_feature2() +{ + if (m_hit_different_volumes.size() == 2) { + m_hit_different_volumes.erase(m_hit_different_volumes.begin() + 1); + } + if (m_hit_order_volumes.size() == 2) { + m_hit_order_volumes.erase(m_hit_order_volumes.begin() + 1); + } + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + m_selected_features.second.reset(); + m_show_reset_first_tip = false; + reset_gripper_pick(GripperType::PLANE_2); + reset_gripper_pick(GripperType::CIRCLE_2); + reset_gripper_pick(GripperType::SPHERE_2); + if (m_feature_plane_second.plane) { + m_feature_plane_second.plane->reset(); + m_feature_plane_second.plane_idx = -1; + } + if (m_feature_circle_second.circle) { + m_feature_circle_second.circle->reset(); + m_feature_circle_second.last_circle_feature = nullptr; + m_feature_circle_second.inv_zoom = 0; + } + update_measurement_result(); +} + +bool Slic3r::GUI::GLGizmoMeasure::is_two_volume_in_same_model_object() +{ + if (m_hit_different_volumes.size() == 2) { + if (m_hit_different_volumes[0]->composite_id.object_id == m_hit_different_volumes[1]->composite_id.object_id) { + return true; + } + } + return false; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp new file mode 100644 index 000000000..d3fd6bf0f --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -0,0 +1,209 @@ +#ifndef slic3r_GLGizmoMeasure_hpp_ +#define slic3r_GLGizmoMeasure_hpp_ + +#include "GLGizmoBase.hpp" +#include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/GUI_Utils.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "libslic3r/Measure.hpp" +#include "libslic3r/Model.hpp" + +namespace Slic3r { + +enum class ModelVolumeType : int; +namespace Measure { class Measuring; } +namespace GUI { + +enum class SLAGizmoEventType : unsigned char; + +class GLGizmoMeasure : public GLGizmoBase +{ + enum class EMode : unsigned char + { + FeatureSelection, + PointSelection + }; + + struct SelectedFeatures + { + struct Item + { + bool is_center{ false }; + std::optional source; + std::optional feature; + + bool operator == (const Item& other) const { + return this->is_center == other.is_center && this->source == other.source && this->feature == other.feature; + } + + bool operator != (const Item& other) const { + return !operator == (other); + } + + void reset() { + is_center = false; + source.reset(); + feature.reset(); + } + }; + + Item first; + Item second; + + void reset() { + first.reset(); + second.reset(); + } + + bool operator == (const SelectedFeatures & other) const { + if (this->first != other.first) return false; + return this->second == other.second; + } + + bool operator != (const SelectedFeatures & other) const { + return !operator == (other); + } + }; + + 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 m_volumes_cache; + + EMode m_mode{ EMode::FeatureSelection }; + Measure::MeasurementResult m_measurement_result; + + std::map> m_mesh_measure_map; + std::shared_ptr m_curr_measuring{nullptr}; + + //first feature + std::shared_ptr m_sphere{nullptr}; + std::shared_ptr m_cylinder{nullptr}; + struct CircleGLModel + { + std::shared_ptr circle{nullptr}; + Measure::SurfaceFeature *last_circle_feature{nullptr}; + float inv_zoom{0}; + }; + CircleGLModel m_curr_circle; + CircleGLModel m_feature_circle_first; + CircleGLModel m_feature_circle_second; + void init_circle_glmodel(GripperType gripper_type, const Measure::SurfaceFeature &feature, CircleGLModel &circle_gl_model, float inv_zoom); + + struct PlaneGLModel { + int plane_idx{0}; + std::shared_ptr plane{nullptr}; + }; + PlaneGLModel m_curr_plane; + PlaneGLModel m_feature_plane_first; + PlaneGLModel m_feature_plane_second; + void init_plane_glmodel(GripperType gripper_type, const Measure::SurfaceFeature &feature, PlaneGLModel &plane_gl_model); + + struct Dimensioning + { + GLModel line; + GLModel triangle; + GLModel arc; + }; + Dimensioning m_dimensioning; + bool m_show_reset_first_tip{false}; + + + std::map> m_mesh_raycaster_map; + std::vector m_hit_different_volumes; + std::vector m_hit_order_volumes; + GLVolume* m_last_hit_volume; + //std::vector> m_plane_models_cache; + unsigned int m_last_active_item_imgui{0}; + Vec3d m_buffered_distance; + // used to keep the raycasters for point/center spheres + //std::vector> m_selected_sphere_raycasters; + std::optional m_curr_feature; + std::optional m_curr_point_on_feature_position; + + // These hold information to decide whether recalculation is necessary: + float m_last_inv_zoom{ 0.0f }; + std::optional m_last_circle_feature; + int m_last_plane_idx{ -1 }; + + bool m_mouse_left_down{ false }; // for detection left_up of this gizmo + bool m_mouse_left_down_mesh_deal{false};//for pick mesh + + KeyAutoRepeatFilter m_shift_kar_filter; + + SelectedFeatures m_selected_features; + bool m_pending_scale{ false }; + bool m_editing_distance{ false }; + bool m_is_editing_distance_first_frame{ true }; + + void update_if_needed(); + + void disable_scene_raycasters(); + void restore_scene_raycasters_state(); + + void render_dimensioning(); + +#if ENABLE_MEASURE_GIZMO_DEBUG + void render_debug_dialog(); +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +public: + GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + /// + /// Apply rotation on select plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void data_changed(bool is_serializing) override; + + 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() const 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; + + virtual void on_render_for_picking() override; + virtual void on_render_input_window(float x, float y, float bottom_limit) override; + + void remove_selected_sphere_raycaster(int id); + void update_measurement_result(); + + void show_tooltip_information(float caption_max, float x, float y); + void reset_all_pick(); + void reset_gripper_pick(GripperType id,bool is_all = false); + void register_single_mesh_pick(); + + void reset_all_feature(); + void reset_feature1(); + void reset_feature2(); + bool is_two_volume_in_same_model_object(); + private: + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. + std::map m_desc; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMeasure_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 1e867af31..1eabff381 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -24,8 +24,12 @@ enum class SLAGizmoEventType : unsigned char { Dragging, Delete, SelectAll, + CtrlDown, + CtrlUp, + ShiftDown, ShiftUp, AltUp, + Escape, ApplyChanges, DiscardChanges, AutomaticGeneration, diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 114502f8c..f4fbb0f7d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -24,6 +24,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" #include "slic3r/GUI/Gizmos/GLGizmoText.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp" #include "libslic3r/format.hpp" #include "libslic3r/Model.hpp" @@ -175,6 +176,9 @@ void GLGizmosManager::switch_gizmos_icon_filename() case(EType::MeshBoolean): gizmo->set_icon_filename(m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg"); break; + case (EType::Measure): + gizmo->set_icon_filename(m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg"); + break; } } @@ -211,6 +215,7 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam)); m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation)); + m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify)); //m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++)); //m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++)); @@ -377,6 +382,7 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const void GLGizmosManager::set_hover_id(int id) { + if (m_current == EType::Measure) { return; } if (!m_enabled || m_current == Undefined) return; @@ -678,6 +684,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Text) return dynamic_cast(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Measure) + return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == Cut) return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == MeshBoolean) @@ -810,7 +818,11 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // when control is down we allow scene pan and rotation even when clicking over some object bool control_down = evt.CmdDown(); - + if (m_current != Undefined) { + // check if gizmo override method could be slower than simple call virtual function + // &m_gizmos[m_current]->on_mouse != &GLGizmoBase::on_mouse && + m_gizmos[m_current]->on_mouse(evt); + } // mouse anywhere if (evt.Moving()) { m_tooltip = update_hover_state(mouse_pos); @@ -933,21 +945,23 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // the gizmo got the event and took some action, there is no need to do anything more processed = true; else if (!selection.is_empty() && grabber_contains_mouse()) { - update_data(); - selection.start_dragging(); - start_dragging(); + if (m_current != Measure) { + update_data(); + selection.start_dragging(); + start_dragging(); - // Let the plater know that the dragging started - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); + // Let the plater know that the dragging started + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED)); - if (m_current == Flatten) { - // Rotate the object so the normal points downward: - m_parent.do_flatten(get_flattening_normal(), L("Tool-Lay on Face")); - // BBS - //wxGetApp().obj_manipul()->set_dirty(); + if (m_current == Flatten) { + // Rotate the object so the normal points downward: + m_parent.do_flatten(get_flattening_normal(), L("Tool-Lay on Face")); + // BBS + // wxGetApp().obj_manipul()->set_dirty(); + } + + m_parent.set_as_dirty(); } - - m_parent.set_as_dirty(); processed = true; } } @@ -1079,9 +1093,10 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) // key ESC case WXK_ESCAPE: { - if (m_current != Undefined) - { - if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) + if (m_current != Undefined) { + if (m_current == Measure && gizmo_event(SLAGizmoEventType::Escape)) { + // do nothing + } else if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) reset_all_states(); processed = true; @@ -1116,13 +1131,11 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) //case WXK_BACK: - //case WXK_DELETE: - //{ - // if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete)) - // processed = true; - - // break; - //} + case WXK_DELETE: { + if ((m_current == Cut || m_current == Measure) && gizmo_event(SLAGizmoEventType::Delete)) + processed = true; + break; + } //case 'A': //case 'a': //{ @@ -1226,7 +1239,12 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) processed = true; } } - + if (m_current == Measure) { + if (keyCode == WXK_CONTROL) + gizmo_event(SLAGizmoEventType::CtrlUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + else if (keyCode == WXK_SHIFT) + gizmo_event(SLAGizmoEventType::ShiftUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + } // if (processed) // m_parent.set_cursor(GLCanvas3D::Standard); } @@ -1305,6 +1323,11 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) // force extra frame to automatically update window size wxGetApp().imgui()->set_requires_extra_frame(); } + } else if (m_current == Measure) { + if (keyCode == WXK_CONTROL) + gizmo_event(SLAGizmoEventType::CtrlDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + else if (keyCode == WXK_SHIFT) + gizmo_event(SLAGizmoEventType::ShiftDown, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); } }