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:
Filip Sykala - NTB T15p 2023-06-07 11:18:26 +02:00
commit e831255018
24 changed files with 2057 additions and 190 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,10 +33,11 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
std::vector<double> scales(m_input.styles.size());
m_images = std::vector<StyleManager::StyleImage>(m_input.styles.size());
auto was_canceled = []() { return false; };
for (auto &item : m_input.styles) {
size_t index = &item - &m_input.styles.front();
ExPolygons &shapes = name_shapes[index];
shapes = text2shapes(item.font, m_input.text.c_str(), item.prop);
shapes = text2shapes(item.font, m_input.text.c_str(), item.prop, was_canceled);
// create image description
StyleManager::StyleImage &image = m_images[index];

View File

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

View File

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

View File

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

View 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;
}

View 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_

View File

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

View File

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

View File

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