From b7549ae414c6c7cb94c581e2c8a8f915b3869fe2 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 28 Apr 2023 15:42:55 +0200 Subject: [PATCH] Add TextLines to add per glyph transformation --- src/libslic3r/Emboss.cpp | 159 +++++++--- src/libslic3r/Emboss.hpp | 38 ++- src/libslic3r/TextConfiguration.hpp | 6 +- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 140 +++------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 + .../GUI/Jobs/CreateFontStyleImagesJob.cpp | 3 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 82 +++++ src/slic3r/GUI/Jobs/EmbossJob.hpp | 5 + src/slic3r/GUI/TextLines.cpp | 285 ++++++++++++++++++ src/slic3r/GUI/TextLines.hpp | 46 +++ tests/libslic3r/test_emboss.cpp | 4 +- 12 files changed, 619 insertions(+), 155 deletions(-) create mode 100644 src/slic3r/GUI/TextLines.cpp create mode 100644 src/slic3r/GUI/TextLines.hpp diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 81a9154e12..c4eafbd8ea 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1207,57 +1207,87 @@ std::optional Emboss::letter2glyph(const FontFile &font, return priv::get_glyph(*font_info_opt, letter, flatness); } -ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, - const char *text, - const FontProp &font_prop, - std::function was_canceled) +namespace { + +ExPolygons letter2shapes( + wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt& font_info_cache) +{ + assert(font_with_cache.has_value()); + if (!font_with_cache.has_value()) + return {}; + + Glyphs &cache = *font_with_cache.cache; + const FontFile &font = *font_with_cache.font_file; + + if (letter == '\n') { + unsigned int font_index = font_prop.collection_number.value_or(0); + assert(priv::is_valid(font, font_index)); + const FontFile::Info &info = font.infos[font_index]; + int line_height = info.ascent - info.descent + info.linegap; + if (font_prop.line_gap.has_value()) + line_height += *font_prop.line_gap; + line_height = static_cast(line_height / SHAPE_SCALE); + + cursor.x() = 0; + cursor.y() -= line_height; + return {}; + } + if (letter == '\t') { + // '\t' = 4*space => same as imgui + const int count_spaces = 4; + const Glyph *space = priv::get_glyph(int(' '), font, font_prop, cache, font_info_cache); + if (space == nullptr) + return {}; + cursor.x() += count_spaces * space->advance_width; + return {}; + } + if (letter == '\r') + return {}; + + int unicode = static_cast(letter); + auto it = cache.find(unicode); + + // Create glyph from font file and cache it + const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : priv::get_glyph(unicode, font, font_prop, cache, font_info_cache); + if (glyph_ptr == nullptr) + return {}; + + // move glyph to cursor position + ExPolygons expolygons = glyph_ptr->shape; // copy + for (ExPolygon &expolygon : expolygons) + expolygon.translate(cursor); + + cursor.x() += glyph_ptr->advance_width; + return expolygons; +} + +// Check cancel every X letters in text +// Lower number - too much checks(slows down) +// Higher number - slows down response on cancelation +const int CANCEL_CHECK = 10; +} // namespace + +ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *text, const FontProp &font_prop, const std::function& was_canceled) { assert(font_with_cache.has_value()); - fontinfo_opt font_info_opt; - Point cursor(0, 0); - ExPolygons result; const FontFile& font = *font_with_cache.font_file; - unsigned int font_index = font_prop.collection_number.has_value()? - *font_prop.collection_number : 0; + unsigned int font_index = font_prop.collection_number.value_or(0); if (!priv::is_valid(font, font_index)) return {}; - const FontFile::Info& info = font.infos[font_index]; - Glyphs& cache = *font_with_cache.cache; + + unsigned counter = 0; + Point cursor(0, 0); + ExPolygons result; + fontinfo_opt font_info_cache; std::wstring ws = boost::nowide::widen(text); for (wchar_t wc: ws){ - if (wc == '\n') { - int line_height = info.ascent - info.descent + info.linegap; - if (font_prop.line_gap.has_value()) - line_height += *font_prop.line_gap; - line_height = static_cast(line_height / SHAPE_SCALE); - - cursor.x() = 0; - cursor.y() -= line_height; - continue; - } - if (wc == '\t') { - // '\t' = 4*space => same as imgui - const int count_spaces = 4; - const Glyph* space = priv::get_glyph(int(' '), font, font_prop, cache, font_info_opt); - if (space == nullptr) continue; - cursor.x() += count_spaces * space->advance_width; - continue; + if (++counter == CANCEL_CHECK) { + counter = 0; + if (was_canceled()) + return {}; } - if (wc == '\r') continue; - - int unicode = static_cast(wc); - // check cancelation only before unknown symbol - loading of symbol could be timeconsuming on slow computer and dificult fonts - auto it = cache.find(unicode); - if (it == cache.end() && was_canceled != nullptr && was_canceled()) return {}; - const Glyph *glyph_ptr = (it != cache.end())? &it->second : - priv::get_glyph(unicode, font, font_prop, cache, font_info_opt); - if (glyph_ptr == nullptr) continue; - - // move glyph to cursor position - ExPolygons expolygons = glyph_ptr->shape; // copy - for (ExPolygon &expolygon : expolygons) - expolygon.translate(cursor); - - cursor.x() += glyph_ptr->advance_width; + ExPolygons expolygons = letter2shapes(wc, cursor, font_with_cache, font_prop, font_info_cache); + if (expolygons.empty()) + continue; expolygons_append(result, std::move(expolygons)); } result = Slic3r::union_ex(result); @@ -1265,6 +1295,47 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, return result; } +std::vector Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled){ + assert(font_with_cache.has_value()); + const FontFile &font = *font_with_cache.font_file; + unsigned int font_index = font_prop.collection_number.value_or(0); + if (!priv::is_valid(font, font_index)) + return {}; + + unsigned counter = 0; + Point cursor(0, 0); + + std::vector result; + fontinfo_opt font_info_cache; + result.reserve(text.size()); + for (wchar_t letter : text) { + if (++counter == CANCEL_CHECK) { + counter = 0; + if (was_canceled()) + return {}; + } + result.emplace_back(letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)); + } + return result; +} + +unsigned Emboss::get_count_lines(const std::wstring& ws) +{ + if (ws.empty()) + return 0; + unsigned count = 1; + for (wchar_t wc : ws) + if (wc == '\n') + ++count; + return count; +} + +unsigned Emboss::get_count_lines(const std::string &text) +{ + std::wstring ws = boost::nowide::widen(text.c_str()); + return get_count_lines(ws); +} + void Emboss::apply_transformation(const FontProp &font_prop, Transform3d &transformation){ apply_transformation(font_prop.angle, font_prop.distance, transformation); } diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index fc0f0a0a3c..be2742f35a 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -112,7 +112,7 @@ namespace Emboss std::shared_ptr cache; FontFileWithCache() : font_file(nullptr), cache(nullptr) {} - FontFileWithCache(std::unique_ptr font_file) + explicit FontFileWithCache(std::unique_ptr font_file) : font_file(std::move(font_file)) , cache(std::make_shared()) {} @@ -151,7 +151,11 @@ namespace Emboss /// User defined property of the font /// Way to interupt processing /// Inner polygon cw(outer ccw) - ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop, std::function was_canceled = nullptr); + ExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function &was_canceled = []() {return false;}); + std::vector text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled = []() {return false;}); + + unsigned get_count_lines(const std::wstring &ws); + unsigned get_count_lines(const std::string &text); /// /// Fix duplicit points and self intersections in polygons. @@ -337,6 +341,36 @@ namespace Emboss } }; + class ProjectTransform : public IProjection + { + std::unique_ptr m_core; + Transform3d m_tr; + Transform3d m_tr_inv; + double z_scale; + public: + ProjectTransform(std::unique_ptr core, const Transform3d &tr) : m_core(std::move(core)), m_tr(tr) + { + m_tr_inv = m_tr.inverse(); + z_scale = (m_tr.linear() * Vec3d::UnitZ()).norm(); + } + + // Inherited via IProject + std::pair create_front_back(const Point &p) const override + { + auto [front, back] = m_core->create_front_back(p); + return std::make_pair(m_tr * front, m_tr * back); + } + Vec3d project(const Vec3d &point) const override{ + return m_core->project(point); + } + std::optional unproject(const Vec3d &p, double *depth = nullptr) const override { + auto res = m_core->unproject(m_tr_inv * p, depth); + if (depth != nullptr) + *depth *= z_scale; + return res; + } + }; + class OrthoProject3d : public Emboss::IProject3d { // size and direction of emboss for ortho projection diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 1c1ce77567..14a9645a07 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -60,6 +60,9 @@ struct FontProp // Select index of font in collection std::optional collection_number; + // Distiguish projection per glyph + bool per_glyph; + //enum class Align { // left, // right, @@ -96,8 +99,7 @@ struct FontProp /// /// Y size of text [in mm] /// Z size of text [in mm] - FontProp(float line_height = 10.f, float depth = 2.f) - : emboss(depth), size_in_mm(line_height), use_surface(false) + FontProp(float line_height = 10.f, float depth = 2.f) : emboss(depth), size_in_mm(line_height), use_surface(false), per_glyph(false) {} bool operator==(const FontProp& other) const { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 35e05f506c..42ab6e4f1f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -161,6 +161,8 @@ set(SLIC3R_GUI_SOURCES GUI/SendSystemInfoDialog.hpp GUI/SurfaceDrag.cpp GUI/SurfaceDrag.hpp + GUI/TextLines.cpp + GUI/TextLines.hpp GUI/BonjourDialog.cpp GUI/BonjourDialog.hpp GUI/ButtonsDescription.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index d029089c77..e684868c9e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -571,116 +571,24 @@ bool GLGizmoEmboss::on_init() std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } - -#include "libslic3r/TriangleMeshSlicer.hpp" -#include "libslic3r/Tesselate.hpp" - -namespace { - -GLModel create_model(const Polygons &polygons, float width_half = 0.5f, ColorRGBA color = ColorRGBA::DARK_GRAY()) -{ - assert(!polygons.empty()); - - // add a small positive offset to avoid z-fighting - float offset = static_cast(scale_(0.015f)); - Polygons polygons_expanded = expand(polygons, offset); - - // inspired by 3DScene.cpp void GLVolume::SinkingContours::update() - GLModel::Geometry init_data; - init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3}; - init_data.color = color; - - size_t count = count_points(polygons); - init_data.reserve_vertices(2 * count); - init_data.reserve_indices(2 * count); - - unsigned int vertices_counter = 0; - for (const Slic3r::Polygon &polygon : polygons_expanded) { - for (const Point &point : polygon.points) { - Vec2f point_d = unscale(point).cast(); - Vec3f vertex(point_d.x(), point_d.y(), width_half); - init_data.add_vertex(vertex); - vertex.z() *= -1; - init_data.add_vertex(vertex); - } - - auto points_count = static_cast(polygon.points.size()); - unsigned int prev_i = points_count - 1; - for (unsigned int i = 0; i < points_count; i++) { - // t .. top - // b .. bottom - unsigned int t1 = vertices_counter + prev_i * 2; - unsigned int b1 = t1 + 1; - unsigned int t2 = vertices_counter + i * 2; - unsigned int b2 = t2 + 1; - init_data.add_triangle(t1, b1, t2); - init_data.add_triangle(b2, t2, b1); - prev_i = i; - } - vertices_counter += 2 * points_count; - } - - assert(!init_data.is_empty()); - - GLModel gl_model; - gl_model.init_from(std::move(init_data)); - return gl_model; -} - -void slice_object(const Selection& selection) { - const GLVolume* gl_volume_ptr = selection.get_first_volume(); - if (gl_volume_ptr == nullptr) - return; - const GLVolume& gl_volume = *gl_volume_ptr; - const ModelObjectPtrs& objects = selection.get_model()->objects; - const ModelObject *mo_ptr = get_model_object(gl_volume, objects); - if (mo_ptr == nullptr) - return; - const ModelObject &mo = *mo_ptr; - const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); - - // contour transformation - auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); - Transform3d c_trafo = mv_trafo * rot; - Transform3d c_trafo_inv = c_trafo.inverse(); - - Polygons contours; - for (const ModelVolume* volume: mo.volumes){ - MeshSlicingParams slicing_params; - slicing_params.trafo = c_trafo_inv * volume->get_matrix(); - const Polygons polys = Slic3r::slice_mesh(volume->mesh().its, 0., slicing_params); - contours.insert(contours.end(), polys.begin(), polys.end()); - } - - const GLShaderProgram *shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - const Camera &camera = wxGetApp().plater()->get_camera(); - - const Transform3d &mi_trafo = gl_volume.get_instance_transformation().get_matrix(); - shader->start_using(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * mi_trafo * c_trafo); // * Geometry::translation_transform(m_shift)); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - create_model(contours).render(); - shader->stop_using(); -} - -} // namespace - - void GLGizmoEmboss::on_render() { // no volume selected if (m_volume == nullptr || get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr) return; - Selection &selection = m_parent.get_selection(); + const Selection &selection = m_parent.get_selection(); if (selection.is_empty()) return; - slice_object(selection); - // prevent get local coordinate system on multi volumes if (!selection.is_single_volume_or_modifier() && !selection.is_single_volume_instance()) return; + + const GLVolume *gl_volume_ptr = m_parent.get_selection().get_first_volume(); + if (gl_volume_ptr == nullptr) return; + + if (m_text_lines.is_init()) + m_text_lines.render(gl_volume_ptr->world_matrix()); + bool is_surface_dragging = m_surface_drag.has_value(); bool is_parent_dragging = m_parent.is_mouse_dragging(); // Do NOT render rotation grabbers when dragging object @@ -1246,6 +1154,9 @@ void GLGizmoEmboss::set_volume_by_selection() m_job_cancel = nullptr; } + if (tc.style.prop.per_glyph) + m_text_lines.init(m_parent.get_selection()); + m_text = tc.text; m_volume = volume; m_volume_id = volume->id(); @@ -3237,11 +3148,6 @@ void GLGizmoEmboss::draw_advanced() } } - if (exist_change) { - m_style_manager.clear_glyphs_cache(); - process(); - } - if (ImGui::Button(_u8L("Set text to face camera").c_str())) { assert(get_selected_volume(m_parent.get_selection()) == m_volume); const Camera &cam = wxGetApp().plater()->get_camera(); @@ -3251,6 +3157,30 @@ void GLGizmoEmboss::draw_advanced() } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Orient the text towards the camera.").c_str()); } + + ImGui::SameLine(); + bool *per_glyph = &font_prop.per_glyph; + if (ImGui::Checkbox("##PerGlyph", per_glyph)) { + if (*per_glyph) { + if (!m_text_lines.is_init()) + m_text_lines.init(m_parent.get_selection()); + } + exist_change = true; + } else if (ImGui::IsItemHovered()) { + if (*per_glyph) { + ImGui::SetTooltip("%s", _u8L("Set global orientation for whole text.").c_str()); + } else { + ImGui::SetTooltip("%s", _u8L("Set position and orientation of projection per Glyph.").c_str()); + if (!m_text_lines.is_init()) + m_text_lines.init(m_parent.get_selection()); + } + } else if (!*per_glyph && m_text_lines.is_init()) + m_text_lines.reset(); + + if (exist_change) { + m_style_manager.clear_glyphs_cache(); + process(); + } #ifdef ALLOW_DEBUG_MODE ImGui::Text("family = %s", (font_prop.family.has_value() ? font_prop.family->c_str() : diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 318917863e..845753d903 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/IconManager.hpp" #include "slic3r/GUI/SurfaceDrag.hpp" #include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/TextLines.hpp" #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/Utils/EmbossStyleManager.hpp" @@ -311,6 +312,9 @@ private: // cancel for previous update of volume to cancel finalize part std::shared_ptr> m_job_cancel; + // Keep information about curvature of text line around surface + TextLinesModel m_text_lines; + // Rotation gizmo GLGizmoRotate m_rotate_gizmo; // Value is set only when dragging rotation to calculate actual angle diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp index 8aa9e23cb9..88babc4645 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp @@ -33,10 +33,11 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) std::vector scales(m_input.styles.size()); m_images = std::vector(m_input.styles.size()); + auto was_canceled = []() { return false; }; for (auto &item : m_input.styles) { size_t index = &item - &m_input.styles.front(); ExPolygons &shapes = name_shapes[index]; - shapes = text2shapes(item.font, m_input.text.c_str(), item.prop); + shapes = text2shapes(item.font, m_input.text.c_str(), item.prop, was_canceled); // create image description StyleManager::StyleImage &image = m_images[index]; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 6d41907c22..5658fee5b6 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -428,6 +428,88 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { template TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled) { + if (!input.text_lines.empty()){ + FontFileWithCache &font = input.font_file; + const TextConfiguration &tc = input.text_configuration; + assert(get_count_lines(tc.text) == input.text_lines.size()); + size_t count_lines = input.text_lines.size(); + std::wstring ws = boost::nowide::widen(tc.text.c_str()); + const FontProp &prop = tc.style.prop; + + assert(font.has_value()); + std::vector shapes = text2vshapes(font, ws, prop, was_canceled); + if (shapes.empty()) + return {}; + + BoundingBox extents; // whole text to find center + // separate lines of text to vector + std::vector bbs(count_lines); + size_t line_index = 0; + // s_i .. shape index + for (size_t s_i = 0; s_i < shapes.size(); ++s_i) { + const ExPolygons &shape = shapes[s_i]; + BoundingBox bb; + if (!shape.empty()) { + bb = get_extents(shape); + extents.merge(bb); + } + BoundingBoxes &line_bbs = bbs[line_index]; + line_bbs.push_back(bb); + if (ws[s_i] == '\n') + ++line_index; + } + + Point center = extents.center(); + size_t s_i_offset = 0; // shape index offset(for next lines) + for (line_index = 0; line_index < input.text_lines.size(); ++line_index) { + const BoundingBoxes &line_bbs = bbs[line_index]; + // find BB in center of line + size_t first_right_index = 0; + for (const BoundingBox &bb : line_bbs) + if (bb.min.x() > center.x()) { + break; + } else { + ++first_right_index; + } + + // calc transformation for letters on the Right side from center + for (size_t index = first_right_index; index != line_bbs.size(); ++index) { + const BoundingBox &bb = line_bbs[index]; + Point letter_center = bb.center(); + + + const ExPolygons &letter_shape = shapes[s_i_offset + index]; + } + + // calc transformation for letters on the Left side from center + for (size_t index = first_right_index; index < line_bbs.size(); --index) { + + } + + size_t s_i = s_i_offset; // shape index + + s_i_offset += line_bbs.size(); + } + + // create per letter transformation + const FontFile &ff = *input.font_file.font_file; + // NOTE: SHAPE_SCALE is applied in ProjectZ + double scale = get_shape_scale(prop, ff) / SHAPE_SCALE; + double depth = prop.emboss / scale; + auto projectZ = std::make_unique(depth); + auto scale_tr = Eigen::Scaling(scale); + + for (wchar_t wc: ws){ + + } + + //ProjectTransform project(std::move(projectZ), ) + //if (was_canceled()) return {}; + //return TriangleMesh(polygons2model(shapes, project)); + //indexed_triangle_set result; + + } + ExPolygons shapes = priv::create_shape(input, was_canceled); if (shapes.empty()) return {}; if (was_canceled()) return {}; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index d4b32cf616..0055d6df6b 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -7,6 +7,7 @@ #include #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/TextLines.hpp" #include "Job.hpp" namespace Slic3r { @@ -31,6 +32,10 @@ struct DataBase // flag that job is canceled // for time after process. std::shared_ptr> cancel; + + // Define per letter projection on one text line + // [optional] It is not used when empty + TextLines text_lines; }; /// diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp new file mode 100644 index 0000000000..dcea0e73ed --- /dev/null +++ b/src/slic3r/GUI/TextLines.cpp @@ -0,0 +1,285 @@ +#include "TextLines.hpp" + +#include + +#include "libslic3r/Model.hpp" + +#include "libslic3r/Emboss.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/Tesselate.hpp" + +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/ExPolygonsIndex.hpp" + +#include "slic3r/GUI/Selection.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/3DScene.hpp" + +using namespace Slic3r; +using namespace Slic3r::GUI; + +namespace { + +const Slic3r::Polygon *largest(const Slic3r::Polygons &polygons) +{ + if (polygons.empty()) + return nullptr; + if (polygons.size() == 1) + return &polygons.front(); + + // compare polygon to find largest + size_t biggest_size = 0; + const Slic3r::Polygon *result = nullptr; + for (const Slic3r::Polygon &polygon : polygons) { + Point s = polygon.bounding_box().size(); + size_t size = s.x() * s.y(); + if (size <= biggest_size) + continue; + biggest_size = size; + result = &polygon; + } + return result; +} + +GLModel create_model(const Slic3r::Polygon &polygon, float width_half = 0.5f, ColorRGBA color = ColorRGBA(0.f, 1.f, .2f, 0.5f)) +{ + // Improve: Create torus instead of flat path (with model overlaps) + + assert(!polygon.empty()); + if (polygon.empty()) + return {}; + + // add a small positive offset to avoid z-fighting + float offset = static_cast(scale_(0.015f)); + Polygons polygons_expanded = expand(polygon, offset); + const Slic3r::Polygon *polygon_expanded_ptr = largest(polygons_expanded); + assert(polygon_expanded_ptr != nullptr); + if (polygon_expanded_ptr == nullptr || polygon_expanded_ptr->empty()) + return {}; + const Slic3r::Polygon &polygon_expanded = *polygon_expanded_ptr; + + // inspired by 3DScene.cpp void GLVolume::SinkingContours::update() + GLModel::Geometry init_data; + init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3}; + init_data.color = color; + + size_t count = polygon_expanded.size(); + init_data.reserve_vertices(2 * count); + init_data.reserve_indices(2 * count); + + for (const Point &point : polygon_expanded.points) { + Vec2f point_d = unscale(point).cast(); + Vec3f vertex(point_d.x(), point_d.y(), width_half); + init_data.add_vertex(vertex); + vertex.z() *= -1; + init_data.add_vertex(vertex); + } + + unsigned int prev_i = count - 1; + for (unsigned int i = 0; i < count; ++i) { + // t .. top + // b .. bottom + unsigned int t1 = prev_i * 2; + unsigned int b1 = t1 + 1; + unsigned int t2 = i * 2; + unsigned int b2 = t2 + 1; + init_data.add_triangle(t1, b1, t2); + init_data.add_triangle(b2, t2, b1); + prev_i = i; + } + + GLModel gl_model; + gl_model.init_from(std::move(init_data)); + return gl_model; +} + +GLModel create_model(const Polygons &polygons, float width_half = 0.5f, ColorRGBA color = ColorRGBA(0.f, 1.f, .2f, 0.5f)) +{ + assert(!polygons.empty()); + + // add a small positive offset to avoid z-fighting + float offset = static_cast(scale_(0.015f)); + Polygons polygons_expanded = expand(polygons, offset); + + // inspired by 3DScene.cpp void GLVolume::SinkingContours::update() + GLModel::Geometry init_data; + init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3}; + init_data.color = color; + + size_t count = count_points(polygons); + init_data.reserve_vertices(2 * count); + init_data.reserve_indices(2 * count); + + unsigned int vertices_counter = 0; + for (const Slic3r::Polygon &polygon : polygons_expanded) { + for (const Point &point : polygon.points) { + Vec2f point_d = unscale(point).cast(); + Vec3f vertex(point_d.x(), point_d.y(), width_half); + init_data.add_vertex(vertex); + vertex.z() *= -1; + init_data.add_vertex(vertex); + } + + auto points_count = static_cast(polygon.points.size()); + unsigned int prev_i = points_count - 1; + for (unsigned int i = 0; i < points_count; i++) { + // t .. top + // b .. bottom + unsigned int t1 = vertices_counter + prev_i * 2; + unsigned int b1 = t1 + 1; + unsigned int t2 = vertices_counter + i * 2; + unsigned int b2 = t2 + 1; + init_data.add_triangle(t1, b1, t2); + init_data.add_triangle(b2, t2, b1); + prev_i = i; + } + vertices_counter += 2 * points_count; + } + + GLModel gl_model; + gl_model.init_from(std::move(init_data)); + return gl_model; +} + +} // namespace + +void TextLinesModel::init(const Selection &selection) +{ + const GLVolume *gl_volume_ptr = selection.get_first_volume(); + if (gl_volume_ptr == nullptr) + return; + const GLVolume &gl_volume = *gl_volume_ptr; + const ModelObjectPtrs &objects = selection.get_model()->objects; + const ModelObject *mo_ptr = get_model_object(gl_volume, objects); + if (mo_ptr == nullptr) + return; + const ModelObject &mo = *mo_ptr; + + const ModelVolume *mv_ptr = get_model_volume(gl_volume, objects); + if (mv_ptr == nullptr) + return; + const ModelVolume &mv = *mv_ptr; + + const std::optional tc_opt = mv.text_configuration; + if (!tc_opt.has_value()) + return; + + unsigned count_lines = Emboss::get_count_lines(tc_opt->text); + if (count_lines == 0) + return; + + // TODO: Calc correct line height by line gap + font file info + // Be carefull it is not correct !!! + double line_height = tc_opt->style.prop.size_in_mm; + double first_line_center = -(count_lines / 2) * line_height - ((count_lines % 2 == 0)? line_height/2. : 0.); + std::vector line_centers(count_lines); + for (size_t i = 0; i < count_lines; ++i) + line_centers[i] = first_line_center + i * line_height; + + const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); + + // contour transformation + auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); + Transform3d c_trafo = mv_trafo * rot; + Transform3d c_trafo_inv = c_trafo.inverse(); + + std::vector line_contours(count_lines); + for (const ModelVolume *volume : mo.volumes) { + // only part could be surface for volumes + if (!volume->is_model_part()) + continue; + + // is selected volume + if (mv.id() == volume->id()) + continue; + + MeshSlicingParams slicing_params; + slicing_params.trafo = c_trafo_inv * volume->get_matrix(); + + for (size_t i = 0; i < count_lines; ++i) { + const Polygons polys = Slic3r::slice_mesh(volume->mesh().its, line_centers[i], slicing_params); + if (polys.empty()) + continue; + Polygons &contours = line_contours[i]; + contours.insert(contours.end(), polys.begin(), polys.end()); + } + } + + lines.reserve(count_lines); + lines.clear(); + // select closest contour + Vec2d zero(0., 0.); + for (const Polygons &polygons : line_contours) { + // Improve: use int values and polygons only + // Slic3r::Polygons polygons = union_(polygons); + // std::vector lines = to_lines(polygons); + // AABBTreeIndirect::Tree<2, Point> tree; + // size_t line_idx; + // Point hit_point; + // Point::Scalar distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, point, line_idx, hit_point); + + ExPolygons expolygons = union_ex(polygons); + std::vector linesf = to_linesf(expolygons); + AABBTreeIndirect::Tree2d tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf); + + size_t line_idx; + Vec2d hit_point; + double distance = AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, zero, line_idx, hit_point); + + // conversion between index of point and expolygon + ExPolygonsIndices cvt(expolygons); + ExPolygonsIndex index = cvt.cvt(static_cast(line_idx)); + + const Polygon& polygon = index.is_contour() ? expolygons[index.expolygons_index].contour : + expolygons[index.expolygons_index].holes[index.hole_index()]; + + Point hit_point_int = hit_point.cast(); + TextLine tl{polygon, index.point_index, hit_point_int}; + lines.emplace_back(tl); + } + + ColorRGBA color(.7f, .7f, .7f, .7f); // Gray + + // TODO: create model from all lines + model = create_model(lines.front().polygon, 0.7f, color); +} + +void TextLinesModel::render(const Transform3d &text_world) +{ + if (!model.is_initialized()) + return; + + GUI_App &app = wxGetApp(); + const GLShaderProgram *shader = app.get_shader("flat"); + if (shader == nullptr) + return; + + const Camera &camera = app.plater()->get_camera(); + auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); + + shader->start_using(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * text_world * rot); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + bool is_depth_test = glIsEnabled(GL_DEPTH_TEST); + if (!is_depth_test) + glsafe(::glEnable(GL_DEPTH_TEST)); + + bool is_blend = glIsEnabled(GL_BLEND); + if (!is_blend) + glsafe(::glEnable(GL_BLEND)); + // glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + model.render(); + + if (!is_depth_test) + glsafe(::glDisable(GL_DEPTH_TEST)); + if (!is_blend) + glsafe(::glDisable(GL_BLEND)); + + shader->stop_using(); +} diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp new file mode 100644 index 0000000000..d0eaeb46e0 --- /dev/null +++ b/src/slic3r/GUI/TextLines.hpp @@ -0,0 +1,46 @@ +#ifndef slic3r_TextLines_hpp_ +#define slic3r_TextLines_hpp_ + +#include +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Point.hpp" +#include "slic3r/GUI/GLModel.hpp" + +namespace Slic3r::GUI { + +class Selection; + +/// +/// Define polygon for draw letters +/// +struct TextLine +{ + // slice of object + Polygon polygon; + + // index to point in polygon which starts line, which is closest to zero + size_t start_index; + + // Point on line closest to zero + Point start_point; +}; +using TextLines = std::vector; + +class TextLinesModel +{ +public: + void init(const Selection &selection); + void render(const Transform3d &text_world); + + bool is_init() const { return model.is_initialized(); } + void reset() { model.reset(); } + const TextLines &get_lines() const { return lines; } +private: + TextLines lines; + + // Keep model for visualization text lines + GLModel model; +}; + +} // namespace Slic3r::GUI +#endif // slic3r_TextLines_hpp_ \ No newline at end of file diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index e90d0de191..fc548bb35a 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -316,7 +316,9 @@ nor why it should choose to collapse on Betelgeuse Seven\"."; Emboss::FontFileWithCache ffwc(std::move(font)); FontProp fp{line_height, depth}; - ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp); + + auto was_canceled = []() { return false; }; + ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp, was_canceled); REQUIRE(!shapes.empty()); Emboss::ProjectZ projection(depth);