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);