mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 06:45:56 +08:00
Merge branch 'fs_dir_per_glyph_SPE-1597' into fs_svg
# Conflicts: # src/libslic3r/Emboss.cpp # src/libslic3r/Format/3mf.cpp # src/libslic3r/TextConfiguration.hpp # src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp # src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp # src/slic3r/GUI/Jobs/EmbossJob.cpp # src/slic3r/GUI/Jobs/EmbossJob.hpp
This commit is contained in:
commit
e831255018
7
resources/icons/align_horizontal_center.svg
Normal file
7
resources/icons/align_horizontal_center.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
|
||||
<path fill="#ED6B21" d="m 7,14 c 0,0 0,1 1,1 1,0 1,-1 1,-1 V 2 C 9,2 9,1 8,1 7,1 7,2 7,2 Z"/>
|
||||
<path fill="#808080" d="M 3,3 H 13 V 4 H 3 Z" />
|
||||
<path fill="#808080" d="m 3.5,9 h 9 v 1 h -9 z" />
|
||||
<path fill="#808080" d="m 5.5,6 h 5 v 1 h -5 z" />
|
||||
<path fill="#808080" d="m 4,12 h 8 v 1 H 4 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 389 B |
7
resources/icons/align_horizontal_left.svg
Normal file
7
resources/icons/align_horizontal_left.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
|
||||
<path fill="#ED6B21" d="m 1,14 c 0,0 0,1 1,1 1,0 1,-1 1,-1 V 2 C 3,2 3,1 2,1 1,1 1,2 1,2 Z" />
|
||||
<path fill="#808080" d="M 2,3 H 12 V 4 H 2 Z" />
|
||||
<path fill="#808080" d="m 2,9 h 9 v 1 H 2 Z" />
|
||||
<path fill="#808080" d="M 2,6 H 7 V 7 H 2 Z" />
|
||||
<path fill="#808080" d="m 2,12 h 8 v 1 H 2 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 383 B |
7
resources/icons/align_horizontal_right.svg
Normal file
7
resources/icons/align_horizontal_right.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16px" height="16px">
|
||||
<path fill="#ED6B21" d="m 15,14 c 0,0 0,1 -1,1 -1,0 -1,-1 -1,-1 V 2 c 0,0 0,-1 1,-1 1,0 1,1 1,1 z" />
|
||||
<path fill="#808080" d="M 14,3 H 4 v 1 h 10 z" />
|
||||
<path fill="#808080" d="M 14,9 H 5 v 1 h 9 z" />
|
||||
<path fill="#808080" d="M 14,6 H 9 v 1 h 5 z" />
|
||||
<path fill="#808080" d="M 14,12 H 6 v 1 h 8 z" />
|
||||
</svg>
|
After Width: | Height: | Size: 395 B |
60
resources/icons/align_vertical_bottom.svg
Normal file
60
resources/icons/align_vertical_bottom.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
width="16px"
|
||||
height="16px"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
sodipodi:docname="vertic_bottom.svg"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs16" />
|
||||
<sodipodi:namedview
|
||||
id="namedview14"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="true"
|
||||
inkscape:zoom="41.7193"
|
||||
inkscape:cx="12.356391"
|
||||
inkscape:cy="8.5092511"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1129"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg12">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid299" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
fill="#ed6b21"
|
||||
d="M 2,11 C 2,11 1,11 1,12.5 1,14 2,14 2,14 h 12 c 0,0 1,0 1,-1.5 C 15,11 14,11 14,11 Z"
|
||||
id="path2"
|
||||
style="stroke-width:1.22474" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="M 3,3 H 13 V 4 H 3 Z"
|
||||
id="path4" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="M 3,6 H 13 V 7 H 3 Z"
|
||||
id="path301" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="m 3,9 h 10 v 1 H 3 Z"
|
||||
id="path303" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="m 3,12 h 10 v 1 H 3 Z"
|
||||
id="path305" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
60
resources/icons/align_vertical_center.svg
Normal file
60
resources/icons/align_vertical_center.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
width="16px"
|
||||
height="16px"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
sodipodi:docname="vertic_center.svg"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs16" />
|
||||
<sodipodi:namedview
|
||||
id="namedview14"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="true"
|
||||
inkscape:zoom="41.7193"
|
||||
inkscape:cx="12.356391"
|
||||
inkscape:cy="8.5092511"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1129"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg12">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid299" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
fill="#ed6b21"
|
||||
d="m 2,6.5 c 0,0 -1,0 -1,1.5 0,1.5 1,1.5 1,1.5 h 12 c 0,0 1,0 1,-1.5 0,-1.5 -1,-1.5 -1,-1.5 z"
|
||||
id="path2"
|
||||
style="stroke-width:1.22474" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="M 3,3 H 13 V 4 H 3 Z"
|
||||
id="path4" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="M 3,6 H 13 V 7 H 3 Z"
|
||||
id="path301" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="m 3,9 h 10 v 1 H 3 Z"
|
||||
id="path303" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="m 3,12 h 10 v 1 H 3 Z"
|
||||
id="path305" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
60
resources/icons/align_vertical_top.svg
Normal file
60
resources/icons/align_vertical_top.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
width="16px"
|
||||
height="16px"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
sodipodi:docname="vertic_top.svg"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs16" />
|
||||
<sodipodi:namedview
|
||||
id="namedview14"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="true"
|
||||
inkscape:zoom="41.7193"
|
||||
inkscape:cx="12.356391"
|
||||
inkscape:cy="8.5092511"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1129"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg12">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid299" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
fill="#ed6b21"
|
||||
d="M 2,2 C 2,2 1,2 1,3.5 1,5 2,5 2,5 H 14 C 14,5 15,5 15,3.5 15,2 14,2 14,2 Z"
|
||||
id="path2"
|
||||
style="stroke-width:1.22474" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="M 3,3 H 13 V 4 H 3 Z"
|
||||
id="path4" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="M 3,6 H 13 V 7 H 3 Z"
|
||||
id="path301" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="m 3,9 h 10 v 1 H 3 Z"
|
||||
id="path303" />
|
||||
<path
|
||||
fill="#808080"
|
||||
d="m 3,12 h 10 v 1 H 3 Z"
|
||||
id="path305" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -1208,63 +1208,156 @@ 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)
|
||||
int Emboss::get_line_height(const FontFile &font, const FontProp &prop) {
|
||||
unsigned int font_index = prop.collection_number.value_or(0);
|
||||
assert(priv::is_valid(font, font_index));
|
||||
const FontFile::Info &info = font.infos[font_index];
|
||||
int line_height = info.ascent - info.descent + info.linegap;
|
||||
line_height += prop.line_gap.value_or(0);
|
||||
return static_cast<int>(line_height / SHAPE_SCALE);
|
||||
}
|
||||
|
||||
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());
|
||||
fontinfo_opt font_info_opt;
|
||||
Point cursor(0, 0);
|
||||
if (!font_with_cache.has_value())
|
||||
return {};
|
||||
|
||||
Glyphs &cache = *font_with_cache.cache;
|
||||
const FontFile &font = *font_with_cache.font_file;
|
||||
|
||||
if (letter == '\n') {
|
||||
cursor.x() = 0;
|
||||
// 2d shape has opposit direction of y
|
||||
cursor.y() -= get_line_height(font, font_prop);
|
||||
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)
|
||||
{
|
||||
std::wstring text_w = boost::nowide::widen(text);
|
||||
std::vector<ExPolygons> vshapes = text2vshapes(font_with_cache, text_w, font_prop, was_canceled);
|
||||
// unify to one expolygon
|
||||
ExPolygons result;
|
||||
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 {};
|
||||
const FontFile::Info& info = font.infos[font_index];
|
||||
Glyphs& cache = *font_with_cache.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;
|
||||
for (ExPolygons &shapes : vshapes) {
|
||||
if (shapes.empty())
|
||||
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 (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_append(result, std::move(expolygons));
|
||||
expolygons_append(result, std::move(shapes));
|
||||
}
|
||||
result = Slic3r::union_ex(result);
|
||||
heal_shape(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// <summary>
|
||||
/// Align shape against pivot
|
||||
/// </summary>
|
||||
/// <param name="type">Horizontal and vertical alignment</param>
|
||||
/// <param name="shapes">Shapes to align
|
||||
/// Prerequisities: shapes are aligned left top</param>
|
||||
/// <param name="text">To detect end of lines</param>
|
||||
/// <param name="line_height">Height of line for align[in font points]</param>
|
||||
void align_shape(FontProp::Align type, std::vector<ExPolygons> &shape, const std::wstring &text, int line_height);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
fontinfo_opt font_info_cache;
|
||||
std::vector<ExPolygons> result;
|
||||
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));
|
||||
}
|
||||
|
||||
align_shape(font_prop.align, result, text, get_line_height(font, font_prop));
|
||||
return result;
|
||||
}
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
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 prev_count = 0;
|
||||
// for (wchar_t wc : ws)
|
||||
// if (wc == '\n')
|
||||
// ++prev_count;
|
||||
// else
|
||||
// break;
|
||||
//
|
||||
// unsigned post_count = 0;
|
||||
// for (wchar_t wc : boost::adaptors::reverse(ws))
|
||||
// if (wc == '\n')
|
||||
// ++post_count;
|
||||
// else
|
||||
// break;
|
||||
//return count - prev_count - post_count;
|
||||
}
|
||||
|
||||
unsigned Emboss::get_count_lines(const std::string &text)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -1653,6 +1746,251 @@ std::optional<Vec2d> Emboss::OrthoProject::unproject(const Vec3d &p, double *dep
|
||||
return Vec2d(pp.x(), pp.y());
|
||||
}
|
||||
|
||||
// sample slice
|
||||
namespace {
|
||||
|
||||
// using coor2 = int64_t;
|
||||
using Coord2 = double;
|
||||
using P2 = Eigen::Matrix<Coord2, 2, 1, Eigen::DontAlign>;
|
||||
|
||||
bool point_in_distance(const Coord2 &distance_sq, PolygonPoint &polygon_point, const size_t &i, const Slic3r::Polygon &polygon, bool is_first, bool is_reverse = false)
|
||||
{
|
||||
size_t s = polygon.size();
|
||||
size_t ii = (i + polygon_point.index) % s;
|
||||
|
||||
// second point of line
|
||||
const Point &p = polygon[ii];
|
||||
Point p_d = p - polygon_point.point;
|
||||
|
||||
P2 p_d2 = p_d.cast<Coord2>();
|
||||
Coord2 p_distance_sq = p_d2.squaredNorm();
|
||||
if (p_distance_sq < distance_sq)
|
||||
return false;
|
||||
|
||||
// found line
|
||||
if (is_first) {
|
||||
// on same line
|
||||
// center also lay on line
|
||||
// new point is distance moved from point by direction
|
||||
polygon_point.point += p_d * sqrt(distance_sq / p_distance_sq);
|
||||
return true;
|
||||
}
|
||||
|
||||
// line cross circle
|
||||
|
||||
// start point of line
|
||||
size_t ii2 = (is_reverse) ? (ii + 1) % s : (ii + s - 1) % s;
|
||||
polygon_point.index = (is_reverse) ? ii : ii2;
|
||||
const Point &p2 = polygon[ii2];
|
||||
|
||||
Point line_dir = p2 - p;
|
||||
P2 line_dir2 = line_dir.cast<Coord2>();
|
||||
|
||||
Coord2 a = line_dir2.dot(line_dir2);
|
||||
Coord2 b = 2 * p_d2.dot(line_dir2);
|
||||
Coord2 c = p_d2.dot(p_d2) - distance_sq;
|
||||
|
||||
double discriminant = b * b - 4 * a * c;
|
||||
if (discriminant < 0) {
|
||||
assert(false);
|
||||
// no intersection
|
||||
polygon_point.point = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ray didn't totally miss sphere,
|
||||
// so there is a solution to
|
||||
// the equation.
|
||||
discriminant = sqrt(discriminant);
|
||||
|
||||
// either solution may be on or off the ray so need to test both
|
||||
// t1 is always the smaller value, because BOTH discriminant and
|
||||
// a are nonnegative.
|
||||
double t1 = (-b - discriminant) / (2 * a);
|
||||
double t2 = (-b + discriminant) / (2 * a);
|
||||
|
||||
double t = std::min(t1, t2);
|
||||
if (t < 0. || t > 1.) {
|
||||
// Bad intersection
|
||||
assert(false);
|
||||
polygon_point.point = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
polygon_point.point = p + (t * line_dir2).cast<Point::coord_type>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void point_in_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon)
|
||||
{
|
||||
Coord2 distance_sq = static_cast<Coord2>(distance) * distance;
|
||||
bool is_first = true;
|
||||
for (size_t i = 1; i < polygon.size(); ++i) {
|
||||
if (point_in_distance(distance_sq, p, i, polygon, is_first))
|
||||
return;
|
||||
is_first = false;
|
||||
}
|
||||
// There is not point on polygon with this distance
|
||||
}
|
||||
|
||||
void point_in_reverse_distance(int32_t distance, PolygonPoint &p, const Slic3r::Polygon &polygon)
|
||||
{
|
||||
Coord2 distance_sq = static_cast<Coord2>(distance) * distance;
|
||||
bool is_first = true;
|
||||
bool is_reverse = true;
|
||||
for (size_t i = polygon.size(); i > 0; --i) {
|
||||
if (point_in_distance(distance_sq, p, i, polygon, is_first, is_reverse))
|
||||
return;
|
||||
is_first = false;
|
||||
}
|
||||
// There is not point on polygon with this distance
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// calculate rotation, need copy of polygon point
|
||||
double Emboss::calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon)
|
||||
{
|
||||
PolygonPoint polygon_point2 = polygon_point; // copy
|
||||
point_in_distance(distance, polygon_point, polygon);
|
||||
point_in_reverse_distance(distance, polygon_point2, polygon);
|
||||
|
||||
Point surface_dir = polygon_point2.point - polygon_point.point;
|
||||
Point norm(-surface_dir.y(), surface_dir.x());
|
||||
Vec2d norm_d = norm.cast<double>();
|
||||
//norm_d.normalize();
|
||||
return std::atan2(norm_d.y(), norm_d.x());
|
||||
}
|
||||
|
||||
std::vector<double> Emboss::calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon)
|
||||
{
|
||||
std::vector<double> result;
|
||||
result.reserve(polygon_points.size());
|
||||
for(const PolygonPoint& pp: polygon_points)
|
||||
result.emplace_back(calculate_angle(distance, pp, polygon));
|
||||
return result;
|
||||
}
|
||||
|
||||
PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale)
|
||||
{
|
||||
// find BB in center of line
|
||||
size_t first_right_index = 0;
|
||||
for (const BoundingBox &bb : bbs)
|
||||
if (!bb.defined) // white char do not have bb
|
||||
continue;
|
||||
else if (bb.min.x() < 0)
|
||||
++first_right_index;
|
||||
else
|
||||
break;
|
||||
|
||||
PolygonPoints samples(bbs.size());
|
||||
int32_t shapes_x_cursor = 0;
|
||||
|
||||
PolygonPoint cursor = slice.start; //copy
|
||||
|
||||
auto create_sample = [&] //polygon_cursor, &polygon_line_index, &line_bbs, &shapes_x_cursor, &shape_scale, &em_2_polygon, &line, &offsets]
|
||||
(const BoundingBox &bb, bool is_reverse) {
|
||||
if (!bb.defined)
|
||||
return cursor;
|
||||
Point letter_center = bb.center();
|
||||
int32_t shape_distance = shapes_x_cursor - letter_center.x();
|
||||
shapes_x_cursor = letter_center.x();
|
||||
double distance_mm = shape_distance * scale;
|
||||
int32_t distance_polygon = static_cast<int32_t>(std::round(scale_(distance_mm)));
|
||||
if (is_reverse)
|
||||
point_in_distance(distance_polygon, cursor, slice.polygon);
|
||||
else
|
||||
point_in_reverse_distance(distance_polygon, cursor, slice.polygon);
|
||||
return cursor;
|
||||
};
|
||||
|
||||
// calc transformation for letters on the Right side from center
|
||||
bool is_reverse = true;
|
||||
for (size_t index = first_right_index; index < bbs.size(); ++index)
|
||||
samples[index] = create_sample(bbs[index], is_reverse);
|
||||
|
||||
// calc transformation for letters on the Left side from center
|
||||
if (first_right_index < bbs.size()) {
|
||||
shapes_x_cursor = bbs[first_right_index].center().x();
|
||||
cursor = samples[first_right_index];
|
||||
}else{
|
||||
// only left side exists
|
||||
shapes_x_cursor = 0;
|
||||
cursor = slice.start; // copy
|
||||
}
|
||||
is_reverse = false;
|
||||
for (size_t index_plus_one = first_right_index; index_plus_one > 0; --index_plus_one) {
|
||||
size_t index = index_plus_one - 1;
|
||||
samples[index] = create_sample(bbs[index], is_reverse);
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template<typename T> T get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, T line_height)
|
||||
{
|
||||
if (count_lines == 0)
|
||||
return 0;
|
||||
|
||||
// direction of Y in 2d is from top to bottom
|
||||
// zero is on base line of first line
|
||||
switch (align) {
|
||||
case FontProp::VerticalAlign::center: return ((count_lines - 1) / 2) * line_height + ((count_lines % 2 == 0) ? (line_height / 2) : 0);
|
||||
case FontProp::VerticalAlign::bottom: return (count_lines - 1) * line_height;
|
||||
case FontProp::VerticalAlign::top: // no change
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t get_align_x_offset(FontProp::HorizontalAlign align, const BoundingBox &shape_bb, const BoundingBox &line_bb)
|
||||
{
|
||||
switch (align) {
|
||||
case FontProp::HorizontalAlign::right: return -shape_bb.max.x() + (shape_bb.size().x() - line_bb.size().x());
|
||||
case FontProp::HorizontalAlign::center: return -shape_bb.center().x() + (shape_bb.size().x() - line_bb.size().x()) / 2;
|
||||
case FontProp::HorizontalAlign::left: // no change
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void align_shape(FontProp::Align type, std::vector<ExPolygons> &shapes, const std::wstring &text, int line_height)
|
||||
{
|
||||
constexpr FontProp::Align no_change(FontProp::HorizontalAlign::left, FontProp::VerticalAlign::top);
|
||||
if (type == no_change)
|
||||
return; // no alignment
|
||||
|
||||
BoundingBox shape_bb;
|
||||
for (const ExPolygons& shape: shapes)
|
||||
shape_bb.merge(get_extents(shape));
|
||||
|
||||
auto get_line_bb = [&](size_t j) {
|
||||
BoundingBox line_bb;
|
||||
for (; j < text.length() && text[j] != '\n'; ++j)
|
||||
line_bb.merge(get_extents(shapes[j]));
|
||||
return line_bb;
|
||||
};
|
||||
|
||||
Point offset(
|
||||
get_align_x_offset(type.first, shape_bb, get_line_bb(0)),
|
||||
get_align_y_offset(type.second, get_count_lines(text), line_height));
|
||||
assert(shapes.size() == text.length());
|
||||
for (size_t i = 0; i < shapes.size(); ++i) {
|
||||
wchar_t letter = text[i];
|
||||
if (letter == '\n'){
|
||||
offset.x() = get_align_x_offset(type.first, shape_bb, get_line_bb(i+1));
|
||||
continue;
|
||||
}
|
||||
ExPolygons &shape = shapes[i];
|
||||
for (ExPolygon &s : shape)
|
||||
s.translate(offset);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
double Emboss::get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, double line_height){
|
||||
return ::get_align_y_offset<double>(align, count_lines, line_height);
|
||||
}
|
||||
|
||||
#ifdef REMOVE_SPIKES
|
||||
#include <Geometry.hpp>
|
||||
void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <admesh/stl.h> // indexed_triangle_set
|
||||
#include "Polygon.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "TextConfiguration.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@ -108,7 +109,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>())
|
||||
{}
|
||||
@ -147,7 +148,12 @@ 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;});
|
||||
|
||||
/// Sum of character '\n'
|
||||
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.
|
||||
@ -218,6 +224,24 @@ namespace Emboss
|
||||
/// <returns>Conversion to mm</returns>
|
||||
double get_text_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||
|
||||
/// <summary>
|
||||
/// Read from font file and properties height of line with spacing
|
||||
/// </summary>
|
||||
/// <param name="font">Infos for collections</param>
|
||||
/// <param name="prop">Collection index + Additional line gap</param>
|
||||
/// <returns>Line height with spacing in ExPolygon size</returns>
|
||||
int get_line_height(const FontFile &font, const FontProp &prop);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Vertical align
|
||||
/// </summary>
|
||||
/// <typeparam name="T">double for mm</typeparam>
|
||||
/// <param name="align">type</param>
|
||||
/// <param name="count_lines"></param>
|
||||
/// <param name="line_height"></param>
|
||||
/// <returns>In same unit as line height</returns>
|
||||
double get_align_y_offset(FontProp::VerticalAlign align, unsigned count_lines, double line_height);
|
||||
|
||||
/// <summary>
|
||||
/// Project spatial point
|
||||
/// </summary>
|
||||
@ -333,6 +357,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
|
||||
@ -356,7 +410,43 @@ namespace Emboss
|
||||
Vec3d project(const Vec3d &point) const override;
|
||||
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
|
||||
};
|
||||
} // namespace Emboss
|
||||
|
||||
/// <summary>
|
||||
/// Define polygon for draw letters
|
||||
/// </summary>
|
||||
struct TextLine
|
||||
{
|
||||
// slice of object
|
||||
Polygon polygon;
|
||||
|
||||
// point laying on polygon closest to zero
|
||||
PolygonPoint start;
|
||||
|
||||
// offset of text line in volume mm
|
||||
float y;
|
||||
};
|
||||
using TextLines = std::vector<TextLine>;
|
||||
|
||||
/// <summary>
|
||||
/// Sample slice polygon by bounding boxes centers
|
||||
/// slice start point has shape_center_x coor
|
||||
/// </summary>
|
||||
/// <param name="slice">Polygon and start point[Slic3r scaled milimeters]</param>
|
||||
/// <param name="bbs">Bounding boxes of letter on one line[in font scales]</param>
|
||||
/// <param name="scale">Scale for bbs (after multiply bb is in milimeters)</param>
|
||||
/// <returns>Sampled polygon by bounding boxes</returns>
|
||||
PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate angle for polygon point
|
||||
/// </summary>
|
||||
/// <param name="distance">Distance for found normal in point</param>
|
||||
/// <param name="polygon_point">Select point on polygon</param>
|
||||
/// <param name="polygon">Polygon know neighbor of point</param>
|
||||
/// <returns>angle(atan2) of normal in polygon point</returns>
|
||||
double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon);
|
||||
std::vector<double> calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon);
|
||||
|
||||
} // namespace Emboss
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_Emboss_hpp_
|
||||
|
@ -161,6 +161,9 @@ static constexpr const char *LINE_GAP_ATTR = "line_gap";
|
||||
static constexpr const char *LINE_HEIGHT_ATTR = "line_height";
|
||||
static constexpr const char *BOLDNESS_ATTR = "boldness";
|
||||
static constexpr const char *SKEW_ATTR = "skew";
|
||||
static constexpr const char *PER_GLYPH_ATTR = "per_glyph";
|
||||
static constexpr const char *HORIZONTAL_ALIGN_ATTR = "horizontal";
|
||||
static constexpr const char *VERTICAL_ALIGN_ATTR = "vertical";
|
||||
static constexpr const char *COLLECTION_NUMBER_ATTR = "collection";
|
||||
|
||||
static constexpr const char *FONT_FAMILY_ATTR = "family";
|
||||
@ -3565,6 +3568,10 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex
|
||||
stream << BOLDNESS_ATTR << "=\"" << *fp.boldness << "\" ";
|
||||
if (fp.skew.has_value())
|
||||
stream << SKEW_ATTR << "=\"" << *fp.skew << "\" ";
|
||||
if (fp.per_glyph)
|
||||
stream << PER_GLYPH_ATTR << "=\"" << 1 << "\" ";
|
||||
stream << HORIZONTAL_ALIGN_ATTR << "=\"" << static_cast<int>(fp.align.first) << "\" ";
|
||||
stream << VERTICAL_ALIGN_ATTR << "=\"" << static_cast<int>(fp.align.second) << "\" ";
|
||||
if (fp.collection_number.has_value())
|
||||
stream << COLLECTION_NUMBER_ATTR << "=\"" << *fp.collection_number << "\" ";
|
||||
// font descriptor
|
||||
@ -3593,7 +3600,17 @@ std::optional<TextConfiguration> TextConfigurationSerialization::read(const char
|
||||
float skew = get_attribute_value_float(attributes, num_attributes, SKEW_ATTR);
|
||||
if (std::fabs(skew) > std::numeric_limits<float>::epsilon())
|
||||
fp.skew = skew;
|
||||
|
||||
int use_surface = get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR);
|
||||
if (use_surface == 1) fp.use_surface = true;
|
||||
int per_glyph = get_attribute_value_int(attributes, num_attributes, PER_GLYPH_ATTR);
|
||||
if (per_glyph == 1) fp.per_glyph = true;
|
||||
|
||||
int horizontal = get_attribute_value_int(attributes, num_attributes, HORIZONTAL_ALIGN_ATTR);
|
||||
int vertical = get_attribute_value_int(attributes, num_attributes, VERTICAL_ALIGN_ATTR);
|
||||
fp.align = FontProp::Align(
|
||||
static_cast<FontProp::HorizontalAlign>(horizontal),
|
||||
static_cast<FontProp::VerticalAlign>(vertical));
|
||||
|
||||
int collection_number = get_attribute_value_int(attributes, num_attributes, COLLECTION_NUMBER_ATTR);
|
||||
if (collection_number > 0) fp.collection_number = static_cast<unsigned int>(collection_number);
|
||||
|
||||
|
@ -268,6 +268,21 @@ bool polygons_match(const Polygon &l, const Polygon &r);
|
||||
Polygon make_circle(double radius, double error);
|
||||
Polygon make_circle_num_segments(double radius, size_t num_segments);
|
||||
|
||||
/// <summary>
|
||||
/// Define point laying on polygon
|
||||
/// keep index of polygon line and point coordinate
|
||||
/// </summary>
|
||||
struct PolygonPoint
|
||||
{
|
||||
// index of line inside of polygon
|
||||
// 0 .. from point polygon[0] to polygon[1]
|
||||
size_t index;
|
||||
|
||||
// Point, which lay on line defined by index
|
||||
Point point;
|
||||
};
|
||||
using PolygonPoints = std::vector<PolygonPoint>;
|
||||
|
||||
} // Slic3r
|
||||
|
||||
// start Boost
|
||||
|
@ -40,6 +40,17 @@ struct FontProp
|
||||
// Select index of font in collection
|
||||
std::optional<unsigned int> collection_number;
|
||||
|
||||
// Distiguish projection per glyph
|
||||
bool per_glyph;
|
||||
|
||||
// NOTE: way of serialize to 3mf force that zero must be default value
|
||||
enum class HorizontalAlign { left = 0, center, right };
|
||||
enum class VerticalAlign { top = 0, center, bottom };
|
||||
using Align = std::pair<HorizontalAlign, VerticalAlign>;
|
||||
// change pivot of text
|
||||
// When not set, center is used and is not stored
|
||||
Align align = Align(HorizontalAlign::left, VerticalAlign::top);
|
||||
|
||||
[[deprecated("Back compatibility only, now it is stored EmbossProjection like depth")]]
|
||||
float emboss;
|
||||
|
||||
@ -73,14 +84,15 @@ 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 {
|
||||
return
|
||||
char_gap == other.char_gap &&
|
||||
line_gap == other.line_gap &&
|
||||
per_glyph == other.per_glyph &&
|
||||
align == other.align &&
|
||||
is_approx(size_in_mm, other.size_in_mm) &&
|
||||
is_approx(boldness, other.boldness) &&
|
||||
is_approx(skew, other.skew);
|
||||
@ -89,7 +101,7 @@ struct FontProp
|
||||
// undo / redo stack recovery
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
ar(size_in_mm);
|
||||
ar(size_in_mm, per_glyph, align.first, align.second);
|
||||
cereal::save(ar, char_gap);
|
||||
cereal::save(ar, line_gap);
|
||||
cereal::save(ar, boldness);
|
||||
@ -98,7 +110,7 @@ struct FontProp
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
ar(size_in_mm);
|
||||
ar(size_in_mm, per_glyph, align.first, align.second);
|
||||
cereal::load(ar, char_gap);
|
||||
cereal::load(ar, line_gap);
|
||||
cereal::load(ar, boldness);
|
||||
|
@ -163,6 +163,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
|
||||
|
@ -2601,6 +2601,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
update_object_list = true;
|
||||
}
|
||||
|
||||
// @Enrico suggest this solution to preven accessing pointer on caster without data
|
||||
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume);
|
||||
m_gizmos.update_data();
|
||||
m_gizmos.refresh_on_off_state();
|
||||
|
||||
@ -2660,7 +2662,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
}
|
||||
|
||||
// refresh volume raycasters for picking
|
||||
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume);
|
||||
for (size_t i = 0; i < m_volumes.volumes.size(); ++i) {
|
||||
const GLVolume* v = m_volumes.volumes[i];
|
||||
assert(v->mesh_raycaster != nullptr);
|
||||
|
@ -96,9 +96,18 @@ static const struct Limits
|
||||
/// </summary>
|
||||
/// <param name="text">Text to emboss</param>
|
||||
/// <param name="style_manager">Keep actual selected style</param>
|
||||
/// <param name="text_lines">Needed when transform per glyph</param>
|
||||
/// <param name="selection">Needed for transform per glyph</param>
|
||||
/// <param name="type">Define type of volume - side of surface(in / out)</param>
|
||||
/// <param name="cancel">Cancel for previous job</param>
|
||||
/// <returns>Base data for emboss text</returns>
|
||||
std::unique_ptr<DataBase> create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>> &cancel);
|
||||
std::unique_ptr<DataBase> create_emboss_data_base(
|
||||
const std::string& text,
|
||||
StyleManager& style_manager,
|
||||
TextLinesModel& text_lines,
|
||||
const Selection& selection,
|
||||
ModelVolumeType type,
|
||||
std::shared_ptr<std::atomic<bool>>& cancel);
|
||||
CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager &raycaster, ModelVolumeType volume_type);
|
||||
|
||||
/// <summary>
|
||||
@ -110,6 +119,23 @@ ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size
|
||||
/// <summary>
|
||||
/// Data for emboss job to create shape
|
||||
/// </summary>
|
||||
/// <param name="emboss_data">Define params of text</param>
|
||||
/// <param name="volume_type">Emboss / engrave</param>
|
||||
/// <param name="screen_coor">Mouse position which define position</param>
|
||||
/// <param name="gl_volume">Volume to find surface for create</param>
|
||||
/// <param name="raycaster">Ability to ray cast to model</param>
|
||||
/// <param name="text_lines">Per glyph transformation</param>
|
||||
/// <param name="style_manager">Line height need font file/param>
|
||||
/// <param name="canvas">Contain already used scene RayCasters</param>
|
||||
/// <returns>True when start creation, False when there is no hit surface by screen coor</returns>
|
||||
static bool start_create_volume_on_surface_job(DataBase &emboss_data,
|
||||
ModelVolumeType volume_type,
|
||||
const Vec2d &screen_coor,
|
||||
const GLVolume *gl_volume,
|
||||
RaycastManager &raycaster,
|
||||
TextLinesModel &text_lines,
|
||||
/*const */ StyleManager &style_manager,
|
||||
GLCanvas3D &canvas);
|
||||
struct TextDataBase : public DataBase
|
||||
{
|
||||
TextDataBase(DataBase &&parent, const FontFileWithCache &font_file,
|
||||
@ -144,6 +170,12 @@ enum class IconType : unsigned {
|
||||
lock_bold,
|
||||
unlock,
|
||||
unlock_bold,
|
||||
align_horizontal_left,
|
||||
align_horizontal_center,
|
||||
align_horizontal_right,
|
||||
align_vertical_top,
|
||||
align_vertical_center,
|
||||
align_vertical_bottom,
|
||||
// automatic calc of icon's count
|
||||
_count
|
||||
};
|
||||
@ -154,6 +186,14 @@ const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType typ
|
||||
// short call of Slic3r::GUI::button
|
||||
bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false);
|
||||
|
||||
/// <summary>
|
||||
/// Apply camera direction for emboss direction
|
||||
/// </summary>
|
||||
/// <param name="camera">Define view vector</param>
|
||||
/// <param name="canvas">Containe Selected Model to modify</param>
|
||||
/// <param name="keep_up">Keep same up vector</param>
|
||||
/// <returns>True when apply change otherwise false</returns>
|
||||
static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas, bool keep_up);
|
||||
struct FaceName
|
||||
{
|
||||
wxString wx_name;
|
||||
@ -257,9 +297,11 @@ struct GuiCfg
|
||||
std::string font;
|
||||
std::string height;
|
||||
std::string depth;
|
||||
std::string use_surface;
|
||||
|
||||
// advanced
|
||||
std::string use_surface;
|
||||
std::string per_glyph;
|
||||
std::string alignment;
|
||||
std::string char_gap;
|
||||
std::string line_gap;
|
||||
std::string boldness;
|
||||
@ -274,6 +316,10 @@ struct GuiCfg
|
||||
GuiCfg create_gui_configuration();
|
||||
|
||||
void draw_font_preview(FaceName &face, const std::string &text, Facenames &faces, const GuiCfg &cfg, bool is_visible);
|
||||
// for existing volume which is selected(could init different(to volume text) lines count when edit text)
|
||||
void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines=0);
|
||||
// before text volume is created
|
||||
void init_new_text_line(TextLinesModel &text_lines, const Transform3d& new_text_tr, const ModelObject& mo, /* const*/ StyleManager &style_manager);
|
||||
} // namespace priv
|
||||
|
||||
// use private definition
|
||||
@ -333,13 +379,20 @@ void GLGizmoEmboss::on_shortcut_key() {
|
||||
}
|
||||
}
|
||||
|
||||
namespace{
|
||||
// verify correct volume type for creation of text
|
||||
bool check(ModelVolumeType volume_type) {
|
||||
return volume_type == ModelVolumeType::MODEL_PART ||
|
||||
volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
|
||||
volume_type == ModelVolumeType::PARAMETER_MODIFIER;
|
||||
}
|
||||
}
|
||||
|
||||
bool GLGizmoEmboss::init_create(ModelVolumeType volume_type)
|
||||
{
|
||||
// check valid volume type
|
||||
if (volume_type != ModelVolumeType::MODEL_PART &&
|
||||
volume_type != ModelVolumeType::NEGATIVE_VOLUME &&
|
||||
volume_type != ModelVolumeType::PARAMETER_MODIFIER) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int)volume_type;
|
||||
if (!check(volume_type)){
|
||||
BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -406,6 +459,8 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event)
|
||||
angle_opt = angle;
|
||||
m_style_manager.get_style().angle = angle_opt;
|
||||
}
|
||||
|
||||
volume_transformation_changing();
|
||||
}
|
||||
return used;
|
||||
}
|
||||
@ -425,15 +480,9 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
|
||||
bool is_dragging = m_surface_drag.has_value();
|
||||
|
||||
// End with surface dragging?
|
||||
if (was_dragging && !is_dragging) {
|
||||
// Update surface by new position
|
||||
if (m_volume->emboss_shape->projection.use_surface)
|
||||
process();
|
||||
|
||||
// Show correct value of height & depth inside of inputs
|
||||
calculate_scale();
|
||||
}
|
||||
|
||||
if (was_dragging && !is_dragging)
|
||||
volume_transformation_changed();
|
||||
|
||||
// Start with dragging
|
||||
else if (!was_dragging && is_dragging) {
|
||||
// Cancel job to prevent interuption of dragging (duplicit result)
|
||||
@ -454,8 +503,10 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
|
||||
if (gl_volume == nullptr || !m_style_manager.is_active_font())
|
||||
return res;
|
||||
|
||||
m_style_manager.get_style().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
|
||||
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
|
||||
}
|
||||
|
||||
volume_transformation_changing();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -556,6 +607,36 @@ bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event)
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::volume_transformation_changing()
|
||||
{
|
||||
if (m_volume == nullptr || !m_volume->text_configuration.has_value()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
const FontProp &prop = m_volume->text_configuration->style.prop;
|
||||
if (prop.per_glyph)
|
||||
init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size());
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::volume_transformation_changed()
|
||||
{
|
||||
if (m_volume == nullptr || !m_volume->text_configuration.has_value()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const FontProp &prop = m_volume->text_configuration->style.prop;
|
||||
if (prop.per_glyph)
|
||||
init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, m_text_lines.get_lines().size());
|
||||
|
||||
// Update surface by new position
|
||||
if (prop.use_surface || prop.per_glyph)
|
||||
process();
|
||||
|
||||
// Show correct value of height & depth inside of inputs
|
||||
calculate_scale();
|
||||
}
|
||||
|
||||
bool GLGizmoEmboss::wants_enter_leave_snapshots() const { return true; }
|
||||
std::string GLGizmoEmboss::get_gizmo_entering_text() const { return _u8L("Enter emboss gizmo"); }
|
||||
std::string GLGizmoEmboss::get_gizmo_leaving_text() const { return _u8L("Leave emboss gizmo"); }
|
||||
@ -591,6 +672,18 @@ void GLGizmoEmboss::on_render() {
|
||||
// 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()) {
|
||||
const Transform3d& tr = gl_volume_ptr->world_matrix();
|
||||
const auto &fix = m_volume->text_configuration->fix_3mf_tr;
|
||||
if (fix.has_value())
|
||||
m_text_lines.render(tr * fix->inverse());
|
||||
else
|
||||
m_text_lines.render(tr);
|
||||
}
|
||||
|
||||
bool is_surface_dragging = m_surface_drag.has_value();
|
||||
bool is_parent_dragging = m_parent.is_mouse_dragging();
|
||||
@ -831,9 +924,7 @@ void GLGizmoEmboss::on_stop_dragging()
|
||||
|
||||
m_rotate_start_angle.reset();
|
||||
|
||||
// recalculate for surface cut
|
||||
if (m_style_manager.get_style().projection.use_surface)
|
||||
process();
|
||||
volume_transformation_changed();
|
||||
}
|
||||
void GLGizmoEmboss::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); }
|
||||
|
||||
@ -954,6 +1045,125 @@ std::optional<wxString> get_installed_face_name(const std::optional<std::string>
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
bool get_line_height_offset(/* const*/ StyleManager &style_manager, double &line_height_mm, double &line_offset_mm)
|
||||
{
|
||||
assert(style_manager.is_active_font());
|
||||
if (!style_manager.is_active_font())
|
||||
return false;
|
||||
const auto &ffc = style_manager.get_font_file_with_cache();
|
||||
assert(ffc.has_value());
|
||||
if (!ffc.has_value())
|
||||
return false;
|
||||
const auto &ff_ptr = ffc.font_file;
|
||||
assert(ff_ptr != nullptr);
|
||||
if (ff_ptr == nullptr)
|
||||
return false;
|
||||
const FontProp &fp = style_manager.get_font_prop();
|
||||
const FontFile &ff = *ff_ptr;
|
||||
|
||||
double third_ascent_shape_size = ff.infos[fp.collection_number.value_or(0)].ascent / 3.;
|
||||
int line_height_shape_size = get_line_height(ff, fp); // In shape size
|
||||
|
||||
double scale = get_shape_scale(fp, ff);
|
||||
line_offset_mm = third_ascent_shape_size * scale / SHAPE_SCALE;
|
||||
line_height_mm = line_height_shape_size * scale;
|
||||
|
||||
if (line_height_mm < 0)
|
||||
return false;
|
||||
|
||||
// fix for bad filled ascent in font file
|
||||
if (line_offset_mm <= 0)
|
||||
line_offset_mm = line_height_mm / 3;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines)
|
||||
{
|
||||
const GLVolume *gl_volume_ptr = selection.get_first_volume();
|
||||
if (gl_volume_ptr == nullptr)
|
||||
return;
|
||||
const GLVolume &gl_volume = *gl_volume_ptr;
|
||||
const ModelObjectPtrs &objects = selection.get_model()->objects;
|
||||
const ModelObject *mo_ptr = get_model_object(gl_volume, objects);
|
||||
if (mo_ptr == nullptr)
|
||||
return;
|
||||
const ModelObject &mo = *mo_ptr;
|
||||
|
||||
const ModelVolume *mv_ptr = get_model_volume(gl_volume, objects);
|
||||
if (mv_ptr == nullptr)
|
||||
return;
|
||||
const ModelVolume &mv = *mv_ptr;
|
||||
if (mv.is_the_only_one_part())
|
||||
return;
|
||||
|
||||
const std::optional<TextConfiguration> &tc_opt = mv.text_configuration;
|
||||
if (!tc_opt.has_value())
|
||||
return;
|
||||
const TextConfiguration &tc = *tc_opt;
|
||||
|
||||
// calculate count lines when not set
|
||||
if (count_lines == 0) {
|
||||
count_lines = get_count_lines(tc.text);
|
||||
if (count_lines == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare volumes to slice
|
||||
ModelVolumePtrs volumes;
|
||||
volumes.reserve(mo.volumes.size());
|
||||
for (ModelVolume *volume : mo.volumes) {
|
||||
// only part could be surface for volumes
|
||||
if (!volume->is_model_part())
|
||||
continue;
|
||||
|
||||
// is selected volume
|
||||
if (mv.id() == volume->id())
|
||||
continue;
|
||||
|
||||
volumes.push_back(volume);
|
||||
}
|
||||
|
||||
// For interactivity during drag over surface it must be from gl_volume not volume.
|
||||
Transform3d mv_trafo = gl_volume.get_volume_transformation().get_matrix();
|
||||
if (tc.fix_3mf_tr.has_value())
|
||||
mv_trafo = mv_trafo * (tc.fix_3mf_tr->inverse());
|
||||
FontProp::VerticalAlign align = style_manager.get_font_prop().align.second;
|
||||
double line_height_mm, line_offset_mm;
|
||||
if (!get_line_height_offset(style_manager, line_height_mm, line_offset_mm))
|
||||
return;
|
||||
|
||||
text_lines.init(mv_trafo, volumes, align, line_height_mm, line_offset_mm, count_lines);
|
||||
}
|
||||
|
||||
void init_new_text_line(TextLinesModel &text_lines, const Transform3d& new_text_tr, const ModelObject& mo, /* const*/ StyleManager &style_manager)
|
||||
{
|
||||
// prepare volumes to slice
|
||||
ModelVolumePtrs volumes;
|
||||
volumes.reserve(mo.volumes.size());
|
||||
for (ModelVolume *volume : mo.volumes) {
|
||||
// only part could be surface for volumes
|
||||
if (!volume->is_model_part())
|
||||
continue;
|
||||
volumes.push_back(volume);
|
||||
}
|
||||
|
||||
FontProp::VerticalAlign align = style_manager.get_font_prop().align.second;
|
||||
double line_height_mm, line_offset_mm;
|
||||
if (!get_line_height_offset(style_manager, line_height_mm, line_offset_mm))
|
||||
return;
|
||||
unsigned count_lines = 1;
|
||||
text_lines.init(new_text_tr, volumes, align, line_height_mm, line_offset_mm, count_lines);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::reinit_text_lines(unsigned count_lines) {
|
||||
init_text_lines(m_text_lines, m_parent.get_selection(), m_style_manager, count_lines);
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::set_volume_by_selection()
|
||||
{
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
@ -1062,6 +1272,14 @@ void GLGizmoEmboss::set_volume_by_selection()
|
||||
m_text = tc.text;
|
||||
m_volume = volume;
|
||||
m_volume_id = volume->id();
|
||||
|
||||
if (tc.style.prop.per_glyph)
|
||||
reinit_text_lines();
|
||||
|
||||
// Calculate current angle of up vector
|
||||
assert(m_style_manager.is_active_font());
|
||||
if (m_style_manager.is_active_font())
|
||||
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit);
|
||||
|
||||
// calculate scale for height and depth inside of scaled object instance
|
||||
calculate_scale();
|
||||
@ -1111,23 +1329,65 @@ bool GLGizmoEmboss::process()
|
||||
if (m_volume == nullptr) return false;
|
||||
|
||||
// without text there is nothing to emboss
|
||||
if (m_text.empty()) return false;
|
||||
if (priv::is_text_empty(m_text)) return false;
|
||||
|
||||
// exist loaded font file?
|
||||
if (!m_style_manager.is_active_font()) return false;
|
||||
|
||||
assert(m_volume->text_configuration.has_value());
|
||||
if (!m_volume->text_configuration.has_value()) return false;
|
||||
assert(m_volume->emboss_shape.has_value());
|
||||
if (!m_volume->emboss_shape.has_value()) return false;
|
||||
|
||||
DataUpdate data{create_emboss_data_base(m_text, m_style_manager, m_job_cancel), m_volume->id()};
|
||||
bool start = start_update_volume(std::move(data), *m_volume, m_parent.get_selection(), m_raycast_manager);
|
||||
if (start)
|
||||
// notification is removed befor object is changed by job
|
||||
remove_notification_not_valid_font();
|
||||
DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), m_volume->type(), m_job_cancel),
|
||||
m_volume->id()};
|
||||
std::unique_ptr<Job> job = nullptr;
|
||||
|
||||
return start;
|
||||
// check cutting from source mesh
|
||||
bool &use_surface = data.text_configuration.style.prop.use_surface;
|
||||
bool is_object = m_volume->get_object()->volumes.size() == 1;
|
||||
if (use_surface && is_object)
|
||||
use_surface = false;
|
||||
|
||||
assert(!data.text_configuration.style.prop.per_glyph ||
|
||||
get_count_lines(m_text) == m_text_lines.get_lines().size());
|
||||
|
||||
if (use_surface) {
|
||||
// Model to cut surface from.
|
||||
SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume);
|
||||
if (sources.empty())
|
||||
return false;
|
||||
|
||||
Transform3d text_tr = m_volume->get_matrix();
|
||||
auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr;
|
||||
if (fix_3mf.has_value())
|
||||
text_tr = text_tr * fix_3mf->inverse();
|
||||
|
||||
// when it is new applying of use surface than move origin onto surfaca
|
||||
if (!m_volume->text_configuration->style.prop.use_surface) {
|
||||
auto offset = calc_surface_offset(m_parent.get_selection(), m_raycast_manager);
|
||||
if (offset.has_value())
|
||||
text_tr *= Eigen::Translation<double, 3>(*offset);
|
||||
}
|
||||
|
||||
// check that there is not unexpected volume type
|
||||
bool is_valid_type = check(m_volume->type());
|
||||
assert(is_valid_type);
|
||||
if (!is_valid_type)
|
||||
return false;
|
||||
|
||||
UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, std::move(sources)}};
|
||||
job = std::make_unique<UpdateSurfaceVolumeJob>(std::move(surface_data));
|
||||
} else {
|
||||
job = std::make_unique<UpdateJob>(std::move(data));
|
||||
}
|
||||
|
||||
#ifndef EXECUTE_PROCESS_ON_MAIN_THREAD
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
queue_job(worker, std::move(job));
|
||||
#else
|
||||
// Run Job on main thread (blocking) - ONLY DEBUG
|
||||
priv::execute_job(std::move(job));
|
||||
#endif // EXECUTE_PROCESS_ON_MAIN_THREAD
|
||||
|
||||
// notification is removed befor object is changed by job
|
||||
remove_notification_not_valid_font();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@ -1311,6 +1571,8 @@ void GLGizmoEmboss::draw_text_input()
|
||||
append_warning(_u8L("Too tall, diminished font height inside text input."));
|
||||
if (imgui_size < StyleManager::min_imgui_font_size)
|
||||
append_warning(_u8L("Too small, enlarged font height inside text input."));
|
||||
if (prop.align.first == FontProp::HorizontalAlign::center || prop.align.first == FontProp::HorizontalAlign::right)
|
||||
append_warning(_u8L("Text doesn't show current horizontal alignment."));
|
||||
}
|
||||
|
||||
// flag for extend font ranges if neccessary
|
||||
@ -1322,6 +1584,12 @@ void GLGizmoEmboss::draw_text_input()
|
||||
ImVec2 input_size(m_gui_cfg->text_size.x, m_gui_cfg->text_size.y + extra_height);
|
||||
const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll;
|
||||
if (ImGui::InputTextMultiline("##Text", &m_text, input_size, flags)) {
|
||||
if (m_style_manager.get_font_prop().per_glyph) {
|
||||
unsigned count_lines = get_count_lines(m_text);
|
||||
if (count_lines != m_text_lines.get_lines().size())
|
||||
// Necesarry to initialize count by given number (differ from stored in volume at the moment)
|
||||
reinit_text_lines(count_lines);
|
||||
}
|
||||
process();
|
||||
range_text = create_range_text_prep();
|
||||
}
|
||||
@ -1457,6 +1725,8 @@ void GLGizmoEmboss::draw_font_list_line()
|
||||
|
||||
if (exist_change) {
|
||||
m_style_manager.clear_glyphs_cache();
|
||||
if (m_style_manager.get_font_prop().per_glyph)
|
||||
reinit_text_lines(m_text_lines.get_lines().size());
|
||||
process();
|
||||
}
|
||||
}
|
||||
@ -1680,13 +1950,11 @@ void GLGizmoEmboss::draw_model_type()
|
||||
Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction);
|
||||
m_volume->set_type(*new_type);
|
||||
|
||||
// Update volume position when switch from part or into part
|
||||
if (m_volume->emboss_shape->projection.use_surface) {
|
||||
// move inside
|
||||
bool is_volume_move_inside = (type == part);
|
||||
bool is_volume_move_outside = (*new_type == part);
|
||||
if (is_volume_move_inside || is_volume_move_outside) process();
|
||||
}
|
||||
bool is_volume_move_inside = (type == part);
|
||||
bool is_volume_move_outside = (*new_type == part);
|
||||
// Update volume position when switch (from part) or (into part)
|
||||
if ((is_volume_move_inside || is_volume_move_outside))
|
||||
process();
|
||||
|
||||
// inspiration in ObjectList::change_part_type()
|
||||
// how to view correct side panel with objects
|
||||
@ -2198,13 +2466,16 @@ bool GLGizmoEmboss::revertible(const std::string &name,
|
||||
|
||||
bool result = draw();
|
||||
// render revert changes button
|
||||
if (changed) {
|
||||
ImGui::SameLine(undo_offset);
|
||||
if (changed) {
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
float prev_x = window->DC.CursorPosPrevLine.x;
|
||||
ImGui::SameLine(undo_offset); // change cursor postion
|
||||
if (draw_button(m_icons, IconType::undo)) {
|
||||
value = *default_value;
|
||||
return true;
|
||||
} else if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", undo_tooltip.c_str());
|
||||
window->DC.CursorPosPrevLine.x = prev_x; // set back previous position
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -2285,7 +2556,7 @@ bool GLGizmoEmboss::rev_checkbox(const std::string &name,
|
||||
}
|
||||
|
||||
bool GLGizmoEmboss::set_height() {
|
||||
float &value = m_style_manager.get_style().prop.size_in_mm;
|
||||
float &value = m_style_manager.get_font_prop().size_in_mm;
|
||||
|
||||
// size can't be zero or negative
|
||||
apply(value, limits.size_in_mm);
|
||||
@ -2299,6 +2570,9 @@ bool GLGizmoEmboss::set_height() {
|
||||
if (is_approx(value, m_volume->text_configuration->style.prop.size_in_mm))
|
||||
return false;
|
||||
|
||||
if (m_style_manager.get_font_prop().per_glyph)
|
||||
reinit_text_lines(m_text_lines.get_lines().size());
|
||||
|
||||
#ifdef USE_PIXEL_SIZE_IN_WX_FONT
|
||||
// store font size into path serialization
|
||||
const wxFont &wx_font = m_style_manager.get_wx_font();
|
||||
@ -2313,7 +2587,7 @@ bool GLGizmoEmboss::set_height() {
|
||||
|
||||
void GLGizmoEmboss::draw_height(bool use_inch)
|
||||
{
|
||||
float &value = m_style_manager.get_style().prop.size_in_mm;
|
||||
float &value = m_style_manager.get_font_prop().size_in_mm;
|
||||
const EmbossStyle* stored_style = m_style_manager.get_stored_style();
|
||||
const float *stored = (stored_style != nullptr)? &stored_style->prop.size_in_mm : nullptr;
|
||||
const char *size_format = use_inch ? "%.2f in" : "%.1f mm";
|
||||
@ -2324,6 +2598,17 @@ void GLGizmoEmboss::draw_height(bool use_inch)
|
||||
process();
|
||||
}
|
||||
|
||||
bool GLGizmoEmboss::set_depth()
|
||||
{
|
||||
float &value = m_style_manager.get_font_prop().emboss;
|
||||
|
||||
// size can't be zero or negative
|
||||
priv::Limits::apply(value, priv::limits.emboss);
|
||||
|
||||
// only different value need process
|
||||
return !is_approx(value, m_volume->text_configuration->style.prop.emboss);
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::draw_depth(bool use_inch)
|
||||
{
|
||||
double &value = m_style_manager.get_style().projection.depth;
|
||||
@ -2431,8 +2716,7 @@ void GLGizmoEmboss::draw_advanced()
|
||||
", unitPerEm=" + std::to_string(font_info.unit_per_em) +
|
||||
", cache(" + std::to_string(cache_size) + " glyphs)";
|
||||
if (font_file->infos.size() > 1) {
|
||||
unsigned int collection = current_prop.collection_number.has_value() ?
|
||||
*current_prop.collection_number : 0;
|
||||
unsigned int collection = current_prop.collection_number.value_or(0);
|
||||
ff_property += ", collect=" + std::to_string(collection+1) + "/" + std::to_string(font_file->infos.size());
|
||||
}
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property);
|
||||
@ -2444,10 +2728,10 @@ void GLGizmoEmboss::draw_advanced()
|
||||
const StyleManager::Style *stored_style = nullptr;
|
||||
if (m_style_manager.exist_stored_style())
|
||||
stored_style = m_style_manager.get_stored_style();
|
||||
|
||||
bool can_use_surface = (m_volume == nullptr)? false :
|
||||
(m_volume->emboss_shape->projection.use_surface)? true : // already used surface must have option to uncheck
|
||||
!m_volume->is_the_only_one_part();
|
||||
|
||||
bool is_the_only_one_part = m_volume->is_the_only_one_part();
|
||||
bool can_use_surface = (m_volume->emboss_shape->projection.use_surface)? true : // already used surface must have option to uncheck
|
||||
!is_the_only_one_part;
|
||||
m_imgui->disabled_begin(!can_use_surface);
|
||||
const bool *def_use_surface = stored_style ?
|
||||
&stored_style->projection.use_surface : nullptr;
|
||||
@ -2460,6 +2744,77 @@ void GLGizmoEmboss::draw_advanced()
|
||||
process();
|
||||
}
|
||||
m_imgui->disabled_end(); // !can_use_surface
|
||||
|
||||
bool &per_glyph = font_prop.per_glyph;
|
||||
bool can_use_per_glyph = (per_glyph) ? true : // already used surface must have option to uncheck
|
||||
!is_the_only_one_part;
|
||||
m_imgui->disabled_begin(!can_use_per_glyph);
|
||||
const bool *def_per_glyph = stored_style ? &stored_style->prop.per_glyph : nullptr;
|
||||
if (rev_checkbox(tr.per_glyph, per_glyph, def_per_glyph,
|
||||
_u8L("Revert Transformation per glyph."))) {
|
||||
if (per_glyph && !m_text_lines.is_init())
|
||||
reinit_text_lines();
|
||||
process();
|
||||
} else if (ImGui::IsItemHovered()) {
|
||||
if (per_glyph) {
|
||||
ImGui::SetTooltip("%s", _u8L("Set global orientation for whole text.").c_str());
|
||||
} else {
|
||||
ImGui::SetTooltip("%s", _u8L("Set position and orientation per Glyph.").c_str());
|
||||
if (!m_text_lines.is_init())
|
||||
reinit_text_lines();
|
||||
}
|
||||
} else if (!per_glyph && m_text_lines.is_init())
|
||||
m_text_lines.reset();
|
||||
m_imgui->disabled_end(); // !can_use_per_glyph
|
||||
|
||||
m_imgui->disabled_begin(!per_glyph);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
if (m_imgui->slider_float("##base_line_y_offset", &m_text_lines.offset, -10.f, 10.f, "%f mm")) {
|
||||
reinit_text_lines(m_text_lines.get_lines().size());
|
||||
process();
|
||||
} else if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("TEST PURPOSE ONLY\nMove base line (up/down) for allign letters");
|
||||
m_imgui->disabled_end(); // !per_glyph
|
||||
|
||||
auto draw_align = [&align = font_prop.align, gui_cfg = m_gui_cfg, &icons = m_icons]() {
|
||||
bool is_change = false;
|
||||
ImGui::SameLine(gui_cfg->advanced_input_offset);
|
||||
if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set left alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set horizont center alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set right alignment").c_str());
|
||||
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set top alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set vertical center alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set bottom alignment").c_str());
|
||||
return is_change;
|
||||
};
|
||||
const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr;
|
||||
float undo_offset = ImGui::GetStyle().FramePadding.x;
|
||||
if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), undo_offset, draw_align)) {
|
||||
if (font_prop.per_glyph)
|
||||
reinit_text_lines(m_text_lines.get_lines().size());
|
||||
// TODO: move with text in finalize to not change position
|
||||
process();
|
||||
}
|
||||
|
||||
// TRN EmbossGizmo: font units
|
||||
std::string units = _u8L("points");
|
||||
std::string units_fmt = "%.0f " + units;
|
||||
@ -2496,6 +2851,8 @@ void GLGizmoEmboss::draw_advanced()
|
||||
!volume_line_gap.has_value() || volume_line_gap != current_prop.line_gap) {
|
||||
// line gap is planed to be stored inside of imgui font atlas
|
||||
m_style_manager.clear_imgui_font();
|
||||
if (font_prop.per_glyph)
|
||||
reinit_text_lines(m_text_lines.get_lines().size());
|
||||
exist_change = true;
|
||||
}
|
||||
}
|
||||
@ -2558,9 +2915,14 @@ void GLGizmoEmboss::draw_advanced()
|
||||
min_distance, max_distance, "%.2f mm", move_tooltip)) is_moved = true;
|
||||
}
|
||||
|
||||
if (is_moved)
|
||||
do_local_z_move(m_parent, distance.value_or(.0f) - prev_distance);
|
||||
m_imgui->disabled_end(); // allowe_surface_distance
|
||||
if (is_moved){
|
||||
if (font_prop.per_glyph){
|
||||
process();
|
||||
} else {
|
||||
do_local_z_move(m_parent, distance.value_or(.0f) - prev_distance);
|
||||
}
|
||||
}
|
||||
m_imgui->disabled_end(); // allowe_surface_distance
|
||||
|
||||
// slider for Clock-wise angle in degress
|
||||
// stored angle is optional CCW and in radians
|
||||
@ -2590,8 +2952,11 @@ void GLGizmoEmboss::draw_advanced()
|
||||
if (m_style_manager.is_active_font() && gl_volume != nullptr)
|
||||
m_style_manager.get_style().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
|
||||
|
||||
if (font_prop.per_glyph)
|
||||
reinit_text_lines(m_text_lines.get_lines().size());
|
||||
|
||||
// recalculate for surface cut
|
||||
if (use_surface)
|
||||
if (use_surface || font_prop.per_glyph)
|
||||
process();
|
||||
}
|
||||
|
||||
@ -2637,17 +3002,23 @@ void GLGizmoEmboss::draw_advanced()
|
||||
|
||||
if (exist_change) {
|
||||
m_style_manager.clear_glyphs_cache();
|
||||
if (m_style_manager.get_font_prop().per_glyph)
|
||||
reinit_text_lines();
|
||||
else
|
||||
m_text_lines.reset();
|
||||
process();
|
||||
}
|
||||
|
||||
if (ImGui::Button(_u8L("Set text to face camera").c_str())) {
|
||||
assert(get_selected_volume(m_parent.get_selection()) == m_volume);
|
||||
const Camera &cam = wxGetApp().plater()->get_camera();
|
||||
if (face_selected_volume_to_camera(cam, m_parent) && use_surface)
|
||||
if (face_selected_volume_to_camera(cam, m_parent) &&
|
||||
(use_surface || prop.per_glyph))
|
||||
process();
|
||||
} else if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", _u8L("Orient the text towards the camera.").c_str());
|
||||
}
|
||||
|
||||
#ifdef ALLOW_DEBUG_MODE
|
||||
ImGui::Text("family = %s", (current_prop.family.has_value() ?
|
||||
current_prop.family->c_str() :
|
||||
@ -2744,7 +3115,7 @@ bool GLGizmoEmboss::choose_font_by_wxdialog()
|
||||
const auto&ff = m_style_manager.get_font_file_with_cache();
|
||||
if (WxFontUtils::is_italic(wx_font) &&
|
||||
!Emboss::is_italic(*ff.font_file, font_collection)) {
|
||||
m_style_manager.get_style().prop.skew = 0.2;
|
||||
m_style_manager.get_font_prop().skew = 0.2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -2856,7 +3227,13 @@ void GLGizmoEmboss::init_icons()
|
||||
"lock_closed.svg", // lock,
|
||||
"lock_closed_f.svg",// lock_bold,
|
||||
"lock_open.svg", // unlock,
|
||||
"lock_open_f.svg" // unlock_bold,
|
||||
"lock_open_f.svg", // unlock_bold,
|
||||
"align_horizontal_left.svg",
|
||||
"align_horizontal_center.svg",
|
||||
"align_horizontal_right.svg",
|
||||
"align_vertical_top.svg",
|
||||
"align_vertical_center.svg",
|
||||
"align_vertical_bottom.svg"
|
||||
};
|
||||
assert(filenames.size() == static_cast<size_t>(IconType::_count));
|
||||
std::string path = resources_dir() + "/icons/";
|
||||
@ -2966,7 +3343,12 @@ void TextDataBase::write(ModelVolume &volume) const
|
||||
fp.emboss = static_cast<float>(ep.depth);
|
||||
}
|
||||
|
||||
std::unique_ptr<DataBase> create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>>& cancel)
|
||||
std::unique_ptr<DataBase> create_emboss_data_base(const std::string &text,
|
||||
StyleManager &style_manager,
|
||||
TextLinesModel &text_lines,
|
||||
const Selection &selection,
|
||||
ModelVolumeType type,
|
||||
std::shared_ptr<std::atomic<bool>> &cancel)
|
||||
{
|
||||
// create volume_name
|
||||
std::string volume_name = text; // copy
|
||||
@ -2988,6 +3370,14 @@ std::unique_ptr<DataBase> create_emboss_data_base(const std::string &text, Style
|
||||
assert(style_manager.get_wx_font().IsOk());
|
||||
assert(style.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0);
|
||||
|
||||
if (es.prop.per_glyph) {
|
||||
if (!text_lines.is_init())
|
||||
init_text_lines(text_lines, selection, style_manager);
|
||||
} else
|
||||
text_lines.reset();
|
||||
|
||||
bool is_outside = (type == ModelVolumeType::MODEL_PART);
|
||||
|
||||
// Cancel previous Job, when it is in process
|
||||
// worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs
|
||||
// Cancel only EmbossUpdateJob no others
|
||||
@ -2998,7 +3388,7 @@ std::unique_ptr<DataBase> create_emboss_data_base(const std::string &text, Style
|
||||
DataBase base(volume_name, cancel);
|
||||
FontFileWithCache &font = style_manager.get_font_file_with_cache();
|
||||
TextConfiguration tc{static_cast<EmbossStyle>(style), text};
|
||||
return std::make_unique<TextDataBase>(std::move(base), font, std::move(tc), style.projection);
|
||||
return std::make_unique<TextDataBase>(std::move(base), font, std::move(tc), style.projection, is_outside, text_lines.get_lines());
|
||||
}
|
||||
|
||||
CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager& raycaster, ModelVolumeType volume_type)
|
||||
@ -3368,6 +3758,51 @@ GuiCfg create_gui_configuration()
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool apply_camera_dir(const Camera& camera, GLCanvas3D& canvas, bool keep_up) {
|
||||
const Vec3d& cam_dir = camera.get_dir_forward();
|
||||
|
||||
Selection& sel = canvas.get_selection();
|
||||
if (sel.is_empty()) return false;
|
||||
|
||||
// camera direction transformed into volume coordinate system
|
||||
Transform3d to_world = world_matrix_fixed(sel);
|
||||
Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir;
|
||||
cam_dir_tr.normalize();
|
||||
|
||||
Vec3d emboss_dir(0., 0., -1.);
|
||||
|
||||
// check wether cam_dir is already used
|
||||
if (is_approx(cam_dir_tr, emboss_dir)) return false;
|
||||
|
||||
assert(sel.get_volume_idxs().size() == 1);
|
||||
GLVolume* gl_volume = sel.get_volume(*sel.get_volume_idxs().begin());
|
||||
|
||||
Transform3d vol_rot;
|
||||
Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix();
|
||||
// check whether cam_dir is opposit to emboss dir
|
||||
if (is_approx(cam_dir_tr, -emboss_dir)) {
|
||||
// rotate 180 DEG by y
|
||||
vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.));
|
||||
}
|
||||
else {
|
||||
// calc params for rotation
|
||||
Vec3d axe = emboss_dir.cross(cam_dir_tr);
|
||||
axe.normalize();
|
||||
double angle = std::acos(emboss_dir.dot(cam_dir_tr));
|
||||
vol_rot = Eigen::AngleAxis(angle, axe);
|
||||
}
|
||||
|
||||
Vec3d offset = vol_tr * Vec3d::Zero();
|
||||
Vec3d offset_inv = vol_rot.inverse() * offset;
|
||||
Transform3d res = vol_tr *
|
||||
Eigen::Translation<double, 3>(-offset) *
|
||||
vol_rot *
|
||||
Eigen::Translation<double, 3>(offset_inv);
|
||||
//Transform3d res = vol_tr * vol_rot;
|
||||
gl_volume->set_volume_transformation(Geometry::Transformation(res));
|
||||
get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res);
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// any existing icon filename to not influence GUI
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "GLGizmoRotate.hpp"
|
||||
#include "slic3r/GUI/IconManager.hpp"
|
||||
#include "slic3r/GUI/SurfaceDrag.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp" // TODO: not needed
|
||||
#include "slic3r/GUI/TextLines.hpp"
|
||||
#include "slic3r/Utils/RaycastManager.hpp"
|
||||
#include "slic3r/Utils/EmbossStyleManager.hpp"
|
||||
|
||||
@ -80,6 +82,9 @@ protected:
|
||||
std::string get_action_snapshot_name() const override;
|
||||
|
||||
private:
|
||||
void volume_transformation_changing();
|
||||
void volume_transformation_changed();
|
||||
|
||||
static EmbossStyles create_default_styles();
|
||||
// localized default text
|
||||
bool init_create(ModelVolumeType volume_type);
|
||||
@ -189,6 +194,10 @@ private:
|
||||
// cancel for previous update of volume to cancel finalize part
|
||||
std::shared_ptr<std::atomic<bool>> m_job_cancel = nullptr;
|
||||
|
||||
// Keep information about curvature of text line around surface
|
||||
TextLinesModel m_text_lines;
|
||||
void reinit_text_lines(unsigned count_lines=0);
|
||||
|
||||
// Rotation gizmo
|
||||
GLGizmoRotate m_rotate_gizmo;
|
||||
// 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];
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "EmbossJob.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/Format/OBJ.hpp> // load_obj for default mesh
|
||||
@ -55,6 +56,9 @@ struct DataCreateVolume
|
||||
GLGizmosManager::EType gizmo;
|
||||
};
|
||||
|
||||
// Offset of clossed side to model
|
||||
constexpr float SAFE_SURFACE_OFFSET = 0.015f; // [in mm]
|
||||
|
||||
/// <summary>
|
||||
/// Create new TextVolume on the surface of ModelObject
|
||||
/// Should not be stopped
|
||||
@ -155,6 +159,8 @@ bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surfac
|
||||
bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||
bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||
|
||||
template<typename Fnc> static ExPolygons create_shape(DataBase &input, Fnc was_canceled);
|
||||
template<typename Fnc> static std::vector<ExPolygons> create_shapes(DataBase &input, Fnc was_canceled);
|
||||
|
||||
// create sure that emboss object is bigger than source object [in mm]
|
||||
constexpr float safe_extension = 1.0f;
|
||||
@ -267,13 +273,10 @@ void Slic3r::GUI::Emboss::DataBase::write(ModelVolume &volume) const{
|
||||
CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input): m_input(std::move(input)){ assert(check(m_input, true)); }
|
||||
|
||||
void CreateVolumeJob::process(Ctl &ctl) {
|
||||
if (!check(m_input))
|
||||
throw JobException("Bad input data for EmbossCreateVolumeJob.");
|
||||
|
||||
m_result = create_mesh(*m_input.base, was_canceled(ctl, *m_input.base), ctl);
|
||||
// center result
|
||||
Vec3f c = m_result.bounding_box().center().cast<float>();
|
||||
if (!c.isApprox(Vec3f::Zero())) m_result.translate(-c);
|
||||
if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
|
||||
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
|
||||
m_result = priv::create_mesh(m_input, was_canceled, ctl);
|
||||
//m_result = create_mesh(*m_input.base, was_canceled(ctl, *m_input.base), ctl); // svg
|
||||
}
|
||||
void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||
if (!::finalize(canceled, eptr, *m_input.base))
|
||||
@ -390,11 +393,7 @@ void UpdateJob::process(Ctl &ctl)
|
||||
m_result = ::try_create_mesh(*m_input.base, was_canceled);
|
||||
if (was_canceled()) return;
|
||||
if (m_result.its.empty())
|
||||
throw JobException("Created text volume is empty. Change text or font.");
|
||||
|
||||
// center triangle mesh
|
||||
Vec3d shift = m_result.bounding_box().center();
|
||||
m_result.translate(-shift.cast<float>());
|
||||
throw priv::JobException("Created text volume is empty. Change text or font.");
|
||||
}
|
||||
|
||||
void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
@ -695,9 +694,14 @@ bool check(const DataBase &input, bool check_fontfile, bool use_surface)
|
||||
// res &= !input.text_configuration.text.empty();
|
||||
assert(!input.volume_name.empty());
|
||||
res &= !input.volume_name.empty();
|
||||
// assert(input.text_configuration.style.prop.use_surface == use_surface);
|
||||
// res &= input.text_configuration.style.prop.use_surface == use_surface;
|
||||
return res;
|
||||
const FontProp& prop = input.text_configuration.style.prop;
|
||||
assert(prop.per_glyph == !input.text_lines.empty());
|
||||
res &= prop.per_glyph == !input.text_lines.empty();
|
||||
if (prop.per_glyph) {
|
||||
assert(get_count_lines(input.text_configuration.text) == input.text_lines.size());
|
||||
res &= get_count_lines(input.text_configuration.text) == input.text_lines.size();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool check(GLGizmosManager::EType gizmo)
|
||||
@ -786,20 +790,218 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread)
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename Fnc> TriangleMesh try_create_mesh(DataBase &base, const Fnc &was_canceled)
|
||||
template<typename Fnc>
|
||||
ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) {
|
||||
FontFileWithCache &font = input.font_file;
|
||||
const TextConfiguration &tc = input.text_configuration;
|
||||
const char *text = tc.text.c_str();
|
||||
const FontProp &prop = tc.style.prop;
|
||||
assert(!prop.per_glyph);
|
||||
assert(font.has_value());
|
||||
if (!font.has_value())
|
||||
return {};
|
||||
|
||||
ExPolygons shapes = text2shapes(font, text, prop, was_canceled);
|
||||
if (shapes.empty())
|
||||
return {};
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
template<typename Fnc>
|
||||
std::vector<ExPolygons> priv::create_shapes(DataBase &input, Fnc was_canceled) {
|
||||
FontFileWithCache &font = input.font_file;
|
||||
const TextConfiguration &tc = input.text_configuration;
|
||||
const char *text = tc.text.c_str();
|
||||
const FontProp &prop = tc.style.prop;
|
||||
assert(prop.per_glyph);
|
||||
assert(font.has_value());
|
||||
if (!font.has_value())
|
||||
return {};
|
||||
|
||||
std::wstring ws = boost::nowide::widen(text);
|
||||
std::vector<ExPolygons> shapes = text2vshapes(font, ws, prop, was_canceled);
|
||||
if (shapes.empty())
|
||||
return {};
|
||||
|
||||
if (was_canceled())
|
||||
return {};
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
//#define STORE_SAMPLING
|
||||
#ifdef STORE_SAMPLING
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#endif // STORE_SAMPLING
|
||||
namespace {
|
||||
|
||||
std::vector<BoundingBoxes> create_line_bounds(const std::vector<ExPolygons> &shapes, const std::wstring& text, size_t count_lines = 0)
|
||||
{
|
||||
const EmbossShape &shape = base.create_shape();
|
||||
assert(text.size() == shapes.size());
|
||||
if (count_lines == 0)
|
||||
count_lines = get_count_lines(text);
|
||||
assert(count_lines == get_count_lines(text));
|
||||
|
||||
std::vector<BoundingBoxes> result(count_lines);
|
||||
size_t text_line_index = 0;
|
||||
// s_i .. shape index
|
||||
for (size_t s_i = 0; s_i < shapes.size(); ++s_i) {
|
||||
const ExPolygons &shape = shapes[s_i];
|
||||
BoundingBox bb;
|
||||
if (!shape.empty()) {
|
||||
bb = get_extents(shape);
|
||||
}
|
||||
BoundingBoxes &line_bbs = result[text_line_index];
|
||||
line_bbs.push_back(bb);
|
||||
if (text[s_i] == '\n'){
|
||||
// skip enters on beginig and tail
|
||||
++text_line_index;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc was_canceled)
|
||||
{
|
||||
// method use square of coord stored into int64_t
|
||||
static_assert(std::is_same<Point::coord_type, int32_t>());
|
||||
|
||||
std::vector<ExPolygons> shapes = priv::create_shapes(input, was_canceled);
|
||||
if (shapes.empty())
|
||||
return {};
|
||||
|
||||
// Precalculate bounding boxes of glyphs
|
||||
// Separate lines of text to vector of Bounds
|
||||
const TextConfiguration &tc = input.text_configuration;
|
||||
std::wstring ws = boost::nowide::widen(tc.text.c_str());
|
||||
assert(get_count_lines(ws) == input.text_lines.size());
|
||||
size_t count_lines = input.text_lines.size();
|
||||
std::vector<BoundingBoxes> bbs = create_line_bounds(shapes, ws, count_lines);
|
||||
|
||||
const FontProp &prop = tc.style.prop;
|
||||
FontFileWithCache &font = input.font_file;
|
||||
double shape_scale = get_shape_scale(prop, *font.font_file);
|
||||
double projec_scale = shape_scale / SHAPE_SCALE;
|
||||
double depth = prop.emboss / projec_scale;
|
||||
auto scale_tr = Eigen::Scaling(projec_scale);
|
||||
|
||||
// half of font em size for direction of letter emboss
|
||||
double em_2_mm = prop.size_in_mm / 2.;
|
||||
int32_t em_2_polygon = static_cast<int32_t>(std::round(scale_(em_2_mm)));
|
||||
|
||||
size_t s_i_offset = 0; // shape index offset(for next lines)
|
||||
indexed_triangle_set result;
|
||||
for (size_t text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) {
|
||||
const BoundingBoxes &line_bbs = bbs[text_line_index];
|
||||
const TextLine &line = input.text_lines[text_line_index];
|
||||
PolygonPoints samples = sample_slice(line, line_bbs, shape_scale);
|
||||
std::vector<double> angles = calculate_angles(em_2_polygon, samples, line.polygon);
|
||||
|
||||
for (size_t i = 0; i < line_bbs.size(); ++i) {
|
||||
const BoundingBox &letter_bb = line_bbs[i];
|
||||
if (!letter_bb.defined)
|
||||
continue;
|
||||
|
||||
Vec2d to_zero_vec = letter_bb.center().cast<double>() * shape_scale; // [in mm]
|
||||
float surface_offset = input.is_outside ? -priv::SAFE_SURFACE_OFFSET : (-prop.emboss + priv::SAFE_SURFACE_OFFSET);
|
||||
if (prop.distance.has_value())
|
||||
surface_offset += *prop.distance;
|
||||
|
||||
Eigen::Translation<double, 3> to_zero(-to_zero_vec.x(), 0., static_cast<double>(surface_offset));
|
||||
|
||||
const double &angle = angles[i];
|
||||
Eigen::AngleAxisd rotate(angle + M_PI_2, Vec3d::UnitY());
|
||||
|
||||
const PolygonPoint &sample = samples[i];
|
||||
Vec2d offset_vec = unscale(sample.point); // [in mm]
|
||||
Eigen::Translation<double, 3> offset_tr(offset_vec.x(), 0., -offset_vec.y());
|
||||
Transform3d tr = offset_tr * rotate * to_zero * scale_tr;
|
||||
|
||||
const ExPolygons &letter_shape = shapes[s_i_offset + i];
|
||||
assert(get_extents(letter_shape) == letter_bb);
|
||||
auto projectZ = std::make_unique<ProjectZ>(depth);
|
||||
ProjectTransform project(std::move(projectZ), tr);
|
||||
indexed_triangle_set glyph_its = polygons2model(letter_shape, project);
|
||||
its_merge(result, std::move(glyph_its));
|
||||
|
||||
if (((s_i_offset + i) % 15) && was_canceled())
|
||||
return {};
|
||||
}
|
||||
s_i_offset += line_bbs.size();
|
||||
|
||||
#ifdef STORE_SAMPLING
|
||||
{ // Debug store polygon
|
||||
//std::string stl_filepath = "C:/data/temp/line" + std::to_string(text_line_index) + "_model.stl";
|
||||
//bool suc = its_write_stl_ascii(stl_filepath.c_str(), "label", result);
|
||||
|
||||
BoundingBox bbox = get_extents(line.polygon);
|
||||
std::string file_path = "C:/data/temp/line" + std::to_string(text_line_index) + "_letter_position.svg";
|
||||
SVG svg(file_path, bbox);
|
||||
svg.draw(line.polygon);
|
||||
int32_t radius = bbox.size().x() / 300;
|
||||
for (size_t i = 0; i < samples.size(); i++) {
|
||||
const PolygonPoint &pp = samples[i];
|
||||
const Point& p = pp.point;
|
||||
svg.draw(p, "green", radius);
|
||||
std::string label = std::string(" ")+tc.text[i];
|
||||
svg.draw_text(p, label.c_str(), "black");
|
||||
|
||||
double a = angles[i];
|
||||
double length = 3.0 * radius;
|
||||
Point n(length * std::cos(a), length * std::sin(a));
|
||||
svg.draw(Slic3r::Line(p - n, p + n), "Lime");
|
||||
}
|
||||
}
|
||||
#endif // STORE_SAMPLING
|
||||
}
|
||||
return TriangleMesh(std::move(result));
|
||||
}
|
||||
|
||||
// svg
|
||||
template<typename Fnc> TriangleMesh try_create_mesh(DataBase& base, const Fnc& was_canceled)
|
||||
{
|
||||
const EmbossShape& shape = base.create_shape();
|
||||
if (shape.shapes.empty())
|
||||
return {};
|
||||
double depth = shape.projection.depth / shape.scale;
|
||||
double depth = shape.projection.depth / shape.scale;
|
||||
auto projectZ = std::make_unique<ProjectZ>(depth);
|
||||
ProjectScale project(std::move(projectZ), shape.scale);
|
||||
if (was_canceled())
|
||||
return {};
|
||||
return TriangleMesh(polygons2model(shape.shapes, project));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template<typename Fnc> TriangleMesh create_mesh(DataBase &input, const Fnc &was_canceled, Job::Ctl &ctl)
|
||||
|
||||
template<typename Fnc>
|
||||
TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled)
|
||||
{
|
||||
if (!input.text_lines.empty()) {
|
||||
TriangleMesh tm = create_mesh_per_glyph(input, was_canceled);
|
||||
if (was_canceled()) return {};
|
||||
if (!tm.empty()) return tm;
|
||||
}
|
||||
|
||||
ExPolygons shapes = priv::create_shape(input, was_canceled);
|
||||
if (shapes.empty()) return {};
|
||||
if (was_canceled()) return {};
|
||||
|
||||
const FontProp &prop = input.text_configuration.style.prop;
|
||||
const FontFile &ff = *input.font_file.font_file;
|
||||
// NOTE: SHAPE_SCALE is applied in ProjectZ
|
||||
double scale = get_shape_scale(prop, ff) / SHAPE_SCALE;
|
||||
double depth = prop.emboss / scale;
|
||||
auto projectZ = std::make_unique<ProjectZ>(depth);
|
||||
float offset = input.is_outside ? -SAFE_SURFACE_OFFSET : (SAFE_SURFACE_OFFSET - prop.emboss);
|
||||
Transform3d tr = Eigen::Translation<double, 3>(0., 0.,static_cast<double>(offset)) * Eigen::Scaling(scale);
|
||||
ProjectTransform project(std::move(projectZ), tr);
|
||||
if (was_canceled()) return {};
|
||||
return TriangleMesh(polygons2model(shapes, project));
|
||||
}
|
||||
|
||||
template<typename Fnc>
|
||||
TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl)
|
||||
{
|
||||
// It is neccessary to create some shape
|
||||
// Emboss text window is opened by creation new emboss text object
|
||||
@ -1065,46 +1267,35 @@ OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const
|
||||
|
||||
OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut)
|
||||
{
|
||||
// Offset of clossed side to model
|
||||
const float surface_offset = 0.015f; // [in mm]
|
||||
float front_move = is_outside ? emboss : surface_offset,
|
||||
back_move = -(is_outside ? surface_offset : emboss);
|
||||
its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move)));
|
||||
float
|
||||
front_move = (is_outside) ? emboss : SAFE_SURFACE_OFFSET,
|
||||
back_move = -((is_outside) ? SAFE_SURFACE_OFFSET : emboss);
|
||||
its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move)));
|
||||
Vec3d from_front_to_back(0., 0., back_move - front_move);
|
||||
return OrthoProject3d(from_front_to_back);
|
||||
}
|
||||
|
||||
// input can't be const - cache of font
|
||||
template<typename Fnc> TriangleMesh cut_surface(DataBase &base, const SurfaceVolumeData &input2, const Fnc &was_canceled)
|
||||
{
|
||||
EmbossShape &emboss_shape = base.create_shape();
|
||||
ExPolygons &shapes = emboss_shape.shapes;
|
||||
if (shapes.empty())
|
||||
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
|
||||
namespace {
|
||||
|
||||
if (was_canceled())
|
||||
return {};
|
||||
indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d& tr,const SurfaceVolumeData::ModelSources &sources, DataBase& input, std::function<bool()> was_canceled) {
|
||||
assert(!sources.empty());
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
const FontFile &ff = *input.font_file.font_file;
|
||||
const FontProp &fp = input.text_configuration.style.prop;
|
||||
double shape_scale = get_shape_scale(fp, ff);
|
||||
|
||||
// Define alignment of text - left, right, center, top bottom, ....
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
Point projection_center = bb.center();
|
||||
for (ExPolygon &shape : shapes)
|
||||
shape.translate(-projection_center);
|
||||
bb.translate(-projection_center);
|
||||
|
||||
const SurfaceVolumeData::ModelSources &sources = input2.sources;
|
||||
const SurfaceVolumeData::ModelSource *biggest = &sources.front();
|
||||
const SurfaceVolumeData::ModelSource *biggest = &sources.front();
|
||||
|
||||
size_t biggest_count = 0;
|
||||
// convert index from (s)ources to (i)ndexed (t)riangle (s)ets
|
||||
std::vector<size_t> s_to_itss(sources.size(), std::numeric_limits<size_t>::max());
|
||||
std::vector<indexed_triangle_set> itss;
|
||||
std::vector<size_t> s_to_itss(sources.size(), std::numeric_limits<size_t>::max());
|
||||
std::vector<indexed_triangle_set> itss;
|
||||
itss.reserve(sources.size());
|
||||
for (const SurfaceVolumeData::ModelSource &s : sources) {
|
||||
Transform3d mesh_tr_inv = s.tr.inverse();
|
||||
Transform3d cut_projection_tr = mesh_tr_inv * input2.transform;
|
||||
Transform3d mesh_tr_inv = s.tr.inverse();
|
||||
Transform3d cut_projection_tr = mesh_tr_inv * tr;
|
||||
std::pair<float, float> z_range{0., 1.};
|
||||
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_shape.scale, z_range);
|
||||
OrthoProject cut_projection = priv::create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
|
||||
// copy only part of source model
|
||||
indexed_triangle_set its = its_cut_AoI(s.mesh->its, bb, cut_projection);
|
||||
if (its.indices.empty())
|
||||
@ -1119,10 +1310,10 @@ template<typename Fnc> TriangleMesh cut_surface(DataBase &base, const SurfaceVol
|
||||
itss.emplace_back(std::move(its));
|
||||
}
|
||||
if (itss.empty())
|
||||
throw JobException(_u8L("There is no volume in projection direction.").c_str());
|
||||
return {};
|
||||
|
||||
Transform3d tr_inv = biggest->tr.inverse();
|
||||
Transform3d cut_projection_tr = tr_inv * input2.transform;
|
||||
Transform3d tr_inv = biggest->tr.inverse();
|
||||
Transform3d cut_projection_tr = tr_inv * tr;
|
||||
|
||||
size_t itss_index = s_to_itss[biggest - &sources.front()];
|
||||
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
|
||||
@ -1145,22 +1336,27 @@ template<typename Fnc> TriangleMesh cut_surface(DataBase &base, const SurfaceVol
|
||||
Transform3d emboss_tr = cut_projection_tr.inverse();
|
||||
BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr);
|
||||
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
|
||||
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_shape.scale, z_range);
|
||||
float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension);
|
||||
OrthoProject cut_projection = priv::create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
|
||||
float projection_ratio = (-z_range.first + priv::safe_extension) /
|
||||
(z_range.second - z_range.first + 2 * priv::safe_extension);
|
||||
|
||||
bool is_text_reflected = Slic3r::has_reflection(input2.transform);
|
||||
ExPolygons shapes_data; // is used only when text is reflected to reverse polygon points order
|
||||
const ExPolygons *shapes_ptr = &shapes;
|
||||
bool is_text_reflected = Slic3r::has_reflection(tr);
|
||||
if (is_text_reflected) {
|
||||
// revert order of points in expolygons
|
||||
// CW --> CCW
|
||||
for (ExPolygon &shape : shapes) {
|
||||
shapes_data = shapes; // copy
|
||||
for (ExPolygon &shape : shapes_data) {
|
||||
shape.contour.reverse();
|
||||
for (Slic3r::Polygon &hole : shape.holes)
|
||||
hole.reverse();
|
||||
}
|
||||
shapes_ptr = &shapes_data;
|
||||
}
|
||||
|
||||
// Use CGAL to cut surface from triangle mesh
|
||||
SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio);
|
||||
SurfaceCut cut = cut_surface(*shapes_ptr, itss, cut_projection, projection_ratio);
|
||||
|
||||
if (is_text_reflected) {
|
||||
for (SurfaceCut::Contour &c : cut.contours)
|
||||
@ -1169,18 +1365,105 @@ template<typename Fnc> TriangleMesh cut_surface(DataBase &base, const SurfaceVol
|
||||
std::swap(t[0], t[1]);
|
||||
}
|
||||
|
||||
if (cut.empty())
|
||||
throw JobException(_u8L("There is no valid surface for text projection.").c_str());
|
||||
if (was_canceled())
|
||||
return {};
|
||||
if (cut.empty()) return {}; // There is no valid surface for text projection.
|
||||
if (was_canceled()) return {};
|
||||
|
||||
// !! Projection needs to transform cut
|
||||
OrthoProject3d projection = create_emboss_projection(input2.is_outside, static_cast<float>(emboss_shape.projection.depth), emboss_tr, cut);
|
||||
indexed_triangle_set new_its = cut2model(cut, projection);
|
||||
assert(!new_its.empty());
|
||||
if (was_canceled())
|
||||
return {};
|
||||
return TriangleMesh(std::move(new_its));
|
||||
OrthoProject3d projection = priv::create_emboss_projection(input.is_outside, fp.emboss, emboss_tr, cut);
|
||||
return cut2model(cut, projection);
|
||||
}
|
||||
|
||||
TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2, std::function<bool()> was_canceled)
|
||||
{
|
||||
std::vector<ExPolygons> shapes = priv::create_shapes(input1, was_canceled);
|
||||
if (was_canceled()) return {};
|
||||
if (shapes.empty())
|
||||
throw priv::JobException(_u8L("Font doesn't have any shape for given text.").c_str());
|
||||
|
||||
// Precalculate bounding boxes of glyphs
|
||||
// Separate lines of text to vector of Bounds
|
||||
const TextConfiguration &tc = input1.text_configuration;
|
||||
std::wstring ws = boost::nowide::widen(tc.text.c_str());
|
||||
assert(get_count_lines(ws) == input1.text_lines.size());
|
||||
size_t count_lines = input1.text_lines.size();
|
||||
std::vector<BoundingBoxes> bbs = create_line_bounds(shapes, ws, count_lines);
|
||||
|
||||
const FontProp &prop = tc.style.prop;
|
||||
FontFileWithCache &font = input1.font_file;
|
||||
double shape_scale = get_shape_scale(prop, *font.font_file);
|
||||
|
||||
// half of font em size for direction of letter emboss
|
||||
double em_2_mm = prop.size_in_mm / 2.;
|
||||
int32_t em_2_polygon = static_cast<int32_t>(std::round(scale_(em_2_mm)));
|
||||
|
||||
size_t s_i_offset = 0; // shape index offset(for next lines)
|
||||
indexed_triangle_set result;
|
||||
for (size_t text_line_index = 0; text_line_index < input1.text_lines.size(); ++text_line_index) {
|
||||
const BoundingBoxes &line_bbs = bbs[text_line_index];
|
||||
const TextLine &line = input1.text_lines[text_line_index];
|
||||
PolygonPoints samples = sample_slice(line, line_bbs, shape_scale);
|
||||
std::vector<double> angles = calculate_angles(em_2_polygon, samples, line.polygon);
|
||||
|
||||
for (size_t i = 0; i < line_bbs.size(); ++i) {
|
||||
const BoundingBox &glyph_bb = line_bbs[i];
|
||||
if (!glyph_bb.defined)
|
||||
continue;
|
||||
|
||||
const double &angle = angles[i];
|
||||
auto rotate = Eigen::AngleAxisd(angle + M_PI_2, Vec3d::UnitY());
|
||||
|
||||
const PolygonPoint &sample = samples[i];
|
||||
Vec2d offset_vec = unscale(sample.point); // [in mm]
|
||||
auto offset_tr = Eigen::Translation<double, 3>(offset_vec.x(), 0., -offset_vec.y());
|
||||
|
||||
ExPolygons &glyph_shape = shapes[s_i_offset + i];
|
||||
assert(get_extents(glyph_shape) == glyph_bb);
|
||||
|
||||
Point offset(-glyph_bb.center().x(), 0);
|
||||
for (ExPolygon& s: glyph_shape)
|
||||
s.translate(offset);
|
||||
|
||||
Transform3d modify = offset_tr * rotate;
|
||||
Transform3d tr = input2.text_tr * modify;
|
||||
indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, tr, input2.sources, input1, was_canceled);
|
||||
// move letter in volume on the right position
|
||||
its_transform(glyph_its, modify);
|
||||
|
||||
// Improve: union instead of merge
|
||||
its_merge(result, std::move(glyph_its));
|
||||
|
||||
if (((s_i_offset + i) % 15) && was_canceled())
|
||||
return {};
|
||||
}
|
||||
s_i_offset += line_bbs.size();
|
||||
}
|
||||
|
||||
if (was_canceled()) return {};
|
||||
if (result.empty())
|
||||
throw priv::JobException(_u8L("There is no valid surface for text projection.").c_str());
|
||||
return TriangleMesh(std::move(result));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// input can't be const - cache of font
|
||||
TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
|
||||
{
|
||||
const FontProp &fp = input1.text_configuration.style.prop;
|
||||
if (fp.per_glyph)
|
||||
return cut_per_glyph_surface(input1, input2, was_canceled);
|
||||
|
||||
ExPolygons shapes = create_shape(input1, was_canceled);
|
||||
if (was_canceled()) return {};
|
||||
if (shapes.empty())
|
||||
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
|
||||
|
||||
indexed_triangle_set its = cut_surface_to_its(shapes, input2.text_tr, input2.sources, input1, was_canceled);
|
||||
if (was_canceled()) return {};
|
||||
if (its.empty())
|
||||
throw JobException(_u8L("There is no valid surface for text projection.").c_str());
|
||||
|
||||
return TriangleMesh(std::move(its));
|
||||
}
|
||||
|
||||
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id)
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
#include "slic3r/GUI/Jobs/EmbossJob.hpp" // Emboss::DataBase
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
|
||||
#include "slic3r/GUI/TextLines.hpp"
|
||||
#include "Job.hpp"
|
||||
|
||||
// forward declarations
|
||||
@ -42,13 +42,43 @@ public:
|
||||
DataBase(DataBase &&) = default;
|
||||
virtual ~DataBase() = default;
|
||||
|
||||
// Define projection move
|
||||
// True (raised) .. move outside from surface
|
||||
// False (engraved).. move into object
|
||||
bool is_outside;
|
||||
|
||||
// flag that job is canceled
|
||||
// for time after process.
|
||||
std::shared_ptr<std::atomic<bool>> cancel;
|
||||
|
||||
// Define per letter projection on one text line
|
||||
// [optional] It is not used when empty
|
||||
Slic3r::Emboss::TextLines text_lines;
|
||||
|
||||
/// <summary>
|
||||
/// Create shape
|
||||
/// e.g. Text extract glyphs from font
|
||||
/// Not 'const' function because it could modify shape
|
||||
/// </summary>
|
||||
virtual EmbossShape &create_shape() { return shape; };
|
||||
virtual EmbossShape& create_shape() { return shape; };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to create ModelVolume in job
|
||||
/// Volume is created on the surface of existing volume in object.
|
||||
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
||||
/// </summary>
|
||||
struct DataCreateVolume : public DataBase
|
||||
{
|
||||
// define embossed volume type
|
||||
ModelVolumeType volume_type;
|
||||
|
||||
// parent ModelObject index where to create volume
|
||||
ObjectID object_id;
|
||||
|
||||
// new created volume transformation
|
||||
Transform3d trmat;
|
||||
};
|
||||
/// <summary>
|
||||
/// Write data how to reconstruct shape to volume
|
||||
/// </summary>
|
||||
@ -121,11 +151,6 @@ struct SurfaceVolumeData
|
||||
// Transformation of volume inside of object
|
||||
Transform3d transform;
|
||||
|
||||
// Define projection move
|
||||
// True (raised) .. move outside from surface
|
||||
// False (engraved).. move into object
|
||||
bool is_outside;
|
||||
|
||||
struct ModelSource
|
||||
{
|
||||
// source volumes
|
||||
|
@ -849,11 +849,31 @@ std::pair<BoundingBoxf3, Transform3d> Selection::get_bounding_box_in_reference_s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Vec3d box_size = max - min;
|
||||
const Vec3d half_box_size = 0.5 * box_size;
|
||||
BoundingBoxf3 out_box(-half_box_size, half_box_size);
|
||||
Vec3d half_box_size = 0.5 * box_size;
|
||||
Geometry::Transformation out_trafo(trafo);
|
||||
const Vec3d center = 0.5 * (min + max);
|
||||
Vec3d center = 0.5 * (min + max);
|
||||
|
||||
// Fix for non centered volume
|
||||
// by move with calculated center(to volume center) and extend half box size
|
||||
// e.g. for right aligned embossed text
|
||||
if (m_list.size() == 1 &&
|
||||
type == ECoordinatesType::Local) {
|
||||
const GLVolume& vol = *get_volume(*m_list.begin());
|
||||
const Transform3d vol_world_trafo = vol.world_matrix();
|
||||
Vec3d world_zero = vol_world_trafo * Vec3d::Zero();
|
||||
for (size_t i = 0; i < 3; i++){
|
||||
// move center to local volume zero
|
||||
center[i] = world_zero.dot(axes[i]);
|
||||
// extend half size to bigger distance from center
|
||||
half_box_size[i] = std::max(
|
||||
abs(center[i] - min[i]),
|
||||
abs(center[i] - max[i]));
|
||||
}
|
||||
}
|
||||
|
||||
const BoundingBoxf3 out_box(-half_box_size, half_box_size);
|
||||
out_trafo.set_offset(basis_trafo * center);
|
||||
return { out_box, out_trafo.get_matrix_no_scaling_factor() };
|
||||
}
|
||||
|
345
src/slic3r/GUI/TextLines.cpp
Normal file
345
src/slic3r/GUI/TextLines.cpp
Normal file
@ -0,0 +1,345 @@
|
||||
#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::Emboss;
|
||||
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;
|
||||
}
|
||||
|
||||
indexed_triangle_set its_create_belt(const Slic3r::Polygon &polygon, float width_half) {
|
||||
// Improve: Create torus instead of flat belt path (with model overlaps)
|
||||
assert(!polygon.empty());
|
||||
if (polygon.empty())
|
||||
return {};
|
||||
|
||||
// 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()
|
||||
indexed_triangle_set model;
|
||||
size_t count = polygon_expanded.size();
|
||||
model.vertices.reserve(2 * count);
|
||||
model.indices.reserve(2 * count);
|
||||
|
||||
for (const Point &point : polygon_expanded.points) {
|
||||
Vec2f point_d = unscale(point).cast<float>();
|
||||
Vec3f vertex(point_d.x(), point_d.y(), width_half);
|
||||
model.vertices.push_back(vertex);
|
||||
vertex.z() *= -1;
|
||||
model.vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
unsigned int prev_i = count - 1;
|
||||
for (unsigned int i = 0; i < count; ++i) {
|
||||
// t .. top
|
||||
// b .. bottom
|
||||
unsigned int t1 = prev_i * 2;
|
||||
unsigned int b1 = t1 + 1;
|
||||
unsigned int t2 = i * 2;
|
||||
unsigned int b2 = t2 + 1;
|
||||
model.indices.emplace_back(t1, b1, t2);
|
||||
model.indices.emplace_back(b2, t2, b1);
|
||||
prev_i = i;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
indexed_triangle_set its_create_torus(const Slic3r::Polygon &polygon, float radius, size_t steps = 20)
|
||||
{
|
||||
assert(!polygon.empty());
|
||||
if (polygon.empty())
|
||||
return {};
|
||||
|
||||
size_t count = polygon.size();
|
||||
if (count < 3)
|
||||
return {};
|
||||
|
||||
// convert and scale to float
|
||||
std::vector<Vec2f> points_d;
|
||||
points_d.reserve(count);
|
||||
for (const Point &point : polygon.points)
|
||||
points_d.push_back(unscale(point).cast<float>());
|
||||
|
||||
// pre calculate normalized line directions
|
||||
auto calc_line_norm = [](const Vec2f &f, const Vec2f &s) -> Vec2f { return (s - f).normalized(); };
|
||||
std::vector<Vec2f> line_norm(points_d.size());
|
||||
for (size_t i = 0; i < count - 1; ++i)
|
||||
line_norm[i] = calc_line_norm(points_d[i], points_d[i + 1]);
|
||||
line_norm.back() = calc_line_norm(points_d.back(), points_d.front());
|
||||
|
||||
// calculate normals for each point
|
||||
auto calc_norm = [](const Vec2f &prev, const Vec2f &next) -> Vec2f {
|
||||
Vec2f dir = prev + next;
|
||||
return Vec2f(-dir.x(), dir.y());
|
||||
};
|
||||
std::vector<Vec2f> points_norm(points_d.size());
|
||||
points_norm.front() = calc_norm(line_norm.back(), line_norm[1]);
|
||||
for (size_t i = 1; i < points_d.size() - 1; ++i)
|
||||
points_norm[i] = calc_norm(line_norm[i - 1], line_norm[i + 1]);
|
||||
points_norm.back() = calc_norm(line_norm[points_d.size() - 2], line_norm.front());
|
||||
|
||||
// precalculate sinus and cosinus
|
||||
double angle_step = 2 * M_PI / steps;
|
||||
std::vector<std::pair<double, float>> sin_cos;
|
||||
sin_cos.reserve(steps);
|
||||
for (size_t s = 0; s < steps; ++s) {
|
||||
double angle = s * angle_step;
|
||||
sin_cos.emplace_back(
|
||||
radius * std::sin(angle),
|
||||
static_cast<float>(radius * std::cos(angle))
|
||||
);
|
||||
}
|
||||
|
||||
// create torus model along polygon path
|
||||
indexed_triangle_set model;
|
||||
model.vertices.reserve(steps * count);
|
||||
model.indices.reserve(2 * steps * count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const Vec2f point_d = points_d[i];
|
||||
const Vec2f norm = points_norm[i];
|
||||
for (const auto &[s, c] : sin_cos) {
|
||||
Vec2f xy = s * norm + point_d;
|
||||
model.vertices.emplace_back(xy.x(), xy.y(), c);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int prev_i = count - 1;
|
||||
for (unsigned int i = 0; i < count; ++i) {
|
||||
// TODO: solve <180, =180 and >180 angle
|
||||
// to not create self intersection
|
||||
|
||||
// t .. top
|
||||
// b .. bottom
|
||||
unsigned int prev_t = (prev_i+1) * steps - 1;
|
||||
unsigned int t = (i+1) * steps - 1;
|
||||
for (size_t s = 0; s < steps; ++s) {
|
||||
unsigned int prev_b = prev_i * steps + s;
|
||||
unsigned int b = i * steps + s;
|
||||
model.indices.emplace_back(prev_t, prev_b, t);
|
||||
model.indices.emplace_back(b, t, prev_b);
|
||||
prev_t = prev_b;
|
||||
t = b;
|
||||
}
|
||||
prev_i = i;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
// select closest contour for each line
|
||||
TextLines select_closest_contour(const std::vector<Polygons> &line_contours) {
|
||||
TextLines result;
|
||||
result.reserve(line_contours.size());
|
||||
Vec2d zero(0., 0.);
|
||||
for (const Polygons &polygons : line_contours){
|
||||
if (polygons.empty()) {
|
||||
result.emplace_back();
|
||||
continue;
|
||||
}
|
||||
// Improve: use int values and polygons only
|
||||
// Slic3r::Polygons polygons = union_(polygons);
|
||||
// std::vector<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 Slic3r::Polygon& polygon = index.is_contour() ?
|
||||
expolygons[index.expolygons_index].contour :
|
||||
expolygons[index.expolygons_index].holes[index.hole_index()];
|
||||
|
||||
Point hit_point_int = hit_point.cast<Point::coord_type>();
|
||||
TextLine tl{polygon, PolygonPoint{index.point_index, hit_point_int}};
|
||||
result.emplace_back(tl);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Eigen::AngleAxis<double> get_rotation() { return Eigen::AngleAxis(-M_PI_2, Vec3d::UnitX()); }
|
||||
|
||||
indexed_triangle_set create_its(const TextLines &lines)
|
||||
{
|
||||
const float model_half_width = 0.75; // [in volume mm]
|
||||
indexed_triangle_set its;
|
||||
// create model from polygons
|
||||
for (const TextLine &line : lines) {
|
||||
const Slic3r::Polygon &polygon = line.polygon;
|
||||
if (polygon.empty()) continue;
|
||||
indexed_triangle_set line_its = its_create_belt(polygon, model_half_width);
|
||||
//indexed_triangle_set line_its = its_create_torus(polygon, model_half_width);
|
||||
auto transl = Eigen::Translation3d(0., line.y, 0.);
|
||||
Transform3d tr = transl * get_rotation();
|
||||
its_transform(line_its, tr);
|
||||
its_merge(its, line_its);
|
||||
}
|
||||
return its;
|
||||
}
|
||||
|
||||
GLModel::Geometry create_geometry(const TextLines &lines)
|
||||
{
|
||||
indexed_triangle_set its = create_its(lines);
|
||||
|
||||
GLModel::Geometry geometry;
|
||||
geometry.format = {GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3};
|
||||
ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray
|
||||
geometry.color = color;
|
||||
|
||||
geometry.reserve_vertices(its.vertices.size());
|
||||
for (Vec3f vertex : its.vertices)
|
||||
geometry.add_vertex(vertex);
|
||||
|
||||
geometry.reserve_indices(its.indices.size() * 3);
|
||||
for (Vec3i t : its.indices)
|
||||
geometry.add_triangle(t[0], t[1], t[2]);
|
||||
return geometry;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void TextLinesModel::init(const Transform3d &text_tr,
|
||||
const ModelVolumePtrs &volumes_to_slice,
|
||||
FontProp::VerticalAlign align,
|
||||
double line_height,
|
||||
double offset,
|
||||
unsigned count_lines)
|
||||
{
|
||||
m_model.reset();
|
||||
m_lines.clear();
|
||||
|
||||
double first_line_center = offset + this->offset + get_align_y_offset(align, count_lines, line_height);
|
||||
std::vector<float> line_centers(count_lines);
|
||||
for (size_t i = 0; i < count_lines; ++i)
|
||||
line_centers[i] = static_cast<float>(first_line_center - i * line_height);
|
||||
|
||||
// contour transformation
|
||||
Transform3d c_trafo = text_tr * get_rotation();
|
||||
Transform3d c_trafo_inv = c_trafo.inverse();
|
||||
|
||||
std::vector<Polygons> line_contours(count_lines);
|
||||
for (const ModelVolume *volume : volumes_to_slice) {
|
||||
MeshSlicingParams slicing_params;
|
||||
slicing_params.trafo = c_trafo_inv * volume->get_matrix();
|
||||
for (size_t i = 0; i < count_lines; ++i) {
|
||||
const Polygons polys = Slic3r::slice_mesh(volume->mesh().its, line_centers[i], slicing_params);
|
||||
if (polys.empty())
|
||||
continue;
|
||||
Polygons &contours = line_contours[i];
|
||||
contours.insert(contours.end(), polys.begin(), polys.end());
|
||||
}
|
||||
}
|
||||
|
||||
m_lines = select_closest_contour(line_contours);
|
||||
assert(m_lines.size() == count_lines);
|
||||
assert(line_centers.size() == count_lines);
|
||||
for (size_t i = 0; i < count_lines; ++i)
|
||||
m_lines[i].y = line_centers[i];
|
||||
|
||||
//*
|
||||
GLModel::Geometry geometry = create_geometry(m_lines);
|
||||
if (geometry.vertices_count() == 0 || geometry.indices_count() == 0)
|
||||
return;
|
||||
m_model.init_from(std::move(geometry));
|
||||
/*/
|
||||
// slower solution
|
||||
ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray
|
||||
m_model.set_color(color);
|
||||
m_model.init_from(create_its(m_lines));
|
||||
//*/
|
||||
}
|
||||
|
||||
void TextLinesModel::render(const Transform3d &text_world)
|
||||
{
|
||||
if (!m_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();
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * text_world);
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
|
||||
bool is_depth_test = glIsEnabled(GL_DEPTH_TEST);
|
||||
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));
|
||||
|
||||
m_model.render();
|
||||
|
||||
if (!is_depth_test)
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
if (!is_blend)
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
double TextLinesModel::calc_line_height(const Slic3r::Emboss::FontFile &ff, const FontProp &fp)
|
||||
{
|
||||
int line_height = Emboss::get_line_height(ff, fp); // In shape size
|
||||
double scale = Emboss::get_shape_scale(fp, ff);
|
||||
return line_height * scale;
|
||||
}
|
48
src/slic3r/GUI/TextLines.hpp
Normal file
48
src/slic3r/GUI/TextLines.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef slic3r_TextLines_hpp_
|
||||
#define slic3r_TextLines_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/Emboss.hpp>
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class ModelVolume;
|
||||
typedef std::vector<ModelVolume *> ModelVolumePtrs;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
class TextLinesModel
|
||||
{
|
||||
public:
|
||||
// line offset in y direction (up/down)
|
||||
float offset = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize model and lines
|
||||
/// </summary>
|
||||
/// <param name="text_tr">Transformation of text volume inside object (aka inside of instance)</param>
|
||||
/// <param name="volumes_to_slice">Vector of volumes to be sliced</param>
|
||||
/// <param name="align">Vertical (Y) align of the text</param>
|
||||
/// <param name="line_height">Distance between lines [in mm]</param>
|
||||
/// <param name="line_height">Offset from baseline [in mm]</param>
|
||||
/// <param name="count_lines">Count lines(slices over volumes)</param>
|
||||
void init(const Transform3d &text_tr, const ModelVolumePtrs& volumes_to_slice, FontProp::VerticalAlign align, double line_height, double offset, unsigned count_lines);
|
||||
|
||||
void render(const Transform3d &text_world);
|
||||
|
||||
bool is_init() const { return m_model.is_initialized(); }
|
||||
void reset() { m_model.reset(); m_lines.clear(); }
|
||||
const Slic3r::Emboss::TextLines &get_lines() const { return m_lines; }
|
||||
|
||||
static double calc_line_height(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); // return lineheight in mm
|
||||
private:
|
||||
Slic3r::Emboss::TextLines m_lines;
|
||||
|
||||
// Keep model for visualization text lines
|
||||
GLModel m_model;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_TextLines_hpp_
|
@ -295,6 +295,25 @@ void StyleManager::clear_glyphs_cache()
|
||||
|
||||
void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); }
|
||||
|
||||
#include "slic3r/GUI/TextLines.hpp"
|
||||
double StyleManager::get_line_height()
|
||||
{
|
||||
assert(is_active_font());
|
||||
if (!is_active_font())
|
||||
return -1;
|
||||
const auto &ffc = get_font_file_with_cache();
|
||||
assert(ffc.has_value());
|
||||
if (!ffc.has_value())
|
||||
return -1;
|
||||
const auto &ff_ptr = ffc.font_file;
|
||||
assert(ff_ptr != nullptr);
|
||||
if (ff_ptr == nullptr)
|
||||
return -1;
|
||||
const FontProp &fp = get_font_prop();
|
||||
const FontFile &ff = *ff_ptr;
|
||||
return TextLinesModel::calc_line_height(ff, fp);
|
||||
}
|
||||
|
||||
ImFont *StyleManager::get_imgui_font()
|
||||
{
|
||||
if (!is_active_font()) return nullptr;
|
||||
|
@ -108,6 +108,10 @@ public:
|
||||
// remove cached imgui font for actual selected font
|
||||
void clear_imgui_font();
|
||||
|
||||
// calculate line height
|
||||
// not const because access to font file which could be created.
|
||||
double get_line_height(); /* const */
|
||||
|
||||
// getters for private data
|
||||
const Style *get_stored_style() const;
|
||||
|
||||
|
@ -318,7 +318,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