From 14c7f27bff71805ddbacd2d05c3160a2167564fe Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 20 Apr 2023 15:49:06 +0200 Subject: [PATCH 01/23] Visualization of slice for object contour to put letters on --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 77 +++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 0d0751b45b..2ba7e3b498 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -571,6 +571,81 @@ bool GLGizmoEmboss::on_init() std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } + +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/Tesselate.hpp" + +namespace { + +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(); + + // Cut transformation + auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); + Transform3d trafo = rot * mv_trafo.inverse(); + //Transform3d trafo_inv = trafo.inverse(); + + Polygons contours; + for (const ModelVolume* volume: mo.volumes){ + MeshSlicingParams slicing_params; + slicing_params.trafo = trafo * volume->get_matrix(); + const Polygons polys = Slic3r::slice_mesh(volume->mesh().its, 0., slicing_params); + contours.insert(contours.end(), polys.begin(), polys.end()); + } + + + // coppied from 3DScene.cpp void GLVolume::SinkingContours::update() + GUI::GLModel::Geometry init_data; + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + unsigned int vertices_counter = 0; + if (contours.empty()) + return; + + float half_width = static_cast(scale_(0.25)); + for (const ExPolygon &expoly : diff_ex(expand(contours, half_width), shrink(contours, half_width))) { + const std::vector triangulation = triangulate_expolygon_3d(expoly); + init_data.reserve_vertices(init_data.vertices_count() + triangulation.size()); + init_data.reserve_indices(init_data.indices_count() + triangulation.size()); + for (const Vec3d& v : triangulation) { + init_data.add_vertex((Vec3f)(v.cast() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); + } + } + + if (init_data.is_empty()) + return; + + GLModel m_model; + m_model.init_from(std::move(init_data)); + + 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 * mv_trafo * rot.inverse()); // * Geometry::translation_transform(m_shift)); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + m_model.render(); + shader->stop_using(); +} + +} // namespace + + void GLGizmoEmboss::on_render() { // no volume selected if (m_volume == nullptr || @@ -579,6 +654,8 @@ void GLGizmoEmboss::on_render() { 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; From 034b0a6118c39a961af696c1dd88076c838b216a Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 20 Apr 2023 17:16:30 +0200 Subject: [PATCH 02/23] Visualize flat --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 90 +++++++++++++++---------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 2ba7e3b498..d029089c77 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -577,6 +577,56 @@ std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); } 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) @@ -589,47 +639,19 @@ void slice_object(const Selection& selection) { const ModelObject &mo = *mo_ptr; const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); - // Cut transformation + // contour transformation auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); - Transform3d trafo = rot * mv_trafo.inverse(); - //Transform3d trafo_inv = trafo.inverse(); + 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 = trafo * volume->get_matrix(); + 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()); } - - // coppied from 3DScene.cpp void GLVolume::SinkingContours::update() - GUI::GLModel::Geometry init_data; - init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = ColorRGBA::WHITE(); - unsigned int vertices_counter = 0; - if (contours.empty()) - return; - - float half_width = static_cast(scale_(0.25)); - for (const ExPolygon &expoly : diff_ex(expand(contours, half_width), shrink(contours, half_width))) { - const std::vector triangulation = triangulate_expolygon_3d(expoly); - init_data.reserve_vertices(init_data.vertices_count() + triangulation.size()); - init_data.reserve_indices(init_data.indices_count() + triangulation.size()); - for (const Vec3d& v : triangulation) { - init_data.add_vertex((Vec3f)(v.cast() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting - ++vertices_counter; - if (vertices_counter % 3 == 0) - init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); - } - } - - if (init_data.is_empty()) - return; - - GLModel m_model; - m_model.init_from(std::move(init_data)); - const GLShaderProgram *shader = wxGetApp().get_shader("flat"); if (shader == nullptr) return; @@ -637,9 +659,9 @@ void slice_object(const Selection& selection) { 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 * mv_trafo * rot.inverse()); // * Geometry::translation_transform(m_shift)); + 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()); - m_model.render(); + create_model(contours).render(); shader->stop_using(); } 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 03/23] 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); From 7fa1e39feca30aff40f0680d3d6b5dd4e73484c4 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 2 May 2023 12:26:58 +0200 Subject: [PATCH 04/23] One GLModel for all lines --- src/libslic3r/Emboss.cpp | 20 ++- src/libslic3r/Emboss.hpp | 9 + src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 26 ++- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 1 + src/slic3r/GUI/TextLines.cpp | 214 ++++++++++++++---------- src/slic3r/GUI/TextLines.hpp | 20 ++- 6 files changed, 181 insertions(+), 109 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index c4eafbd8ea..317936b576 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1207,6 +1207,15 @@ std::optional Emboss::letter2glyph(const FontFile &font, return priv::get_glyph(*font_info_opt, letter, flatness); } +int Emboss::get_line_height(const FontFile &font, const FontProp &prop) { + unsigned int font_index = 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; + line_height += prop.line_gap.value_or(0); + return static_cast(line_height / SHAPE_SCALE); +} + namespace { ExPolygons letter2shapes( @@ -1220,16 +1229,9 @@ ExPolygons letter2shapes( 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; + // 2d shape has opposit direction of y + cursor.y() -= get_line_height(font, font_prop); return {}; } if (letter == '\t') { diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index be2742f35a..899bd02a2c 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -154,6 +154,7 @@ namespace Emboss 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;}); + /// Sum of character '\n' unsigned get_count_lines(const std::wstring &ws); unsigned get_count_lines(const std::string &text); @@ -226,6 +227,14 @@ namespace Emboss /// Conversion to mm double get_shape_scale(const FontProp &fp, const FontFile &ff); + /// + /// Read from font file and properties height of line with spacing + /// + /// Infos for collections + /// Collection index + Additional line gap + /// Line height with spacing in ExPolygon size + int get_line_height(const FontFile &font, const FontProp &prop); + /// /// Project spatial point /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e684868c9e..4c4d9f836d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1020,6 +1020,26 @@ EmbossStyles GLGizmoEmboss::create_default_styles() return styles; } +void GLGizmoEmboss::init_text_lines(){ + assert(m_style_manager.is_active_font()); + if (!m_style_manager.is_active_font()) + return; + const auto& ffc = m_style_manager.get_font_file_with_cache(); + assert(ffc.has_value()); + if (!ffc.has_value()) + return; + const auto &ff_ptr = ffc.font_file; + assert(ff_ptr != nullptr); + if (ff_ptr == nullptr) + return; + + const FontProp& fp = m_style_manager.get_font_prop(); + const FontFile &ff = *ff_ptr; + + double line_height = TextLinesModel::calc_line_height(ff, fp); + m_text_lines.init(m_parent.get_selection(), line_height); +} + void GLGizmoEmboss::set_volume_by_selection() { const Selection &selection = m_parent.get_selection(); @@ -1155,7 +1175,7 @@ void GLGizmoEmboss::set_volume_by_selection() } if (tc.style.prop.per_glyph) - m_text_lines.init(m_parent.get_selection()); + init_text_lines(); m_text = tc.text; m_volume = volume; @@ -3163,7 +3183,7 @@ void GLGizmoEmboss::draw_advanced() if (ImGui::Checkbox("##PerGlyph", per_glyph)) { if (*per_glyph) { if (!m_text_lines.is_init()) - m_text_lines.init(m_parent.get_selection()); + init_text_lines(); } exist_change = true; } else if (ImGui::IsItemHovered()) { @@ -3172,7 +3192,7 @@ void GLGizmoEmboss::draw_advanced() } 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()); + init_text_lines(); } } else if (!*per_glyph && m_text_lines.is_init()) m_text_lines.reset(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 845753d903..a31b0c6124 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -314,6 +314,7 @@ private: // Keep information about curvature of text line around surface TextLinesModel m_text_lines; + void init_text_lines(); // Rotation gizmo GLGizmoRotate m_rotate_gizmo; diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index dcea0e73ed..b479990fca 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -45,10 +45,54 @@ const Slic3r::Polygon *largest(const Slic3r::Polygons &polygons) return result; } -GLModel create_model(const Slic3r::Polygon &polygon, float width_half = 0.5f, ColorRGBA color = ColorRGBA(0.f, 1.f, .2f, 0.5f)) +indexed_triangle_set create_its(const Slic3r::Polygon &polygon, float width_half) { + // 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() + indexed_triangle_set model; + size_t count = polygon_expanded.size(); + model.vertices.reserve(2 * count); + model.indices.reserve(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); + model.vertices.push_back(vertex); + vertex.z() *= -1; + model.vertices.push_back(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; + model.indices.emplace_back(t1, b1, t2); + model.indices.emplace_back(b2, t2, b1); + prev_i = i; + } + + return model; +} + +/* 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 {}; @@ -92,62 +136,86 @@ GLModel create_model(const Slic3r::Polygon &polygon, float width_half = 0.5f, Co prev_i = i; } + + // line .. y offset from volume(define line for sliced polygon) + + GLModel gl_model; gl_model.init_from(std::move(init_data)); return gl_model; +}*/ + + +// select closest contour for each line +TextLines select_closest_contour(const std::vector &line_contours) { + TextLines result; + result.reserve(line_contours.size()); + Vec2d zero(0., 0.); + for (const Polygons &polygons : line_contours){ + if (polygons.empty()) { + result.emplace_back(); + continue; + } + // 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 Slic3r::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}; + result.emplace_back(tl); + } + return result; } -GLModel create_model(const Polygons &polygons, float width_half = 0.5f, ColorRGBA color = ColorRGBA(0.f, 1.f, .2f, 0.5f)) +GLModel create_model(const TextLines &lines, const std::vector &line_centers) { - assert(!polygons.empty()); + double model_half_width = 0.5; // [in volume mm] + ColorRGBA color(.7f, .7f, .7f, .7f); // Gray - // add a small positive offset to avoid z-fighting - float offset = static_cast(scale_(0.015f)); - Polygons polygons_expanded = expand(polygons, offset); + indexed_triangle_set its; + auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); - // 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(lines.size() == line_centers.size()); + // create model from polygons + for (size_t i = 0; i < lines.size(); ++i) { + const Slic3r::Polygon &polygon = lines[i].polygon; + if (polygon.empty()) continue; + double line_center = line_centers[i]; + indexed_triangle_set line_its = create_its(polygon, model_half_width); + auto transl = Eigen::Translation3d(0., -line_center, 0.); + Transform3d tr = transl * rot; + its_transform(line_its, tr); + its_merge(its, line_its); } GLModel gl_model; - gl_model.init_from(std::move(init_data)); + gl_model.init_from(its); + gl_model.set_color(color); return gl_model; } - } // namespace -void TextLinesModel::init(const Selection &selection) +void TextLinesModel::init(const Selection &selection, double line_height) { const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) @@ -172,13 +240,10 @@ void TextLinesModel::init(const Selection &selection) 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.); + 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; + line_centers[i] = static_cast(first_line_center + i * line_height); const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); @@ -209,48 +274,13 @@ void TextLinesModel::init(const Selection &selection) } } - 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); + m_lines = select_closest_contour(line_contours); + m_model = create_model(m_lines, line_centers); } void TextLinesModel::render(const Transform3d &text_world) { - if (!model.is_initialized()) + if (!m_model.is_initialized()) return; GUI_App &app = wxGetApp(); @@ -259,10 +289,9 @@ void TextLinesModel::render(const Transform3d &text_world) 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("view_model_matrix", camera.get_view_matrix() * text_world); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); bool is_depth_test = glIsEnabled(GL_DEPTH_TEST); @@ -274,7 +303,7 @@ void TextLinesModel::render(const Transform3d &text_world) glsafe(::glEnable(GL_BLEND)); // glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - model.render(); + m_model.render(); if (!is_depth_test) glsafe(::glDisable(GL_DEPTH_TEST)); @@ -283,3 +312,10 @@ void TextLinesModel::render(const Transform3d &text_world) shader->stop_using(); } + +double TextLinesModel::calc_line_height(const Slic3r::Emboss::FontFile &ff, const FontProp &fp) +{ + int line_height = Emboss::get_line_height(ff, fp); // In shape size + double scale = Emboss::get_shape_scale(fp, ff); + return line_height * scale; +} diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index d0eaeb46e0..886d1532a2 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -2,10 +2,12 @@ #define slic3r_TextLines_hpp_ #include -#include "libslic3r/Polygon.hpp" -#include "libslic3r/Point.hpp" +#include +#include +#include #include "slic3r/GUI/GLModel.hpp" + namespace Slic3r::GUI { class Selection; @@ -29,17 +31,19 @@ using TextLines = std::vector; class TextLinesModel { public: - void init(const Selection &selection); + // line_height in mm + void init(const Selection &selection, double line_height); 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; } + bool is_init() const { return m_model.is_initialized(); } + void reset() { m_model.reset(); } + const TextLines &get_lines() const { return m_lines; } + static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); private: - TextLines lines; + TextLines m_lines; // Keep model for visualization text lines - GLModel model; + GLModel m_model; }; } // namespace Slic3r::GUI From ea2b2147412c3f1a4ef73f227ebea6b41f720b59 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 2 May 2023 14:01:34 +0200 Subject: [PATCH 05/23] @Enrico suggest this solution to preven accessing pointer on caster without data --- src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 72aaa59448..859a9a5dd0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2174,6 +2174,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re else m_selection.volumes_changed(map_glvolume_old_to_new); + // @Enrico suggest this solution to preven accessing pointer on caster without data + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); m_gizmos.update_data(); m_gizmos.refresh_on_off_state(); @@ -2233,7 +2235,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } // refresh volume raycasters for picking - m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { const GLVolume* v = m_volumes.volumes[i]; assert(v->mesh_raycaster != nullptr); From af0ef21ce32585cc6434346a250aa1c73e57c8f5 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 4 May 2023 11:57:33 +0200 Subject: [PATCH 06/23] Emboss text along base line(object cut) - need alignment of text(center, left, right) - (NOTE) edit text change its position + add polygon point --- src/libslic3r/Emboss.cpp | 190 ++++++++++++++++++++- src/libslic3r/Emboss.hpp | 40 ++++- src/libslic3r/Polygon.hpp | 15 ++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 56 ++++-- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 3 + src/slic3r/GUI/Jobs/EmbossJob.cpp | 215 +++++++++++++++--------- src/slic3r/GUI/Jobs/EmbossJob.hpp | 4 +- src/slic3r/GUI/TextLines.cpp | 183 ++++++++++++-------- src/slic3r/GUI/TextLines.hpp | 32 +--- 9 files changed, 547 insertions(+), 191 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 317936b576..f660a444cf 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1321,15 +1321,31 @@ std::vector Emboss::text2vshapes(FontFileWithCache &font_with_cache, return result; } +#include unsigned Emboss::get_count_lines(const std::wstring& ws) { if (ws.empty()) return 0; + unsigned prev_count = 0; + for (wchar_t wc : ws) + if (wc == '\n') + ++prev_count; + else + break; + + unsigned post_count = 0; + for (wchar_t wc : boost::adaptors::reverse(ws)) + if (wc == '\n') + ++post_count; + else + break; + unsigned count = 1; for (wchar_t wc : ws) if (wc == '\n') ++count; - return count; + + return count - prev_count - post_count; } unsigned Emboss::get_count_lines(const std::string &text) @@ -1730,6 +1746,178 @@ std::optional Emboss::OrthoProject::unproject(const Vec3d &p, double *dep return Vec2d(pp.x(), pp.y()); } +// sample slice +namespace { + +// using coor2 = int64_t; +using Coord2 = double; +using P2 = Eigen::Matrix; + +bool point_in_distance(const Coord2 &distance_sq, PolygonPoint &polygon_point, const size_t &i, const Slic3r::Polygon &polygon, bool is_first, bool is_reverse = false) +{ + size_t s = polygon.size(); + size_t ii = (i + polygon_point.index) % s; + + // second point of line + const Point &p = polygon[ii]; + Point p_d = p - polygon_point.point; + + P2 p_d2 = p_d.cast(); + Coord2 p_distance_sq = p_d2.squaredNorm(); + if (p_distance_sq < distance_sq) + return false; + + // found line + if (is_first) { + // on same line + // center also lay on line + // new point is distance moved from point by direction + polygon_point.point += p_d * sqrt(distance_sq / p_distance_sq); + return true; + } + + // line cross circle + + // start point of line + size_t ii2 = (is_reverse) ? (ii + 1) % s : (ii + s - 1) % s; + polygon_point.index = (is_reverse) ? ii : ii2; + const Point &p2 = polygon[ii2]; + + Point line_dir = p2 - p; + P2 line_dir2 = line_dir.cast(); + + Coord2 a = line_dir2.dot(line_dir2); + Coord2 b = 2 * p_d2.dot(line_dir2); + Coord2 c = p_d2.dot(p_d2) - distance_sq; + + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) { + assert(false); + // no intersection + polygon_point.point = p; + return true; + } + + // ray didn't totally miss sphere, + // so there is a solution to + // the equation. + discriminant = sqrt(discriminant); + + // either solution may be on or off the ray so need to test both + // t1 is always the smaller value, because BOTH discriminant and + // a are nonnegative. + double t1 = (-b - discriminant) / (2 * a); + double t2 = (-b + discriminant) / (2 * a); + + double t = std::min(t1, t2); + if (t < 0. || t > 1.) { + // Bad intersection + assert(false); + polygon_point.point = p; + return true; + } + + polygon_point.point = p + (t * line_dir2).cast(); + return true; +} + +void point_in_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon) +{ + Coord2 distance_sq = static_cast(distance) * distance; + bool is_first = true; + for (size_t i = 1; i < polygon.size(); ++i) { + if (point_in_distance(distance_sq, p, i, polygon, is_first)) + return; + is_first = false; + } + // There is not point on polygon with this distance +} + +void point_in_reverse_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon) +{ + Coord2 distance_sq = static_cast(distance) * distance; + bool is_first = true; + bool is_reverse = true; + for (size_t i = polygon.size(); i > 0; --i) { + if (point_in_distance(distance_sq, p, i, polygon, is_first, is_reverse)) + return; + is_first = false; + } + // There is not point on polygon with this distance +} +} // namespace + +// calculate rotation, need copy of polygon point +double Emboss::calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon) +{ + PolygonPoint polygon_point2 = polygon_point; // copy + point_in_distance(distance, polygon_point, polygon); + point_in_reverse_distance(distance, polygon_point2, polygon); + + Point surface_dir = polygon_point2.point - polygon_point.point; + Point norm(-surface_dir.y(), surface_dir.x()); + Vec2d norm_d = norm.cast(); + //norm_d.normalize(); + return std::atan2(norm_d.y(), norm_d.x()); +} + +std::vector Emboss::calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon) +{ + std::vector result; + result.reserve(polygon_points.size()); + for(const PolygonPoint& pp: polygon_points) + result.emplace_back(calculate_angle(distance, pp, polygon)); + return result; +} + +PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &bbs, int32_t center_x, double scale) +{ + // find BB in center of line + size_t first_right_index = 0; + for (const BoundingBox &bb : bbs) + if (bb.min.x() > center_x) { + break; + } else { + ++first_right_index; + } + + PolygonPoints samples(bbs.size()); + int32_t shapes_x_cursor = center_x; + + PolygonPoint cursor = slice.start; //copy + + auto create_sample = [&] //polygon_cursor, &polygon_line_index, &line_bbs, &shapes_x_cursor, &shape_scale, &em_2_polygon, &line, &offsets] + (const BoundingBox &bb, bool is_reverse) { + if (!bb.defined) + return cursor; + Point letter_center = bb.center(); + int32_t shape_distance = shapes_x_cursor - letter_center.x(); + shapes_x_cursor = letter_center.x(); + double distance_mm = shape_distance * scale; + int32_t distance_polygon = static_cast(std::round(scale_(distance_mm))); + if (is_reverse) + point_in_distance(distance_polygon, cursor, slice.polygon); + else + point_in_reverse_distance(distance_polygon, cursor, slice.polygon); + return cursor; + }; + + // calc transformation for letters on the Right side from center + bool is_reverse = true; + for (size_t index = first_right_index; index < bbs.size(); ++index) + samples[index] = create_sample(bbs[index], is_reverse); + + // calc transformation for letters on the Left side from center + shapes_x_cursor = center_x; + cursor = slice.start; // copy + is_reverse = false; + for (size_t index_plus_one = first_right_index; index_plus_one > 0; --index_plus_one) { + size_t index = index_plus_one - 1; + samples[index] = create_sample(bbs[index], is_reverse); + } + return samples; +} + #ifdef REMOVE_SPIKES #include void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc) diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 899bd02a2c..0779aee080 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -8,6 +8,7 @@ #include // indexed_triangle_set #include "Polygon.hpp" #include "ExPolygon.hpp" +#include "BoundingBox.hpp" #include "TextConfiguration.hpp" namespace Slic3r { @@ -403,7 +404,44 @@ namespace Emboss Vec3d project(const Vec3d &point) const override; std::optional unproject(const Vec3d &p, double * depth = nullptr) const override; }; -} // namespace Emboss + /// + /// Define polygon for draw letters + /// + struct TextLine + { + // slice of object + Polygon polygon; + + // point laying on polygon closest to zero + PolygonPoint start; + + // offset of text line in volume mm + float y; + }; + using TextLines = std::vector; + + /// + /// Sample slice polygon by bounding boxes centers + /// slice start point has shape_center_x coor + /// + /// Polygon and start point + /// Bounding boxes of letter on one line + /// Center x coor of bbs line + /// Scale for bbs + /// Sampled polygon by bounding boxes + PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, int32_t center_x, double scale); + + /// + /// Calculate angle for polygon point + /// + /// Distance for found normal in point + /// Select point on polygon + /// Polygon know neighbor of point + /// angle(atan2) of normal in polygon point + double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); + std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); + +} // namespace Emboss } // namespace Slic3r #endif // slic3r_Emboss_hpp_ diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 3c4bb0e2ad..39376ca4b6 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -266,6 +266,21 @@ bool polygons_match(const Polygon &l, const Polygon &r); Polygon make_circle(double radius, double error); Polygon make_circle_num_segments(double radius, size_t num_segments); +/// +/// Define point laying on polygon +/// keep index of polygon line and point coordinate +/// +struct PolygonPoint +{ + // index of line inside of polygon + // 0 .. from point polygon[0] to polygon[1] + size_t index; + + // Point, which lay on line defined by index + Point point; +}; +using PolygonPoints = std::vector; + } // Slic3r // start Boost diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 4c4d9f836d..b14e84baf7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -403,6 +403,8 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle_opt = angle; m_style_manager.get_font_prop().angle = angle_opt; } + + volume_transformation_changing(); } return used; } @@ -421,15 +423,9 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) bool is_dragging = m_surface_drag.has_value(); // End with surface dragging? - if (was_dragging && !is_dragging) { - // Update surface by new position - if (m_volume->text_configuration->style.prop.use_surface) - process(); - - // Show correct value of height & depth inside of inputs - calculate_scale(); - } - + if (was_dragging && !is_dragging) + volume_transformation_changed(); + // Start with dragging else if (!was_dragging && is_dragging) { // Cancel job to prevent interuption of dragging (duplicit result) @@ -452,6 +448,8 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), priv::up_limit); } + + volume_transformation_changing(); } return res; } @@ -552,6 +550,36 @@ bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event) return false; } +void GLGizmoEmboss::volume_transformation_changing() +{ + if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { + assert(false); + return; + } + const FontProp &prop = m_volume->text_configuration->style.prop; + if (prop.per_glyph) + init_text_lines(); +} + +void GLGizmoEmboss::volume_transformation_changed() +{ + if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { + assert(false); + return; + } + + const FontProp &prop = m_volume->text_configuration->style.prop; + if (prop.per_glyph) + init_text_lines(); + + // Update surface by new position + if (prop.use_surface || prop.per_glyph) + process(); + + // Show correct value of height & depth inside of inputs + calculate_scale(); +} + bool GLGizmoEmboss::on_init() { m_rotate_gizmo.init(); @@ -848,9 +876,7 @@ void GLGizmoEmboss::on_stop_dragging() m_rotate_start_angle.reset(); - // recalculate for surface cut - const FontProp &font_prop = m_style_manager.get_style().prop; - if (font_prop.use_surface) process(); + volume_transformation_changed(); } void GLGizmoEmboss::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } @@ -1263,7 +1289,11 @@ bool GLGizmoEmboss::process() if (!m_style_manager.is_active_font()) return false; DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel), m_volume->id()}; - + if (data.text_configuration.style.prop.per_glyph){ + if (!m_text_lines.is_init()) + init_text_lines(); + data.text_lines = m_text_lines.get_lines(); // copy + } std::unique_ptr job = nullptr; // check cutting from source mesh diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index a31b0c6124..36ceb68fe0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -82,6 +82,9 @@ protected: std::string get_gizmo_leaving_text() const override { return _u8L("Leave emboss gizmo"); } std::string get_action_snapshot_name() const override { return _u8L("Embossing actions"); } private: + void volume_transformation_changing(); + void volume_transformation_changed(); + static EmbossStyles create_default_styles(); // localized default text bool init_create(ModelVolumeType volume_type); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 5658fee5b6..256dbca68b 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -1,6 +1,7 @@ #include "EmbossJob.hpp" #include +#include #include #include // load_obj for default mesh @@ -247,8 +248,8 @@ void UpdateJob::process(Ctl &ctl) throw priv::JobException("Created text volume is empty. Change text or font."); // center triangle mesh - Vec3d shift = m_result.bounding_box().center(); - m_result.translate(-shift.cast()); + //Vec3d shift = m_result.bounding_box().center(); + //m_result.translate(-shift.cast()); } void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) @@ -286,6 +287,8 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_vo return create_sources(volumes, text_volume->id().id); } + + } // namespace Slic3r::GUI::Emboss ///////////////// @@ -360,6 +363,8 @@ bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface) res &= !input.volume_name.empty(); assert(input.text_configuration.style.prop.use_surface == use_surface); res &= input.text_configuration.style.prop.use_surface == use_surface; + assert(input.text_configuration.style.prop.per_glyph == !input.text_lines.empty()); + res &= input.text_configuration.style.prop.per_glyph == !input.text_lines.empty(); return res; } bool priv::check(const DataCreateVolume &input, bool is_main_thread) { @@ -425,89 +430,135 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { return text2shapes(font, text, prop, was_canceled); } +#define STORE_SAMPLING +#ifdef STORE_SAMPLING +#include "libslic3r/SVG.hpp" +#endif // STORE_SAMPLING +namespace { +template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc was_canceled) +{ + // method use square of coord stored into int64_t + static_assert(std::is_same()); + + 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 {}; + + const FontFile &ff = *font.font_file; + double shape_scale = get_shape_scale(prop, ff); + + BoundingBox extents; // whole text to find center + // separate lines of text to vector + std::vector bbs(count_lines); + size_t text_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[text_line_index]; + line_bbs.push_back(bb); + if (ws[s_i] == '\n') + ++text_line_index; + } + + double projec_scale = shape_scale / SHAPE_SCALE; + double depth = prop.emboss / projec_scale; + auto projectZ = std::make_unique(depth); + auto scale_tr = Eigen::Scaling(projec_scale); + + // half of font em size for direction of letter emboss + double em_2_mm = prop.size_in_mm / 2.; + int32_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); + + Point center = extents.center(); + size_t s_i_offset = 0; // shape index offset(for next lines) + indexed_triangle_set result; + for (text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) { + const BoundingBoxes &line_bbs = bbs[text_line_index]; + const TextLine &line = input.text_lines[text_line_index]; + // IMPROVE: do not precalculate samples do it inline - store result its + PolygonPoints samples = sample_slice(line, line_bbs, center.x(), shape_scale); + std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + + for (size_t i = 0; i < line_bbs.size(); ++i) { + const BoundingBox &line_bb = line_bbs[i]; + if (!line_bb.defined) + continue; + + Vec2d to_zero_vec = line_bb.center().cast() * shape_scale; // [in mm] + auto to_zero = Eigen::Translation(-to_zero_vec.x(), 0., 0.); + + const double &angle = angles[i]; + auto rotate = Eigen::AngleAxisd(angle + M_PI_2, Vec3d::UnitY()); + + const PolygonPoint &sample = samples[i]; + Vec2d offset_vec = unscale(sample.point); // [in mm] + auto offset_tr = Eigen::Translation(offset_vec.x(), 0., -offset_vec.y()); + + //Transform3d tr = offset_tr * rotate * to_zero * scale_tr; + Transform3d tr = offset_tr * rotate * to_zero * scale_tr; + //Transform3d tr = Transform3d::Identity() * scale_tr; + const ExPolygons &letter_shape = shapes[s_i_offset + i]; + + auto projectZ = std::make_unique(depth); + ProjectTransform project(std::move(projectZ), tr); + indexed_triangle_set glyph_its = polygons2model(letter_shape, project); + its_merge(result, std::move(glyph_its)); + } + s_i_offset += line_bbs.size(); + +#ifdef STORE_SAMPLING + { // Debug store polygon + //std::string stl_filepath = "C:/data/temp/line" + std::to_string(text_line_index) + "_model.stl"; + //bool suc = its_write_stl_ascii(stl_filepath.c_str(), "label", result); + + BoundingBox bbox = get_extents(line.polygon); + std::string file_path = "C:/data/temp/line" + std::to_string(text_line_index) + "_letter_position.svg"; + SVG svg(file_path, bbox); + svg.draw(line.polygon); + int32_t radius = bbox.size().x() / 300; + for (size_t i = 0; i < samples.size(); i++) { + const PolygonPoint &pp = samples[i]; + const Point& p = pp.point; + svg.draw(p, "green", radius); + std::string label = std::string(" ")+tc.text[i]; + svg.draw_text(p, label.c_str(), "black"); + + double a = angles[i]; + double length = 3.0 * radius; + Point n(length * std::cos(a), length * std::sin(a)); + svg.draw(Slic3r::Line(p - n, p + n), "Lime"); + } + } +#endif // STORE_SAMPLING + } + + // ProjectTransform project(std::move(projectZ), ) + // if (was_canceled()) return {}; + return TriangleMesh(result); +} +} // namespace + + 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; - + if (!input.text_lines.empty()) { + TriangleMesh tm = create_mesh_per_glyph(input, was_canceled); + if (!tm.empty()) + return tm; } ExPolygons shapes = priv::create_shape(input, was_canceled); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 0055d6df6b..bbe9c940c1 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -35,7 +35,7 @@ struct DataBase // Define per letter projection on one text line // [optional] It is not used when empty - TextLines text_lines; + Slic3r::Emboss::TextLines text_lines; }; /// @@ -220,7 +220,6 @@ SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, s /// Source data for cut surface from SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume); - /// /// Update text volume to use surface from object /// @@ -235,7 +234,6 @@ public: void process(Ctl &ctl) override; void finalize(bool canceled, std::exception_ptr &eptr) override; }; - } // namespace Slic3r::GUI #endif // slic3r_EmbossJob_hpp_ diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index b479990fca..0a4652f942 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -20,10 +20,10 @@ #include "slic3r/GUI/3DScene.hpp" using namespace Slic3r; +using namespace Slic3r::Emboss; using namespace Slic3r::GUI; namespace { - const Slic3r::Polygon *largest(const Slic3r::Polygons &polygons) { if (polygons.empty()) @@ -45,8 +45,8 @@ const Slic3r::Polygon *largest(const Slic3r::Polygons &polygons) return result; } -indexed_triangle_set create_its(const Slic3r::Polygon &polygon, float width_half) { - // Improve: Create torus instead of flat path (with model overlaps) +indexed_triangle_set its_create_belt(const Slic3r::Polygon &polygon, float width_half) { + // Improve: Create torus instead of flat belt path (with model overlaps) assert(!polygon.empty()); if (polygon.empty()) return {}; @@ -86,65 +86,89 @@ indexed_triangle_set create_its(const Slic3r::Polygon &polygon, float width_half model.indices.emplace_back(b2, t2, b1); prev_i = i; } - return model; } -/* GLModel create_model(const Slic3r::Polygon &polygon, float width_half = 0.5f, ColorRGBA color = ColorRGBA(0.f, 1.f, .2f, 0.5f)) +indexed_triangle_set its_create_torus(const Slic3r::Polygon &polygon, float radius, size_t steps = 20) { - // 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()) + size_t count = polygon.size(); + if (count < 3) 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; + // convert and scale to float + std::vector points_d; + points_d.reserve(count); + for (const Point &point : polygon.points) + points_d.push_back(unscale(point).cast()); - size_t count = polygon_expanded.size(); - init_data.reserve_vertices(2 * count); - init_data.reserve_indices(2 * count); + // pre calculate normalized line directions + auto calc_line_norm = [](const Vec2f &f, const Vec2f &s) -> Vec2f { return (s - f).normalized(); }; + std::vector line_norm(points_d.size()); + for (size_t i = 0; i < count - 1; ++i) + line_norm[i] = calc_line_norm(points_d[i], points_d[i + 1]); + line_norm.back() = calc_line_norm(points_d.back(), points_d.front()); - 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); + // calculate normals for each point + auto calc_norm = [](const Vec2f &prev, const Vec2f &next) -> Vec2f { + Vec2f dir = prev + next; + return Vec2f(-dir.x(), dir.y()); + }; + std::vector points_norm(points_d.size()); + points_norm.front() = calc_norm(line_norm.back(), line_norm[1]); + for (size_t i = 1; i < points_d.size() - 1; ++i) + points_norm[i] = calc_norm(line_norm[i - 1], line_norm[i + 1]); + points_norm.back() = calc_norm(line_norm[points_d.size() - 2], line_norm.front()); + + // precalculate sinus and cosinus + double angle_step = 2 * M_PI / steps; + std::vector> sin_cos; + sin_cos.reserve(steps); + for (size_t s = 0; s < steps; ++s) { + double angle = s * angle_step; + sin_cos.emplace_back( + radius * std::sin(angle), + static_cast(radius * std::cos(angle)) + ); + } + + // create torus model along polygon path + indexed_triangle_set model; + model.vertices.reserve(steps * count); + model.indices.reserve(2 * steps * count); + for (size_t i = 0; i < count; ++i) { + const Vec2f point_d = points_d[i]; + const Vec2f norm = points_norm[i]; + for (const auto &[s, c] : sin_cos) { + Vec2f xy = s * norm + point_d; + model.vertices.emplace_back(xy.x(), xy.y(), c); + } } unsigned int prev_i = count - 1; - for (unsigned int i = 0; i < count; ++i) { + for (unsigned int i = 0; i < count; ++i) { + // TODO: solve <180, =180 and >180 angle + // to not create self intersection + // 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); + unsigned int prev_t = (prev_i+1) * steps - 1; + unsigned int t = (i+1) * steps - 1; + for (size_t s = 0; s < steps; ++s) { + unsigned int prev_b = prev_i * steps + s; + unsigned int b = i * steps + s; + model.indices.emplace_back(prev_t, prev_b, t); + model.indices.emplace_back(b, t, prev_b); + prev_t = prev_b; + t = b; + } prev_i = i; } - - - // line .. y offset from volume(define line for sliced polygon) - - - GLModel gl_model; - gl_model.init_from(std::move(init_data)); - return gl_model; -}*/ - + return model; +} // select closest contour for each line TextLines select_closest_contour(const std::vector &line_contours) { @@ -181,41 +205,53 @@ TextLines select_closest_contour(const std::vector &line_contours) { expolygons[index.expolygons_index].holes[index.hole_index()]; Point hit_point_int = hit_point.cast(); - TextLine tl{polygon, index.point_index, hit_point_int}; + TextLine tl{polygon, PolygonPoint{index.point_index, hit_point_int}}; result.emplace_back(tl); } return result; } -GLModel create_model(const TextLines &lines, const std::vector &line_centers) -{ - double model_half_width = 0.5; // [in volume mm] - ColorRGBA color(.7f, .7f, .7f, .7f); // Gray +inline Eigen::AngleAxis get_rotation() { return Eigen::AngleAxis(-M_PI_2, Vec3d::UnitX()); } +indexed_triangle_set create_its(const TextLines &lines) +{ + const float model_half_width = 0.75; // [in volume mm] indexed_triangle_set its; - auto rot = Eigen::AngleAxis(M_PI_2, Vec3d::UnitX()); - - assert(lines.size() == line_centers.size()); // create model from polygons - for (size_t i = 0; i < lines.size(); ++i) { - const Slic3r::Polygon &polygon = lines[i].polygon; + for (const TextLine &line : lines) { + const Slic3r::Polygon &polygon = line.polygon; if (polygon.empty()) continue; - double line_center = line_centers[i]; - indexed_triangle_set line_its = create_its(polygon, model_half_width); - auto transl = Eigen::Translation3d(0., -line_center, 0.); - Transform3d tr = transl * rot; + indexed_triangle_set line_its = its_create_belt(polygon, model_half_width); + //indexed_triangle_set line_its = its_create_torus(polygon, model_half_width); + auto transl = Eigen::Translation3d(0., -line.y, 0.); + Transform3d tr = transl * get_rotation(); its_transform(line_its, tr); its_merge(its, line_its); } + return its; +} - GLModel gl_model; - gl_model.init_from(its); - gl_model.set_color(color); - return gl_model; +GLModel::Geometry create_geometry(const TextLines &lines) +{ + indexed_triangle_set its = create_its(lines); + + GLModel::Geometry geometry; + geometry.format = {GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3}; + ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray + geometry.color = color; + + geometry.reserve_vertices(its.vertices.size()); + for (Vec3f vertex : its.vertices) + geometry.add_vertex(vertex); + + geometry.reserve_indices(its.indices.size() * 3); + for (Vec3i t : its.indices) + geometry.add_triangle(t[0], t[1], t[2]); + return geometry; } } // namespace -void TextLinesModel::init(const Selection &selection, double line_height) +void TextLinesModel::init(const Selection &selection, double line_height, double line_offset) { const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) @@ -240,16 +276,15 @@ void TextLinesModel::init(const Selection &selection, double line_height) if (count_lines == 0) return; - double first_line_center = -((count_lines / 2) * line_height) - ((count_lines % 2 == 0)? line_height/2. : 0.); + double first_line_center = line_offset + (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] = static_cast(first_line_center + i * line_height); + line_centers[i] = static_cast(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 = mv_trafo * get_rotation(); Transform3d c_trafo_inv = c_trafo.inverse(); std::vector line_contours(count_lines); @@ -274,8 +309,20 @@ void TextLinesModel::init(const Selection &selection, double line_height) } } - m_lines = select_closest_contour(line_contours); - m_model = create_model(m_lines, line_centers); + m_lines = select_closest_contour(line_contours); + assert(m_lines.size() == count_lines); + assert(line_centers.size() == count_lines); + for (size_t i = 0; i < count_lines; ++i) + m_lines[i].y = line_centers[i]; + + m_model.reset(); + //* + m_model.init_from(create_geometry(m_lines)); + /*/ + ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray + m_model.set_color(color); + m_model.init_from(create_its(m_lines)); + //*/ } void TextLinesModel::render(const Transform3d &text_world) diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index 886d1532a2..a4ed36f3e8 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -7,40 +7,26 @@ #include #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: - // line_height in mm - void init(const Selection &selection, double line_height); + /// + /// Initialize model and lines + /// + /// Must be selected text volume + /// Height of text line with spacing [in mm] + /// Offset of base line from center [in mm] + void init(const Selection &selection, double line_height, double line_offset = 0.); void render(const Transform3d &text_world); bool is_init() const { return m_model.is_initialized(); } void reset() { m_model.reset(); } - const TextLines &get_lines() const { return m_lines; } + const Slic3r::Emboss::TextLines &get_lines() const { return m_lines; } static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); private: - TextLines m_lines; + Slic3r::Emboss::TextLines m_lines; // Keep model for visualization text lines GLModel m_model; From 28b526167a71a1d9d1e5d7efc70516b8d6cd15e8 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 4 May 2023 19:13:13 +0200 Subject: [PATCH 07/23] Add text alignement --- src/libslic3r/Emboss.cpp | 67 +++++++++++++++++++++++++ src/libslic3r/Emboss.hpp | 9 ++++ src/libslic3r/TextConfiguration.hpp | 29 +++++------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 29 +++++++++++ src/slic3r/GUI/Jobs/EmbossJob.cpp | 23 +++++---- src/slic3r/GUI/TextLines.cpp | 6 +-- src/slic3r/GUI/TextLines.hpp | 4 +- 7 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index f660a444cf..9f9d182d98 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1918,6 +1918,73 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b return samples; } +namespace { +Point get_align_offset(FontProp::Align type, const BoundingBox &bb) +{ + Point offset; + switch (type) { + // case Slic3r::FontProp::Align::start_first_line: break; + case Slic3r::FontProp::Align::center_left: + case Slic3r::FontProp::Align::center_right: + case Slic3r::FontProp::Align::center_center: offset.y() = bb.center().y(); break; + case Slic3r::FontProp::Align::top_left: + case Slic3r::FontProp::Align::top_right: + case Slic3r::FontProp::Align::top_center: offset.y() = bb.min.y(); break; + case Slic3r::FontProp::Align::bottom_left: + case Slic3r::FontProp::Align::bottom_right: + case Slic3r::FontProp::Align::bottom_center: offset.y() = bb.max.y(); break; + default: break; + } + + switch (type) { + // case Slic3r::FontProp::Align::start_first_line: break; + case Slic3r::FontProp::Align::center_center: + case Slic3r::FontProp::Align::top_center: + case Slic3r::FontProp::Align::bottom_center: offset.x() = bb.center().x(); break; + case Slic3r::FontProp::Align::center_left: + case Slic3r::FontProp::Align::top_left: + case Slic3r::FontProp::Align::bottom_left: offset.x() = bb.min.x(); break; + case Slic3r::FontProp::Align::center_right: + case Slic3r::FontProp::Align::top_right: + case Slic3r::FontProp::Align::bottom_right: offset.x() = bb.max.x(); break; + default: break; + } + return -offset; +} +} // namespace + +void Emboss::align_shape(FontProp::Align type, ExPolygons &shape, BoundingBox *bb) +{ + if (type == FontProp::Align::start_first_line) + return; // no alignement + + BoundingBox shape_bb_data; + BoundingBox &shape_bb = (bb != nullptr) ? *bb : shape_bb_data; + if (!shape_bb.defined) + shape_bb = get_extents(shape); + + Point offset = get_align_offset(type, shape_bb); + for (ExPolygon &s : shape) + s.translate(offset); +} + +void Emboss::align_shape(FontProp::Align type, std::vector &shapes, BoundingBox *bb) +{ + if (type == FontProp::Align::start_first_line) + return; // no alignement + + BoundingBox shape_bb_data; + BoundingBox &shape_bb = (bb != nullptr) ? *bb : shape_bb_data; + if (!shape_bb.defined) + for (const ExPolygons& shape: shapes) + shape_bb.merge(get_extents(shape)); + + Point offset = get_align_offset(type, shape_bb); + for (ExPolygons &shape : shapes) + for (ExPolygon &s : shape) + s.translate(offset); +} + #ifdef REMOVE_SPIKES #include void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc) diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 0779aee080..db864df9b4 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -442,6 +442,15 @@ namespace Emboss double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); + /// + /// Align expolygons by type + /// + /// Type of alignement + /// shapes to align + /// extents of shape + void align_shape(FontProp::Align type, ExPolygons& shape, BoundingBox* bb = nullptr); + void align_shape(FontProp::Align type, std::vector &shape, BoundingBox *bb = nullptr); + } // namespace Emboss } // namespace Slic3r #endif // slic3r_Emboss_hpp_ diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 14a9645a07..bdc9371a3d 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -63,20 +63,21 @@ struct FontProp // Distiguish projection per glyph bool per_glyph; - //enum class Align { - // left, - // right, - // center, - // top_left, - // top_right, - // top_center, - // bottom_left, - // bottom_right, - // bottom_center - //}; - //// change pivot of text - //// When not set, center is used and is not stored - //std::optional align; + enum class Align { + start_first_line, // it depends on position of zero for first letter + center_left, + center_right, + center_center, + top_left, + top_right, + top_center, + bottom_left, + bottom_right, + bottom_center + }; + // change pivot of text + // When not set, center is used and is not stored + Align align = Align::center_center; ////// // Duplicit data to wxFontDescriptor diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index b14e84baf7..e2d22ae60e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1059,6 +1059,9 @@ void GLGizmoEmboss::init_text_lines(){ if (ff_ptr == nullptr) return; + if (m_volume->is_the_only_one_part()) + return; + const FontProp& fp = m_style_manager.get_font_prop(); const FontFile &ff = *ff_ptr; @@ -3226,6 +3229,32 @@ void GLGizmoEmboss::draw_advanced() } } else if (!*per_glyph && m_text_lines.is_init()) m_text_lines.reset(); + + ImGui::SameLine(); + ImGui::SetNextItemWidth(100); + if (ImGui::SliderFloat("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { + init_text_lines(); + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); + + // order must match align enum + const char* align_names[] = { + "start_first_line", + "center_left", + "center_right", + "center_center", + "top_left", + "top_right", + "top_center", + "bottom_left", + "bottom_right", + "bottom_center" + }; + int selected_align = static_cast(font_prop.align); + if (ImGui::Combo("align", &selected_align, align_names, IM_ARRAYSIZE(align_names))) { + font_prop.align = static_cast(selected_align); + } + if (exist_change) { m_style_manager.clear_glyphs_cache(); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 256dbca68b..d31c7dfd28 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -427,7 +427,12 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { if (!font.has_value()) return {}; - return text2shapes(font, text, prop, was_canceled); + ExPolygons shapes = text2shapes(font, text, prop, was_canceled); + if (shapes.empty()) + return {}; + + align_shape(input.text_configuration.style.prop.align, shapes); + return shapes; } #define STORE_SAMPLING @@ -563,15 +568,16 @@ TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled) ExPolygons shapes = priv::create_shape(input, was_canceled); if (shapes.empty()) return {}; - if (was_canceled()) return {}; - + if (was_canceled()) return {}; + const FontProp &prop = input.text_configuration.style.prop; 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); - ProjectScale project(std::move(projectZ), scale); + auto projectZ = std::make_unique(depth); + //auto scaled = std::make_unique(std::move(projectZ), scale); + ProjectTransform project(std::move(projectZ), Eigen::Translation(0., 0., -prop.emboss / 2) * Eigen::Scaling(scale)); if (was_canceled()) return {}; return TriangleMesh(polygons2model(shapes, project)); } @@ -867,12 +873,7 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 if (was_canceled()) return {}; - // Define alignment of text - left, right, center, top bottom, .... - BoundingBox bb = get_extents(shapes); - Point projection_center = bb.center(); - for (ExPolygon &shape : shapes) shape.translate(-projection_center); - bb.translate(-projection_center); - + BoundingBox bb = get_extents(shapes); const FontFile &ff = *input1.font_file.font_file; const FontProp &fp = input1.text_configuration.style.prop; double shape_scale = get_shape_scale(fp, ff); diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 0a4652f942..50d6109c6b 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -223,7 +223,7 @@ indexed_triangle_set create_its(const TextLines &lines) if (polygon.empty()) continue; indexed_triangle_set line_its = its_create_belt(polygon, model_half_width); //indexed_triangle_set line_its = its_create_torus(polygon, model_half_width); - auto transl = Eigen::Translation3d(0., -line.y, 0.); + auto transl = Eigen::Translation3d(0., line.y, 0.); Transform3d tr = transl * get_rotation(); its_transform(line_its, tr); its_merge(its, line_its); @@ -251,7 +251,7 @@ GLModel::Geometry create_geometry(const TextLines &lines) } } // namespace -void TextLinesModel::init(const Selection &selection, double line_height, double line_offset) +void TextLinesModel::init(const Selection &selection, double line_height) { const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) @@ -276,7 +276,7 @@ void TextLinesModel::init(const Selection &selection, double line_height, double if (count_lines == 0) return; - double first_line_center = line_offset + (count_lines / 2) * line_height - ((count_lines % 2 == 0) ? line_height / 2. : 0.); + double first_line_center = offset + (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] = static_cast(first_line_center - i * line_height); diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index a4ed36f3e8..741577c143 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -12,13 +12,15 @@ class Selection; class TextLinesModel { public: + // line offset in y direction (up/down) + float offset = 0; /// /// Initialize model and lines /// /// Must be selected text volume /// Height of text line with spacing [in mm] /// Offset of base line from center [in mm] - void init(const Selection &selection, double line_height, double line_offset = 0.); + void init(const Selection &selection, double line_height); void render(const Transform3d &text_world); bool is_init() const { return m_model.is_initialized(); } From 43294950bdc1aab62a34aaf53ec2a4f293ccc1ad Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 9 May 2023 13:00:49 +0200 Subject: [PATCH 08/23] Per letter emboss with align --- src/libslic3r/Emboss.cpp | 8 ++++---- src/libslic3r/Emboss.hpp | 3 +-- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 27 ++++++++++++++----------- src/slic3r/GUI/Jobs/EmbossJob.cpp | 14 ++++++------- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 9f9d182d98..03637ab576 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1870,19 +1870,19 @@ std::vector Emboss::calculate_angles(int32_t distance, const PolygonPoin return result; } -PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &bbs, int32_t center_x, double scale) +PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale) { // find BB in center of line size_t first_right_index = 0; for (const BoundingBox &bb : bbs) - if (bb.min.x() > center_x) { + if (bb.min.x() > 0) { break; } else { ++first_right_index; } PolygonPoints samples(bbs.size()); - int32_t shapes_x_cursor = center_x; + int32_t shapes_x_cursor = 0; PolygonPoint cursor = slice.start; //copy @@ -1908,7 +1908,7 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b samples[index] = create_sample(bbs[index], is_reverse); // calc transformation for letters on the Left side from center - shapes_x_cursor = center_x; + shapes_x_cursor = 0; cursor = slice.start; // copy is_reverse = false; for (size_t index_plus_one = first_right_index; index_plus_one > 0; --index_plus_one) { diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index db864df9b4..5c10eae10a 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -427,10 +427,9 @@ namespace Emboss /// /// Polygon and start point /// Bounding boxes of letter on one line - /// Center x coor of bbs line /// Scale for bbs /// Sampled polygon by bounding boxes - PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, int32_t center_x, double scale); + PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale); /// /// Calculate angle for polygon point diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e2d22ae60e..9fba25cce5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -236,8 +236,9 @@ static bool draw_button(const IconManager::VIcons& icons, IconType type, bool di /// /// Define view vector /// Containe Selected Model to modify +/// Keep same up vector /// True when apply change otherwise false -static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); +static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas, bool keep_up); } // namespace priv @@ -3201,11 +3202,16 @@ 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(); - bool use_surface = m_style_manager.get_style().prop.use_surface; - if (priv::apply_camera_dir(cam, m_parent) && use_surface) + const Camera &cam = wxGetApp().plater()->get_camera(); + bool use_surface = m_style_manager.get_style().prop.use_surface; + if (priv::apply_camera_dir(cam, m_parent, m_keep_up) && use_surface) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Orient the text towards the camera.").c_str()); @@ -3218,7 +3224,7 @@ void GLGizmoEmboss::draw_advanced() if (!m_text_lines.is_init()) init_text_lines(); } - exist_change = true; + process(); } else if (ImGui::IsItemHovered()) { if (*per_glyph) { ImGui::SetTooltip("%s", _u8L("Set global orientation for whole text.").c_str()); @@ -3234,6 +3240,7 @@ void GLGizmoEmboss::draw_advanced() ImGui::SetNextItemWidth(100); if (ImGui::SliderFloat("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { init_text_lines(); + process(); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); @@ -3253,13 +3260,10 @@ void GLGizmoEmboss::draw_advanced() int selected_align = static_cast(font_prop.align); if (ImGui::Combo("align", &selected_align, align_names, IM_ARRAYSIZE(align_names))) { font_prop.align = static_cast(selected_align); - } - - - if (exist_change) { - m_style_manager.clear_glyphs_cache(); + // TODO: move with text in finalize to not change position process(); } + #ifdef ALLOW_DEBUG_MODE ImGui::Text("family = %s", (font_prop.family.has_value() ? font_prop.family->c_str() : @@ -3743,8 +3747,7 @@ void priv::change_window_position(std::optional& output_window_offset, b output_window_offset = ImVec2(-1, -1); // Cannot } - -bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { +bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas, bool keep_up) { const Vec3d &cam_dir = camera.get_dir_forward(); Selection &sel = canvas.get_selection(); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index d31c7dfd28..fc7d35131d 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -457,11 +457,13 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w if (shapes.empty()) return {}; - const FontFile &ff = *font.font_file; - double shape_scale = get_shape_scale(prop, ff); + align_shape(prop.align, shapes); - BoundingBox extents; // whole text to find center - // separate lines of text to vector + const FontFile &ff = *font.font_file; + double shape_scale = get_shape_scale(prop, ff); + + // Precalculate bounding boxes of glyphs + // Separate lines of text to vector of Bounds std::vector bbs(count_lines); size_t text_line_index = 0; // s_i .. shape index @@ -470,7 +472,6 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w BoundingBox bb; if (!shape.empty()) { bb = get_extents(shape); - extents.merge(bb); } BoundingBoxes &line_bbs = bbs[text_line_index]; line_bbs.push_back(bb); @@ -487,14 +488,13 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w double em_2_mm = prop.size_in_mm / 2.; int32_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); - Point center = extents.center(); size_t s_i_offset = 0; // shape index offset(for next lines) indexed_triangle_set result; for (text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) { const BoundingBoxes &line_bbs = bbs[text_line_index]; const TextLine &line = input.text_lines[text_line_index]; // IMPROVE: do not precalculate samples do it inline - store result its - PolygonPoints samples = sample_slice(line, line_bbs, center.x(), shape_scale); + PolygonPoints samples = sample_slice(line, line_bbs, shape_scale); std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); for (size_t i = 0; i < line_bbs.size(); ++i) { From cf57bf02ff9ba0183bacb6fd4d7a39f498a5a24d Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 9 May 2023 13:43:57 +0200 Subject: [PATCH 09/23] Fix init after set --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 9fba25cce5..f536c70909 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1204,12 +1204,12 @@ void GLGizmoEmboss::set_volume_by_selection() m_job_cancel = nullptr; } - if (tc.style.prop.per_glyph) - init_text_lines(); - m_text = tc.text; m_volume = volume; m_volume_id = volume->id(); + + if (tc.style.prop.per_glyph) + init_text_lines(); // Calculate current angle of up vector assert(m_style_manager.is_active_font()); From 50f09a751b99859f0562f9decc82171dc29b1b87 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 10 May 2023 16:08:49 +0200 Subject: [PATCH 10/23] Use surface when emboss per letter. --- src/libslic3r/Emboss.cpp | 2 +- src/libslic3r/Emboss.hpp | 6 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 244 ++++++++++++++++++++++-------- src/slic3r/GUI/TextLines.cpp | 5 +- 4 files changed, 189 insertions(+), 68 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 03637ab576..d8ef2071b7 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1875,7 +1875,7 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b // find BB in center of line size_t first_right_index = 0; for (const BoundingBox &bb : bbs) - if (bb.min.x() > 0) { + if (bb.min.x() >= 0) { break; } else { ++first_right_index; diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 5c10eae10a..fc0a1831d3 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -425,9 +425,9 @@ namespace Emboss /// Sample slice polygon by bounding boxes centers /// slice start point has shape_center_x coor /// - /// Polygon and start point - /// Bounding boxes of letter on one line - /// Scale for bbs + /// Polygon and start point[Slic3r scaled milimeters] + /// Bounding boxes of letter on one line[in font scales] + /// Scale for bbs (after multiply bb is in milimeters) /// Sampled polygon by bounding boxes PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index fc7d35131d..ca2f04327b 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -42,6 +42,7 @@ bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); template static ExPolygons create_shape(DataBase &input, Fnc was_canceled); +template static std::vector create_shapes(DataBase &input, Fnc was_canceled); // /// Try to create mesh from text @@ -422,7 +423,7 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { const TextConfiguration &tc = input.text_configuration; const char *text = tc.text.c_str(); const FontProp &prop = tc.style.prop; - + assert(!prop.per_glyph); assert(font.has_value()); if (!font.has_value()) return {}; @@ -435,37 +436,44 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { return shapes; } -#define STORE_SAMPLING -#ifdef STORE_SAMPLING -#include "libslic3r/SVG.hpp" -#endif // STORE_SAMPLING -namespace { -template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc was_canceled) -{ - // method use square of coord stored into int64_t - static_assert(std::is_same()); - +template +std::vector priv::create_shapes(DataBase &input, Fnc was_canceled) { 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; - + const char *text = tc.text.c_str(); + const FontProp &prop = tc.style.prop; + assert(prop.per_glyph); assert(font.has_value()); + if (!font.has_value()) + return {}; + + std::wstring ws = boost::nowide::widen(text); std::vector shapes = text2vshapes(font, ws, prop, was_canceled); if (shapes.empty()) return {}; - align_shape(prop.align, shapes); + align_shape(prop.align, shapes); + if (was_canceled()) + return {}; - const FontFile &ff = *font.font_file; - double shape_scale = get_shape_scale(prop, ff); + return shapes; +} - // Precalculate bounding boxes of glyphs - // Separate lines of text to vector of Bounds - std::vector bbs(count_lines); - size_t text_line_index = 0; +//#define STORE_SAMPLING +#ifdef STORE_SAMPLING +#include "libslic3r/SVG.hpp" +#endif // STORE_SAMPLING +namespace { + +std::vector create_line_bounds(const std::vector &shapes, const std::wstring& text, size_t count_lines = 0) +{ + assert(text.size() == shapes.size()); + if (count_lines == 0) + count_lines = get_count_lines(text); + assert(count_lines == get_count_lines(text)); + + std::vector result(count_lines); + size_t text_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]; @@ -473,12 +481,36 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w if (!shape.empty()) { bb = get_extents(shape); } - BoundingBoxes &line_bbs = bbs[text_line_index]; + BoundingBoxes &line_bbs = result[text_line_index]; line_bbs.push_back(bb); - if (ws[s_i] == '\n') + if (text[s_i] == '\n'){ + // skip enters on beginig and tail ++text_line_index; + } } + return result; +} +template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc was_canceled) +{ + // method use square of coord stored into int64_t + static_assert(std::is_same()); + + std::vector shapes = priv::create_shapes(input, was_canceled); + if (shapes.empty()) + return {}; + + // Precalculate bounding boxes of glyphs + // Separate lines of text to vector of Bounds + const TextConfiguration &tc = input.text_configuration; + std::wstring ws = boost::nowide::widen(tc.text.c_str()); + assert(get_count_lines(ws) == input.text_lines.size()); + size_t count_lines = input.text_lines.size(); + std::vector bbs = create_line_bounds(shapes, ws, count_lines); + + const FontProp &prop = tc.style.prop; + FontFileWithCache &font = input.font_file; + double shape_scale = get_shape_scale(prop, *font.font_file); double projec_scale = shape_scale / SHAPE_SCALE; double depth = prop.emboss / projec_scale; auto projectZ = std::make_unique(depth); @@ -490,19 +522,18 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w size_t s_i_offset = 0; // shape index offset(for next lines) indexed_triangle_set result; - for (text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) { + for (size_t text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) { const BoundingBoxes &line_bbs = bbs[text_line_index]; const TextLine &line = input.text_lines[text_line_index]; - // IMPROVE: do not precalculate samples do it inline - store result its PolygonPoints samples = sample_slice(line, line_bbs, shape_scale); std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); for (size_t i = 0; i < line_bbs.size(); ++i) { - const BoundingBox &line_bb = line_bbs[i]; - if (!line_bb.defined) + const BoundingBox &letter_bb = line_bbs[i]; + if (!letter_bb.defined) continue; - Vec2d to_zero_vec = line_bb.center().cast() * shape_scale; // [in mm] + Vec2d to_zero_vec = letter_bb.center().cast() * shape_scale; // [in mm] auto to_zero = Eigen::Translation(-to_zero_vec.x(), 0., 0.); const double &angle = angles[i]; @@ -512,15 +543,16 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w Vec2d offset_vec = unscale(sample.point); // [in mm] auto offset_tr = Eigen::Translation(offset_vec.x(), 0., -offset_vec.y()); - //Transform3d tr = offset_tr * rotate * to_zero * scale_tr; Transform3d tr = offset_tr * rotate * to_zero * scale_tr; - //Transform3d tr = Transform3d::Identity() * scale_tr; const ExPolygons &letter_shape = shapes[s_i_offset + i]; - + assert(get_extents(letter_shape) == letter_bb); auto projectZ = std::make_unique(depth); ProjectTransform project(std::move(projectZ), tr); indexed_triangle_set glyph_its = polygons2model(letter_shape, project); its_merge(result, std::move(glyph_its)); + + if (((s_i_offset + i) % 15) && was_canceled()) + return {}; } s_i_offset += line_bbs.size(); @@ -549,10 +581,7 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w } #endif // STORE_SAMPLING } - - // ProjectTransform project(std::move(projectZ), ) - // if (was_canceled()) return {}; - return TriangleMesh(result); + return TriangleMesh(std::move(result)); } } // namespace @@ -864,22 +893,16 @@ OrthoProject3d priv::create_emboss_projection( return OrthoProject3d(from_front_to_back); } -// input can't be const - cache of font -TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function was_canceled) -{ - ExPolygons shapes = create_shape(input1, was_canceled); - if (shapes.empty()) - throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); - - if (was_canceled()) return {}; +namespace { +indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d& tr,const SurfaceVolumeData::ModelSources &sources, bool is_outside, DataBase& input, std::function was_canceled) { + assert(!sources.empty()); BoundingBox bb = get_extents(shapes); - const FontFile &ff = *input1.font_file.font_file; - const FontProp &fp = input1.text_configuration.style.prop; + const FontFile &ff = *input.font_file.font_file; + const FontProp &fp = input.text_configuration.style.prop; double shape_scale = get_shape_scale(fp, ff); - const SurfaceVolumeData::ModelSources &sources = input2.sources; - const SurfaceVolumeData::ModelSource *biggest = nullptr; + const SurfaceVolumeData::ModelSource *biggest = &sources.front(); size_t biggest_count = 0; // convert index from (s)ources to (i)ndexed (t)riangle (s)ets @@ -888,9 +911,9 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 itss.reserve(sources.size()); for (const SurfaceVolumeData::ModelSource &s : sources) { Transform3d mesh_tr_inv = s.tr.inverse(); - Transform3d cut_projection_tr = mesh_tr_inv * input2.text_tr; + Transform3d cut_projection_tr = mesh_tr_inv * tr; std::pair z_range{0., 1.}; - OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); + OrthoProject cut_projection = priv::create_projection_for_cut(cut_projection_tr, shape_scale, z_range); // copy only part of source model indexed_triangle_set its = its_cut_AoI(s.mesh->its, bb, cut_projection); if (its.indices.empty()) continue; @@ -904,10 +927,10 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 itss.emplace_back(std::move(its)); } if (itss.empty()) - throw JobException(_u8L("There is no volume in projection direction.").c_str()); + return {}; Transform3d tr_inv = biggest->tr.inverse(); - Transform3d cut_projection_tr = tr_inv * input2.text_tr; + Transform3d cut_projection_tr = tr_inv * tr; size_t itss_index = s_to_itss[biggest - &sources.front()]; BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]); @@ -929,22 +952,27 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 Transform3d emboss_tr = cut_projection_tr.inverse(); BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); std::pair z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()}; - OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); - float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension); + OrthoProject cut_projection = priv::create_projection_for_cut(cut_projection_tr, shape_scale, z_range); + float projection_ratio = (-z_range.first + priv::safe_extension) / + (z_range.second - z_range.first + 2 * priv::safe_extension); - bool is_text_reflected = Slic3r::has_reflection(input2.text_tr); + ExPolygons shapes_data; // is used only when text is reflected to reverse polygon points order + const ExPolygons *shapes_ptr = &shapes; + bool is_text_reflected = Slic3r::has_reflection(tr); if (is_text_reflected) { // revert order of points in expolygons // CW --> CCW - for (ExPolygon &shape : shapes) { + shapes_data = shapes; // copy + for (ExPolygon &shape : shapes_data) { shape.contour.reverse(); for (Slic3r::Polygon &hole : shape.holes) hole.reverse(); } + shapes_ptr = &shapes_data; } // Use CGAL to cut surface from triangle mesh - SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio); + SurfaceCut cut = cut_surface(*shapes_ptr, itss, cut_projection, projection_ratio); if (is_text_reflected) { for (SurfaceCut::Contour &c : cut.contours) @@ -953,15 +981,105 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 std::swap(t[0], t[1]); } - if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str()); + if (cut.empty()) return {}; // There is no valid surface for text projection. if (was_canceled()) return {}; // !! Projection needs to transform cut - OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, emboss_tr, cut); - indexed_triangle_set new_its = cut2model(cut, projection); - assert(!new_its.empty()); + OrthoProject3d projection = priv::create_emboss_projection(is_outside, fp.emboss, emboss_tr, cut); + return cut2model(cut, projection); +} + +TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2, std::function was_canceled) +{ + std::vector shapes = priv::create_shapes(input1, was_canceled); if (was_canceled()) return {}; - return TriangleMesh(std::move(new_its)); + if (shapes.empty()) + throw priv::JobException(_u8L("Font doesn't have any shape for given text.").c_str()); + + // Precalculate bounding boxes of glyphs + // Separate lines of text to vector of Bounds + const TextConfiguration &tc = input1.text_configuration; + std::wstring ws = boost::nowide::widen(tc.text.c_str()); + assert(get_count_lines(ws) == input1.text_lines.size()); + size_t count_lines = input1.text_lines.size(); + std::vector bbs = create_line_bounds(shapes, ws, count_lines); + + const FontProp &prop = tc.style.prop; + FontFileWithCache &font = input1.font_file; + double shape_scale = get_shape_scale(prop, *font.font_file); + + // half of font em size for direction of letter emboss + double em_2_mm = prop.size_in_mm / 2.; + int32_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); + + size_t s_i_offset = 0; // shape index offset(for next lines) + indexed_triangle_set result; + for (size_t text_line_index = 0; text_line_index < input1.text_lines.size(); ++text_line_index) { + const BoundingBoxes &line_bbs = bbs[text_line_index]; + const TextLine &line = input1.text_lines[text_line_index]; + PolygonPoints samples = sample_slice(line, line_bbs, shape_scale); + std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + + for (size_t i = 0; i < line_bbs.size(); ++i) { + const BoundingBox &glyph_bb = line_bbs[i]; + if (!glyph_bb.defined) + continue; + + const double &angle = angles[i]; + auto rotate = Eigen::AngleAxisd(angle + M_PI_2, Vec3d::UnitY()); + + const PolygonPoint &sample = samples[i]; + Vec2d offset_vec = unscale(sample.point); // [in mm] + auto offset_tr = Eigen::Translation(offset_vec.x(), 0., -offset_vec.y()); + + ExPolygons &glyph_shape = shapes[s_i_offset + i]; + assert(get_extents(glyph_shape) == glyph_bb); + + Point offset(-glyph_bb.center().x(), 0); + for (ExPolygon& s: glyph_shape) + s.translate(offset); + + Transform3d modify = offset_tr * rotate; + Transform3d tr = input2.text_tr * modify; + indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, tr, input2.sources, input2.is_outside, input1, was_canceled); + // move letter in volume on the right position + its_transform(glyph_its, modify); + + // Improve: union instead of merge + its_merge(result, std::move(glyph_its)); + + if (((s_i_offset + i) % 15) && was_canceled()) + return {}; + } + s_i_offset += line_bbs.size(); + } + + if (was_canceled()) return {}; + if (result.empty()) + throw priv::JobException(_u8L("There is no valid surface for text projection.").c_str()); + return TriangleMesh(std::move(result)); +} + +} // namespace + +// input can't be const - cache of font +TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function was_canceled) +{ + const FontProp &fp = input1.text_configuration.style.prop; + if (fp.per_glyph) + return cut_per_glyph_surface(input1, input2, was_canceled); + + ExPolygons shapes = create_shape(input1, was_canceled); + if (was_canceled()) return {}; + if (shapes.empty()) + throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); + + indexed_triangle_set its = cut_surface_to_its(shapes, input2.text_tr, input2.sources, input2.is_outside, input1, was_canceled); + if (was_canceled()) return {}; + if (its.empty()) + throw JobException(_u8L("There is no valid surface for text projection.").c_str()); + + return TriangleMesh(std::move(its)); } bool priv::process(std::exception_ptr &eptr) { diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 50d6109c6b..a61133055e 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -317,7 +317,10 @@ void TextLinesModel::init(const Selection &selection, double line_height) m_model.reset(); //* - m_model.init_from(create_geometry(m_lines)); + GLModel::Geometry geometry = create_geometry(m_lines); + if (geometry.vertices_count() == 0 || geometry.indices_count() == 0) + return; + m_model.init_from(std::move(geometry)); /*/ ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray m_model.set_color(color); From 15af50d92361017e1c9c044302b7bf104cc1b479 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 17 May 2023 12:57:06 +0200 Subject: [PATCH 11/23] Fix left side offset from center of base line. --- src/libslic3r/Emboss.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index d8ef2071b7..68a6f87e65 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1908,8 +1908,14 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b samples[index] = create_sample(bbs[index], is_reverse); // calc transformation for letters on the Left side from center - shapes_x_cursor = 0; - cursor = slice.start; // copy + if (first_right_index < bbs.size()) { + shapes_x_cursor = bbs[first_right_index].center().x(); + cursor = samples[first_right_index]; + }else{ + // only left side exists + shapes_x_cursor = 0; + cursor = slice.start; // copy + } is_reverse = false; for (size_t index_plus_one = first_right_index; index_plus_one > 0; --index_plus_one) { size_t index = index_plus_one - 1; From 4be73de02fdf48bc080351aa5d0977e93b1bae62 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 19 May 2023 14:11:15 +0200 Subject: [PATCH 12/23] Partialy fix align: whitechar in center enters at begining enters at end Align --- src/libslic3r/Emboss.cpp | 157 +++++++++++++----------- src/libslic3r/Emboss.hpp | 9 -- src/libslic3r/TextConfiguration.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 61 +++++---- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 2 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 19 +-- src/slic3r/GUI/TextLines.cpp | 18 +-- src/slic3r/GUI/TextLines.hpp | 3 +- 8 files changed, 145 insertions(+), 130 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 68a6f87e65..dd156da8ad 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1271,32 +1271,30 @@ const int CANCEL_CHECK = 10; 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()); - 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::wstring text_w = boost::nowide::widen(text); + std::vector vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled); + // unify to one expolygon ExPolygons result; - fontinfo_opt font_info_cache; - std::wstring ws = boost::nowide::widen(text); - for (wchar_t wc: ws){ - if (++counter == CANCEL_CHECK) { - counter = 0; - if (was_canceled()) - return {}; - } - ExPolygons expolygons = letter2shapes(wc, cursor, font_with_cache, font_prop, font_info_cache); - if (expolygons.empty()) + for (ExPolygons &shapes : vshapes) { + if (shapes.empty()) continue; - expolygons_append(result, std::move(expolygons)); + expolygons_append(result, std::move(shapes)); } result = Slic3r::union_ex(result); heal_shape(result); return result; } +namespace { +/// +/// Align expolygons by type +/// +/// Type of alignement +/// shapes to align +/// Same size as shape for align per line(detect of end line - '\n') +void align_shape(FontProp::Align type, std::vector &shape, const std::wstring &text); +} + 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; @@ -1307,8 +1305,8 @@ std::vector Emboss::text2vshapes(FontFileWithCache &font_with_cache, unsigned counter = 0; Point cursor(0, 0); - std::vector result; fontinfo_opt font_info_cache; + std::vector result; result.reserve(text.size()); for (wchar_t letter : text) { if (++counter == CANCEL_CHECK) { @@ -1318,6 +1316,8 @@ std::vector Emboss::text2vshapes(FontFileWithCache &font_with_cache, } result.emplace_back(letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)); } + + align_shape(font_prop.align, result, text); return result; } @@ -1326,26 +1326,27 @@ unsigned Emboss::get_count_lines(const std::wstring& ws) { if (ws.empty()) return 0; - unsigned prev_count = 0; - for (wchar_t wc : ws) - if (wc == '\n') - ++prev_count; - else - break; - - unsigned post_count = 0; - for (wchar_t wc : boost::adaptors::reverse(ws)) - if (wc == '\n') - ++post_count; - else - break; unsigned count = 1; for (wchar_t wc : ws) if (wc == '\n') ++count; + return count; - return count - prev_count - post_count; + // unsigned prev_count = 0; + // for (wchar_t wc : ws) + // if (wc == '\n') + // ++prev_count; + // else + // break; + // + // unsigned post_count = 0; + // for (wchar_t wc : boost::adaptors::reverse(ws)) + // if (wc == '\n') + // ++post_count; + // else + // break; + //return count - prev_count - post_count; } unsigned Emboss::get_count_lines(const std::string &text) @@ -1875,11 +1876,12 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b // find BB in center of line size_t first_right_index = 0; for (const BoundingBox &bb : bbs) - if (bb.min.x() >= 0) { - break; - } else { + if (!bb.defined) // white char do not have bb + continue; + else if (bb.min.x() < 0) ++first_right_index; - } + else + break; PolygonPoints samples(bbs.size()); int32_t shapes_x_cursor = 0; @@ -1925,71 +1927,76 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b } namespace { -Point get_align_offset(FontProp::Align type, const BoundingBox &bb) -{ - Point offset; +int32_t get_align_y_offset(FontProp::Align type, const BoundingBox &bb){ switch (type) { - // case Slic3r::FontProp::Align::start_first_line: break; + case Slic3r::FontProp::Align::first_line_left: + case Slic3r::FontProp::Align::first_line_right: + case Slic3r::FontProp::Align::first_line_center: break; // No change case Slic3r::FontProp::Align::center_left: case Slic3r::FontProp::Align::center_right: - case Slic3r::FontProp::Align::center_center: offset.y() = bb.center().y(); break; + case Slic3r::FontProp::Align::center_center: return -bb.center().y(); case Slic3r::FontProp::Align::top_left: case Slic3r::FontProp::Align::top_right: - case Slic3r::FontProp::Align::top_center: offset.y() = bb.min.y(); break; + case Slic3r::FontProp::Align::top_center: return -bb.max.y(); break; // direction of Y in 2d is from top to bottom case Slic3r::FontProp::Align::bottom_left: case Slic3r::FontProp::Align::bottom_right: - case Slic3r::FontProp::Align::bottom_center: offset.y() = bb.max.y(); break; + case Slic3r::FontProp::Align::bottom_center: return -bb.min.y(); // direction of Y in 2d is from top to bottom default: break; } - + return 0; +} +int32_t get_align_x_offset(FontProp::Align type, const BoundingBox &shape_bb, const BoundingBox &line_bb) +{ switch (type) { - // case Slic3r::FontProp::Align::start_first_line: break; + case Slic3r::FontProp::Align::first_line_center: case Slic3r::FontProp::Align::center_center: case Slic3r::FontProp::Align::top_center: - case Slic3r::FontProp::Align::bottom_center: offset.x() = bb.center().x(); break; + case Slic3r::FontProp::Align::bottom_center: return -shape_bb.center().x() + (shape_bb.size().x() - line_bb.size().x())/2; + case Slic3r::FontProp::Align::first_line_left: break; // special case do not use offset case Slic3r::FontProp::Align::center_left: case Slic3r::FontProp::Align::top_left: - case Slic3r::FontProp::Align::bottom_left: offset.x() = bb.min.x(); break; + case Slic3r::FontProp::Align::bottom_left: return -shape_bb.min.x(); + case Slic3r::FontProp::Align::first_line_right: case Slic3r::FontProp::Align::center_right: case Slic3r::FontProp::Align::top_right: - case Slic3r::FontProp::Align::bottom_right: offset.x() = bb.max.x(); break; + case Slic3r::FontProp::Align::bottom_right: return -shape_bb.max.x() + (shape_bb.size().x() - line_bb.size().x()); default: break; } - return -offset; -} -} // namespace - -void Emboss::align_shape(FontProp::Align type, ExPolygons &shape, BoundingBox *bb) -{ - if (type == FontProp::Align::start_first_line) - return; // no alignement - - BoundingBox shape_bb_data; - BoundingBox &shape_bb = (bb != nullptr) ? *bb : shape_bb_data; - if (!shape_bb.defined) - shape_bb = get_extents(shape); - - Point offset = get_align_offset(type, shape_bb); - for (ExPolygon &s : shape) - s.translate(offset); + return 0; } -void Emboss::align_shape(FontProp::Align type, std::vector &shapes, BoundingBox *bb) +void align_shape(FontProp::Align type, std::vector &shapes, const std::wstring &text) { - if (type == FontProp::Align::start_first_line) + if (type == FontProp::Align::first_line_left) return; // no alignement - BoundingBox shape_bb_data; - BoundingBox &shape_bb = (bb != nullptr) ? *bb : shape_bb_data; - if (!shape_bb.defined) - for (const ExPolygons& shape: shapes) - shape_bb.merge(get_extents(shape)); + BoundingBox shape_bb; + for (const ExPolygons& shape: shapes) + shape_bb.merge(get_extents(shape)); - Point offset = get_align_offset(type, shape_bb); - for (ExPolygons &shape : shapes) + auto get_line_bb = [&](size_t j) { + BoundingBox line_bb; + for (; j < text.length() && text[j] != '\n'; ++j) + line_bb.merge(get_extents(shapes[j])); + return line_bb; + }; + + Point offset( + get_align_x_offset(type, shape_bb, get_line_bb(0)), + get_align_y_offset(type, shape_bb)); + assert(shapes.size() == text.length()); + for (size_t i = 0; i < shapes.size(); ++i) { + wchar_t letter = text[i]; + if (letter == '\n'){ + offset.x() = get_align_x_offset(type, shape_bb, get_line_bb(i+1)); + continue; + } + ExPolygons &shape = shapes[i]; for (ExPolygon &s : shape) s.translate(offset); + } } +} // namespace #ifdef REMOVE_SPIKES #include diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index fc0a1831d3..cb6aac8df9 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -441,15 +441,6 @@ namespace Emboss double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); - /// - /// Align expolygons by type - /// - /// Type of alignement - /// shapes to align - /// extents of shape - void align_shape(FontProp::Align type, ExPolygons& shape, BoundingBox* bb = nullptr); - void align_shape(FontProp::Align type, std::vector &shape, BoundingBox *bb = nullptr); - } // namespace Emboss } // namespace Slic3r #endif // slic3r_Emboss_hpp_ diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index bdc9371a3d..634284bf73 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -64,7 +64,9 @@ struct FontProp bool per_glyph; enum class Align { - start_first_line, // it depends on position of zero for first letter + first_line_left, // it depends on position of zero for first letter (no shape move) + first_line_right, // use Y zero same as first letter + first_line_center, // use Y zero same as first letter center_left, center_right, center_center, @@ -77,7 +79,7 @@ struct FontProp }; // change pivot of text // When not set, center is used and is not stored - Align align = Align::center_center; + Align align = Align::first_line_center; ////// // Duplicit data to wxFontDescriptor diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index f536c70909..13eee61f83 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -447,7 +447,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) if (gl_volume == nullptr || !m_style_manager.is_active_font()) return res; - m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); } volume_transformation_changing(); @@ -559,7 +559,7 @@ void GLGizmoEmboss::volume_transformation_changing() } const FontProp &prop = m_volume->text_configuration->style.prop; if (prop.per_glyph) - init_text_lines(); + init_text_lines(m_text_lines.get_lines().size()); } void GLGizmoEmboss::volume_transformation_changed() @@ -571,7 +571,7 @@ void GLGizmoEmboss::volume_transformation_changed() const FontProp &prop = m_volume->text_configuration->style.prop; if (prop.per_glyph) - init_text_lines(); + init_text_lines(m_text_lines.get_lines().size()); // Update surface by new position if (prop.use_surface || prop.per_glyph) @@ -1047,7 +1047,7 @@ EmbossStyles GLGizmoEmboss::create_default_styles() return styles; } -void GLGizmoEmboss::init_text_lines(){ +void GLGizmoEmboss::init_text_lines(unsigned count_lines){ assert(m_style_manager.is_active_font()); if (!m_style_manager.is_active_font()) return; @@ -1067,7 +1067,7 @@ void GLGizmoEmboss::init_text_lines(){ const FontFile &ff = *ff_ptr; double line_height = TextLinesModel::calc_line_height(ff, fp); - m_text_lines.init(m_parent.get_selection(), line_height); + m_text_lines.init(m_parent.get_selection(), line_height, count_lines); } void GLGizmoEmboss::set_volume_by_selection() @@ -1306,6 +1306,9 @@ bool GLGizmoEmboss::process() if (use_surface && is_object) use_surface = false; + assert(!data.text_configuration.style.prop.per_glyph || + get_count_lines(m_text) == m_text_lines.get_lines().size()); + if (use_surface) { // Model to cut surface from. SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); @@ -1540,6 +1543,12 @@ void GLGizmoEmboss::draw_text_input() ImVec2 input_size(m_gui_cfg->text_size.x, m_gui_cfg->text_size.y + extra_height); const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll; if (ImGui::InputTextMultiline("##Text", &m_text, input_size, flags)) { + if (m_style_manager.get_font_prop().per_glyph) { + unsigned count_lines = get_count_lines(m_text); + if (count_lines != m_text_lines.get_lines().size()) + // Necesarry to initialize count by given number (differ from stored in volume at the moment) + init_text_lines(count_lines); + } process(); range_text = create_range_text_prep(); } @@ -2801,7 +2810,7 @@ bool GLGizmoEmboss::rev_checkbox(const std::string &name, } bool GLGizmoEmboss::set_height() { - float &value = m_style_manager.get_style().prop.size_in_mm; + float &value = m_style_manager.get_font_prop().size_in_mm; // size can't be zero or negative priv::Limits::apply(value, priv::limits.size_in_mm); @@ -2829,7 +2838,7 @@ bool GLGizmoEmboss::set_height() { void GLGizmoEmboss::draw_height(bool use_inch) { - float &value = m_style_manager.get_style().prop.size_in_mm; + float &value = m_style_manager.get_font_prop().size_in_mm; const EmbossStyle* stored_style = m_style_manager.get_stored_style(); const float *stored = (stored_style != nullptr)? &stored_style->prop.size_in_mm : nullptr; const char *size_format = use_inch ? "%.2f in" : "%.1f mm"; @@ -2842,7 +2851,7 @@ void GLGizmoEmboss::draw_height(bool use_inch) bool GLGizmoEmboss::set_depth() { - float &value = m_style_manager.get_style().prop.emboss; + float &value = m_style_manager.get_font_prop().emboss; // size can't be zero or negative priv::Limits::apply(value, priv::limits.emboss); @@ -2853,7 +2862,7 @@ bool GLGizmoEmboss::set_depth() void GLGizmoEmboss::draw_depth(bool use_inch) { - float &value = m_style_manager.get_style().prop.emboss; + float &value = m_style_manager.get_font_prop().emboss; const EmbossStyle* stored_style = m_style_manager.get_stored_style(); const float *stored = ((stored_style)? &stored_style->prop.emboss : nullptr); const std::string revert_emboss_depth = _u8L("Revert embossed depth."); @@ -2971,7 +2980,7 @@ void GLGizmoEmboss::draw_advanced() return; } - FontProp &font_prop = m_style_manager.get_style().prop; + FontProp &font_prop = m_style_manager.get_font_prop(); const auto &cn = m_style_manager.get_font_prop().collection_number; unsigned int font_index = (cn.has_value()) ? *cn : 0; const auto &font_info = ff.font_file->infos[font_index]; @@ -3210,7 +3219,7 @@ void GLGizmoEmboss::draw_advanced() 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(); - bool use_surface = m_style_manager.get_style().prop.use_surface; + bool use_surface = m_style_manager.get_font_prop().use_surface; if (priv::apply_camera_dir(cam, m_parent, m_keep_up) && use_surface) process(); } else if (ImGui::IsItemHovered()) { @@ -3239,24 +3248,24 @@ void GLGizmoEmboss::draw_advanced() ImGui::SameLine(); ImGui::SetNextItemWidth(100); if (ImGui::SliderFloat("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { - init_text_lines(); + init_text_lines(m_text_lines.get_lines().size()); process(); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); // order must match align enum - const char* align_names[] = { - "start_first_line", - "center_left", - "center_right", - "center_center", - "top_left", - "top_right", - "top_center", - "bottom_left", - "bottom_right", - "bottom_center" - }; + const char* align_names[] = {"first_line_left", + "first_line_right", + "first_line_center", + "center_left", + "center_right", + "center_center", + "top_left", + "top_right", + "top_center", + "bottom_left", + "bottom_right", + "bottom_center"}; int selected_align = static_cast(font_prop.align); if (ImGui::Combo("align", &selected_align, align_names, IM_ARRAYSIZE(align_names))) { font_prop.align = static_cast(selected_align); @@ -3359,7 +3368,7 @@ bool GLGizmoEmboss::choose_font_by_wxdialog() const auto&ff = m_style_manager.get_font_file_with_cache(); if (WxFontUtils::is_italic(wx_font) && !Emboss::is_italic(*ff.font_file, font_collection)) { - m_style_manager.get_style().prop.skew = 0.2; + m_style_manager.get_font_prop().skew = 0.2; } return true; } @@ -3428,7 +3437,7 @@ bool GLGizmoEmboss::choose_svg_file() BoundingBox bb; for (const auto &p : polys) bb.merge(p.contour.points); - const FontProp &fp = m_style_manager.get_style().prop; + const FontProp &fp = m_style_manager.get_font_prop(); float scale = fp.size_in_mm / std::max(bb.max.x(), bb.max.y()); auto project = std::make_unique( std::make_unique(fp.emboss / scale), scale); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 36ceb68fe0..c7a4f3ef3e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -317,7 +317,7 @@ private: // Keep information about curvature of text line around surface TextLinesModel m_text_lines; - void init_text_lines(); + void init_text_lines(unsigned count_lines = 0); // Rotation gizmo GLGizmoRotate m_rotate_gizmo; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index ca2f04327b..4d14008893 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -362,10 +362,15 @@ bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface) res &= !input.text_configuration.text.empty(); assert(!input.volume_name.empty()); res &= !input.volume_name.empty(); - assert(input.text_configuration.style.prop.use_surface == use_surface); - res &= input.text_configuration.style.prop.use_surface == use_surface; - assert(input.text_configuration.style.prop.per_glyph == !input.text_lines.empty()); - res &= input.text_configuration.style.prop.per_glyph == !input.text_lines.empty(); + const FontProp& prop = input.text_configuration.style.prop; + assert(prop.use_surface == use_surface); + res &= prop.use_surface == use_surface; + assert(prop.per_glyph == !input.text_lines.empty()); + res &= prop.per_glyph == !input.text_lines.empty(); + if (prop.per_glyph) { + assert(get_count_lines(input.text_configuration.text) == input.text_lines.size()); + res &= get_count_lines(input.text_configuration.text) == input.text_lines.size(); + } return res; } bool priv::check(const DataCreateVolume &input, bool is_main_thread) { @@ -432,7 +437,6 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { if (shapes.empty()) return {}; - align_shape(input.text_configuration.style.prop.align, shapes); return shapes; } @@ -452,7 +456,6 @@ std::vector priv::create_shapes(DataBase &input, Fnc was_canceled) { if (shapes.empty()) return {}; - align_shape(prop.align, shapes); if (was_canceled()) return {}; @@ -591,8 +594,8 @@ TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled) { if (!input.text_lines.empty()) { TriangleMesh tm = create_mesh_per_glyph(input, was_canceled); - if (!tm.empty()) - return tm; + if (was_canceled()) return {}; + if (!tm.empty()) return tm; } ExPolygons shapes = priv::create_shape(input, was_canceled); diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index a61133055e..2cca79f4b3 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -251,7 +251,7 @@ GLModel::Geometry create_geometry(const TextLines &lines) } } // namespace -void TextLinesModel::init(const Selection &selection, double line_height) +void TextLinesModel::init(const Selection &selection, double line_height, unsigned count_lines) { const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) @@ -268,13 +268,15 @@ void TextLinesModel::init(const Selection &selection, double line_height) 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; + // calculate count lines when not set + if (count_lines == 0) { + const std::optional tc_opt = mv.text_configuration; + if (!tc_opt.has_value()) + return; + count_lines = Emboss::get_count_lines(tc_opt->text); + if (count_lines == 0) + return; + } double first_line_center = offset + (count_lines / 2) * line_height - ((count_lines % 2 == 0) ? line_height / 2. : 0.); std::vector line_centers(count_lines); diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index 741577c143..449c913f42 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -20,7 +20,8 @@ public: /// Must be selected text volume /// Height of text line with spacing [in mm] /// Offset of base line from center [in mm] - void init(const Selection &selection, double line_height); + /// [Optional] Count lines when not set it is calculated from vodel volume text + void init(const Selection &selection, double line_height, unsigned count_lines = 0); void render(const Transform3d &text_world); bool is_init() const { return m_model.is_initialized(); } From 35044e29c364a02f7655b385a52576e34441aab4 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 23 May 2023 10:30:14 +0200 Subject: [PATCH 13/23] Create text volume with feature per letter transformation. --- src/libslic3r/Format/3mf.cpp | 11 ++++ src/libslic3r/TextConfiguration.hpp | 19 +++--- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 82 ++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 1 - src/slic3r/GUI/TextLines.cpp | 6 +- src/slic3r/GUI/TextLines.hpp | 2 +- 6 files changed, 76 insertions(+), 45 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index f7f68f43f7..dfe2a90a4c 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -163,6 +163,8 @@ static constexpr const char *BOLDNESS_ATTR = "boldness"; static constexpr const char *SKEW_ATTR = "skew"; static constexpr const char *DISTANCE_ATTR = "distance"; static constexpr const char *ANGLE_ATTR = "angle"; +static constexpr const char *PER_GLYPH_ATTR = "per_glyph"; +static constexpr const char *ALIGN_ATTR = "align"; static constexpr const char *COLLECTION_NUMBER_ATTR = "collection"; static constexpr const char *FONT_FAMILY_ATTR = "family"; @@ -3530,6 +3532,10 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex stream << DISTANCE_ATTR << "=\"" << *fp.distance << "\" "; if (fp.angle.has_value()) stream << ANGLE_ATTR << "=\"" << *fp.angle << "\" "; + if (fp.per_glyph) + stream << PER_GLYPH_ATTR << "=\"" << 1 << "\" "; + if (fp.align != FontProp().align) // differ to default value? back compatibility + stream << ALIGN_ATTR << "=\"" << static_cast(fp.align) << "\" "; if (fp.collection_number.has_value()) stream << COLLECTION_NUMBER_ATTR << "=\"" << *fp.collection_number << "\" "; // font descriptor @@ -3609,6 +3615,11 @@ std::optional TextConfigurationSerialization::read(const char float angle = get_attribute_value_float(attributes, num_attributes, ANGLE_ATTR); if (std::fabs(angle) > std::numeric_limits::epsilon()) fp.angle = angle; + int per_glyph = get_attribute_value_int(attributes, num_attributes, PER_GLYPH_ATTR); + if (per_glyph == 1) fp.per_glyph = true; + int align = get_attribute_value_int(attributes, num_attributes, ALIGN_ATTR); + fp.align = static_cast(align); + int collection_number = get_attribute_value_int(attributes, num_attributes, COLLECTION_NUMBER_ATTR); if (collection_number > 0) fp.collection_number = static_cast(collection_number); diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 634284bf73..79712b8795 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -63,20 +63,23 @@ struct FontProp // Distiguish projection per glyph bool per_glyph; + // Enumerate type of allowed text align enum class Align { + // NOTE: default value must be zero - 3mf store + first_line_center = 0, // use Y zero same as first letter first_line_left, // it depends on position of zero for first letter (no shape move) first_line_right, // use Y zero same as first letter - first_line_center, // use Y zero same as first letter + center_center, center_left, center_right, - center_center, + top_center, top_left, top_right, - top_center, + bottom_center, bottom_left, - bottom_right, - bottom_center + bottom_right }; + // change pivot of text // When not set, center is used and is not stored Align align = Align::first_line_center; @@ -110,6 +113,8 @@ struct FontProp char_gap == other.char_gap && line_gap == other.line_gap && use_surface == other.use_surface && + per_glyph == other.per_glyph && + align == other.align && is_approx(emboss, other.emboss) && is_approx(size_in_mm, other.size_in_mm) && is_approx(boldness, other.boldness) && @@ -121,7 +126,7 @@ struct FontProp // undo / redo stack recovery template void save(Archive &ar) const { - ar(emboss, use_surface, size_in_mm); + ar(emboss, use_surface, size_in_mm, per_glyph, align); cereal::save(ar, char_gap); cereal::save(ar, line_gap); cereal::save(ar, boldness); @@ -136,7 +141,7 @@ struct FontProp } template void load(Archive &ar) { - ar(emboss, use_surface, size_in_mm); + ar(emboss, use_surface, size_in_mm, per_glyph, align); cereal::load(ar, char_gap); cereal::load(ar, line_gap); cereal::load(ar, boldness); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 13eee61f83..e0498cba3e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -146,9 +146,15 @@ namespace priv { /// /// Text to emboss /// Keep actual selected style +/// Needed when transform per glyph +/// Needed for transform per glyph /// Cancel for previous job /// Base data for emboss text -static DataBase create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr> &cancel); +static DataBase create_emboss_data_base(const std::string &text, + StyleManager &style_manager, + TextLinesModel &text_lines, + const Selection &selection, + std::shared_ptr> &cancel); /// /// Start job for add new volume to object with given transformation @@ -242,13 +248,17 @@ static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas, bool keep } // namespace priv +namespace { +void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines=0); +} + void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) { if (!init_create(volume_type)) return; const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_job_cancel); bool is_simple_mode = wxGetApp().get_mode() == comSimple; if (gl_volume != nullptr && !is_simple_mode) { // Try to cast ray into scene and find object for add volume @@ -275,7 +285,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) Size s = m_parent.get_canvas_size(); Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_job_cancel); const ModelObjectPtrs &objects = selection.get_model()->objects; bool is_simple_mode = wxGetApp().get_mode() == comSimple; // No selected object so create new object @@ -559,7 +569,7 @@ void GLGizmoEmboss::volume_transformation_changing() } const FontProp &prop = m_volume->text_configuration->style.prop; if (prop.per_glyph) - init_text_lines(m_text_lines.get_lines().size()); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); } void GLGizmoEmboss::volume_transformation_changed() @@ -571,7 +581,7 @@ void GLGizmoEmboss::volume_transformation_changed() const FontProp &prop = m_volume->text_configuration->style.prop; if (prop.per_glyph) - init_text_lines(m_text_lines.get_lines().size()); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); // Update surface by new position if (prop.use_surface || prop.per_glyph) @@ -1047,11 +1057,13 @@ EmbossStyles GLGizmoEmboss::create_default_styles() return styles; } -void GLGizmoEmboss::init_text_lines(unsigned count_lines){ - assert(m_style_manager.is_active_font()); - if (!m_style_manager.is_active_font()) +namespace { +void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines) +{ + assert(style_manager.is_active_font()); + if (!style_manager.is_active_font()) return; - const auto& ffc = m_style_manager.get_font_file_with_cache(); + const auto &ffc = style_manager.get_font_file_with_cache(); assert(ffc.has_value()); if (!ffc.has_value()) return; @@ -1060,14 +1072,12 @@ void GLGizmoEmboss::init_text_lines(unsigned count_lines){ if (ff_ptr == nullptr) return; - if (m_volume->is_the_only_one_part()) - return; - - const FontProp& fp = m_style_manager.get_font_prop(); + const FontProp &fp = style_manager.get_font_prop(); const FontFile &ff = *ff_ptr; - double line_height = TextLinesModel::calc_line_height(ff, fp); - m_text_lines.init(m_parent.get_selection(), line_height, count_lines); + double line_height = TextLinesModel::calc_line_height(ff, fp); + text_lines.init(selection, line_height, count_lines); +} } void GLGizmoEmboss::set_volume_by_selection() @@ -1209,7 +1219,7 @@ void GLGizmoEmboss::set_volume_by_selection() m_volume_id = volume->id(); if (tc.style.prop.per_glyph) - init_text_lines(); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); // Calculate current angle of up vector assert(m_style_manager.is_active_font()); @@ -1292,12 +1302,8 @@ bool GLGizmoEmboss::process() // exist loaded font file? if (!m_style_manager.is_active_font()) return false; - DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel), m_volume->id()}; - if (data.text_configuration.style.prop.per_glyph){ - if (!m_text_lines.is_init()) - init_text_lines(); - data.text_lines = m_text_lines.get_lines(); // copy - } + DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_job_cancel), + m_volume->id()}; std::unique_ptr job = nullptr; // check cutting from source mesh @@ -1547,7 +1553,7 @@ void GLGizmoEmboss::draw_text_input() unsigned count_lines = get_count_lines(m_text); if (count_lines != m_text_lines.get_lines().size()) // Necesarry to initialize count by given number (differ from stored in volume at the moment) - init_text_lines(count_lines); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, count_lines); } process(); range_text = create_range_text_prep(); @@ -3231,7 +3237,7 @@ void GLGizmoEmboss::draw_advanced() if (ImGui::Checkbox("##PerGlyph", per_glyph)) { if (*per_glyph) { if (!m_text_lines.is_init()) - init_text_lines(); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); } process(); } else if (ImGui::IsItemHovered()) { @@ -3240,7 +3246,7 @@ void GLGizmoEmboss::draw_advanced() } else { ImGui::SetTooltip("%s", _u8L("Set position and orientation of projection per Glyph.").c_str()); if (!m_text_lines.is_init()) - init_text_lines(); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); } } else if (!*per_glyph && m_text_lines.is_init()) m_text_lines.reset(); @@ -3248,24 +3254,24 @@ void GLGizmoEmboss::draw_advanced() ImGui::SameLine(); ImGui::SetNextItemWidth(100); if (ImGui::SliderFloat("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { - init_text_lines(m_text_lines.get_lines().size()); + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); process(); } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); // order must match align enum - const char* align_names[] = {"first_line_left", + const char* align_names[] = { "first_line_center", + "first_line_left", "first_line_right", - "first_line_center", + "center_center", "center_left", "center_right", - "center_center", - "top_left", - "top_right", "top_center", + "top_left", + "top_right", + "bottom_center", "bottom_left", - "bottom_right", - "bottom_center"}; + "bottom_right"}; int selected_align = static_cast(font_prop.align); if (ImGui::Combo("align", &selected_align, align_names, IM_ARRAYSIZE(align_names))) { font_prop.align = static_cast(selected_align); @@ -3542,7 +3548,7 @@ bool priv::draw_button(const IconManager::VIcons &icons, IconType type, bool dis // priv namespace implementation /////////////// -DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr>& cancel) +DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, TextLinesModel& text_lines, const Selection& selection, std::shared_ptr>& cancel) { // create volume_name std::string volume_name = text; // copy @@ -3565,6 +3571,12 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st assert(es.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0); TextConfiguration tc{es, text}; + if (es.prop.per_glyph) { + if (!text_lines.is_init()) + init_text_lines(text_lines, selection, style_manager); + } else + text_lines.reset(); + // Cancel previous Job, when it is in process // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs // Cancel only EmbossUpdateJob no others @@ -3572,7 +3584,7 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st cancel->store(true); // create new shared ptr to cancel new job cancel = std::make_shared>(false); - return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), tc, volume_name, cancel}; + return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), tc, volume_name, cancel, text_lines.get_lines()}; } void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index c7a4f3ef3e..34b7dd5de4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -317,7 +317,6 @@ private: // Keep information about curvature of text line around surface TextLinesModel m_text_lines; - void init_text_lines(unsigned count_lines = 0); // Rotation gizmo GLGizmoRotate m_rotate_gizmo; diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 2cca79f4b3..785b21d52c 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -253,6 +253,9 @@ GLModel::Geometry create_geometry(const TextLines &lines) void TextLinesModel::init(const Selection &selection, double line_height, unsigned count_lines) { + m_model.reset(); + m_lines.clear(); + const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) return; @@ -267,6 +270,8 @@ void TextLinesModel::init(const Selection &selection, double line_height, unsign if (mv_ptr == nullptr) return; const ModelVolume &mv = *mv_ptr; + if (mv.is_the_only_one_part()) + return; // calculate count lines when not set if (count_lines == 0) { @@ -317,7 +322,6 @@ void TextLinesModel::init(const Selection &selection, double line_height, unsign for (size_t i = 0; i < count_lines; ++i) m_lines[i].y = line_centers[i]; - m_model.reset(); //* GLModel::Geometry geometry = create_geometry(m_lines); if (geometry.vertices_count() == 0 || geometry.indices_count() == 0) diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index 449c913f42..58f27fe22c 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -25,7 +25,7 @@ public: void render(const Transform3d &text_world); bool is_init() const { return m_model.is_initialized(); } - void reset() { m_model.reset(); } + void reset() { m_model.reset(); m_lines.clear(); } const Slic3r::Emboss::TextLines &get_lines() const { return m_lines; } static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); private: From 9b8f910c61f48fd6313ee38f63461858c727e2d3 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 23 May 2023 20:32:54 +0200 Subject: [PATCH 14/23] Ui for per glyph checkbox --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 131 +++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 5 +- 2 files changed, 77 insertions(+), 59 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e0498cba3e..0c4720c6b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -926,24 +926,26 @@ GLGizmoEmboss::GuiCfg GLGizmoEmboss::create_gui_configuration() cfg.input_offset = style.WindowPadding.x + cfg.indent + max_text_width + space; tr.use_surface = _u8L("Use surface"); + tr.per_glyph = _u8L("Per glyph orientation"); + tr.alignment = _u8L("Alignment"); tr.char_gap = _u8L("Char gap"); tr.line_gap = _u8L("Line gap"); tr.boldness = _u8L("Boldness"); tr.skew_ration = _u8L("Skew ratio"); tr.from_surface = _u8L("From surface"); tr.rotation = _u8L("Rotation"); - tr.keep_up = "Keep Rotation"; tr.collection = _u8L("Collection"); float max_advanced_text_width = std::max({ ImGui::CalcTextSize(tr.use_surface.c_str()).x, + ImGui::CalcTextSize(tr.per_glyph.c_str()).x, + ImGui::CalcTextSize(tr.alignment.c_str()).x, ImGui::CalcTextSize(tr.char_gap.c_str()).x, ImGui::CalcTextSize(tr.line_gap.c_str()).x, ImGui::CalcTextSize(tr.boldness.c_str()).x, ImGui::CalcTextSize(tr.skew_ration.c_str()).x, ImGui::CalcTextSize(tr.from_surface.c_str()).x, ImGui::CalcTextSize(tr.rotation.c_str()).x + cfg.icon_width + 2*space, - ImGui::CalcTextSize(tr.keep_up.c_str()).x, ImGui::CalcTextSize(tr.collection.c_str()).x }); cfg.advanced_input_offset = max_advanced_text_width + 3 * space + cfg.indent; @@ -969,9 +971,9 @@ GLGizmoEmboss::GuiCfg GLGizmoEmboss::create_gui_configuration() + 2 * (cfg.icon_width + space); cfg.minimal_window_size = ImVec2(window_width, window_height); - // 8 = useSurface, charGap, lineGap, bold, italic, surfDist, rotation, textFaceToCamera + // 8 = useSurface, per glyph, charGap, lineGap, bold, italic, surfDist, rotation, textFaceToCamera // 4 = 1px for fix each edit image of drag float - float advance_height = input_height * 8 + 8; + float advance_height = input_height * 10 + 9; cfg.minimal_window_size_with_advance = ImVec2(cfg.minimal_window_size.x, cfg.minimal_window_size.y + advance_height); @@ -2729,13 +2731,16 @@ bool GLGizmoEmboss::revertible(const std::string &name, bool result = draw(); // render revert changes button - if (changed) { - ImGui::SameLine(undo_offset); + if (changed) { + ImGuiWindow *window = ImGui::GetCurrentWindow(); + float prev_x = window->DC.CursorPosPrevLine.x; + ImGui::SameLine(undo_offset); // change cursor postion if (draw_button(m_icons, IconType::undo)) { value = *default_value; return true; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", undo_tooltip.c_str()); + window->DC.CursorPosPrevLine.x = prev_x; // set back previous position } return result; } @@ -3014,10 +3019,10 @@ void GLGizmoEmboss::draw_advanced() const EmbossStyle *stored_style = nullptr; if (m_style_manager.exist_stored_style()) stored_style = m_style_manager.get_stored_style(); - - bool can_use_surface = (m_volume==nullptr)? false : - (font_prop.use_surface)? true : // already used surface must have option to uncheck - (m_volume->get_object()->volumes.size() > 1); + + bool is_the_only_one_part = m_volume->is_the_only_one_part(); + bool can_use_surface = (font_prop.use_surface)? true : // already used surface must have option to uncheck + !is_the_only_one_part; m_imgui->disabled_begin(!can_use_surface); const bool *def_use_surface = stored_style ? &stored_style->prop.use_surface : nullptr; @@ -3034,6 +3039,65 @@ void GLGizmoEmboss::draw_advanced() process(); } m_imgui->disabled_end(); // !can_use_surface + + bool &per_glyph = font_prop.per_glyph; + bool can_use_per_glyph = (per_glyph) ? true : // already used surface must have option to uncheck + !is_the_only_one_part; + m_imgui->disabled_begin(!can_use_per_glyph); + const bool *def_per_glyph = stored_style ? &stored_style->prop.per_glyph : nullptr; + if (rev_checkbox(tr.per_glyph, per_glyph, def_per_glyph, + _u8L("Revert Transformation per glyph."))) { + process(); + } 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 per Glyph.").c_str()); + if (!m_text_lines.is_init()) + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); + } + } else if (!per_glyph && m_text_lines.is_init()) + m_text_lines.reset(); + m_imgui->disabled_end(); // !can_use_per_glyph + + m_imgui->disabled_begin(!per_glyph); + ImGui::SameLine(); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + if (m_imgui->slider_float("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); + process(); + } else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); + m_imgui->disabled_end(); // !per_glyph + + int selected_align = static_cast(font_prop.align); + int def_align_data = !stored_style ? 0 : static_cast(stored_style->prop.align); + int * def_align = stored_style ? &def_align_data : nullptr; + float undo_offset = ImGui::GetStyle().FramePadding.x; + auto draw = [&selected_align, gui_cfg = m_gui_cfg]() { + // order must match align enum + const char* align_names[] = { "first_line_center", + "first_line_left", + "first_line_right", + "center_center", + "center_left", + "center_right", + "top_center", + "top_left", + "top_right", + "bottom_center", + "bottom_left", + "bottom_right"}; + ImGui::SameLine(gui_cfg->advanced_input_offset); + ImGui::SetNextItemWidth(gui_cfg->input_width); + return ImGui::Combo("##text_alignment", &selected_align, align_names, IM_ARRAYSIZE(align_names)); + }; + if (revertible(tr.alignment, selected_align, def_align, _u8L("Revert alignment."), undo_offset, draw)){ + font_prop.align = static_cast(selected_align); + // TODO: move with text in finalize to not change position + process(); + } + // TRN EmbossGizmo: font units std::string units = _u8L("points"); std::string units_fmt = "%.0f " + units; @@ -3232,53 +3296,6 @@ void GLGizmoEmboss::draw_advanced() 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()) - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); - } - process(); - } 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()) - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); - } - } else if (!*per_glyph && m_text_lines.is_init()) - m_text_lines.reset(); - - ImGui::SameLine(); - ImGui::SetNextItemWidth(100); - if (ImGui::SliderFloat("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); - process(); - } else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); - - // order must match align enum - const char* align_names[] = { "first_line_center", - "first_line_left", - "first_line_right", - "center_center", - "center_left", - "center_right", - "top_center", - "top_left", - "top_right", - "bottom_center", - "bottom_left", - "bottom_right"}; - int selected_align = static_cast(font_prop.align); - if (ImGui::Combo("align", &selected_align, align_names, IM_ARRAYSIZE(align_names))) { - font_prop.align = static_cast(selected_align); - // TODO: move with text in finalize to not change position - 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 34b7dd5de4..05ce3875b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -209,16 +209,17 @@ private: std::string font; std::string height; std::string depth; - std::string use_surface; // advanced + std::string use_surface; + std::string per_glyph; + std::string alignment; std::string char_gap; std::string line_gap; std::string boldness; std::string skew_ration; std::string from_surface; std::string rotation; - std::string keep_up; std::string collection; }; Translations translations; From f146cd022476dc650ccdbdfb3e3faa6ce8fbf645 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 23 May 2023 20:38:18 +0200 Subject: [PATCH 15/23] Do not center volume when create new --- src/libslic3r/Emboss.cpp | 4 ++-- src/slic3r/GUI/Jobs/EmbossJob.cpp | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index dd156da8ad..bfd377fa02 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1289,7 +1289,7 @@ namespace { /// /// Align expolygons by type /// -/// Type of alignement +/// Type of alignment /// shapes to align /// Same size as shape for align per line(detect of end line - '\n') void align_shape(FontProp::Align type, std::vector &shape, const std::wstring &text); @@ -1968,7 +1968,7 @@ int32_t get_align_x_offset(FontProp::Align type, const BoundingBox &shape_bb, co void align_shape(FontProp::Align type, std::vector &shapes, const std::wstring &text) { if (type == FontProp::Align::first_line_left) - return; // no alignement + return; // no alignment BoundingBox shape_bb; for (const ExPolygons& shape: shapes) diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 4d14008893..368d494372 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -138,9 +138,6 @@ void CreateVolumeJob::process(Ctl &ctl) { if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob."); auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); }; m_result = priv::create_mesh(m_input, was_canceled, ctl); - // center result - Vec3f c = m_result.bounding_box().center().cast(); - if (!c.isApprox(Vec3f::Zero())) m_result.translate(-c); } void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { From 496cc54bd735a526c84037f7944e307bd14b1cce Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 24 May 2023 10:17:57 +0200 Subject: [PATCH 16/23] Change local Bounding box position and size to respect local zero inside gizmos (for scale, move and rotate) --- src/slic3r/GUI/Selection.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e9d131c433..294c815be1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -849,11 +849,31 @@ std::pair Selection::get_bounding_box_in_reference_s } } } + const Vec3d box_size = max - min; - const Vec3d half_box_size = 0.5 * box_size; - BoundingBoxf3 out_box(-half_box_size, half_box_size); + Vec3d half_box_size = 0.5 * box_size; Geometry::Transformation out_trafo(trafo); - const Vec3d center = 0.5 * (min + max); + Vec3d center = 0.5 * (min + max); + + // Fix for non centered volume + // by move with calculated center(to volume center) and extend half box size + // e.g. for right aligned embossed text + if (m_list.size() == 1 && + type == ECoordinatesType::Local) { + const GLVolume& vol = *get_volume(*m_list.begin()); + const Transform3d vol_world_trafo = vol.world_matrix(); + Vec3d world_zero = vol_world_trafo * Vec3d::Zero(); + for (size_t i = 0; i < 3; i++){ + // move center to local volume zero + center[i] = world_zero.dot(axes[i]); + // extend half size to bigger distance from center + half_box_size[i] = std::max( + abs(center[i] - min[i]), + abs(center[i] - max[i])); + } + } + + const BoundingBoxf3 out_box(-half_box_size, half_box_size); out_trafo.set_offset(basis_trafo * center); return { out_box, out_trafo.get_matrix_no_scaling_factor() }; } From d2a605ee2e2298f775f98cc41dfccb16f64029f2 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 25 May 2023 13:01:30 +0200 Subject: [PATCH 17/23] add option to Create per glyph volume (initialize text lines before text volume) Per glyph for negative volume and modifiers are moved into object --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 200 ++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 1 + src/slic3r/GUI/Jobs/EmbossJob.cpp | 30 ++-- src/slic3r/GUI/Jobs/EmbossJob.hpp | 10 +- src/slic3r/GUI/TextLines.cpp | 47 +----- src/slic3r/GUI/TextLines.hpp | 22 ++- src/slic3r/Utils/EmbossStyleManager.cpp | 19 +++ src/slic3r/Utils/EmbossStyleManager.hpp | 4 + 8 files changed, 222 insertions(+), 111 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 0c4720c6b5..a313ed0d37 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -148,12 +148,14 @@ namespace priv { /// Keep actual selected style /// Needed when transform per glyph /// Needed for transform per glyph +/// Define type of volume - side of surface(in / out) /// Cancel for previous job /// Base data for emboss text static DataBase create_emboss_data_base(const std::string &text, StyleManager &style_manager, TextLinesModel &text_lines, const Selection &selection, + ModelVolumeType type, std::shared_ptr> &cancel); /// @@ -176,6 +178,8 @@ static void start_create_volume_job(const ModelObject *object, /// Mouse position which define position /// Volume to find surface for create /// Ability to ray cast to model +/// Per glyph transformation +/// Line height need font file/param> /// Contain already used scene RayCasters /// True when start creation, False when there is no hit surface by screen coor static bool start_create_volume_on_surface_job(DataBase &emboss_data, @@ -183,6 +187,8 @@ static bool start_create_volume_on_surface_job(DataBase &emboss_data, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster, + TextLinesModel &text_lines, + /*const */ StyleManager &style_manager, GLCanvas3D &canvas); /// @@ -249,7 +255,29 @@ static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas, bool keep } // namespace priv namespace { +// for existing volume which is selected(could init different(to volume text) lines count when edit text) void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines=0); +// before text volume is created +void init_new_text_line(TextLinesModel &text_lines, const Transform3d& new_text_tr, const ModelObject& mo, /* const*/ StyleManager &style_manager) +{ + // prepare volumes to slice + ModelVolumePtrs volumes; + volumes.reserve(mo.volumes.size()); + for (ModelVolume *volume : mo.volumes) { + // only part could be surface for volumes + if (!volume->is_model_part()) + continue; + volumes.push_back(volume); + } + + double line_height = style_manager.get_line_height(); + if (line_height < 0) + return; + + FontProp::Align align = style_manager.get_font_prop().align; + unsigned count_lines = 1; + text_lines.init(new_text_tr, volumes, align, line_height, count_lines); +} } void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) @@ -258,11 +286,11 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous return; const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_job_cancel); + DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), volume_type, m_job_cancel); bool is_simple_mode = wxGetApp().get_mode() == comSimple; if (gl_volume != nullptr && !is_simple_mode) { // Try to cast ray into scene and find object for add volume - if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) { + if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager, m_text_lines, m_style_manager, m_parent)) { // When model is broken. It could appear that hit miss the object. // So add part near by in simmilar manner as right panel do create_volume(volume_type); @@ -285,7 +313,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) Size s = m_parent.get_canvas_size(); Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_job_cancel); + DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), volume_type, m_job_cancel); const ModelObjectPtrs &objects = selection.get_model()->objects; bool is_simple_mode = wxGetApp().get_mode() == comSimple; // No selected object so create new object @@ -303,7 +331,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) priv::find_closest_volume(selection, screen_center, camera, objects, &coor, &vol); if (vol == nullptr) { priv::start_create_object_job(emboss_data, screen_center); - } else if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager, m_parent)) { + } else if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager, m_text_lines, m_style_manager, m_parent)) { // in centroid of convex hull is not hit with object // soo create transfomation on border of object @@ -321,7 +349,11 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) - instance_bb.size().y() / 2 - prop.size_in_mm / 2, // under prop.emboss / 2 - instance_bb.size().z() / 2 // lay on bed ); - Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); + Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); + if (prop.per_glyph) { + init_new_text_line(m_text_lines, volume_trmat, *obj, m_style_manager); + emboss_data.text_lines = m_text_lines.get_lines(); + } priv::start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); } } @@ -1062,26 +1094,65 @@ EmbossStyles GLGizmoEmboss::create_default_styles() namespace { void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines) { - assert(style_manager.is_active_font()); - if (!style_manager.is_active_font()) + double line_height = style_manager.get_line_height(); + if (line_height < 0) return; - const auto &ffc = style_manager.get_font_file_with_cache(); - assert(ffc.has_value()); - if (!ffc.has_value()) + + const GLVolume *gl_volume_ptr = selection.get_first_volume(); + if (gl_volume_ptr == nullptr) return; - const auto &ff_ptr = ffc.font_file; - assert(ff_ptr != nullptr); - if (ff_ptr == nullptr) + 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; + if (mv.is_the_only_one_part()) return; + const std::optional &tc_opt = mv.text_configuration; + if (!tc_opt.has_value()) + return; + const TextConfiguration &tc = *tc_opt; + + // calculate count lines when not set + if (count_lines == 0) { + count_lines = get_count_lines(tc.text); + if (count_lines == 0) + return; + } + + // prepare volumes to slice + ModelVolumePtrs volumes; + volumes.reserve(mo.volumes.size()); + for (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; + + volumes.push_back(volume); + } + + // For interactivity during drag over surface it must be from gl_volume not volume. + const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); const FontProp &fp = style_manager.get_font_prop(); - const FontFile &ff = *ff_ptr; - - double line_height = TextLinesModel::calc_line_height(ff, fp); - text_lines.init(selection, line_height, count_lines); + text_lines.init(mv_trafo, volumes, fp.align, line_height, count_lines); } } +void GLGizmoEmboss::reinit_text_lines(unsigned count_lines) { + init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, count_lines); +} + void GLGizmoEmboss::set_volume_by_selection() { const Selection &selection = m_parent.get_selection(); @@ -1221,7 +1292,7 @@ void GLGizmoEmboss::set_volume_by_selection() m_volume_id = volume->id(); if (tc.style.prop.per_glyph) - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); + reinit_text_lines(); // Calculate current angle of up vector assert(m_style_manager.is_active_font()); @@ -1304,7 +1375,7 @@ bool GLGizmoEmboss::process() // exist loaded font file? if (!m_style_manager.is_active_font()) return false; - DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_job_cancel), + DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_volume->type(), m_job_cancel), m_volume->id()}; std::unique_ptr job = nullptr; @@ -1338,7 +1409,7 @@ bool GLGizmoEmboss::process() // check that there is not unexpected volume type assert(is_outside || m_volume->is_negative_volume() || m_volume->is_modifier()); - UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, is_outside, std::move(sources)}}; + UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, std::move(sources)}}; job = std::make_unique(std::move(surface_data)); } else { job = std::make_unique(std::move(data)); @@ -1555,7 +1626,7 @@ void GLGizmoEmboss::draw_text_input() unsigned count_lines = get_count_lines(m_text); if (count_lines != m_text_lines.get_lines().size()) // Necesarry to initialize count by given number (differ from stored in volume at the moment) - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, count_lines); + reinit_text_lines(count_lines); } process(); range_text = create_range_text_prep(); @@ -1982,6 +2053,8 @@ void GLGizmoEmboss::draw_font_list_line() if (exist_change) { m_style_manager.clear_glyphs_cache(); + if (m_style_manager.get_font_prop().per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); process(); } } @@ -2206,7 +2279,8 @@ void GLGizmoEmboss::draw_model_type() m_volume->set_type(*new_type); // Update volume position when switch from part or into part - if (m_volume->text_configuration->style.prop.use_surface) { + const FontProp& prop = m_volume->text_configuration->style.prop; + if (prop.use_surface || prop.per_glyph) { // move inside bool is_volume_move_inside = (type == part); bool is_volume_move_outside = (*new_type == part); @@ -2835,6 +2909,9 @@ bool GLGizmoEmboss::set_height() { if (is_approx(value, m_volume->text_configuration->style.prop.size_in_mm)) return false; + if (m_style_manager.get_font_prop().per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + #ifdef USE_PIXEL_SIZE_IN_WX_FONT // store font size into path serialization const wxFont &wx_font = m_style_manager.get_wx_font(); @@ -3047,6 +3124,8 @@ void GLGizmoEmboss::draw_advanced() const bool *def_per_glyph = stored_style ? &stored_style->prop.per_glyph : nullptr; if (rev_checkbox(tr.per_glyph, per_glyph, def_per_glyph, _u8L("Revert Transformation per glyph."))) { + if (per_glyph && !m_text_lines.is_init()) + reinit_text_lines(); process(); } else if (ImGui::IsItemHovered()) { if (per_glyph) { @@ -3054,7 +3133,7 @@ void GLGizmoEmboss::draw_advanced() } else { ImGui::SetTooltip("%s", _u8L("Set position and orientation per Glyph.").c_str()); if (!m_text_lines.is_init()) - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager); + reinit_text_lines(); } } else if (!per_glyph && m_text_lines.is_init()) m_text_lines.reset(); @@ -3064,10 +3143,10 @@ void GLGizmoEmboss::draw_advanced() ImGui::SameLine(); ImGui::SetNextItemWidth(m_gui_cfg->input_width); if (m_imgui->slider_float("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) { - init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size()); + reinit_text_lines(m_text_lines.get_lines().size()); process(); } else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Move base line (up/down) for allign letters").c_str()); + ImGui::SetTooltip("TEST PURPOSE ONLY\nMove base line (up/down) for allign letters"); m_imgui->disabled_end(); // !per_glyph int selected_align = static_cast(font_prop.align); @@ -3094,6 +3173,8 @@ void GLGizmoEmboss::draw_advanced() }; if (revertible(tr.alignment, selected_align, def_align, _u8L("Revert alignment."), undo_offset, draw)){ font_prop.align = static_cast(selected_align); + if (font_prop.per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); // TODO: move with text in finalize to not change position process(); } @@ -3132,6 +3213,8 @@ void GLGizmoEmboss::draw_advanced() m_volume->text_configuration->style.prop.line_gap != font_prop.line_gap) { // line gap is planed to be stored inside of imgui font atlas m_style_manager.clear_imgui_font(); + if (font_prop.per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); exist_change = true; } } @@ -3198,9 +3281,13 @@ void GLGizmoEmboss::draw_advanced() } if (is_moved){ - m_volume->text_configuration->style.prop.distance = font_prop.distance; - float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f; - do_translate(Vec3d::UnitZ() * (act_distance - prev_distance)); + if (font_prop.per_glyph){ + process(); + } else { + m_volume->text_configuration->style.prop.distance = font_prop.distance; + float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f; + do_translate(Vec3d::UnitZ() * (act_distance - prev_distance)); + } } m_imgui->disabled_end(); @@ -3234,8 +3321,11 @@ void GLGizmoEmboss::draw_advanced() if (m_style_manager.is_active_font() && gl_volume != nullptr) m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + if (font_prop.per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + // recalculate for surface cut - if (font_prop.use_surface) + if (font_prop.use_surface || font_prop.per_glyph) process(); } @@ -3283,14 +3373,19 @@ void GLGizmoEmboss::draw_advanced() if (exist_change) { m_style_manager.clear_glyphs_cache(); + if (m_style_manager.get_font_prop().per_glyph) + reinit_text_lines(); + else + m_text_lines.reset(); 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(); - bool use_surface = m_style_manager.get_font_prop().use_surface; - if (priv::apply_camera_dir(cam, m_parent, m_keep_up) && use_surface) + const FontProp &prop = m_style_manager.get_font_prop(); + if (priv::apply_camera_dir(cam, m_parent, m_keep_up) && + prop.use_surface || prop.per_glyph) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Orient the text towards the camera.").c_str()); @@ -3565,7 +3660,12 @@ bool priv::draw_button(const IconManager::VIcons &icons, IconType type, bool dis // priv namespace implementation /////////////// -DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, TextLinesModel& text_lines, const Selection& selection, std::shared_ptr>& cancel) +DataBase priv::create_emboss_data_base(const std::string &text, + StyleManager &style_manager, + TextLinesModel &text_lines, + const Selection &selection, + ModelVolumeType type, + std::shared_ptr> &cancel) { // create volume_name std::string volume_name = text; // copy @@ -3594,6 +3694,8 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st } else text_lines.reset(); + bool is_outside = (type == ModelVolumeType::MODEL_PART); + // Cancel previous Job, when it is in process // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs // Cancel only EmbossUpdateJob no others @@ -3601,7 +3703,7 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st cancel->store(true); // create new shared ptr to cancel new job cancel = std::make_shared>(false); - return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), tc, volume_name, cancel, text_lines.get_lines()}; + return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), tc, volume_name, is_outside, cancel, text_lines.get_lines()}; } void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) @@ -3639,10 +3741,7 @@ void priv::start_create_volume_job(const ModelObject *object, if (sources.empty()) { use_surface = false; } else { - bool is_outside = volume_type == ModelVolumeType::MODEL_PART; - // check that there is not unexpected volume type - assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); - SurfaceVolumeData sfvd{volume_trmat, is_outside, std::move(sources)}; + SurfaceVolumeData sfvd{volume_trmat, std::move(sources)}; CreateSurfaceVolumeData surface_data{std::move(emboss_data), std::move(sfvd), volume_type, object->id()}; job = std::make_unique(std::move(surface_data)); } @@ -3658,8 +3757,14 @@ void priv::start_create_volume_job(const ModelObject *object, queue_job(worker, std::move(job)); } -bool priv::start_create_volume_on_surface_job( - DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster, GLCanvas3D& canvas) +bool priv::start_create_volume_on_surface_job(DataBase &emboss_data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume *gl_volume, + RaycastManager &raycaster, + TextLinesModel &text_lines, + StyleManager &style_manager, + GLCanvas3D &canvas) { assert(gl_volume != nullptr); if (gl_volume == nullptr) return false; @@ -3670,12 +3775,14 @@ bool priv::start_create_volume_on_surface_job( int object_idx = gl_volume->object_idx(); if (object_idx < 0 || static_cast(object_idx) >= objects.size()) return false; - ModelObject *obj = objects[object_idx]; - size_t vol_id = obj->volumes[gl_volume->volume_idx()]->id().id; + const ModelObject *obj_ptr = objects[object_idx]; + if (obj_ptr == nullptr) return false; + const ModelObject &obj = *obj_ptr; + size_t vol_id = obj.volumes[gl_volume->volume_idx()]->id().id; auto cond = RaycastManager::AllowVolumes({vol_id}); RaycastManager::Meshes meshes = create_meshes(canvas, cond); - raycaster.actualize(*obj, &cond, &meshes); + raycaster.actualize(obj, &cond, &meshes); const Camera &camera = plater->get_camera(); std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); @@ -3691,8 +3798,13 @@ bool priv::start_create_volume_on_surface_job( const FontProp &font_prop = emboss_data.text_configuration.style.prop; apply_transformation(font_prop, surface_trmat); Transform3d instance = gl_volume->get_instance_transformation().get_matrix(); - Transform3d volume_trmat = instance.inverse() * surface_trmat; - start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); + Transform3d volume_trmat = instance.inverse() * surface_trmat; + + if (font_prop.per_glyph){ + init_new_text_line(text_lines, volume_trmat, obj, style_manager); + emboss_data.text_lines = text_lines.get_lines(); + } + start_create_volume_job(obj_ptr, volume_trmat, emboss_data, volume_type); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 05ce3875b9..2ac0a9eda3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -318,6 +318,7 @@ private: // Keep information about curvature of text line around surface TextLinesModel m_text_lines; + void reinit_text_lines(unsigned count_lines=0); // Rotation gizmo GLGizmoRotate m_rotate_gizmo; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 368d494372..c1c14b6459 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -29,6 +29,9 @@ namespace priv{ // create sure that emboss object is bigger than source object [in mm] constexpr float safe_extension = 1.0f; +// Offset of clossed side to model +constexpr float SAFE_SURFACE_OFFSET = 0.015f; // [in mm] + /// /// Assert check of inputs data /// @@ -513,7 +516,6 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w double shape_scale = get_shape_scale(prop, *font.font_file); double projec_scale = shape_scale / SHAPE_SCALE; double depth = prop.emboss / projec_scale; - auto projectZ = std::make_unique(depth); auto scale_tr = Eigen::Scaling(projec_scale); // half of font em size for direction of letter emboss @@ -534,16 +536,20 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w continue; Vec2d to_zero_vec = letter_bb.center().cast() * shape_scale; // [in mm] - auto to_zero = Eigen::Translation(-to_zero_vec.x(), 0., 0.); + float surface_offset = input.is_outside ? -priv::SAFE_SURFACE_OFFSET : (-prop.emboss + priv::SAFE_SURFACE_OFFSET); + if (prop.distance.has_value()) + surface_offset += *prop.distance; + + Eigen::Translation to_zero(-to_zero_vec.x(), 0., static_cast(surface_offset)); const double &angle = angles[i]; - auto rotate = Eigen::AngleAxisd(angle + M_PI_2, Vec3d::UnitY()); + Eigen::AngleAxisd rotate(angle + M_PI_2, Vec3d::UnitY()); const PolygonPoint &sample = samples[i]; Vec2d offset_vec = unscale(sample.point); // [in mm] - auto offset_tr = Eigen::Translation(offset_vec.x(), 0., -offset_vec.y()); - + Eigen::Translation offset_tr(offset_vec.x(), 0., -offset_vec.y()); Transform3d tr = offset_tr * rotate * to_zero * scale_tr; + const ExPolygons &letter_shape = shapes[s_i_offset + i]; assert(get_extents(letter_shape) == letter_bb); auto projectZ = std::make_unique(depth); @@ -883,11 +889,9 @@ OrthoProject priv::create_projection_for_cut( OrthoProject3d priv::create_emboss_projection( bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut) { - // Offset of clossed side to model - const float surface_offset = 0.015f; // [in mm] float - front_move = (is_outside) ? emboss : surface_offset, - back_move = -((is_outside) ? surface_offset : emboss); + front_move = (is_outside) ? emboss : SAFE_SURFACE_OFFSET, + back_move = -((is_outside) ? SAFE_SURFACE_OFFSET : emboss); its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move))); Vec3d from_front_to_back(0., 0., back_move - front_move); return OrthoProject3d(from_front_to_back); @@ -895,7 +899,7 @@ OrthoProject3d priv::create_emboss_projection( namespace { -indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d& tr,const SurfaceVolumeData::ModelSources &sources, bool is_outside, DataBase& input, std::function was_canceled) { +indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d& tr,const SurfaceVolumeData::ModelSources &sources, DataBase& input, std::function was_canceled) { assert(!sources.empty()); BoundingBox bb = get_extents(shapes); const FontFile &ff = *input.font_file.font_file; @@ -985,7 +989,7 @@ indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transfor if (was_canceled()) return {}; // !! Projection needs to transform cut - OrthoProject3d projection = priv::create_emboss_projection(is_outside, fp.emboss, emboss_tr, cut); + OrthoProject3d projection = priv::create_emboss_projection(input.is_outside, fp.emboss, emboss_tr, cut); return cut2model(cut, projection); } @@ -1041,7 +1045,7 @@ TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &in Transform3d modify = offset_tr * rotate; Transform3d tr = input2.text_tr * modify; - indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, tr, input2.sources, input2.is_outside, input1, was_canceled); + indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, tr, input2.sources, input1, was_canceled); // move letter in volume on the right position its_transform(glyph_its, modify); @@ -1074,7 +1078,7 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 if (shapes.empty()) throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); - indexed_triangle_set its = cut_surface_to_its(shapes, input2.text_tr, input2.sources, input2.is_outside, input1, was_canceled); + indexed_triangle_set its = cut_surface_to_its(shapes, input2.text_tr, input2.sources, input1, was_canceled); if (was_canceled()) return {}; if (its.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str()); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index bbe9c940c1..c15381416a 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -29,6 +29,11 @@ struct DataBase // new volume name created from text std::string volume_name; + // Define projection move + // True (raised) .. move outside from surface + // False (engraved).. move into object + bool is_outside; + // flag that job is canceled // for time after process. std::shared_ptr> cancel; @@ -158,11 +163,6 @@ struct SurfaceVolumeData // Transformation of text volume inside of object Transform3d text_tr; - // Define projection move - // True (raised) .. move outside from surface - // False (engraved).. move into object - bool is_outside; - struct ModelSource { // source volumes diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 785b21d52c..7e6e71a580 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -251,62 +251,24 @@ GLModel::Geometry create_geometry(const TextLines &lines) } } // namespace -void TextLinesModel::init(const Selection &selection, double line_height, unsigned count_lines) +void TextLinesModel::init(const Transform3d &text_tr, const ModelVolumePtrs &volumes_to_slice, FontProp::Align align, double line_height, unsigned count_lines) { m_model.reset(); - m_lines.clear(); + m_lines.clear(); - 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; - if (mv.is_the_only_one_part()) - return; - - // calculate count lines when not set - if (count_lines == 0) { - const std::optional tc_opt = mv.text_configuration; - if (!tc_opt.has_value()) - return; - count_lines = Emboss::get_count_lines(tc_opt->text); - if (count_lines == 0) - return; - } - double first_line_center = offset + (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] = static_cast(first_line_center - i * line_height); - const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); - // contour transformation - Transform3d c_trafo = mv_trafo * get_rotation(); + Transform3d c_trafo = text_tr * get_rotation(); 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; - + for (const ModelVolume *volume : volumes_to_slice) { 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()) @@ -328,6 +290,7 @@ void TextLinesModel::init(const Selection &selection, double line_height, unsign return; m_model.init_from(std::move(geometry)); /*/ + // slower solution ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray m_model.set_color(color); m_model.init_from(create_its(m_lines)); diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index 58f27fe22c..bef7e2262f 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -7,27 +7,35 @@ #include #include "slic3r/GUI/GLModel.hpp" +namespace Slic3r { +class ModelVolume; +typedef std::vector ModelVolumePtrs; +} + namespace Slic3r::GUI { -class Selection; class TextLinesModel { public: // line offset in y direction (up/down) float offset = 0; + /// /// Initialize model and lines /// - /// Must be selected text volume - /// Height of text line with spacing [in mm] - /// Offset of base line from center [in mm] - /// [Optional] Count lines when not set it is calculated from vodel volume text - void init(const Selection &selection, double line_height, unsigned count_lines = 0); + /// Transformation of text volume inside object (aka inside of instance) + /// Vector of volumes to be sliced + /// Vertical (Y) align of the text + /// Distance between lines [in mm] + /// Count lines(slices over volumes) + void init(const Transform3d &text_tr, const ModelVolumePtrs& volumes_to_slice, FontProp::Align align, double line_height, unsigned count_lines); + void render(const Transform3d &text_world); bool is_init() const { return m_model.is_initialized(); } void reset() { m_model.reset(); m_lines.clear(); } const Slic3r::Emboss::TextLines &get_lines() const { return m_lines; } - static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); + + static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); // return lineheight in mm private: Slic3r::Emboss::TextLines m_lines; diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 4f066b9c85..623c147176 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -282,6 +282,25 @@ void StyleManager::clear_glyphs_cache() void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); } +#include "slic3r/GUI/TextLines.hpp" +double StyleManager::get_line_height() +{ + assert(is_active_font()); + if (!is_active_font()) + return -1; + const auto &ffc = get_font_file_with_cache(); + assert(ffc.has_value()); + if (!ffc.has_value()) + return -1; + const auto &ff_ptr = ffc.font_file; + assert(ff_ptr != nullptr); + if (ff_ptr == nullptr) + return -1; + const FontProp &fp = get_font_prop(); + const FontFile &ff = *ff_ptr; + return TextLinesModel::calc_line_height(ff, fp); +} + ImFont *StyleManager::get_imgui_font() { if (!is_active_font()) return nullptr; diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index dd6b9ca129..1f3032ac70 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -106,6 +106,10 @@ public: // remove cached imgui font for actual selected font void clear_imgui_font(); + // calculate line height + // not const because access to font file which could be created. + double get_line_height(); /* const */ + // getters for private data const EmbossStyle *get_stored_style() const; From 1e2943b942cd12e2aaa481ed86cbfca21b835dda Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 26 May 2023 09:23:09 +0200 Subject: [PATCH 18/23] Align by buttons --- resources/icons/align_horizontal_center.svg | 7 ++ resources/icons/align_horizontal_left.svg | 7 ++ resources/icons/align_horizontal_right.svg | 7 ++ resources/icons/align_vertical_bottom.svg | 60 +++++++++ resources/icons/align_vertical_center.svg | 60 +++++++++ resources/icons/align_vertical_top.svg | 60 +++++++++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 127 +++++++++++++++----- src/slic3r/GUI/Jobs/EmbossJob.cpp | 7 +- 8 files changed, 301 insertions(+), 34 deletions(-) create mode 100644 resources/icons/align_horizontal_center.svg create mode 100644 resources/icons/align_horizontal_left.svg create mode 100644 resources/icons/align_horizontal_right.svg create mode 100644 resources/icons/align_vertical_bottom.svg create mode 100644 resources/icons/align_vertical_center.svg create mode 100644 resources/icons/align_vertical_top.svg diff --git a/resources/icons/align_horizontal_center.svg b/resources/icons/align_horizontal_center.svg new file mode 100644 index 0000000000..7234939204 --- /dev/null +++ b/resources/icons/align_horizontal_center.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/icons/align_horizontal_left.svg b/resources/icons/align_horizontal_left.svg new file mode 100644 index 0000000000..1b88ee7193 --- /dev/null +++ b/resources/icons/align_horizontal_left.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/resources/icons/align_horizontal_right.svg b/resources/icons/align_horizontal_right.svg new file mode 100644 index 0000000000..b4dffb09e0 --- /dev/null +++ b/resources/icons/align_horizontal_right.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/resources/icons/align_vertical_bottom.svg b/resources/icons/align_vertical_bottom.svg new file mode 100644 index 0000000000..5c0a94b06e --- /dev/null +++ b/resources/icons/align_vertical_bottom.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + diff --git a/resources/icons/align_vertical_center.svg b/resources/icons/align_vertical_center.svg new file mode 100644 index 0000000000..e3655be39b --- /dev/null +++ b/resources/icons/align_vertical_center.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + diff --git a/resources/icons/align_vertical_top.svg b/resources/icons/align_vertical_top.svg new file mode 100644 index 0000000000..a88217696e --- /dev/null +++ b/resources/icons/align_vertical_top.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index a313ed0d37..ffbb3c1e98 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -233,6 +233,12 @@ enum class IconType : unsigned { lock_bold, unlock, unlock_bold, + align_horizontal_left, + align_horizontal_center, + align_horizontal_right, + align_vertical_top, + align_vertical_center, + align_vertical_bottom, // automatic calc of icon's count _count }; @@ -2278,14 +2284,12 @@ void GLGizmoEmboss::draw_model_type() Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction); m_volume->set_type(*new_type); - // Update volume position when switch from part or into part - const FontProp& prop = m_volume->text_configuration->style.prop; - if (prop.use_surface || prop.per_glyph) { - // move inside - bool is_volume_move_inside = (type == part); - bool is_volume_move_outside = (*new_type == part); - if (is_volume_move_inside || is_volume_move_outside) process(); - } + // move inside + bool is_volume_move_inside = (type == part); + bool is_volume_move_outside = (*new_type == part); + // Update volume position when switch (from part) or (into part) + if ((is_volume_move_inside || is_volume_move_outside)) + process(); // inspiration in ObjectList::change_part_type() // how to view correct side panel with objects @@ -3059,6 +3063,39 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle) m_parent.do_rotate(snapshot_name); } +namespace{ +bool is_left( FontProp::Align align){ return align == FontProp::Align::bottom_left || align == FontProp::Align::center_left || align == FontProp::Align::top_left; } +bool is_center_h(FontProp::Align align){ return align == FontProp::Align::bottom_center || align == FontProp::Align::center_center || align == FontProp::Align::top_center; } +bool is_right( FontProp::Align align){ return align == FontProp::Align::bottom_right || align == FontProp::Align::center_right || align == FontProp::Align::top_right; } +bool is_top( FontProp::Align align){ return align == FontProp::Align::top_left || align == FontProp::Align::top_center || align == FontProp::Align::top_right; } +bool is_center_v(FontProp::Align align){ return align == FontProp::Align::center_left || align == FontProp::Align::center_center || align == FontProp::Align::center_right; } +bool is_bottom( FontProp::Align align){ return align == FontProp::Align::bottom_left || align == FontProp::Align::bottom_center || align == FontProp::Align::bottom_right; } +void to_left(FontProp::Align &align){ + align = (align == FontProp::Align::bottom_right || align == FontProp::Align::bottom_center) ? FontProp::Align::bottom_left : + (align == FontProp::Align::center_right || align == FontProp::Align::center_center) ? FontProp::Align::center_left : + FontProp::Align::top_left;} +void to_center_h(FontProp::Align &align){ + align = (align == FontProp::Align::bottom_right || align == FontProp::Align::bottom_left) ? FontProp::Align::bottom_center : + (align == FontProp::Align::center_right || align == FontProp::Align::center_left) ? FontProp::Align::center_center : + FontProp::Align::top_center;} +void to_right(FontProp::Align &align){ + align = (align == FontProp::Align::bottom_left || align == FontProp::Align::bottom_center) ? FontProp::Align::bottom_right : + (align == FontProp::Align::center_left || align == FontProp::Align::center_center) ? FontProp::Align::center_right : + FontProp::Align::top_right;} +void to_top(FontProp::Align &align){ + align = (align == FontProp::Align::bottom_left || align == FontProp::Align::center_left) ? FontProp::Align::top_left : + (align == FontProp::Align::bottom_right || align == FontProp::Align::center_right) ? FontProp::Align::top_right : + FontProp::Align::top_center;} +void to_center_v(FontProp::Align &align){ + align = (align == FontProp::Align::bottom_left || align == FontProp::Align::top_left) ? FontProp::Align::center_left : + (align == FontProp::Align::bottom_right || align == FontProp::Align::top_right) ? FontProp::Align::center_right : + FontProp::Align::center_center;} +void to_bottom(FontProp::Align &align){ + align = (align == FontProp::Align::top_left || align == FontProp::Align::center_left) ? FontProp::Align::bottom_left : + (align == FontProp::Align::top_right || align == FontProp::Align::center_right) ? FontProp::Align::bottom_right : + FontProp::Align::bottom_center;} +} + void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); @@ -3149,36 +3186,58 @@ void GLGizmoEmboss::draw_advanced() ImGui::SetTooltip("TEST PURPOSE ONLY\nMove base line (up/down) for allign letters"); m_imgui->disabled_end(); // !per_glyph - int selected_align = static_cast(font_prop.align); - int def_align_data = !stored_style ? 0 : static_cast(stored_style->prop.align); - int * def_align = stored_style ? &def_align_data : nullptr; + const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr; float undo_offset = ImGui::GetStyle().FramePadding.x; - auto draw = [&selected_align, gui_cfg = m_gui_cfg]() { - // order must match align enum - const char* align_names[] = { "first_line_center", - "first_line_left", - "first_line_right", - "center_center", - "center_left", - "center_right", - "top_center", - "top_left", - "top_right", - "bottom_center", - "bottom_left", - "bottom_right"}; + //auto draw = [&selected_align, gui_cfg = m_gui_cfg]() { + // // order must match align enum + // const char* align_names[] = { "first_line_center", + // "first_line_left", + // "first_line_right", + // "center_center", + // "center_left", + // "center_right", + // "top_center", + // "top_left", + // "top_right", + // "bottom_center", + // "bottom_left", + // "bottom_right"}; + // ImGui::SameLine(gui_cfg->advanced_input_offset); + // ImGui::SetNextItemWidth(gui_cfg->input_width); + // return ImGui::Combo("##text_alignment", &selected_align, align_names, IM_ARRAYSIZE(align_names)); + //}; + + auto draw_align = [&align = font_prop.align, gui_cfg = m_gui_cfg, &icons = m_icons]() { + bool is_change = false; ImGui::SameLine(gui_cfg->advanced_input_offset); - ImGui::SetNextItemWidth(gui_cfg->input_width); - return ImGui::Combo("##text_alignment", &selected_align, align_names, IM_ARRAYSIZE(align_names)); + if (is_left(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered)); + else if (draw_button(icons, IconType::align_horizontal_left)) { to_left(align); is_change = true; } + ImGui::SameLine(); + if (is_center_h(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered)); + else if (draw_button(icons, IconType::align_horizontal_center)) { to_center_h(align); is_change = true; } + ImGui::SameLine(); + if (is_right(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered)); + else if (draw_button(icons, IconType::align_horizontal_right)) { to_right(align); is_change = true; } + + ImGui::SameLine(); + if (is_top(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered)); + else if (draw_button(icons, IconType::align_vertical_top)) { to_top(align); is_change = true; } + ImGui::SameLine(); + if (is_center_v(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered)); + else if (draw_button(icons, IconType::align_vertical_center)) { to_center_v(align); is_change = true; } + ImGui::SameLine(); + if (is_bottom(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered)); + else if (draw_button(icons, IconType::align_vertical_bottom)) { to_bottom(align); is_change = true; } + return is_change; }; - if (revertible(tr.alignment, selected_align, def_align, _u8L("Revert alignment."), undo_offset, draw)){ - font_prop.align = static_cast(selected_align); + + if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), undo_offset, draw_align)) { if (font_prop.per_glyph) reinit_text_lines(m_text_lines.get_lines().size()); // TODO: move with text in finalize to not change position process(); } - + // TRN EmbossGizmo: font units std::string units = _u8L("points"); std::string units_fmt = "%.0f " + units; @@ -3634,7 +3693,13 @@ void GLGizmoEmboss::init_icons() "lock_closed.svg", // lock, "lock_closed_f.svg",// lock_bold, "lock_open.svg", // unlock, - "lock_open_f.svg" // unlock_bold, + "lock_open_f.svg", // unlock_bold, + "align_horizontal_left.svg", + "align_horizontal_center.svg", + "align_horizontal_right.svg", + "align_vertical_top.svg", + "align_vertical_center.svg", + "align_vertical_bottom.svg" }; assert(filenames.size() == static_cast(IconType::_count)); std::string path = resources_dir() + "/icons/"; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index c1c14b6459..b3afec427a 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -610,9 +610,10 @@ TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled) // 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 scaled = std::make_unique(std::move(projectZ), scale); - ProjectTransform project(std::move(projectZ), Eigen::Translation(0., 0., -prop.emboss / 2) * Eigen::Scaling(scale)); + auto projectZ = std::make_unique(depth); + float offset = input.is_outside ? -SAFE_SURFACE_OFFSET : (SAFE_SURFACE_OFFSET - prop.emboss); + Transform3d tr = Eigen::Translation(0., 0.,static_cast(offset)) * Eigen::Scaling(scale); + ProjectTransform project(std::move(projectZ), tr); if (was_canceled()) return {}; return TriangleMesh(polygons2model(shapes, project)); } From 733b70b26f3405a34ba0f5e78e44295592642aa8 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 26 May 2023 16:50:27 +0200 Subject: [PATCH 19/23] Separate horizontal and vertical align Vertically align on base line of text to be able set base line of per glyph independent on align --- src/libslic3r/Emboss.cpp | 74 ++++++++++----------- src/libslic3r/Format/3mf.cpp | 15 +++-- src/libslic3r/TextConfiguration.hpp | 29 +++------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 87 ++++++------------------- src/slic3r/GUI/Jobs/EmbossJob.cpp | 4 -- 5 files changed, 71 insertions(+), 138 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index bfd377fa02..7352516633 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1287,12 +1287,14 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const char *t namespace { /// -/// Align expolygons by type +/// Align shape against pivot /// -/// Type of alignment -/// shapes to align -/// Same size as shape for align per line(detect of end line - '\n') -void align_shape(FontProp::Align type, std::vector &shape, const std::wstring &text); +/// Horizontal and vertical alignment +/// Shapes to align +/// Prerequisities: shapes are aligned left top +/// To detect end of lines +/// Height of line for align[in font points] +void align_shape(FontProp::Align type, std::vector &shape, const std::wstring &text, int line_height); } std::vector Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled){ @@ -1317,7 +1319,7 @@ std::vector Emboss::text2vshapes(FontFileWithCache &font_with_cache, result.emplace_back(letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)); } - align_shape(font_prop.align, result, text); + align_shape(font_prop.align, result, text, get_line_height(font, font_prop)); return result; } @@ -1927,47 +1929,37 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b } namespace { -int32_t get_align_y_offset(FontProp::Align type, const BoundingBox &bb){ - switch (type) { - case Slic3r::FontProp::Align::first_line_left: - case Slic3r::FontProp::Align::first_line_right: - case Slic3r::FontProp::Align::first_line_center: break; // No change - case Slic3r::FontProp::Align::center_left: - case Slic3r::FontProp::Align::center_right: - case Slic3r::FontProp::Align::center_center: return -bb.center().y(); - case Slic3r::FontProp::Align::top_left: - case Slic3r::FontProp::Align::top_right: - case Slic3r::FontProp::Align::top_center: return -bb.max.y(); break; // direction of Y in 2d is from top to bottom - case Slic3r::FontProp::Align::bottom_left: - case Slic3r::FontProp::Align::bottom_right: - case Slic3r::FontProp::Align::bottom_center: return -bb.min.y(); // direction of Y in 2d is from top to bottom - default: break; +int32_t get_align_y_offset(FontProp::VerticalAlign align, int count_lines, int line_height) +{ + // direction of Y in 2d is from top to bottom + // zero is on base line of first line + switch (align) { + case FontProp::VerticalAlign::center: + return ((count_lines-1) / 2) * line_height + + ((count_lines % 2 == 0) ? (line_height / 2) : 0); + case FontProp::VerticalAlign::bottom: + return (count_lines-1) * line_height; + case FontProp::VerticalAlign::top: // no change + default: + break; } return 0; } -int32_t get_align_x_offset(FontProp::Align type, const BoundingBox &shape_bb, const BoundingBox &line_bb) +int32_t get_align_x_offset(FontProp::HorizontalAlign align, const BoundingBox &shape_bb, const BoundingBox &line_bb) { - switch (type) { - case Slic3r::FontProp::Align::first_line_center: - case Slic3r::FontProp::Align::center_center: - case Slic3r::FontProp::Align::top_center: - case Slic3r::FontProp::Align::bottom_center: return -shape_bb.center().x() + (shape_bb.size().x() - line_bb.size().x())/2; - case Slic3r::FontProp::Align::first_line_left: break; // special case do not use offset - case Slic3r::FontProp::Align::center_left: - case Slic3r::FontProp::Align::top_left: - case Slic3r::FontProp::Align::bottom_left: return -shape_bb.min.x(); - case Slic3r::FontProp::Align::first_line_right: - case Slic3r::FontProp::Align::center_right: - case Slic3r::FontProp::Align::top_right: - case Slic3r::FontProp::Align::bottom_right: return -shape_bb.max.x() + (shape_bb.size().x() - line_bb.size().x()); + switch (align) { + case FontProp::HorizontalAlign::right: return -shape_bb.max.x() + (shape_bb.size().x() - line_bb.size().x()); + case FontProp::HorizontalAlign::center: return -shape_bb.center().x() + (shape_bb.size().x() - line_bb.size().x()) / 2; + case FontProp::HorizontalAlign::left: // no change default: break; } return 0; } -void align_shape(FontProp::Align type, std::vector &shapes, const std::wstring &text) +void align_shape(FontProp::Align type, std::vector &shapes, const std::wstring &text, int line_height) { - if (type == FontProp::Align::first_line_left) + constexpr FontProp::Align no_change(FontProp::HorizontalAlign::left, FontProp::VerticalAlign::top); + if (type == no_change) return; // no alignment BoundingBox shape_bb; @@ -1982,13 +1974,13 @@ void align_shape(FontProp::Align type, std::vector &shapes, const st }; Point offset( - get_align_x_offset(type, shape_bb, get_line_bb(0)), - get_align_y_offset(type, shape_bb)); + get_align_x_offset(type.first, shape_bb, get_line_bb(0)), + get_align_y_offset(type.second, get_count_lines(text), line_height)); assert(shapes.size() == text.length()); for (size_t i = 0; i < shapes.size(); ++i) { - wchar_t letter = text[i]; + wchar_t letter = text[i]; if (letter == '\n'){ - offset.x() = get_align_x_offset(type, shape_bb, get_line_bb(i+1)); + offset.x() = get_align_x_offset(type.first, shape_bb, get_line_bb(i+1)); continue; } ExPolygons &shape = shapes[i]; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index dfe2a90a4c..f3ad9535d4 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -164,7 +164,8 @@ static constexpr const char *SKEW_ATTR = "skew"; static constexpr const char *DISTANCE_ATTR = "distance"; static constexpr const char *ANGLE_ATTR = "angle"; static constexpr const char *PER_GLYPH_ATTR = "per_glyph"; -static constexpr const char *ALIGN_ATTR = "align"; +static constexpr const char *HORIZONTAL_ALIGN_ATTR = "horizontal"; +static constexpr const char *VERTICAL_ALIGN_ATTR = "vertical"; static constexpr const char *COLLECTION_NUMBER_ATTR = "collection"; static constexpr const char *FONT_FAMILY_ATTR = "family"; @@ -3534,8 +3535,8 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex stream << ANGLE_ATTR << "=\"" << *fp.angle << "\" "; if (fp.per_glyph) stream << PER_GLYPH_ATTR << "=\"" << 1 << "\" "; - if (fp.align != FontProp().align) // differ to default value? back compatibility - stream << ALIGN_ATTR << "=\"" << static_cast(fp.align) << "\" "; + stream << HORIZONTAL_ALIGN_ATTR << "=\"" << static_cast(fp.align.first) << "\" "; + stream << VERTICAL_ALIGN_ATTR << "=\"" << static_cast(fp.align.second) << "\" "; if (fp.collection_number.has_value()) stream << COLLECTION_NUMBER_ATTR << "=\"" << *fp.collection_number << "\" "; // font descriptor @@ -3617,8 +3618,12 @@ std::optional TextConfigurationSerialization::read(const char fp.angle = angle; int per_glyph = get_attribute_value_int(attributes, num_attributes, PER_GLYPH_ATTR); if (per_glyph == 1) fp.per_glyph = true; - int align = get_attribute_value_int(attributes, num_attributes, ALIGN_ATTR); - fp.align = static_cast(align); + + int horizontal = get_attribute_value_int(attributes, num_attributes, HORIZONTAL_ALIGN_ATTR); + int vertical = get_attribute_value_int(attributes, num_attributes, VERTICAL_ALIGN_ATTR); + fp.align = FontProp::Align( + static_cast(horizontal), + static_cast(vertical)); int collection_number = get_attribute_value_int(attributes, num_attributes, COLLECTION_NUMBER_ATTR); if (collection_number > 0) fp.collection_number = static_cast(collection_number); diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 79712b8795..f60096edad 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -63,27 +63,14 @@ struct FontProp // Distiguish projection per glyph bool per_glyph; - // Enumerate type of allowed text align - enum class Align { - // NOTE: default value must be zero - 3mf store - first_line_center = 0, // use Y zero same as first letter - first_line_left, // it depends on position of zero for first letter (no shape move) - first_line_right, // use Y zero same as first letter - center_center, - center_left, - center_right, - top_center, - top_left, - top_right, - bottom_center, - bottom_left, - bottom_right - }; - + // NOTE: way of serialize to 3mf force that zero must be default value + enum class HorizontalAlign { left = 0, center, right }; + enum class VerticalAlign { top = 0, center, bottom }; + using Align = std::pair; // change pivot of text // When not set, center is used and is not stored - Align align = Align::first_line_center; - + Align align = Align(HorizontalAlign::left, VerticalAlign::top); + ////// // Duplicit data to wxFontDescriptor // used for store/load .3mf file @@ -126,7 +113,7 @@ struct FontProp // undo / redo stack recovery template void save(Archive &ar) const { - ar(emboss, use_surface, size_in_mm, per_glyph, align); + ar(emboss, use_surface, size_in_mm, per_glyph, align.first, align.second); cereal::save(ar, char_gap); cereal::save(ar, line_gap); cereal::save(ar, boldness); @@ -141,7 +128,7 @@ struct FontProp } template void load(Archive &ar) { - ar(emboss, use_surface, size_in_mm, per_glyph, align); + ar(emboss, use_surface, size_in_mm, per_glyph, align.first, align.second); cereal::load(ar, char_gap); cereal::load(ar, line_gap); cereal::load(ar, boldness); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index ffbb3c1e98..bd78b66193 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -3063,39 +3063,6 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle) m_parent.do_rotate(snapshot_name); } -namespace{ -bool is_left( FontProp::Align align){ return align == FontProp::Align::bottom_left || align == FontProp::Align::center_left || align == FontProp::Align::top_left; } -bool is_center_h(FontProp::Align align){ return align == FontProp::Align::bottom_center || align == FontProp::Align::center_center || align == FontProp::Align::top_center; } -bool is_right( FontProp::Align align){ return align == FontProp::Align::bottom_right || align == FontProp::Align::center_right || align == FontProp::Align::top_right; } -bool is_top( FontProp::Align align){ return align == FontProp::Align::top_left || align == FontProp::Align::top_center || align == FontProp::Align::top_right; } -bool is_center_v(FontProp::Align align){ return align == FontProp::Align::center_left || align == FontProp::Align::center_center || align == FontProp::Align::center_right; } -bool is_bottom( FontProp::Align align){ return align == FontProp::Align::bottom_left || align == FontProp::Align::bottom_center || align == FontProp::Align::bottom_right; } -void to_left(FontProp::Align &align){ - align = (align == FontProp::Align::bottom_right || align == FontProp::Align::bottom_center) ? FontProp::Align::bottom_left : - (align == FontProp::Align::center_right || align == FontProp::Align::center_center) ? FontProp::Align::center_left : - FontProp::Align::top_left;} -void to_center_h(FontProp::Align &align){ - align = (align == FontProp::Align::bottom_right || align == FontProp::Align::bottom_left) ? FontProp::Align::bottom_center : - (align == FontProp::Align::center_right || align == FontProp::Align::center_left) ? FontProp::Align::center_center : - FontProp::Align::top_center;} -void to_right(FontProp::Align &align){ - align = (align == FontProp::Align::bottom_left || align == FontProp::Align::bottom_center) ? FontProp::Align::bottom_right : - (align == FontProp::Align::center_left || align == FontProp::Align::center_center) ? FontProp::Align::center_right : - FontProp::Align::top_right;} -void to_top(FontProp::Align &align){ - align = (align == FontProp::Align::bottom_left || align == FontProp::Align::center_left) ? FontProp::Align::top_left : - (align == FontProp::Align::bottom_right || align == FontProp::Align::center_right) ? FontProp::Align::top_right : - FontProp::Align::top_center;} -void to_center_v(FontProp::Align &align){ - align = (align == FontProp::Align::bottom_left || align == FontProp::Align::top_left) ? FontProp::Align::center_left : - (align == FontProp::Align::bottom_right || align == FontProp::Align::top_right) ? FontProp::Align::center_right : - FontProp::Align::center_center;} -void to_bottom(FontProp::Align &align){ - align = (align == FontProp::Align::top_left || align == FontProp::Align::center_left) ? FontProp::Align::bottom_left : - (align == FontProp::Align::top_right || align == FontProp::Align::center_right) ? FontProp::Align::bottom_right : - FontProp::Align::bottom_center;} -} - void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); @@ -3186,51 +3153,37 @@ void GLGizmoEmboss::draw_advanced() ImGui::SetTooltip("TEST PURPOSE ONLY\nMove base line (up/down) for allign letters"); m_imgui->disabled_end(); // !per_glyph - const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr; - float undo_offset = ImGui::GetStyle().FramePadding.x; - //auto draw = [&selected_align, gui_cfg = m_gui_cfg]() { - // // order must match align enum - // const char* align_names[] = { "first_line_center", - // "first_line_left", - // "first_line_right", - // "center_center", - // "center_left", - // "center_right", - // "top_center", - // "top_left", - // "top_right", - // "bottom_center", - // "bottom_left", - // "bottom_right"}; - // ImGui::SameLine(gui_cfg->advanced_input_offset); - // ImGui::SetNextItemWidth(gui_cfg->input_width); - // return ImGui::Combo("##text_alignment", &selected_align, align_names, IM_ARRAYSIZE(align_names)); - //}; - auto draw_align = [&align = font_prop.align, gui_cfg = m_gui_cfg, &icons = m_icons]() { bool is_change = false; ImGui::SameLine(gui_cfg->advanced_input_offset); - if (is_left(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_left)) { to_left(align); is_change = true; } + if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered)); + else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; } + else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set left alignment").c_str()); ImGui::SameLine(); - if (is_center_h(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_center)) { to_center_h(align); is_change = true; } + if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered)); + else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; } + else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set horizont center alignment").c_str()); ImGui::SameLine(); - if (is_right(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_right)) { to_right(align); is_change = true; } + if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered)); + else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; } + else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set right alignment").c_str()); ImGui::SameLine(); - if (is_top(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_top)) { to_top(align); is_change = true; } + if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered)); + else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; } + else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set top alignment").c_str()); ImGui::SameLine(); - if (is_center_v(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_center)) { to_center_v(align); is_change = true; } + if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered)); + else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; } + else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set vertical center alignment").c_str()); ImGui::SameLine(); - if (is_bottom(align)) Slic3r::GUI::draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_bottom)) { to_bottom(align); is_change = true; } + if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered)); + else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; } + else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set bottom alignment").c_str()); return is_change; }; - + const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr; + float undo_offset = ImGui::GetStyle().FramePadding.x; if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), undo_offset, draw_align)) { if (font_prop.per_glyph) reinit_text_lines(m_text_lines.get_lines().size()); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index b3afec427a..3b631b86ca 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -247,10 +247,6 @@ void UpdateJob::process(Ctl &ctl) if (was_canceled()) return; if (m_result.its.empty()) throw priv::JobException("Created text volume is empty. Change text or font."); - - // center triangle mesh - //Vec3d shift = m_result.bounding_box().center(); - //m_result.translate(-shift.cast()); } void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) From 331bfaa556dfcbc978b49c5601f9cf37de90f3e2 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 26 May 2023 19:44:32 +0200 Subject: [PATCH 20/23] Fix position of base line for per glyph position to be on the half of ascent in all vertical alignment --- src/libslic3r/Emboss.cpp | 20 +++--- src/libslic3r/Emboss.hpp | 10 +++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 94 +++++++++++++++++-------- src/slic3r/GUI/TextLines.cpp | 9 ++- src/slic3r/GUI/TextLines.hpp | 3 +- 5 files changed, 97 insertions(+), 39 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 7352516633..3a2bb6802e 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1929,22 +1929,22 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b } namespace { -int32_t get_align_y_offset(FontProp::VerticalAlign align, int count_lines, int line_height) +template T get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, T line_height) { + if (count_lines == 0) + return 0; + // direction of Y in 2d is from top to bottom // zero is on base line of first line switch (align) { - case FontProp::VerticalAlign::center: - return ((count_lines-1) / 2) * line_height - + ((count_lines % 2 == 0) ? (line_height / 2) : 0); - case FontProp::VerticalAlign::bottom: - return (count_lines-1) * line_height; + case FontProp::VerticalAlign::center: return ((count_lines - 1) / 2) * line_height + ((count_lines % 2 == 0) ? (line_height / 2) : 0); + case FontProp::VerticalAlign::bottom: return (count_lines - 1) * line_height; case FontProp::VerticalAlign::top: // no change - default: - break; + default: break; } return 0; } + int32_t get_align_x_offset(FontProp::HorizontalAlign align, const BoundingBox &shape_bb, const BoundingBox &line_bb) { switch (align) { @@ -1990,6 +1990,10 @@ void align_shape(FontProp::Align type, std::vector &shapes, const st } } // namespace +double Emboss::get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, double line_height){ + return ::get_align_y_offset(align, count_lines, line_height); +} + #ifdef REMOVE_SPIKES #include void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc) diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index cb6aac8df9..bef95735b1 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -236,6 +236,16 @@ namespace Emboss /// Line height with spacing in ExPolygon size int get_line_height(const FontFile &font, const FontProp &prop); + /// + /// Calculate Vertical align + /// + /// double for mm + /// type + /// + /// + /// In same unit as line height + double get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, double line_height); + /// /// Project spatial point /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index bd78b66193..e1d28882c2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -264,26 +264,7 @@ namespace { // for existing volume which is selected(could init different(to volume text) lines count when edit text) void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines=0); // before text volume is created -void init_new_text_line(TextLinesModel &text_lines, const Transform3d& new_text_tr, const ModelObject& mo, /* const*/ StyleManager &style_manager) -{ - // prepare volumes to slice - ModelVolumePtrs volumes; - volumes.reserve(mo.volumes.size()); - for (ModelVolume *volume : mo.volumes) { - // only part could be surface for volumes - if (!volume->is_model_part()) - continue; - volumes.push_back(volume); - } - - double line_height = style_manager.get_line_height(); - if (line_height < 0) - return; - - FontProp::Align align = style_manager.get_font_prop().align; - unsigned count_lines = 1; - text_lines.init(new_text_tr, volumes, align, line_height, count_lines); -} +void init_new_text_line(TextLinesModel &text_lines, const Transform3d& new_text_tr, const ModelObject& mo, /* const*/ StyleManager &style_manager); } void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) @@ -1098,12 +1079,42 @@ EmbossStyles GLGizmoEmboss::create_default_styles() } namespace { -void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines) + +bool get_line_height_offset(/* const*/ StyleManager &style_manager, double &line_height_mm, double &line_offset_mm) { - double line_height = style_manager.get_line_height(); - if (line_height < 0) - return; - + assert(style_manager.is_active_font()); + if (!style_manager.is_active_font()) + return false; + const auto &ffc = style_manager.get_font_file_with_cache(); + assert(ffc.has_value()); + if (!ffc.has_value()) + return false; + const auto &ff_ptr = ffc.font_file; + assert(ff_ptr != nullptr); + if (ff_ptr == nullptr) + return false; + const FontProp &fp = style_manager.get_font_prop(); + const FontFile &ff = *ff_ptr; + + double half_ascent_shape_size = ff.infos[fp.collection_number.value_or(0)].ascent / 2.; + int line_height_shape_size = get_line_height(ff, fp); // In shape size + + double scale = get_shape_scale(fp, ff); + line_offset_mm = half_ascent_shape_size * scale / SHAPE_SCALE; + line_height_mm = line_height_shape_size * scale; + + if (line_height_mm < 0) + return false; + + // fix for bad filled ascent in font file + if (line_offset_mm <= 0) + line_offset_mm = line_height_mm / 3; + + return true; +} + +void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines) +{ const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) return; @@ -1149,10 +1160,35 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* } // For interactivity during drag over surface it must be from gl_volume not volume. - const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); - const FontProp &fp = style_manager.get_font_prop(); - text_lines.init(mv_trafo, volumes, fp.align, line_height, count_lines); + const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); + FontProp::VerticalAlign align = style_manager.get_font_prop().align.second; + double line_height_mm, line_offset_mm; + if (!get_line_height_offset(style_manager, line_height_mm, line_offset_mm)) + return; + + text_lines.init(mv_trafo, volumes, align, line_height_mm, line_offset_mm, count_lines); } + +void init_new_text_line(TextLinesModel &text_lines, const Transform3d& new_text_tr, const ModelObject& mo, /* const*/ StyleManager &style_manager) +{ + // prepare volumes to slice + ModelVolumePtrs volumes; + volumes.reserve(mo.volumes.size()); + for (ModelVolume *volume : mo.volumes) { + // only part could be surface for volumes + if (!volume->is_model_part()) + continue; + volumes.push_back(volume); + } + + FontProp::VerticalAlign align = style_manager.get_font_prop().align.second; + double line_height_mm, line_offset_mm; + if (!get_line_height_offset(style_manager, line_height_mm, line_offset_mm)) + return; + unsigned count_lines = 1; + text_lines.init(new_text_tr, volumes, align, line_height_mm, line_offset_mm, count_lines); +} + } void GLGizmoEmboss::reinit_text_lines(unsigned count_lines) { @@ -1617,6 +1653,8 @@ void GLGizmoEmboss::draw_text_input() append_warning(_u8L("Too tall, diminished font height inside text input.")); if (imgui_size < StyleManager::min_imgui_font_size) append_warning(_u8L("Too small, enlarged font height inside text input.")); + if (prop.align.first == FontProp::HorizontalAlign::center || prop.align.first == FontProp::HorizontalAlign::right) + append_warning(_u8L("Text doesn't show current horizontal alignment.")); } // flag for extend font ranges if neccessary diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 7e6e71a580..55002fcf63 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -251,12 +251,17 @@ GLModel::Geometry create_geometry(const TextLines &lines) } } // namespace -void TextLinesModel::init(const Transform3d &text_tr, const ModelVolumePtrs &volumes_to_slice, FontProp::Align align, double line_height, unsigned count_lines) +void TextLinesModel::init(const Transform3d &text_tr, + const ModelVolumePtrs &volumes_to_slice, + FontProp::VerticalAlign align, + double line_height, + double offset, + unsigned count_lines) { m_model.reset(); m_lines.clear(); - double first_line_center = offset + (count_lines / 2) * line_height - ((count_lines % 2 == 0) ? line_height / 2. : 0.); + double first_line_center = offset + this->offset + get_align_y_offset(align, count_lines, line_height); std::vector line_centers(count_lines); for (size_t i = 0; i < count_lines; ++i) line_centers[i] = static_cast(first_line_center - i * line_height); diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index bef7e2262f..2a5f8ca8fa 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -26,8 +26,9 @@ public: /// Vector of volumes to be sliced /// Vertical (Y) align of the text /// Distance between lines [in mm] + /// Offset from baseline [in mm] /// Count lines(slices over volumes) - void init(const Transform3d &text_tr, const ModelVolumePtrs& volumes_to_slice, FontProp::Align align, double line_height, unsigned count_lines); + void init(const Transform3d &text_tr, const ModelVolumePtrs& volumes_to_slice, FontProp::VerticalAlign align, double line_height, double offset, unsigned count_lines); void render(const Transform3d &text_world); From 2471e072d30c0b70fc60b264ebe0c12e2d6393f9 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 26 May 2023 19:57:49 +0200 Subject: [PATCH 21/23] Base line on third of ascent is better --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e1d28882c2..4b2f2ba5fd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1096,11 +1096,11 @@ bool get_line_height_offset(/* const*/ StyleManager &style_manager, double &line const FontProp &fp = style_manager.get_font_prop(); const FontFile &ff = *ff_ptr; - double half_ascent_shape_size = ff.infos[fp.collection_number.value_or(0)].ascent / 2.; + double third_ascent_shape_size = ff.infos[fp.collection_number.value_or(0)].ascent / 3.; int line_height_shape_size = get_line_height(ff, fp); // In shape size double scale = get_shape_scale(fp, ff); - line_offset_mm = half_ascent_shape_size * scale / SHAPE_SCALE; + line_offset_mm = third_ascent_shape_size * scale / SHAPE_SCALE; line_height_mm = line_height_shape_size * scale; if (line_height_mm < 0) From e05ff3e7a6af8261ba60b2b50edc0e15872a30e6 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 29 May 2023 06:08:25 +0200 Subject: [PATCH 22/23] fix load of 3mf with per glyph transformation by stored fix matrix --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 4b2f2ba5fd..33fed06c7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -644,8 +644,14 @@ void GLGizmoEmboss::on_render() { 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()); + if (m_text_lines.is_init()) { + const Transform3d& tr = gl_volume_ptr->world_matrix(); + const auto &fix = m_volume->text_configuration->fix_3mf_tr; + if (fix.has_value()) + m_text_lines.render(tr * fix->inverse()); + else + m_text_lines.render(tr); + } bool is_surface_dragging = m_surface_drag.has_value(); bool is_parent_dragging = m_parent.is_mouse_dragging(); @@ -1160,7 +1166,9 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* } // For interactivity during drag over surface it must be from gl_volume not volume. - const Transform3d &mv_trafo = gl_volume.get_volume_transformation().get_matrix(); + Transform3d mv_trafo = gl_volume.get_volume_transformation().get_matrix(); + if (tc.fix_3mf_tr.has_value()) + mv_trafo = mv_trafo * (tc.fix_3mf_tr->inverse()); FontProp::VerticalAlign align = style_manager.get_font_prop().align.second; double line_height_mm, line_offset_mm; if (!get_line_height_offset(style_manager, line_height_mm, line_offset_mm)) From ed4948742cd4adbab0f8533bf098170427e3c56d Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 2 Jun 2023 10:33:21 +0200 Subject: [PATCH 23/23] fix for: ../src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp:1458:14: warning: unused variable 'is_outside' [-Wunused-variable] ../src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp:3445:62: warning: suggest parentheses around '&&' within '||' [-Wparentheses] --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 28 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 33fed06c7a..acd62e54df 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -360,12 +360,19 @@ void GLGizmoEmboss::on_shortcut_key() { } } +namespace{ +// verify correct volume type for creation of text +bool check(ModelVolumeType volume_type) { + return volume_type == ModelVolumeType::MODEL_PART || + volume_type == ModelVolumeType::NEGATIVE_VOLUME || + volume_type == ModelVolumeType::PARAMETER_MODIFIER; +} +} + bool GLGizmoEmboss::init_create(ModelVolumeType volume_type) { // check valid volume type - if (volume_type != ModelVolumeType::MODEL_PART && - volume_type != ModelVolumeType::NEGATIVE_VOLUME && - volume_type != ModelVolumeType::PARAMETER_MODIFIER){ + if (!check(volume_type)){ BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type; return false; } @@ -1420,7 +1427,7 @@ bool GLGizmoEmboss::process() if (m_volume == nullptr) return false; // without text there is nothing to emboss - if (m_text.empty()) return false; + if (priv::is_text_empty(m_text)) return false; // exist loaded font file? if (!m_style_manager.is_active_font()) return false; @@ -1441,7 +1448,8 @@ bool GLGizmoEmboss::process() if (use_surface) { // Model to cut surface from. SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); - if (sources.empty()) return false; + if (sources.empty()) + return false; Transform3d text_tr = m_volume->get_matrix(); auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr; @@ -1455,10 +1463,12 @@ bool GLGizmoEmboss::process() text_tr *= Eigen::Translation(*offset); } - bool is_outside = m_volume->is_model_part(); // check that there is not unexpected volume type - assert(is_outside || m_volume->is_negative_volume() || - m_volume->is_modifier()); + bool is_valid_type = check(m_volume->type()); + assert(is_valid_type); + if (!is_valid_type) + return false; + UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, std::move(sources)}}; job = std::make_unique(std::move(surface_data)); } else { @@ -3443,7 +3453,7 @@ void GLGizmoEmboss::draw_advanced() const Camera &cam = wxGetApp().plater()->get_camera(); const FontProp &prop = m_style_manager.get_font_prop(); if (priv::apply_camera_dir(cam, m_parent, m_keep_up) && - prop.use_surface || prop.per_glyph) + (prop.use_surface || prop.per_glyph)) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Orient the text towards the camera.").c_str());