Add TextLines to add per glyph transformation

This commit is contained in:
Filip Sykala - NTB T15p 2023-04-28 15:42:55 +02:00
parent 034b0a6118
commit b7549ae414
12 changed files with 619 additions and 155 deletions

View File

@ -1207,57 +1207,87 @@ std::optional<Glyph> 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<bool()> 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<int>(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<int>(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<bool()>& 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<int>(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<int>(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<ExPolygons> Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& 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<ExPolygons> 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);
}

View File

@ -112,7 +112,7 @@ namespace Emboss
std::shared_ptr<Emboss::Glyphs> cache;
FontFileWithCache() : font_file(nullptr), cache(nullptr) {}
FontFileWithCache(std::unique_ptr<FontFile> font_file)
explicit FontFileWithCache(std::unique_ptr<FontFile> font_file)
: font_file(std::move(font_file))
, cache(std::make_shared<Emboss::Glyphs>())
{}
@ -151,7 +151,11 @@ namespace Emboss
/// <param name="font_prop">User defined property of the font</param>
/// <param name="was_canceled">Way to interupt processing</param>
/// <returns>Inner polygon cw(outer ccw)</returns>
ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop, std::function<bool()> was_canceled = nullptr);
ExPolygons text2shapes (FontFileWithCache &font, const char *text, const FontProp &font_prop, const std::function<bool()> &was_canceled = []() {return false;});
std::vector<ExPolygons> text2vshapes(FontFileWithCache &font, const std::wstring& text, const FontProp &font_prop, const std::function<bool()>& was_canceled = []() {return false;});
unsigned get_count_lines(const std::wstring &ws);
unsigned get_count_lines(const std::string &text);
/// <summary>
/// Fix duplicit points and self intersections in polygons.
@ -337,6 +341,36 @@ namespace Emboss
}
};
class ProjectTransform : public IProjection
{
std::unique_ptr<IProjection> m_core;
Transform3d m_tr;
Transform3d m_tr_inv;
double z_scale;
public:
ProjectTransform(std::unique_ptr<IProjection> 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<Vec3d, Vec3d> 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<Vec2d> 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

View File

@ -60,6 +60,9 @@ struct FontProp
// Select index of font in collection
std::optional<unsigned int> collection_number;
// Distiguish projection per glyph
bool per_glyph;
//enum class Align {
// left,
// right,
@ -96,8 +99,7 @@ struct FontProp
/// </summary>
/// <param name="line_height">Y size of text [in mm]</param>
/// <param name="depth">Z size of text [in mm]</param>
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 {

View File

@ -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

View File

@ -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<float>(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<float>();
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<unsigned int>(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() :

View File

@ -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<std::atomic<bool>> 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

View File

@ -33,10 +33,11 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
std::vector<double> scales(m_input.styles.size());
m_images = std::vector<StyleManager::StyleImage>(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];

View File

@ -428,6 +428,88 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) {
template<typename Fnc>
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<ExPolygons> 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<BoundingBoxes> 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<ProjectZ>(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 {};

View File

@ -7,6 +7,7 @@
#include <libslic3r/Emboss.hpp>
#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<std::atomic<bool>> cancel;
// Define per letter projection on one text line
// [optional] It is not used when empty
TextLines text_lines;
};
/// <summary>

View File

@ -0,0 +1,285 @@
#include "TextLines.hpp"
#include <GL/glew.h>
#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<float>(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<float>();
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<float>(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<float>();
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<unsigned int>(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<TextConfiguration> 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<float> 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<Polygons> 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<Slic3r::Line> 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<Linef> 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<uint32_t>(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<Point::coord_type>();
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();
}

View File

@ -0,0 +1,46 @@
#ifndef slic3r_TextLines_hpp_
#define slic3r_TextLines_hpp_
#include <vector>
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Point.hpp"
#include "slic3r/GUI/GLModel.hpp"
namespace Slic3r::GUI {
class Selection;
/// <summary>
/// Define polygon for draw letters
/// </summary>
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<TextLine>;
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_

View File

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