diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a9acd2db5..c682af16c 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -650,6 +650,16 @@ private: friend class ModelVolume; }; +struct RaycastResult +{ + Vec2d mouse_position = Vec2d::Zero(); + int mesh_id = -1; + Vec3f hit = Vec3f::Zero(); + Vec3f normal = Vec3f::Zero(); + + template void serialize(Archive &ar) { ar(mouse_position, mesh_id, hit, normal); } +}; + struct TextInfo { std::string m_font_name; @@ -658,9 +668,18 @@ struct TextInfo bool m_bold = true; bool m_italic = false; float m_thickness = 2.f; + float m_embeded_depth = 0.f; + float m_rotate_angle = 0; + float m_text_gap = 0.f; + bool m_is_surface_text = false; + bool m_keep_horizontal = false; std::string m_text; - template void serialize(Archive &ar) { ar(m_font_name, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_text); } + RaycastResult m_rr; + + template void serialize(Archive &ar) { + ar(m_font_name, m_font_size, m_curr_font_idx, m_bold, m_italic, m_thickness, m_embeded_depth, m_rotate_angle, m_text_gap, m_is_surface_text, m_keep_horizontal, m_text, m_rr); + } }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. @@ -914,7 +933,8 @@ private: ObjectBase(other), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) + supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), + m_text_info(other.m_text_info) { assert(this->id().valid()); assert(this->config.id().valid()); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 81410bd88..82a5adcbd 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2170,52 +2170,54 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name #endif /* _DEBUG */ } -void ObjectList::load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool center) +int ObjectList::load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp) { wxDataViewItem item = GetSelection(); // we can add volumes for Object or Instance if (!item || !(m_objects_model->GetItemType(item) & (itObject | itInstance))) - return; + return -1; const int obj_idx = m_objects_model->GetObjectIdByItem(item); - if (obj_idx < 0) return; + if (obj_idx < 0) + return -1; // Get object item, if Instance is selected if (m_objects_model->GetItemType(item) & itInstance) item = m_objects_model->GetItemById(obj_idx); - take_snapshot("Load Mesh Part"); - ModelObject* mo = (*m_objects)[obj_idx]; + Geometry::Transformation instance_transformation = mo->instances[0]->get_transformation(); + // apply the instance transform to all volumes and reset instance transform except the offset apply_object_instance_transfrom_to_all_volumes(mo); - double old_top_position = mo->mesh().bounding_box().max(2) - mo->instances[0]->get_offset().z(); - ModelVolume* mv = mo->add_volume(mesh); - Vec3d offset = Vec3d(0, 0, old_top_position + mv->get_offset(Axis::Z)); - mv->set_offset(offset); + ModelVolume *mv = mo->add_volume(mesh); mv->name = name.ToStdString(); if (!text_info.m_text.empty()) mv->set_text_info(text_info); - std::vector volumes; - volumes.push_back(mv); - wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume* volume) { - return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); }); + if (!is_temp) { + std::vector volumes; + volumes.push_back(mv); + wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume *volume) { + return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); + }); - wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); + wxGetApp().plater()->get_view3D_canvas3D()->update_instance_printable_state_for_object((size_t) obj_idx); - if (items.size() > 1) { - m_selection_mode = smVolume; - m_last_selected_item = wxDataViewItem(nullptr); + if (items.size() > 1) { + m_selection_mode = smVolume; + m_last_selected_item = wxDataViewItem(nullptr); + } + select_items(items); + + selection_changed(); } - select_items(items); - - selection_changed(); //BBS: notify partplate the modify notify_instance_updated(obj_idx); + return mo->volumes.size() - 1; } //BBS diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index a06482f99..a69e4f63e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -284,7 +284,7 @@ public: void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true); // BBS void switch_to_object_process(); - void load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool center = true); + int load_mesh_part(const TriangleMesh &mesh, const wxString &name, const TextInfo &text_info, bool is_temp); void del_object(const int obj_idx, bool refresh_immediately = true); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp index 4841b8e52..0a0f3ff76 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp @@ -20,6 +20,7 @@ #define IMGUI_DEFINE_MATH_OPERATORS #endif #include +#include "libslic3r/SVG.hpp" namespace Slic3r { namespace GUI { @@ -29,6 +30,99 @@ static const wxColour FONT_TEXTURE_FG = *wxWHITE; static const int FONT_SIZE = 12; static const float SELECTABLE_INNER_OFFSET = 8.0f; +class Line_3D +{ +public: + Line_3D(Vec3d i_a, Vec3d i_b) : a(i_a), b(i_b) {} + + double length() { return (b - a).cast().norm(); } + + Vec3d vector() + { + Vec3d new_vec = b - a; + new_vec.normalize(); + return new_vec; + } + + void reverse() { std::swap(this->a, this->b); } + + Vec3d a; + Vec3d b; +}; + +class Polygon_3D +{ +public: + Polygon_3D(const std::vector &points) : m_points(points) {} + + std::vector get_lines() + { + std::vector lines; + lines.reserve(m_points.size()); + if (m_points.size() > 2) { + for (int i = 0; i < m_points.size() - 1; ++i) { lines.push_back(Line_3D(m_points[i], m_points[i + 1])); } + lines.push_back(Line_3D(m_points.back(), m_points.front())); + } + return lines; + } + std::vector m_points; +}; + +// for debug +void export_regions_to_svg(const Point &point, const Polygons &polylines) +{ + std::string path = "D:/svg_profiles/text_poly.svg"; + //BoundingBox bbox = get_extents(polylines); + SVG svg(path.c_str()); + svg.draw(polylines, "green"); + svg.draw(point, "red", 5e6); +} + +int preNUm(unsigned char byte) +{ + unsigned char mask = 0x80; + int num = 0; + for (int i = 0; i < 8; i++) { + if ((byte & mask) == mask) { + mask = mask >> 1; + num++; + } else { + break; + } + } + return num; +} + +// https://www.jianshu.com/p/a83d398e3606 +bool get_utf8_sub_strings(char *data, int len, std::vector &out_strs) +{ + out_strs.clear(); + std::string str = std::string(data); + + int num = 0; + int i = 0; + while (i < len) { + if ((data[i] & 0x80) == 0x00) { + out_strs.emplace_back(str.substr(i, 1)); + i++; + continue; + } else if ((num = preNUm(data[i])) > 2) { + int start = i; + i++; + for (int j = 0; j < num - 1; j++) { + if ((data[i] & 0xc0) != 0x80) { return false; } + i++; + } + out_strs.emplace_back(str.substr(start, i - start)); + } else { + return false; + } + } + return true; +} + +/////////////////////// +/// GLGizmoText start GLGizmoText::GLGizmoText(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) { @@ -48,6 +142,11 @@ bool GLGizmoText::on_init() update_font_texture(); m_scale = m_imgui->get_font_size(); m_shortcut_key = WXK_CONTROL_T; + + reset_text_info(); + + m_desc["rotate_text_caption"] = _L("Shift + Mouse movement"); + m_desc["rotate_text"] = _L("Rotate preview text"); return true; } @@ -86,30 +185,149 @@ void GLGizmoText::update_font_texture() m_combo_height = m_imgui->scaled(32.f / 15.f); } +bool GLGizmoText::is_mesh_point_clipped(const Vec3d &point, const Transform3d &trafo) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + +BoundingBoxf3 GLGizmoText::bounding_box() const +{ + BoundingBoxf3 ret; + const Selection & selection = m_parent.get_selection(); + const Selection::IndicesList &idxs = selection.get_volume_idxs(); + for (unsigned int i : idxs) { + const GLVolume *volume = selection.get_volume(i); + if (!volume->is_modifier) ret.merge(volume->transformed_convex_hull_bounding_box()); + } + return ret; +} + +bool GLGizmoText::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + std::string text = std::string(m_text); + if (text.empty()) + return false; + + const ModelObject * mo = m_c->selection_info()->model_object(); + if (mo == nullptr) + return false; + + const Selection & selection = m_parent.get_selection(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Camera & camera = wxGetApp().plater()->get_camera(); + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + for (const ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + } + } + if (action == SLAGizmoEventType::Moving) { + if (shift_down) { + float angle = m_rotate_angle + 0.5 * (m_mouse_position - mouse_position).y(); + while (angle < 0) + angle += 360; + + while (angle >= 360) + angle -= 360; + + m_rotate_angle = angle; + m_shift_down = true; + } else { + m_shift_down = false; + m_origin_mouse_position = mouse_position; + } + m_mouse_position = mouse_position; + } + else if (action == SLAGizmoEventType::LeftDown) { + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + Vec3f closest_normal = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + int closest_hit_mesh_id = -1; + + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { + if (mesh_id == m_preview_text_volume_id) + continue; + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, trafo_matrices[mesh_id], camera, hit, normal, + m_c->object_clipper()->get_clipping_plane(), &facet)) { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) + continue; + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position() - trafo_matrices[mesh_id] * hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + closest_normal = normal; + } + } + } + + if (closest_hit == Vec3f::Zero() && closest_normal == Vec3f::Zero()) + return false; + + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_normal}; + + Plater *plater = wxGetApp().plater(); + if (!plater) + return false; + + ModelObject *model_object = selection.get_model()->objects[m_object_idx]; + if (m_preview_text_volume_id > 0) { + model_object->delete_volume(m_preview_text_volume_id); + plater->update(); + m_preview_text_volume_id = -1; + } + + m_is_modify = true; + generate_text_volume(false); + plater->update(); + } + + return true; +} + void GLGizmoText::on_set_state() { - if (m_state == EState::On && m_parent.get_selection().is_single_volume()) { - ModelVolume *model_volume = get_selected_single_volume(m_object_idx, m_volume_idx); - TextInfo text_info = model_volume->get_text_info(); - if (!text_info.m_text.empty()) { - m_font_name = text_info.m_font_name; - m_font_size = text_info.m_font_size; - m_curr_font_idx = text_info.m_curr_font_idx; - m_bold = text_info.m_bold; - m_italic = text_info.m_italic; - m_thickness = text_info.m_thickness; - strcpy(m_text, text_info.m_text.c_str()); - m_is_modify = true; + if (m_state == EState::On) { + if (m_parent.get_selection().is_single_volume()) { + ModelVolume *model_volume = get_selected_single_volume(m_object_idx, m_volume_idx); + TextInfo text_info = model_volume->get_text_info(); + if (!text_info.m_text.empty()) { + load_from_text_info(text_info); + m_is_modify = true; + } } } else if (m_state == EState::Off) { reset_text_info(); + delete_temp_preview_text_volume(); + m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); } } CommonGizmosDataID GLGizmoText::on_get_requirements() const { - return CommonGizmosDataID::SelectionInfo; + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::ObjectClipper)); } std::string GLGizmoText::on_get_name() const @@ -134,7 +352,58 @@ bool GLGizmoText::on_is_activable() const void GLGizmoText::on_render() { - // TODO: + std::string text = std::string(m_text); + if (text.empty()) { + delete_temp_preview_text_volume(); + return; + } + + ModelObject *mo = nullptr; + mo = m_c->selection_info()->model_object(); + + if (mo == nullptr) { + const Selection &selection = m_parent.get_selection(); + mo = selection.get_model()->objects[m_object_idx]; + } + + if (mo == nullptr) { + BOOST_LOG_TRIVIAL(info) << boost::format("Text: selected object is null"); + return; + } + + // First check that the mouse pointer is on an object. + const Selection & selection = m_parent.get_selection(); + const ModelInstance *mi = mo->instances[0]; + Plater *plater = wxGetApp().plater(); + if (!plater) + return; + + if (!m_is_modify) { + const Camera &camera = wxGetApp().plater()->get_camera(); + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + for (const ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + } + // Raycast and return if there's no hit. + bool position_changed = update_raycast_cache(m_shift_down ? m_origin_mouse_position : m_parent.get_local_mouse_position(), camera, trafo_matrices); + + if (m_rr.mesh_id == -1) { + delete_temp_preview_text_volume(); + return; + } + + if (!position_changed && !m_need_update_text && !m_shift_down) + return; + } + + delete_temp_preview_text_volume(); + + if (m_is_modify && !m_need_update_text) + return; + + generate_text_volume(); + plater->update(); } void GLGizmoText::on_render_for_picking() @@ -236,14 +505,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) if ((object_idx != m_object_idx || (object_idx == m_object_idx && volume_idx != m_volume_idx)) && model_volume) { TextInfo text_info = model_volume->get_text_info(); - m_font_name = text_info.m_font_name; - m_font_size = text_info.m_font_size; - m_curr_font_idx = text_info.m_curr_font_idx; - m_bold = text_info.m_bold; - m_italic = text_info.m_italic; - m_thickness = text_info.m_thickness; - strcpy(m_text, text_info.m_text.c_str()); - + load_from_text_info(text_info); m_is_modify = true; m_volume_idx = volume_idx; m_object_idx = object_idx; @@ -268,7 +530,8 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) float size_cap = m_imgui->calc_text_size(_L("Size")).x; float thickness_cap = m_imgui->calc_text_size(_L("Thickness")).x; float input_cap = m_imgui->calc_text_size(_L("Input text")).x; - float caption_size = std::max(std::max(font_cap, size_cap), std::max(thickness_cap, input_cap)) + space_size + ImGui::GetStyle().WindowPadding.x; + float depth_cap = m_imgui->calc_text_size(_L("Embeded depth")).x; + float caption_size = std::max(std::max(font_cap, size_cap), std::max(depth_cap, input_cap)) + space_size + ImGui::GetStyle().WindowPadding.x; float input_text_size = m_imgui->scaled(12.0f); float button_size = ImGui::GetFrameHeight(); @@ -321,6 +584,7 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) m_curr_font_idx = idx; m_font_name = m_textures[m_curr_font_idx].font_name; ImGui::CloseCurrentPopup(); + m_need_update_text = true; } if (is_selected) { ImGui::SetItemDefaultFocus(); @@ -337,7 +601,8 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) m_imgui->text(_L("Size")); ImGui::SameLine(caption_size); ImGui::PushItemWidth(input_size); - ImGui::InputFloat("###font_size", &m_font_size, 0.0f, 0.0f, "%.2f"); + if(ImGui::InputFloat("###font_size", &m_font_size, 0.0f, 0.0f, "%.2f")) + m_need_update_text = true; if (m_font_size < 3.0f)m_font_size = 3.0f; ImGui::SameLine(); @@ -345,13 +610,17 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {1.0f * currt_scale, 1.0f * currt_scale }); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0f * currt_scale); push_button_style(m_bold); - if (ImGui::ImageButton(m_is_dark_mode ? normal_B_dark : normal_B, { button_size - 2 * ImGui::GetStyle().FramePadding.x, button_size - 2 * ImGui::GetStyle().FramePadding.y })) + if (ImGui::ImageButton(m_is_dark_mode ? normal_B_dark : normal_B, {button_size - 2 * ImGui::GetStyle().FramePadding.x, button_size - 2 * ImGui::GetStyle().FramePadding.y})) { m_bold = !m_bold; + m_need_update_text = true; + } pop_button_style(); ImGui::SameLine(); push_button_style(m_italic); - if (ImGui::ImageButton(m_is_dark_mode ? normal_T_dark : normal_T, { button_size - 2 * ImGui::GetStyle().FramePadding.x, button_size - 2 * ImGui::GetStyle().FramePadding.y })) + if (ImGui::ImageButton(m_is_dark_mode ? normal_T_dark : normal_T, {button_size - 2 * ImGui::GetStyle().FramePadding.x, button_size - 2 * ImGui::GetStyle().FramePadding.y})) { m_italic = !m_italic; + m_need_update_text = true; + } pop_button_style(); ImGui::PopStyleVar(3); @@ -359,62 +628,76 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) m_imgui->text(_L("Thickness")); ImGui::SameLine(caption_size); ImGui::PushItemWidth(list_width); - ImGui::InputFloat("###text_thickness", &m_thickness,0.0f, 0.0f, "%.2f"); - if (m_thickness < 0.1f)m_thickness = 0.1f; + if(ImGui::InputFloat("###text_thickness", &m_thickness,0.0f, 0.0f, "%.2f")) + m_need_update_text = true; + if (m_thickness < 0.1f) + m_thickness = 0.1f; + + const float sliders_width = m_imgui->scaled(7.0f); + const float slider_icon_width = m_imgui->get_slider_icon_size().x; + const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_width + space_size; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Text Gap")); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(list_width); + if (m_imgui->bbl_slider_float_style("##text_gap", &m_text_gap, -100, 100, "%.2f", 1.0f, true)) + m_need_update_text = true; + ImGui::SameLine(drag_left_width + list_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + if (ImGui::BBLDragFloat("##text_gap_input", &m_text_gap, 0.05f, 0.0f, 0.0f, "%.2f")) + m_need_update_text = true; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Angle")); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(list_width); + if (m_imgui->bbl_slider_float_style("##angle", &m_rotate_angle, 0, 360, "%.2f", 1.0f, true)) + m_need_update_text = true; + ImGui::SameLine(drag_left_width + list_width); + ImGui::PushItemWidth(1.5 * slider_icon_width); + if (ImGui::BBLDragFloat("##angle_input", &m_rotate_angle, 0.05f, 0.0f, 0.0f, "%.2f")) + m_need_update_text = true; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Embeded depth")); + ImGui::SameLine(caption_size); + ImGui::PushItemWidth(list_width); + if (ImGui::InputFloat("###text_embeded_depth", &m_embeded_depth, 0.0f, 0.0f, "%.2f")) + m_need_update_text = true; + if (m_embeded_depth < 0.f) + m_embeded_depth = 0.f; ImGui::AlignTextToFramePadding(); m_imgui->text(_L("Input text")); ImGui::SameLine(caption_size); ImGui::PushItemWidth(list_width); - ImGui::InputText("", m_text, sizeof(m_text)); + if(ImGui::InputText("", m_text, sizeof(m_text))) + m_need_update_text = true; + if (m_imgui->bbl_checkbox(_L("Surface"), m_is_surface_text)) + m_need_update_text = true; + + if (m_imgui->bbl_checkbox(_L("Keep horizontal"), m_keep_horizontal)) + m_need_update_text = true; + ImGui::Separator(); - m_imgui->disabled_begin(m_text[0] == '\0' || m_text[0] == ' '); - float offset = caption_size + list_width - m_imgui->calc_text_size(m_is_modify ? _L("Modify") : _L("Add")).x - space_size; - ImGui::Dummy({0.0, 0.0}); - ImGui::SameLine(offset); - bool btn_clicked = m_imgui->button(m_is_modify ? _L("Modify") : _L("Add")); - if (btn_clicked) { - m_imgui->disabled_end(); - GizmoImguiEnd(); - ImGui::PopStyleVar(2); - ImGuiWrapper::pop_toolbar_style(); - TextInfo text_info; - text_info.m_font_name = m_font_name; - text_info.m_font_size = m_font_size; - text_info.m_curr_font_idx = m_curr_font_idx; - text_info.m_bold = m_bold; - text_info.m_italic = m_italic; - text_info.m_thickness = m_thickness; - text_info.m_text = m_text; - TriangleMesh mesh; - load_text_shape(m_text, m_font_name.c_str(), m_font_size, m_thickness, m_bold, m_italic, mesh); - - if (m_is_modify) { - Plater *plater = wxGetApp().plater(); - if (!plater) - return; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f)); + float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y; + show_tooltip_information(x, get_cur_y); - plater->take_snapshot("Modify Text"); - const Selection &selection = m_parent.get_selection(); - ModelObject * model_object = selection.get_model()->objects[m_object_idx]; - ModelVolume * model_volume = model_object->volumes[m_volume_idx]; - ModelVolume * new_model_volume = model_object->add_volume(*model_volume, std::move(mesh)); - new_model_volume->set_text_info(text_info); - new_model_volume->set_transformation(model_volume->get_transformation()); - std::swap(model_object->volumes[m_volume_idx], model_object->volumes.back()); - model_object->delete_volume(model_object->volumes.size() - 1); - plater->update(); - } else { - ObjectList *obj_list = wxGetApp().obj_list(); - obj_list->load_mesh_part(mesh, "text_shape", text_info); - } + float f_scale = m_parent.get_gizmos_manager().get_layout_scale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale)); - return; - } - m_imgui->disabled_end(); + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Status:")); + float status_cap = m_imgui->calc_text_size(_L("Status:")).x + space_size + ImGui::GetStyle().WindowPadding.x; + ImGui::SameLine(); + m_imgui->text(m_is_modify ? _L("Modify") : _L("Add")); + ImGui::PopStyleVar(2); #if 0 ImGuiIO& io = ImGui::GetIO(); @@ -430,6 +713,37 @@ void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) ImGuiWrapper::pop_toolbar_style(); } +void GLGizmoText::show_tooltip_information(float x, float y) +{ + std::array info_array = std::array{"rotate_text"}; + float caption_max = 0.f; + for (const auto &t : info_array) { caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); } + + 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 : info_array) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t)); + ImGui::EndTooltip(); + } + ImGui::PopStyleVar(2); +} + ModelVolume *GLGizmoText::get_selected_single_volume(int &out_object_idx, int &out_volume_idx) const { if (m_parent.get_selection().is_single_volume()) { @@ -453,8 +767,615 @@ void GLGizmoText::reset_text_info() m_italic = false; m_thickness = 2.f; strcpy(m_text, m_font_name.c_str()); + m_embeded_depth = 0.f; + m_rotate_angle = 0; + m_text_gap = 0.f; + m_is_surface_text = false; + m_keep_horizontal = false; + m_is_modify = false; } +bool GLGizmoText::update_text_positions(const std::vector& texts) +{ + std::vector text_lengths; + for (int i = 0; i < texts.size(); ++i) { + TriangleMesh mesh; + load_text_shape(texts[i].c_str(), m_font_name.c_str(), m_font_size, m_thickness + m_embeded_depth, m_bold, m_italic, mesh); + auto center = mesh.bounding_box().center(); + double half_x_length = center.x(); + text_lengths.emplace_back(half_x_length); + } + + int text_num = texts.size(); + m_position_points.clear(); + m_normal_points.clear(); + ModelObject *mo = m_c->selection_info()->model_object(); + if (m_is_modify) { + const Selection &selection = m_parent.get_selection(); + mo = selection.get_model()->objects[m_object_idx]; + } + if (mo == nullptr) + return false; + + const Selection & selection = m_parent.get_selection(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + std::vector rotate_trafo_matrices; + for (const ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + rotate_trafo_matrices.emplace_back(mi->get_transformation().get_matrix(true, false, true, true) * mv->get_matrix(true, false, true, true)); + } + } + + if (m_rr.mesh_id == -1) { + BOOST_LOG_TRIVIAL(info) << boost::format("Text: mrr_mesh_id is -1"); + return false; + } + + m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); + m_mouse_normal_world = rotate_trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.normal(0), m_rr.normal(1), m_rr.normal(2)); + + auto is_point_on_line = [](const Line &line, const Point &point) { + double value = abs((point.x() - line.a.x()) * (line.b.y() - line.a.y()) - (line.b.x() - line.a.x()) * (point.y() - line.a.y())); + bool cross_value = value < 1e9; + bool in_rectange = (std::min(line.a.x(), line.b.x()) - 1000) <= point.x() && point.x() <= (std::max(line.a.x(), line.b.x()) + 1000) && + (std::min(line.a.y(), line.b.y()) - 1000) <= point.y() && point.y() <= (std::max(line.a.y(), line.b.y()) + 1000); + return cross_value && in_rectange; + }; + + TriangleMesh slice_meshs; + int mesh_index = 0; + for (int i = 0; i < mo->volumes.size(); ++i) { + ModelVolume *mv = mo->volumes[i]; + if (mv->is_model_part()) { + if (mesh_index == m_rr.mesh_id) { + TriangleMesh vol_mesh(mv->mesh()); + vol_mesh.transform(mv->get_matrix()); + slice_meshs = vol_mesh; + break; + } + mesh_index++; + } + } + + ModelVolume* volume = mo->volumes[mesh_index]; + + Vec3d temp_position = m_mouse_position_world; + Vec3d temp_normal = m_mouse_normal_world; + + Vec3d cut_plane = Vec3d::UnitY(); + if (temp_normal != Vec3d::UnitZ()) { + Vec3d v_plane = temp_normal.cross(Vec3d::UnitZ()); + cut_plane = v_plane.cross(temp_normal); + } + + Transform3d rotate_trans; + rotate_trans.setIdentity(); + rotate_trans.rotate(Eigen::AngleAxisd(Geometry::deg2rad(m_rotate_angle), temp_normal)); + cut_plane = rotate_trans * cut_plane; + + m_cut_plane_dir = cut_plane; + + if (m_keep_horizontal && m_mouse_normal_world != Vec3d::UnitZ()) + m_cut_plane_dir = Vec3d::UnitZ(); + + if (!m_is_surface_text) { + m_position_points.resize(text_num); + m_normal_points.resize(text_num); + + Vec3d pos_dir = m_cut_plane_dir.cross(m_mouse_normal_world); + pos_dir.normalize(); + if (text_num % 2 == 1) { + m_position_points[text_num / 2] = m_mouse_position_world; + for (int i = 0; i < text_num / 2; ++i) { + double left_gap = text_lengths[text_num / 2 - i - 1] + m_text_gap + text_lengths[text_num / 2 - i]; + if (left_gap < 0) + left_gap = 0; + + double right_gap = text_lengths[text_num / 2 + i + 1] + m_text_gap + text_lengths[text_num / 2 + i]; + if (right_gap < 0) + right_gap = 0; + + m_position_points[text_num / 2 - 1 - i] = m_position_points[text_num / 2 - i] - left_gap * pos_dir; + m_position_points[text_num / 2 + 1 + i] = m_position_points[text_num / 2 + i] + right_gap * pos_dir; + } + } else { + for (int i = 0; i < text_num / 2; ++i) { + double left_gap = i == 0 ? (text_lengths[text_num / 2 - i - 1] + m_text_gap / 2) : + (text_lengths[text_num / 2 - i - 1] + m_text_gap + text_lengths[text_num / 2 - i]); + if (left_gap < 0) + left_gap = 0; + + double right_gap = i == 0 ? (text_lengths[text_num / 2 + i] + m_text_gap / 2) : + (text_lengths[text_num / 2 + i] + m_text_gap + text_lengths[text_num / 2 + i - 1]); + if (right_gap < 0) + right_gap = 0; + + if (i == 0) { + m_position_points[text_num / 2 - 1 - i] = m_mouse_position_world - left_gap * pos_dir; + m_position_points[text_num / 2 + i] = m_mouse_position_world + right_gap * pos_dir; + continue; + } + + m_position_points[text_num / 2 - 1 - i] = m_position_points[text_num / 2 - i] - left_gap * pos_dir; + m_position_points[text_num / 2 + i] = m_position_points[text_num / 2 + i - 1] + right_gap * pos_dir; + } + } + + for (int i = 0; i < text_num; ++i) { + m_normal_points[i] = m_mouse_normal_world; + } + + return true; + } + + double phi; + Vec3d rotation_axis; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(m_cut_plane_dir, Vec3d::UnitZ(), rotation_axis, phi, &rotation_matrix); + + Transform3d transfo1; + transfo1.setIdentity(); + transfo1.translate(-(mi->get_transformation().get_offset() + volume->get_transformation().get_offset())); + transfo1 = rotation_matrix * transfo1; + + Transform3d transfo2; + transfo2.setIdentity(); + transfo2.translate(mi->get_transformation().get_offset() + volume->get_transformation().get_offset()); + Transform3d transfo = transfo2 * transfo1; + + Vec3d click_point = transfo * temp_position; + + MeshSlicingParams slicing_params; + slicing_params.trafo = transfo * mi->get_transformation().get_matrix() /** volume->get_transformation().get_matrix()*/; + // generate polygons + const Polygons temp_polys = slice_mesh(slice_meshs.its, click_point.z(), slicing_params); + + m_mouse_position_world = click_point; + m_mouse_normal_world = transfo * temp_normal; + + m_mouse_position_world.x() *= 1e6; + m_mouse_position_world.y() *= 1e6; + + // for debug + //export_regions_to_svg(Point(m_mouse_position_world.x(), m_mouse_position_world.y()), temp_polys); + + Polygons polys = temp_polys; + + int index = 0; + Polygon hit_ploy; + for (const Polygon poly : polys) { + if (poly.points.size() == 0) + continue; + + Lines lines = poly.lines(); + for (int i = 0; i < lines.size(); ++i) { + Line line = lines[i]; + if (is_point_on_line(line, Point(m_mouse_position_world.x(), m_mouse_position_world.y()))) { + index = i; + hit_ploy = poly; + } + } + } + + if (hit_ploy.points.size() == 0) { + BOOST_LOG_TRIVIAL(info) << boost::format("Text: the hit polygon is null"); + return false; + } + + auto make_trafo_for_slicing = [](const Transform3d &trafo) -> Transform3d { + auto t = trafo; + static constexpr const double s = 1. / SCALING_FACTOR; + t.prescale(Vec3d(s, s, 1.)); + return t.cast(); + }; + transfo = make_trafo_for_slicing(transfo); + Transform3d transfo_inv = transfo.inverse(); + std::vector new_points; + for (int i = 0; i < hit_ploy.points.size(); ++i) { + new_points.emplace_back(transfo_inv * Vec3d(hit_ploy.points[i].x(), hit_ploy.points[i].y(), click_point.z())); + } + m_mouse_position_world = transfo_inv * m_mouse_position_world; + + Polygon_3D new_polygon(new_points); + m_position_points.resize(text_num); + if (text_num % 2 == 1) { + m_position_points[text_num / 2] = Vec3d(m_mouse_position_world.x(), m_mouse_position_world.y(), m_mouse_position_world.z()); + + std::vector lines = new_polygon.get_lines(); + Line_3D line = lines[index]; + { + int index1 = index; + double left_length = (m_mouse_position_world - line.a).cast().norm(); + int left_num = text_num / 2; + while (left_num > 0) { + double gap_length = (text_lengths[left_num] + m_text_gap + text_lengths[left_num - 1]); + if (gap_length < 0) + gap_length = 0; + + while (gap_length > left_length) { + gap_length -= left_length; + if (index1 == 0) + index1 = lines.size() - 1; + else + --index1; + left_length = lines[index1].length(); + } + + Vec3d direction = lines[index1].vector(); + direction.normalize(); + double distance_to_a = (left_length - gap_length); + Line_3D new_line = lines[index1]; + + double norm_value = direction.cast().norm(); + double deta_x = distance_to_a * direction.x() / norm_value; + double deta_y = distance_to_a * direction.y() / norm_value; + double deta_z = distance_to_a * direction.z() / norm_value; + Vec3d new_pos = new_line.a + Vec3d(deta_x, deta_y, deta_z); + left_num--; + m_position_points[left_num] = new_pos; + left_length = distance_to_a; + } + } + + { + int index2 = index; + double right_length = (line.b - m_mouse_position_world).cast().norm(); + int right_num = text_num / 2; + while (right_num > 0) { + double gap_length = (text_lengths[text_num - right_num] + m_text_gap + text_lengths[text_num - right_num - 1]); + if (gap_length < 0) + gap_length = 0; + + while (gap_length > right_length) { + gap_length -= right_length; + if (index2 == lines.size() - 1) + index2 = 0; + else + ++index2; + right_length = lines[index2].length(); + } + + Line_3D line2 = lines[index2]; + line2.reverse(); + Vec3d direction = line2.vector(); + direction.normalize(); + double distance_to_b = (right_length - gap_length); + Line_3D new_line = lines[index2]; + + double norm_value = direction.cast().norm(); + double deta_x = distance_to_b * direction.x() / norm_value; + double deta_y = distance_to_b * direction.y() / norm_value; + double deta_z = distance_to_b * direction.z() / norm_value; + Vec3d new_pos = new_line.b + Vec3d(deta_x, deta_y, deta_z); + m_position_points[text_num - right_num] = new_pos; + right_length = distance_to_b; + right_num--; + } + } + } + else { + for (int i = 0; i < text_num / 2; ++i) { + std::vector lines = new_polygon.get_lines(); + Line_3D line = lines[index]; + { + int index1 = index; + double left_length = (m_mouse_position_world - line.a).cast().norm(); + int left_num = text_num / 2; + for (int i = 0; i < text_num / 2; ++i) { + double gap_length = 0; + if (i == 0) { + gap_length = m_text_gap / 2 + text_lengths[text_num / 2 - 1 - i]; + } + else { + gap_length = text_lengths[text_num / 2 - i] + m_text_gap + text_lengths[text_num / 2 - 1 - i]; + } + if (gap_length < 0) + gap_length = 0; + + while (gap_length > left_length) { + gap_length -= left_length; + if (index1 == 0) + index1 = lines.size() - 1; + else + --index1; + left_length = lines[index1].length(); + } + + Vec3d direction = lines[index1].vector(); + direction.normalize(); + double distance_to_a = (left_length - gap_length); + Line_3D new_line = lines[index1]; + + double norm_value = direction.cast().norm(); + double deta_x = distance_to_a * direction.x() / norm_value; + double deta_y = distance_to_a * direction.y() / norm_value; + double deta_z = distance_to_a * direction.z() / norm_value; + Vec3d new_pos = new_line.a + Vec3d(deta_x, deta_y,deta_z); + + m_position_points[text_num / 2 - 1 - i] = new_pos; + left_length = distance_to_a; + } + } + + { + int index2 = index; + double right_length = (line.b - m_mouse_position_world).cast().norm(); + int right_num = text_num / 2; + double gap_length = 0; + for (int i = 0; i < text_num / 2; ++i) { + double gap_length = 0; + if (i == 0) { + gap_length = m_text_gap / 2 + text_lengths[text_num / 2 + i]; + } else { + gap_length = text_lengths[text_num / 2 + i] + m_text_gap + text_lengths[text_num / 2 + i - 1]; + } + if (gap_length < 0) + gap_length = 0; + + while (gap_length > right_length) { + gap_length -= right_length; + if (index2 == lines.size() - 1) + index2 = 0; + else + ++index2; + right_length = lines[index2].length(); + } + + Line_3D line2 = lines[index2]; + line2.reverse(); + Vec3d direction = line2.vector(); + direction.normalize(); + double distance_to_b = (right_length - gap_length); + Line_3D new_line = lines[index2]; + + double norm_value = direction.cast().norm(); + double deta_x = distance_to_b * direction.x() / norm_value; + double deta_y = distance_to_b * direction.y() / norm_value; + double deta_z = distance_to_b * direction.z() / norm_value; + Vec3d new_pos = new_line.b + Vec3d(deta_x, deta_y, deta_z); + m_position_points[text_num / 2 + i] = new_pos; + right_length = distance_to_b; + } + } + } + } + + TriangleMesh mesh = slice_meshs; + std::vector mesh_values(m_position_points.size(), 1'000'000'000); + m_normal_points.resize(m_position_points.size()); + auto point_in_triangle_delete_area = [](const Vec3d &point, const Vec3d &point0, const Vec3d &point1, const Vec3d &point2) { + Vec3d p0_p = point - point0; + Vec3d p0_p1 = point1 - point0; + Vec3d p0_p2 = point2 - point0; + Vec3d p_p0 = point0 - point; + Vec3d p_p1 = point1 - point; + Vec3d p_p2 = point2 - point; + + double s = p0_p1.cross(p0_p2).norm(); + double s0 = p_p0.cross(p_p1).norm(); + double s1 = p_p1.cross(p_p2).norm(); + double s2 = p_p2.cross(p_p0).norm(); + + return abs(s0 + s1 + s2 - s); + }; + for (int i = 0; i < m_position_points.size(); ++i) { + for (auto indice : mesh.its.indices) { + stl_vertex stl_point0 = mesh.its.vertices[indice[0]]; + stl_vertex stl_point1 = mesh.its.vertices[indice[1]]; + stl_vertex stl_point2 = mesh.its.vertices[indice[2]]; + + Vec3d point0 = Vec3d(stl_point0[0], stl_point0[1], stl_point0[2]); + Vec3d point1 = Vec3d(stl_point1[0], stl_point1[1], stl_point1[2]); + Vec3d point2 = Vec3d(stl_point2[0], stl_point2[1], stl_point2[2]); + + point0 = mi->get_transformation().get_matrix() * point0; + point1 = mi->get_transformation().get_matrix() * point1; + point2 = mi->get_transformation().get_matrix() * point2; + + double abs_area = point_in_triangle_delete_area(m_position_points[i], point0, point1, point2); + if (mesh_values[i] > abs_area) { + mesh_values[i] = abs_area; + + Vec3d s1 = point1 - point0; + Vec3d s2 = point2 - point0; + m_normal_points[i] = s1.cross(s2); + m_normal_points[i].normalize(); + } + } + } + return true; +} + +TriangleMesh GLGizmoText::get_text_mesh(const char* text_str, const Vec3d &position, const Vec3d &normal, const Vec3d& text_up_dir) +{ + TriangleMesh mesh; + load_text_shape(text_str, m_font_name.c_str(), m_font_size, m_thickness + m_embeded_depth, m_bold, m_italic, mesh); + + auto center = mesh.bounding_box().center(); + double mesh_offset = center.z(); + + mesh.translate(-center.x(), -center.y(), -center.z()); + + double phi; + Vec3d rotation_axis; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(Vec3d::UnitZ(), normal, rotation_axis, phi, &rotation_matrix); + mesh.rotate(phi, rotation_axis); + + auto project_on_plane = [](const Vec3d& dir, const Vec3d& plane_normal) -> Vec3d { + return dir - (plane_normal.dot(dir) * plane_normal.dot(plane_normal)) * plane_normal; + }; + + Vec3d old_text_dir = Vec3d::UnitY(); + old_text_dir = rotation_matrix * old_text_dir; + Vec3d new_text_dir = project_on_plane(text_up_dir, normal); + new_text_dir.normalize(); + Geometry::rotation_from_two_vectors(old_text_dir, new_text_dir, rotation_axis, phi, &rotation_matrix); + mesh.rotate(phi, rotation_axis); + + const Selection & selection = m_parent.get_selection(); + ModelObject * model_object = selection.get_model()->objects[m_object_idx]; + Geometry::Transformation instance_transformation = model_object->instances[0]->get_transformation(); + Vec3d offset = position - instance_transformation.get_offset(); + offset = offset + mesh_offset * normal; + offset = offset - m_embeded_depth * normal; + mesh.translate(offset.x(), offset.y(), offset.z()); + + return mesh; +} + +bool GLGizmoText::update_raycast_cache(const Vec2d &mouse_position, const Camera &camera, const std::vector &trafo_matrices) +{ + if (m_rr.mouse_position == mouse_position) { + return false; + } + + if (m_is_modify) + return false; + + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + Vec3f closest_nromal = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + int closest_hit_mesh_id = -1; + + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { + if (m_preview_text_volume_id != -1 && mesh_id == int(trafo_matrices.size()) - 1) + continue; + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, trafo_matrices[mesh_id], camera, hit, normal, m_c->object_clipper()->get_clipping_plane(), + &facet)) { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) + continue; + + double hit_squared_distance = (camera.get_position() - trafo_matrices[mesh_id] * hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + closest_nromal = normal; + } + } + } + + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_nromal}; + return true; +} + +void GLGizmoText::generate_text_volume(bool is_temp) +{ + std::string text = std::string(m_text); + if (text.empty()) + return; + + std::vector alphas; + if (!get_utf8_sub_strings(m_text, strlen(m_text), alphas)) { + BOOST_LOG_TRIVIAL(info) << boost::format("Text: input text is not utf8"); + return; + } + + update_text_positions(alphas); + + if (m_position_points.size() == 0) + return; + + TriangleMesh mesh; + for (int i = 0; i < alphas.size(); ++i) { + TriangleMesh sub_mesh = get_text_mesh(alphas[i].c_str(), m_position_points[i], m_normal_points[i], m_cut_plane_dir); + mesh.merge(sub_mesh); + } + + Plater *plater = wxGetApp().plater(); + if (!plater) + return; + + TextInfo text_info = get_text_info(); + if (m_is_modify && m_need_update_text) { + plater->take_snapshot("Modify Text"); + const Selection &selection = m_parent.get_selection(); + ModelObject * model_object = selection.get_model()->objects[m_object_idx]; + ModelVolume * model_volume = model_object->volumes[m_volume_idx]; + ModelVolume * new_model_volume = model_object->add_volume(std::move(mesh)); + new_model_volume->set_text_info(text_info); + new_model_volume->name = model_volume->name; + if (model_volume->config.option("extruder")) + new_model_volume->config.set("extruder", model_volume->config.extruder()); + + std::swap(model_object->volumes[m_volume_idx], model_object->volumes.back()); + model_object->delete_volume(model_object->volumes.size() - 1); + plater->update(); + } else { + if (m_need_update_text) + plater->take_snapshot("Add Text"); + ObjectList *obj_list = wxGetApp().obj_list(); + int volume_id = obj_list->load_mesh_part(mesh, "text_shape", text_info, is_temp); + m_preview_text_volume_id = is_temp ? volume_id : -1; + } + m_need_update_text = false; +} + +void GLGizmoText::delete_temp_preview_text_volume() +{ + const Selection &selection = m_parent.get_selection(); + if (m_preview_text_volume_id > 0) { + ModelObject *model_object = selection.get_model()->objects[m_object_idx]; + if (m_preview_text_volume_id < model_object->volumes.size()) { + Plater *plater = wxGetApp().plater(); + if (!plater) + return; + + model_object->delete_volume(m_preview_text_volume_id); + + plater->update(); + } + m_preview_text_volume_id = -1; + } +} + +TextInfo GLGizmoText::get_text_info() +{ + TextInfo text_info; + text_info.m_font_name = m_font_name; + text_info.m_font_size = m_font_size; + text_info.m_curr_font_idx = m_curr_font_idx; + text_info.m_bold = m_bold; + text_info.m_italic = m_italic; + text_info.m_thickness = m_thickness; + text_info.m_text = m_text; + text_info.m_rr = m_rr; + text_info.m_embeded_depth = m_embeded_depth; + text_info.m_rotate_angle = m_rotate_angle; + text_info.m_text_gap = m_text_gap; + text_info.m_is_surface_text = m_is_surface_text; + text_info.m_keep_horizontal = m_keep_horizontal; + return text_info; +} + +void GLGizmoText::load_from_text_info(const TextInfo &text_info) +{ + m_font_name = text_info.m_font_name; + m_font_size = text_info.m_font_size; + m_curr_font_idx = text_info.m_curr_font_idx; + m_bold = text_info.m_bold; + m_italic = text_info.m_italic; + m_thickness = text_info.m_thickness; + strcpy(m_text, text_info.m_text.c_str()); + m_rr = text_info.m_rr; + m_embeded_depth = text_info.m_embeded_depth; + m_rotate_angle = text_info.m_rotate_angle; + m_text_gap = text_info.m_text_gap; + m_is_surface_text = text_info.m_is_surface_text; + m_keep_horizontal = text_info.m_keep_horizontal; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp index d1494f80e..9fee02ce0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp @@ -4,6 +4,8 @@ #include "GLGizmoBase.hpp" #include "slic3r/GUI/3DScene.hpp" #include "../GLTexture.hpp" +#include "../Camera.hpp" +#include "libslic3r/Model.hpp" namespace Slic3r { @@ -12,6 +14,7 @@ class ModelVolume; namespace GUI { +enum class SLAGizmoEventType : unsigned char; class GLGizmoText : public GLGizmoBase { private: @@ -23,10 +26,21 @@ private: bool m_bold = true; bool m_italic = false; float m_thickness = 2.f; + float m_embeded_depth = 0.f; + float m_rotate_angle = 0; + float m_text_gap = 0.f; + bool m_is_surface_text = false; + bool m_keep_horizontal = false; + mutable RaycastResult m_rr; + float m_combo_height = 0.0f; float m_combo_width = 0.0f; float m_scale; + Vec2d m_mouse_position = Vec2d::Zero(); + Vec2d m_origin_mouse_position = Vec2d::Zero(); + bool m_shift_down = false; + class TextureInfo { public: GLTexture* texture { nullptr }; @@ -42,8 +56,24 @@ private: std::vector m_font_names; bool m_is_modify = false; - int m_object_idx; - int m_volume_idx; + bool m_need_update_text = false; + + int m_object_idx = -1; + int m_volume_idx = -1; + + int m_preview_text_volume_id = -1; + + Vec3d m_mouse_position_world = Vec3d::Zero(); + Vec3d m_mouse_normal_world = Vec3d::Zero(); + + Vec3d m_cut_plane_dir = Vec3d::UnitZ(); + + std::vector m_position_points; + std::vector m_normal_points; + + // 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; public: GLGizmoText(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -51,6 +81,11 @@ public: void update_font_texture(); + bool gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down); + + bool is_mesh_point_clipped(const Vec3d &point, const Transform3d &trafo) const; + BoundingBoxf3 bounding_box() const; + protected: virtual bool on_init() override; virtual std::string on_get_name() const override; @@ -65,9 +100,20 @@ protected: virtual CommonGizmosDataID on_get_requirements() const override; virtual void on_render_input_window(float x, float y, float bottom_limit); + void show_tooltip_information(float x, float y); + private: ModelVolume *get_selected_single_volume(int& out_object_idx, int& out_volume_idx) const; void reset_text_info(); + bool update_text_positions(const std::vector& texts); + TriangleMesh get_text_mesh(const char* text_str, const Vec3d &position, const Vec3d &normal, const Vec3d &text_up_dir); + + bool update_raycast_cache(const Vec2d &mouse_position, const Camera &camera, const std::vector &trafo_matrices); + void generate_text_volume(bool is_temp = true); + void delete_temp_preview_text_volume(); + + TextInfo get_text_info(); + void load_from_text_info(const TextInfo &text_info); }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 438e062f8..f1ee1753e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -625,6 +625,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == MmuSegmentation) 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 return false; } @@ -757,7 +759,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // mouse anywhere if (evt.Moving()) { m_tooltip = update_hover_state(mouse_pos); - if (m_current == MmuSegmentation || m_current == FdmSupports) + if (m_current == MmuSegmentation || m_current == FdmSupports || m_current == Text) // BBS gizmo_event(SLAGizmoEventType::Moving, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); } else if (evt.LeftUp()) { @@ -870,7 +872,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_tooltip.clear(); if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == Text) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown())) // the gizmo got the event and took some action, there is no need to do anything more processed = true;