mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 00:36:12 +08:00
Convert text to mesh
This commit is contained in:
parent
e33c665876
commit
026ad24b64
@ -6,6 +6,87 @@
|
|||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
// do not expose out of this file stbtt_ data types
|
||||||
|
class Privat
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Privat() = delete;
|
||||||
|
|
||||||
|
static std::optional<stbtt_fontinfo> load_font_info(const Emboss::Font &font);
|
||||||
|
|
||||||
|
struct Glyph
|
||||||
|
{
|
||||||
|
Polygons polygons;
|
||||||
|
int advance_width, left_side_bearing;
|
||||||
|
};
|
||||||
|
static std::optional<Glyph> get_glyph(stbtt_fontinfo &font_info, int unicode_letter, float flatness = 2.f);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<stbtt_fontinfo> Privat::load_font_info(const Emboss::Font &font)
|
||||||
|
{
|
||||||
|
int font_offset = stbtt_GetFontOffsetForIndex(font.buffer.data(), font.index);
|
||||||
|
if (font_offset < 0) {
|
||||||
|
std::cerr << "Font index("<<font.index<<") doesn't exist.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
stbtt_fontinfo font_info;
|
||||||
|
if (stbtt_InitFont(&font_info, font.buffer.data(), font_offset) == 0) {
|
||||||
|
std::cerr << "Can't initialize font.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return font_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Privat::Glyph> Privat::get_glyph(stbtt_fontinfo &font_info, int unicode_letter, float flatness)
|
||||||
|
{
|
||||||
|
int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter);
|
||||||
|
if (glyph_index == 0) {
|
||||||
|
std::cerr << "Character codepoint(" << unicode_letter
|
||||||
|
<< " = '" << (char) unicode_letter << "') is not defined in the font.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Privat::Glyph glyph;
|
||||||
|
stbtt_GetGlyphHMetrics(&font_info, glyph_index, &glyph.advance_width, &glyph.left_side_bearing);
|
||||||
|
|
||||||
|
stbtt_vertex *vertices;
|
||||||
|
int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices);
|
||||||
|
if (num_verts <= 0) return glyph; // no shape
|
||||||
|
|
||||||
|
int * contour_lengths = NULL;
|
||||||
|
int num_countour = 0;
|
||||||
|
stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts,
|
||||||
|
flatness,
|
||||||
|
&contour_lengths,
|
||||||
|
&num_countour,
|
||||||
|
font_info.userdata);
|
||||||
|
|
||||||
|
glyph.polygons.reserve(num_countour);
|
||||||
|
size_t pi = 0; // point index
|
||||||
|
for (size_t ci = 0; ci < num_countour; ++ci) {
|
||||||
|
int length = contour_lengths[ci];
|
||||||
|
if (length <= 0) continue;
|
||||||
|
Points pts;
|
||||||
|
pts.reserve(length);
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
const stbtt__point &point = points[pi];
|
||||||
|
pi++;
|
||||||
|
pts.emplace_back(point.x, point.y);
|
||||||
|
}
|
||||||
|
if (pts.front() == pts.back()) {
|
||||||
|
pts.pop_back();
|
||||||
|
} else {
|
||||||
|
int j = 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
glyph.polygons.emplace_back(pts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inner ccw
|
||||||
|
// outer cw
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<Emboss::Font> Emboss::load_font(const char *file_path)
|
std::optional<Emboss::Font> Emboss::load_font(const char *file_path)
|
||||||
{
|
{
|
||||||
FILE *file = fopen(file_path, "rb");
|
FILE *file = fopen(file_path, "rb");
|
||||||
@ -43,69 +124,361 @@ std::optional<Emboss::Font> Emboss::load_font(const char *file_path)
|
|||||||
// select default font on index 0
|
// select default font on index 0
|
||||||
res.index = 0;
|
res.index = 0;
|
||||||
res.count = index;
|
res.count = index;
|
||||||
// first fonst has offset zero
|
|
||||||
font_offset = 0;
|
|
||||||
|
|
||||||
stbtt_fontinfo font_info;
|
auto font_info = Privat::load_font_info(res);
|
||||||
if (stbtt_InitFont(&font_info, res.buffer.data(), font_offset) == 0) {
|
if (!font_info.has_value()) return {};
|
||||||
std::cerr << "Can't initialize font.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// load information about line gap
|
// load information about line gap
|
||||||
stbtt_GetFontVMetrics(&(font_info), &res.ascent, &res.descent, &res.linegap);
|
stbtt_GetFontVMetrics(&(*font_info), &res.ascent, &res.descent, &res.linegap);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Polygons Emboss::letter2polygons(const Font &font, char letter)
|
Polygons Emboss::letter2polygons(const Font &font, char letter)
|
||||||
{
|
{
|
||||||
int font_offset = stbtt_GetFontOffsetForIndex(font.buffer.data(), font.index);
|
auto font_info_opt = Privat::load_font_info(font);
|
||||||
stbtt_fontinfo font_info;
|
if (!font_info_opt.has_value()) return Polygons();
|
||||||
if (stbtt_InitFont(&font_info, font.buffer.data(), font_offset) == 0)
|
stbtt_fontinfo *font_info = &(*font_info_opt);
|
||||||
return Polygons();
|
|
||||||
|
|
||||||
int glyph_index = stbtt_FindGlyphIndex(&(font_info), letter);
|
auto glyph_opt = Privat::get_glyph(*font_info_opt, (int) letter, font.flatness);
|
||||||
int advanceWidth, leftSideBearing;
|
if (!glyph_opt.has_value()) return Polygons();
|
||||||
stbtt_GetGlyphHMetrics(&(font_info), glyph_index, &advanceWidth,
|
|
||||||
&leftSideBearing);
|
|
||||||
|
|
||||||
stbtt_vertex *vertices;
|
return glyph_opt->polygons;
|
||||||
int num_verts = stbtt_GetGlyphShape(&(font_info), glyph_index, &vertices);
|
}
|
||||||
if (num_verts < 0) return {}; // no shape
|
|
||||||
|
|
||||||
int * contour_lengths = NULL;
|
Polygons Emboss::text2polygons(const Font &font, const std::string &text)
|
||||||
int num_countour = 0;
|
{
|
||||||
stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, font.flatness,
|
auto font_info_opt = Privat::load_font_info(font);
|
||||||
&contour_lengths,
|
if (!font_info_opt.has_value()) return Polygons();
|
||||||
&num_countour,
|
stbtt_fontinfo *font_info = &(*font_info_opt);
|
||||||
(font_info).userdata);
|
|
||||||
|
|
||||||
|
Point cursor(0, 0);
|
||||||
Polygons result;
|
Polygons result;
|
||||||
result.reserve(num_countour);
|
for (const char &letter : text) {
|
||||||
size_t pi = 0; // point index
|
if (letter == '\0') break;
|
||||||
for (size_t ci = 0; ci < num_countour; ++ci) {
|
|
||||||
int length = contour_lengths[ci];
|
|
||||||
Points pts;
|
|
||||||
pts.reserve(length);
|
|
||||||
for (size_t i = 0; i < length; i++) {
|
|
||||||
const stbtt__point &point = points[pi];
|
|
||||||
pi++;
|
|
||||||
pts.emplace_back(point.x, point.y);
|
|
||||||
}
|
|
||||||
result.emplace_back(pts);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoundingBox bb;
|
auto glyph_opt = Privat::get_glyph(*font_info_opt, (int) letter, font.flatness);
|
||||||
for (auto &r : result) bb.merge(r.points);
|
if (!glyph_opt.has_value()) continue;
|
||||||
|
|
||||||
// inner ccw
|
// move glyph to cursor position
|
||||||
// outer cw
|
Polygons polygons = glyph_opt->polygons; // copy
|
||||||
|
for (Polygon &polygon : polygons)
|
||||||
|
for (Point &p : polygon.points) p += cursor;
|
||||||
|
|
||||||
|
cursor.x() += glyph_opt->advance_width;
|
||||||
|
|
||||||
|
polygons_append(result, polygons);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
indexed_triangle_set Emboss::create_model(const std::string &text,
|
std::vector<Vec3i> its_create_neighbors_index_2(const indexed_triangle_set &its)
|
||||||
const Font & font,
|
|
||||||
float z_size)
|
|
||||||
{
|
{
|
||||||
return indexed_triangle_set();
|
std::vector<Vec3i> out(its.indices.size(), Vec3i(-1, -1, -1));
|
||||||
|
|
||||||
|
// Create a mapping from triangle edge into face.
|
||||||
|
struct EdgeToFace
|
||||||
|
{
|
||||||
|
// Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high.
|
||||||
|
int vertex_low;
|
||||||
|
// Index of the 2nd vertex of the triangle edge.
|
||||||
|
int vertex_high;
|
||||||
|
// Index of a triangular face.
|
||||||
|
int face;
|
||||||
|
// Index of edge in the face, starting with 1. Negative indices if the
|
||||||
|
// edge was stored reverse in (vertex_low, vertex_high).
|
||||||
|
int face_edge;
|
||||||
|
bool operator==(const EdgeToFace &other) const
|
||||||
|
{
|
||||||
|
return vertex_low == other.vertex_low &&
|
||||||
|
vertex_high == other.vertex_high;
|
||||||
|
}
|
||||||
|
bool operator<(const EdgeToFace &other) const
|
||||||
|
{
|
||||||
|
return vertex_low < other.vertex_low ||
|
||||||
|
(vertex_low == other.vertex_low &&
|
||||||
|
vertex_high < other.vertex_high);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<EdgeToFace> edges_map;
|
||||||
|
edges_map.assign(its.indices.size() * 3, EdgeToFace());
|
||||||
|
for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++facet_idx)
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
EdgeToFace &e2f = edges_map[facet_idx * 3 + i];
|
||||||
|
e2f.vertex_low = its.indices[facet_idx][i];
|
||||||
|
e2f.vertex_high = its.indices[facet_idx][(i + 1) % 3];
|
||||||
|
e2f.face = facet_idx;
|
||||||
|
// 1 based indexing, to be always strictly positive.
|
||||||
|
e2f.face_edge = i + 1;
|
||||||
|
if (e2f.vertex_low > e2f.vertex_high) {
|
||||||
|
// Sort the vertices
|
||||||
|
std::swap(e2f.vertex_low, e2f.vertex_high);
|
||||||
|
// and make the face_edge negative to indicate a flipped edge.
|
||||||
|
e2f.face_edge = -e2f.face_edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(edges_map.begin(), edges_map.end());
|
||||||
|
|
||||||
|
// Assign a unique common edge id to touching triangle edges.
|
||||||
|
int num_edges = 0;
|
||||||
|
for (size_t i = 0; i < edges_map.size(); ++i) {
|
||||||
|
EdgeToFace &edge_i = edges_map[i];
|
||||||
|
if (edge_i.face == -1)
|
||||||
|
// This edge has been connected to some neighbor already.
|
||||||
|
continue;
|
||||||
|
// Unconnected edge. Find its neighbor with the correct orientation.
|
||||||
|
size_t j;
|
||||||
|
bool found = false;
|
||||||
|
for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++j)
|
||||||
|
if (edge_i.face_edge * edges_map[j].face_edge < 0 &&
|
||||||
|
edges_map[j].face != -1) {
|
||||||
|
// Faces touching with opposite oriented edges and none of the
|
||||||
|
// edges is connected yet.
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
// FIXME Vojtech: Trying to find an edge with equal orientation.
|
||||||
|
// This smells.
|
||||||
|
// admesh can assign the same edge ID to more than two facets (which is
|
||||||
|
// still topologically correct), so we have to search for a
|
||||||
|
// duplicate of this edge too in case it was already seen in this
|
||||||
|
// orientation
|
||||||
|
for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j];
|
||||||
|
++j)
|
||||||
|
if (edges_map[j].face != -1) {
|
||||||
|
// Faces touching with equally oriented edges and none of
|
||||||
|
// the edges is connected yet.
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assign an edge index to the 1st face.
|
||||||
|
// out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges;
|
||||||
|
if (found) {
|
||||||
|
EdgeToFace &edge_j = edges_map[j];
|
||||||
|
out[edge_i.face](std::abs(edge_i.face_edge) - 1) = edge_j.face;
|
||||||
|
out[edge_j.face](std::abs(edge_j.face_edge) - 1) = edge_i.face;
|
||||||
|
// Mark the edge as connected.
|
||||||
|
edge_j.face = -1;
|
||||||
|
}
|
||||||
|
++num_edges;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void its_remove_edge_triangles(indexed_triangle_set &its)
|
||||||
|
{
|
||||||
|
//// start, count
|
||||||
|
//std::vector<std::pair<uint32_t, uint32_t>> neighbors_vertices(its.vertices.size(), {0,0});
|
||||||
|
//// calc counts
|
||||||
|
//for (const auto &i : its.indices) {
|
||||||
|
// for (size_t j = 0; j < 3; j++)
|
||||||
|
// ++neighbors_vertices[i[j]].second;
|
||||||
|
//}
|
||||||
|
//uint32_t triangle_start = 0;
|
||||||
|
//for (auto &neighbor : neighbors_vertices) {
|
||||||
|
// neighbor.first = triangle_start;
|
||||||
|
// triangle_start += neighbor.second;
|
||||||
|
// neighbor.second = 0;
|
||||||
|
//}
|
||||||
|
//std::vector<uint32_t> neighbors_data(its.indices.size()*3);
|
||||||
|
//for (const auto &i : its.indices) {
|
||||||
|
// uint32_t index = &i - &its.indices.front();
|
||||||
|
// for (size_t j = 0; j < 3; j++) {
|
||||||
|
// auto & neighbor = neighbors_vertices[i[j]];
|
||||||
|
// size_t index_data = neighbor.second + neighbor.first;
|
||||||
|
// neighbors_data[index_data] = index;
|
||||||
|
// ++neighbor.second;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
auto neighbors = its_create_neighbors_index_2(its);
|
||||||
|
std::set<uint32_t> remove;
|
||||||
|
std::queue<uint32_t> insert;
|
||||||
|
int no_value = -1;
|
||||||
|
for (const auto &neighbor : neighbors) {
|
||||||
|
uint32_t index = &neighbor - &neighbors.front();
|
||||||
|
auto it = remove.find(index);
|
||||||
|
if (it != remove.end()) continue; // already removed
|
||||||
|
|
||||||
|
if (neighbor[0] != no_value &&
|
||||||
|
neighbor[1] != no_value &&
|
||||||
|
neighbor[2] != no_value)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
insert.push(index);
|
||||||
|
while (!insert.empty()) {
|
||||||
|
uint32_t i = insert.front();
|
||||||
|
insert.pop();
|
||||||
|
if (remove.find(i) != remove.end()) continue;
|
||||||
|
remove.insert(i);
|
||||||
|
for (size_t j = 0; j < 3; j++) {
|
||||||
|
if (neighbor[j] == no_value) continue;
|
||||||
|
uint32_t i2 = static_cast<uint32_t>(neighbor[j]);
|
||||||
|
insert.push(i2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<uint32_t> rem(remove.begin(), remove.end());
|
||||||
|
std::sort(rem.begin(), rem.end());
|
||||||
|
uint32_t offset = 0;
|
||||||
|
for (uint32_t i : rem) {
|
||||||
|
its.indices.erase(its.indices.begin() + i - offset);
|
||||||
|
++offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexed_triangle_set Emboss::polygons2model(const Polygons &shape2d,
|
||||||
|
const IProject &projection)
|
||||||
|
{
|
||||||
|
indexed_triangle_set result;
|
||||||
|
size_t count_point = count_points(shape2d);
|
||||||
|
result.vertices.reserve(2 * count_point);
|
||||||
|
|
||||||
|
std::vector<Vec3f> &front_points = result.vertices;
|
||||||
|
std::vector<Vec3f> back_points;
|
||||||
|
back_points.reserve(count_point);
|
||||||
|
|
||||||
|
for (const Polygon &polygon : shape2d) {
|
||||||
|
for (const Point &p : polygon.points) {
|
||||||
|
auto p2 = projection.project(p);
|
||||||
|
front_points.emplace_back(p2.first);
|
||||||
|
back_points.emplace_back(p2.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// insert back points, front are already in
|
||||||
|
result.vertices.insert(result.vertices.end(),
|
||||||
|
std::make_move_iterator(back_points.begin()),
|
||||||
|
std::make_move_iterator(back_points.end()));
|
||||||
|
|
||||||
|
// CW order of triangle indices
|
||||||
|
std::vector<Vec3i> shape_triangles = triangulate(shape2d);
|
||||||
|
result.indices.reserve(shape_triangles.size() * 2 + count_point * 2);
|
||||||
|
// top triangles - change to CCW
|
||||||
|
for (const Vec3i &t : shape_triangles)
|
||||||
|
result.indices.emplace_back(t.x(), t.z(), t.y());
|
||||||
|
// bottom triangles - use CW
|
||||||
|
for (const Vec3i &t : shape_triangles)
|
||||||
|
result.indices.emplace_back(t.x() + count_point, t.y() + count_point,
|
||||||
|
t.z() + count_point);
|
||||||
|
|
||||||
|
// quads around - zig zag by triangles
|
||||||
|
size_t polygon_offset = 0;
|
||||||
|
for (const Polygon &polygon : shape2d) {
|
||||||
|
uint32_t polygon_points = polygon.points.size();
|
||||||
|
for (uint32_t p = 0; p < polygon_points; p++) {
|
||||||
|
uint32_t i = polygon_offset + p;
|
||||||
|
// previous index
|
||||||
|
uint32_t ip = (p == 0) ? (polygon_offset + polygon_points - 1) : (i - 1);
|
||||||
|
// bottom indices
|
||||||
|
uint32_t i2 = i + count_point;
|
||||||
|
uint32_t ip2 = ip + count_point;
|
||||||
|
|
||||||
|
result.indices.emplace_back(i, i2, ip);
|
||||||
|
result.indices.emplace_back(ip2, ip, i2);
|
||||||
|
}
|
||||||
|
polygon_offset += polygon_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove bad triangulated faces
|
||||||
|
its_remove_edge_triangles(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||||
|
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
|
||||||
|
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
|
||||||
|
std::vector<Vec3i> Emboss::triangulate(
|
||||||
|
const Points& points,
|
||||||
|
const std::set<std::pair<uint32_t, uint32_t>> &edges)
|
||||||
|
{
|
||||||
|
// IMPROVE use int point insted of float !!!
|
||||||
|
|
||||||
|
// use cgal triangulation
|
||||||
|
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
|
||||||
|
using Itag = CGAL::Exact_predicates_tag;
|
||||||
|
using CDT = CGAL::Constrained_Delaunay_triangulation_2<K, CGAL::Default, Itag>;
|
||||||
|
using Point = CDT::Point;
|
||||||
|
|
||||||
|
// construct a constrained triangulation
|
||||||
|
CDT cdt;
|
||||||
|
std::map<CDT::Vertex_handle, int> map; // for indices
|
||||||
|
std::vector<CDT::Vertex_handle> vertices_handle; // for constriants
|
||||||
|
vertices_handle.reserve(points.size());
|
||||||
|
for (const auto& p: points) {
|
||||||
|
Point cdt_p(p.x(), p.y());
|
||||||
|
auto handl = cdt.insert(cdt_p);
|
||||||
|
vertices_handle.push_back(handl);
|
||||||
|
size_t i = &p - &points.front();
|
||||||
|
map[handl] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// triangle can not contain forbiden edge
|
||||||
|
for (const std::pair<uint32_t, uint32_t> &edge : edges) {
|
||||||
|
const CDT::Vertex_handle& vh1 = vertices_handle[edge.first];
|
||||||
|
const CDT::Vertex_handle& vh2 = vertices_handle[edge.second];
|
||||||
|
cdt.insert_constraint(vh1, vh2);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto faces = cdt.finite_face_handles();
|
||||||
|
std::vector<Vec3i> indices;
|
||||||
|
indices.reserve(faces.size());
|
||||||
|
for (CDT::Face_handle face : faces) {
|
||||||
|
auto v0 = face->vertex(0);
|
||||||
|
auto v1 = face->vertex(1);
|
||||||
|
auto v2 = face->vertex(2);
|
||||||
|
uint32_t i0 = map[v0];
|
||||||
|
uint32_t i1 = map[v1];
|
||||||
|
uint32_t i2 = map[v2];
|
||||||
|
|
||||||
|
// check forbiden triangle edge - opposit order
|
||||||
|
if (edges.find(std::make_pair(i0, i1)) != edges.end()) continue;
|
||||||
|
if (edges.find(std::make_pair(i1, i2)) != edges.end()) continue;
|
||||||
|
if (edges.find(std::make_pair(i2, i0)) != edges.end()) continue;
|
||||||
|
|
||||||
|
indices.emplace_back(map[v0], map[v1], map[v2]);
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vec3i> Emboss::triangulate(const Polygon &polygon)
|
||||||
|
{
|
||||||
|
const Points & pts = polygon.points;
|
||||||
|
std::set<std::pair<uint32_t, uint32_t>> edges;
|
||||||
|
for (uint32_t i = 1; i < pts.size(); ++i) edges.insert({i - 1, i});
|
||||||
|
edges.insert({(uint32_t)pts.size() - 1, uint32_t(0)});
|
||||||
|
return triangulate(pts, edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vec3i> Emboss::triangulate(const Polygons &polygons)
|
||||||
|
{
|
||||||
|
size_t count = count_points(polygons);
|
||||||
|
Points points;
|
||||||
|
points.reserve(count);
|
||||||
|
for (const Polygon &polygon : polygons)
|
||||||
|
points.insert(points.end(), polygon.points.begin(),
|
||||||
|
polygon.points.end());
|
||||||
|
|
||||||
|
std::set<std::pair<uint32_t, uint32_t>> edges;
|
||||||
|
uint32_t offset = 0;
|
||||||
|
for (const Polygon& polygon : polygons) {
|
||||||
|
const Points &pts = polygon.points;
|
||||||
|
for (uint32_t i = 1; i < pts.size(); ++i) {
|
||||||
|
uint32_t i2 = i + offset;
|
||||||
|
edges.insert({i2 - 1, i2});
|
||||||
|
}
|
||||||
|
uint32_t size = static_cast<uint32_t>(pts.size());
|
||||||
|
// add connection from first to last point
|
||||||
|
edges.insert({offset + size - 1, offset});
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
return triangulate(points, edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<Vec3f, Vec3f> Emboss::ProjectZ::project(const Point &p) const
|
||||||
|
{
|
||||||
|
Vec3f front(p.x(),p.y(),0.f);
|
||||||
|
Vec3f back = front; // copy
|
||||||
|
back.z() = m_depth;
|
||||||
|
return std::make_pair(front, back);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
#define slic3r_Emboss_hpp_
|
#define slic3r_Emboss_hpp_
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <memory>
|
||||||
#include <admesh/stl.h> // indexed_triangle_set
|
#include <admesh/stl.h> // indexed_triangle_set
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
|
|
||||||
@ -38,6 +40,8 @@ public:
|
|||||||
|
|
||||||
// change size of font
|
// change size of font
|
||||||
float scale = 1.;
|
float scale = 1.;
|
||||||
|
|
||||||
|
// enum class Align: center/left/right
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -55,18 +59,78 @@ public:
|
|||||||
/// <returns>inner polygon ccw(outer cw)</returns>
|
/// <returns>inner polygon ccw(outer cw)</returns>
|
||||||
static Polygons letter2polygons(const Font &font, char letter);
|
static Polygons letter2polygons(const Font &font, char letter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert text into polygons
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="font">Define fonts</param>
|
||||||
|
/// <param name="letter">Character to convert</param>
|
||||||
|
/// <returns>inner polygon ccw(outer cw)</returns>
|
||||||
static Polygons text2polygons(const Font &font, const std::string &text);
|
static Polygons text2polygons(const Font &font, const std::string &text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Project 2d point into space
|
||||||
|
/// Could be plane, sphere, cylindric, ...
|
||||||
|
/// </summary>
|
||||||
|
class IProject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IProject() = default;
|
||||||
|
/// <summary>
|
||||||
|
/// convert 2d point to 3d point
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="p">2d coordinate</param>
|
||||||
|
/// <returns>
|
||||||
|
/// first - front spatial point
|
||||||
|
/// second - back spatial point
|
||||||
|
/// </returns>
|
||||||
|
virtual std::pair<Vec3f, Vec3f> project(const Point &p) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create triangle model for text
|
/// Create triangle model for text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="shape2d">text or image</param>
|
||||||
/// <param name="font"></param>
|
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
|
||||||
/// <param name="z_size"></param>
|
/// <returns>Projected shape into space</returns>
|
||||||
/// <returns></returns>
|
static indexed_triangle_set polygons2model(const Polygons &shape2d, const IProject& projection);
|
||||||
static indexed_triangle_set create_model(const std::string &text,
|
|
||||||
const Font & font,
|
/// <summary>
|
||||||
float z_size);
|
/// Connect points by triangulation to create filled surface by triangle indices
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="points">Points to connect</param>
|
||||||
|
/// <param name="edges">Constraint for edges, pair is from point(first) to point(second)</param>
|
||||||
|
/// <returns>Triangles</returns>
|
||||||
|
static std::vector<Vec3i> triangulate(const Points &points, const std::set<std::pair<uint32_t, uint32_t>> &edges);
|
||||||
|
static std::vector<Vec3i> triangulate(const Polygon &polygon);
|
||||||
|
static std::vector<Vec3i> triangulate(const Polygons &polygons);
|
||||||
|
|
||||||
|
class ProjectZ : public IProject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProjectZ(float depth) : m_depth(depth) {}
|
||||||
|
// Inherited via IProject
|
||||||
|
virtual std::pair<Vec3f, Vec3f> project(const Point &p) const override;
|
||||||
|
float m_depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProjectScale : public IProject
|
||||||
|
{
|
||||||
|
std::unique_ptr<IProject> core;
|
||||||
|
public:
|
||||||
|
ProjectScale(std::unique_ptr<IProject> core, float scale)
|
||||||
|
: m_scale(scale)
|
||||||
|
, core(std::move(core))
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Inherited via IProject
|
||||||
|
virtual std::pair<Vec3f, Vec3f> project(const Point &p) const override
|
||||||
|
{
|
||||||
|
auto res = core->project(p);
|
||||||
|
return std::make_pair(res.first * m_scale, res.second * m_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
float m_scale;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -154,13 +154,17 @@ inline Points to_points(const Polygon &poly)
|
|||||||
return poly.points;
|
return poly.points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline size_t count_points(const Polygons &polys) {
|
||||||
|
size_t n_points = 0;
|
||||||
|
for (size_t i = 0; i < polys.size(); ++i)
|
||||||
|
n_points += polys[i].points.size();
|
||||||
|
return n_points;
|
||||||
|
}
|
||||||
|
|
||||||
inline Points to_points(const Polygons &polys)
|
inline Points to_points(const Polygons &polys)
|
||||||
{
|
{
|
||||||
size_t n_points = 0;
|
|
||||||
for (size_t i = 0; i < polys.size(); ++ i)
|
|
||||||
n_points += polys[i].points.size();
|
|
||||||
Points points;
|
Points points;
|
||||||
points.reserve(n_points);
|
points.reserve(count_points(polys));
|
||||||
for (const Polygon &poly : polys)
|
for (const Polygon &poly : polys)
|
||||||
append(points, poly.points);
|
append(points, poly.points);
|
||||||
return points;
|
return points;
|
||||||
@ -180,11 +184,8 @@ inline Lines to_lines(const Polygon &poly)
|
|||||||
|
|
||||||
inline Lines to_lines(const Polygons &polys)
|
inline Lines to_lines(const Polygons &polys)
|
||||||
{
|
{
|
||||||
size_t n_lines = 0;
|
|
||||||
for (size_t i = 0; i < polys.size(); ++ i)
|
|
||||||
n_lines += polys[i].points.size();
|
|
||||||
Lines lines;
|
Lines lines;
|
||||||
lines.reserve(n_lines);
|
lines.reserve(count_points(polys));
|
||||||
for (size_t i = 0; i < polys.size(); ++ i) {
|
for (size_t i = 0; i < polys.size(); ++ i) {
|
||||||
const Polygon &poly = polys[i];
|
const Polygon &poly = polys[i];
|
||||||
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
|
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
#include "libslic3r/Emboss.hpp"
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
|
|
||||||
@ -19,15 +19,10 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D & parent,
|
|||||||
Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"},
|
Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"},
|
||||||
{"Arial", "C:/windows/fonts/arialbd.ttf"}})
|
{"Arial", "C:/windows/fonts/arialbd.ttf"}})
|
||||||
, m_selected(1)
|
, m_selected(1)
|
||||||
, m_text_size(255)
|
, m_text(_u8L("Embossed text"))
|
||||||
, m_text(new char[255])
|
|
||||||
{
|
{
|
||||||
// TODO: suggest to use https://fontawesome.com/
|
// TODO: suggest to use https://fontawesome.com/
|
||||||
int index = 0;
|
m_text.reserve(255);
|
||||||
for (char &c : _u8L("Embossed text")) {
|
|
||||||
m_text[index++] = c;
|
|
||||||
}
|
|
||||||
m_text[index] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLGizmoEmboss::~GLGizmoEmboss() {}
|
GLGizmoEmboss::~GLGizmoEmboss() {}
|
||||||
@ -66,16 +61,47 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
|
|||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Button("Add");
|
ImGui::Button("Add");
|
||||||
|
static float scale = 0.01f;
|
||||||
|
ImGui::InputFloat("Scale", &scale);
|
||||||
|
static float emboss = 100.f;
|
||||||
|
ImGui::InputFloat("Emboss", &emboss);
|
||||||
|
|
||||||
|
if(ImGui::Button("Preview")){
|
||||||
|
|
||||||
|
auto project = std::make_unique<Emboss::ProjectScale>(
|
||||||
|
std::make_unique<Emboss::ProjectZ>(emboss)
|
||||||
|
,scale);
|
||||||
|
|
||||||
|
auto font_path = m_fonts[m_selected].file_path.c_str();
|
||||||
|
auto font_opt = Emboss::load_font(font_path);
|
||||||
|
if (font_opt.has_value()) {
|
||||||
|
Polygons polygons = Emboss::text2polygons(*font_opt, m_text);
|
||||||
|
indexed_triangle_set its = Emboss::polygons2model(polygons, *project);
|
||||||
|
// add object
|
||||||
|
TriangleMesh tm(its);
|
||||||
|
tm.repair();
|
||||||
|
|
||||||
|
tm.WriteOBJFile("text_preview.obj");
|
||||||
|
|
||||||
|
const Selection &selection = m_parent.get_selection();
|
||||||
|
int object_idx = selection.get_object_idx();
|
||||||
|
ModelObject *obj = wxGetApp().plater()->model().objects[object_idx];
|
||||||
|
ModelVolume *v = obj->volumes.front();
|
||||||
|
v->set_mesh(tm);
|
||||||
|
v->set_new_unique_id();
|
||||||
|
obj->invalidate_bounding_box();
|
||||||
|
m_parent.reload_scene(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size_t text_size = 255;
|
|
||||||
//sizeof(*m_text.get());
|
|
||||||
ImVec2 input_size(-FLT_MIN, ImGui::GetTextLineHeight() * 6);
|
ImVec2 input_size(-FLT_MIN, ImGui::GetTextLineHeight() * 6);
|
||||||
ImGuiInputTextFlags flags =
|
ImGuiInputTextFlags flags =
|
||||||
ImGuiInputTextFlags_::ImGuiInputTextFlags_AllowTabInput
|
ImGuiInputTextFlags_::ImGuiInputTextFlags_AllowTabInput
|
||||||
| ImGuiInputTextFlags_::ImGuiInputTextFlags_AutoSelectAll
|
| ImGuiInputTextFlags_::ImGuiInputTextFlags_AutoSelectAll
|
||||||
//|ImGuiInputTextFlags_::ImGuiInputTextFlags_CtrlEnterForNewLine
|
//|ImGuiInputTextFlags_::ImGuiInputTextFlags_CtrlEnterForNewLine
|
||||||
;
|
;
|
||||||
if (ImGui::InputTextMultiline("##Text", m_text.get(), text_size, input_size, flags)) {
|
if (ImGui::InputTextMultiline("##Text", (char *) m_text.c_str(),
|
||||||
|
m_text.capacity() + 1, input_size, flags)) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,26 +30,6 @@ protected:
|
|||||||
private:
|
private:
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
struct MyFont;
|
|
||||||
MyFont createFont(const char * fileName);
|
|
||||||
|
|
||||||
struct Configuration
|
|
||||||
{
|
|
||||||
bool use_count = false;
|
|
||||||
// minimal triangle count
|
|
||||||
float decimate_ratio = 50.f; // in percent
|
|
||||||
uint32_t wanted_count = 0; // initialize by percents
|
|
||||||
|
|
||||||
// maximal quadric error
|
|
||||||
float max_error = 1.;
|
|
||||||
|
|
||||||
void fix_count_by_ratio(size_t triangle_count)
|
|
||||||
{
|
|
||||||
wanted_count = static_cast<uint32_t>(
|
|
||||||
std::round(triangle_count * (100.f-decimate_ratio) / 100.f));
|
|
||||||
}
|
|
||||||
} m_configuration;
|
|
||||||
|
|
||||||
// This configs holds GUI layout size given by translated texts.
|
// This configs holds GUI layout size given by translated texts.
|
||||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||||
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
|
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
|
||||||
@ -72,9 +52,7 @@ private:
|
|||||||
std::vector<MyFont> m_fonts;
|
std::vector<MyFont> m_fonts;
|
||||||
size_t m_selected;// index to m_fonts
|
size_t m_selected;// index to m_fonts
|
||||||
|
|
||||||
|
std::string m_text;
|
||||||
size_t m_text_size; // allocated size by m_text
|
|
||||||
std::unique_ptr<char[]> m_text;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
|
@ -62,8 +62,12 @@ Polygons ttf2polygons(const char * font_name, char letter, float flatness = 1.f)
|
|||||||
void store_to_svg(Polygons polygons,std::string file_name = "letter.svg")
|
void store_to_svg(Polygons polygons,std::string file_name = "letter.svg")
|
||||||
{
|
{
|
||||||
double scale = 1e6;
|
double scale = 1e6;
|
||||||
for (auto &p : polygons) p.scale(scale);
|
BoundingBox bb;
|
||||||
SVG svg("letter.svg", BoundingBox(polygons.front().points));
|
for (auto& p : polygons) {
|
||||||
|
p.scale(scale);
|
||||||
|
bb.merge(p.points);
|
||||||
|
}
|
||||||
|
SVG svg(file_name, bb);
|
||||||
svg.draw(polygons);
|
svg.draw(polygons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user