mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 00:26:01 +08:00
Add TextLines to add per glyph transformation
This commit is contained in:
parent
034b0a6118
commit
b7549ae414
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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() :
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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 {};
|
||||
|
@ -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>
|
||||
|
285
src/slic3r/GUI/TextLines.cpp
Normal file
285
src/slic3r/GUI/TextLines.cpp
Normal 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();
|
||||
}
|
46
src/slic3r/GUI/TextLines.hpp
Normal file
46
src/slic3r/GUI/TextLines.hpp
Normal 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_
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user