mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 10:06:02 +08:00
Add new volume as cut surface by definition in style
This commit is contained in:
parent
c052ff403a
commit
58592593af
@ -20,9 +20,6 @@
|
|||||||
#include "libslic3r/Line.hpp"
|
#include "libslic3r/Line.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
double Emboss::SHAPE_SCALE = 0.001;//SCALING_FACTOR;
|
|
||||||
|
|
||||||
// do not expose out of this file stbtt_ data types
|
// do not expose out of this file stbtt_ data types
|
||||||
namespace priv{
|
namespace priv{
|
||||||
|
|
||||||
|
@ -16,23 +16,21 @@ namespace Slic3r {
|
|||||||
/// class with only static function add ability to engraved OR raised
|
/// class with only static function add ability to engraved OR raised
|
||||||
/// text OR polygons onto model surface
|
/// text OR polygons onto model surface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class Emboss
|
namespace Emboss
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
Emboss() = delete;
|
|
||||||
|
|
||||||
// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value
|
// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value
|
||||||
static double SHAPE_SCALE;
|
// stored in fonts (to be able represents curve by sequence of lines)
|
||||||
|
static constexpr double SHAPE_SCALE = 0.001; // SCALING_FACTOR promile is fine enough
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collect fonts registred inside OS
|
/// Collect fonts registred inside OS
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>OS registred TTF font files(full path) with names</returns>
|
/// <returns>OS registred TTF font files(full path) with names</returns>
|
||||||
static EmbossStyles get_font_list();
|
EmbossStyles get_font_list();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static EmbossStyles get_font_list_by_register();
|
EmbossStyles get_font_list_by_register();
|
||||||
static EmbossStyles get_font_list_by_enumeration();
|
EmbossStyles get_font_list_by_enumeration();
|
||||||
static EmbossStyles get_font_list_by_folder();
|
EmbossStyles get_font_list_by_folder();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -40,7 +38,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="font_face_name">Unique identificator for font</param>
|
/// <param name="font_face_name">Unique identificator for font</param>
|
||||||
/// <returns>File path to font when found</returns>
|
/// <returns>File path to font when found</returns>
|
||||||
static std::optional<std::wstring> get_font_path(const std::wstring &font_face_name);
|
std::optional<std::wstring> get_font_path(const std::wstring &font_face_name);
|
||||||
|
|
||||||
// description of one letter
|
// description of one letter
|
||||||
struct Glyph
|
struct Glyph
|
||||||
@ -126,14 +124,14 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="file_path">Location of .ttf or .ttc font file</param>
|
/// <param name="file_path">Location of .ttf or .ttc font file</param>
|
||||||
/// <returns>Font object when loaded.</returns>
|
/// <returns>Font object when loaded.</returns>
|
||||||
static std::unique_ptr<FontFile> create_font_file(const char *file_path);
|
std::unique_ptr<FontFile> create_font_file(const char *file_path);
|
||||||
// data = raw file data
|
// data = raw file data
|
||||||
static std::unique_ptr<FontFile> create_font_file(std::unique_ptr<std::vector<unsigned char>> data);
|
std::unique_ptr<FontFile> create_font_file(std::unique_ptr<std::vector<unsigned char>> data);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// fix for unknown pointer HFONT
|
// fix for unknown pointer HFONT
|
||||||
using HFONT = void*;
|
using HFONT = void*;
|
||||||
static void * can_load(HFONT hfont);
|
void * can_load(HFONT hfont);
|
||||||
static std::unique_ptr<FontFile> create_font_file(HFONT hfont);
|
std::unique_ptr<FontFile> create_font_file(HFONT hfont);
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -144,7 +142,7 @@ public:
|
|||||||
/// <param name="letter">One character defined by unicode codepoint</param>
|
/// <param name="letter">One character defined by unicode codepoint</param>
|
||||||
/// <param name="flatness">Precision of lettter outline curve in conversion to lines</param>
|
/// <param name="flatness">Precision of lettter outline curve in conversion to lines</param>
|
||||||
/// <returns>inner polygon cw(outer ccw)</returns>
|
/// <returns>inner polygon cw(outer ccw)</returns>
|
||||||
static std::optional<Glyph> letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness);
|
std::optional<Glyph> letter2glyph(const FontFile &font, unsigned int font_index, int letter, float flatness);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert text into polygons
|
/// Convert text into polygons
|
||||||
@ -154,14 +152,14 @@ public:
|
|||||||
/// <param name="font_prop">User defined property of the font</param>
|
/// <param name="font_prop">User defined property of the font</param>
|
||||||
/// <param name="was_canceled">Way to interupt processing</param>
|
/// <param name="was_canceled">Way to interupt processing</param>
|
||||||
/// <returns>Inner polygon cw(outer ccw)</returns>
|
/// <returns>Inner polygon cw(outer ccw)</returns>
|
||||||
static 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, std::function<bool()> was_canceled = nullptr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fix intersections and self intersections in polygons glyph shape
|
/// Fix intersections and self intersections in polygons glyph shape
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shape">Input shape to heal</param>
|
/// <param name="shape">Input shape to heal</param>
|
||||||
/// <returns>Healed shapes</returns>
|
/// <returns>Healed shapes</returns>
|
||||||
static ExPolygons heal_shape(const Polygons &shape);
|
ExPolygons heal_shape(const Polygons &shape);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// NOTE: call Slic3r::union_ex before this call
|
/// NOTE: call Slic3r::union_ex before this call
|
||||||
@ -175,7 +173,7 @@ public:
|
|||||||
/// <param name="max_iteration">Heal could create another issue,
|
/// <param name="max_iteration">Heal could create another issue,
|
||||||
/// After healing it is checked again until shape is good or maximal count of iteration</param>
|
/// After healing it is checked again until shape is good or maximal count of iteration</param>
|
||||||
/// <returns>True when shapes is good otherwise False</returns>
|
/// <returns>True when shapes is good otherwise False</returns>
|
||||||
static bool heal_shape(ExPolygons &shape, unsigned max_iteration = 10);
|
bool heal_shape(ExPolygons &shape, unsigned max_iteration = 10);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Divide line segments in place near to point
|
/// Divide line segments in place near to point
|
||||||
@ -186,7 +184,7 @@ public:
|
|||||||
/// <param name="expolygons">Expolygon to edit</param>
|
/// <param name="expolygons">Expolygon to edit</param>
|
||||||
/// <param name="distance">(epsilon)Euclidean distance from point to line which divide line</param>
|
/// <param name="distance">(epsilon)Euclidean distance from point to line which divide line</param>
|
||||||
/// <returns>True when some division was made otherwise false</returns>
|
/// <returns>True when some division was made otherwise false</returns>
|
||||||
static bool divide_segments_for_close_point(ExPolygons &expolygons, double distance);
|
bool divide_segments_for_close_point(ExPolygons &expolygons, double distance);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Use data from font property to modify transformation
|
/// Use data from font property to modify transformation
|
||||||
@ -194,7 +192,7 @@ public:
|
|||||||
/// <param name="font_prop">Z-move as surface distance(FontProp::distance)
|
/// <param name="font_prop">Z-move as surface distance(FontProp::distance)
|
||||||
/// Z-rotation as angle to Y axis(FontProp::angle)</param>
|
/// Z-rotation as angle to Y axis(FontProp::angle)</param>
|
||||||
/// <param name="transformation">In / Out transformation to modify by property</param>
|
/// <param name="transformation">In / Out transformation to modify by property</param>
|
||||||
static void apply_transformation(const FontProp &font_prop, Transform3d &transformation);
|
void apply_transformation(const FontProp &font_prop, Transform3d &transformation);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read information from naming table of font file
|
/// Read information from naming table of font file
|
||||||
@ -203,7 +201,7 @@ public:
|
|||||||
/// <param name="font">Selector of font</param>
|
/// <param name="font">Selector of font</param>
|
||||||
/// <param name="font_index">Index of font in collection</param>
|
/// <param name="font_index">Index of font in collection</param>
|
||||||
/// <returns>True when the font description contains italic/obligue otherwise False</returns>
|
/// <returns>True when the font description contains italic/obligue otherwise False</returns>
|
||||||
static bool is_italic(const FontFile &font, unsigned int font_index);
|
bool is_italic(const FontFile &font, unsigned int font_index);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create unique character set from string with filtered from text with only character from font
|
/// Create unique character set from string with filtered from text with only character from font
|
||||||
@ -213,7 +211,7 @@ public:
|
|||||||
/// <param name="font_index">Define font in collection</param>
|
/// <param name="font_index">Define font in collection</param>
|
||||||
/// <param name="exist_unknown">True when text contain glyph unknown in font</param>
|
/// <param name="exist_unknown">True when text contain glyph unknown in font</param>
|
||||||
/// <returns>Unique set of character from text contained in font</returns>
|
/// <returns>Unique set of character from text contained in font</returns>
|
||||||
static std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr);
|
std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculate scale for glyph shape convert from shape points to mm
|
/// Calculate scale for glyph shape convert from shape points to mm
|
||||||
@ -221,7 +219,7 @@ public:
|
|||||||
/// <param name="fp">Property of font</param>
|
/// <param name="fp">Property of font</param>
|
||||||
/// <param name="ff">Font data</param>
|
/// <param name="ff">Font data</param>
|
||||||
/// <returns>Conversion to mm</returns>
|
/// <returns>Conversion to mm</returns>
|
||||||
static double get_shape_scale(const FontProp &fp, const FontFile &ff);
|
double get_shape_scale(const FontProp &fp, const FontFile &ff);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Project spatial point
|
/// Project spatial point
|
||||||
@ -274,7 +272,7 @@ public:
|
|||||||
/// <param name="shape2d">text or image</param>
|
/// <param name="shape2d">text or image</param>
|
||||||
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
|
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
|
||||||
/// <returns>Projected shape into space</returns>
|
/// <returns>Projected shape into space</returns>
|
||||||
static indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection);
|
indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create transformation for emboss text object to lay on surface point
|
/// Create transformation for emboss text object to lay on surface point
|
||||||
@ -283,7 +281,7 @@ public:
|
|||||||
/// <param name="normal">Normal of surface point</param>
|
/// <param name="normal">Normal of surface point</param>
|
||||||
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
/// <param name="up_limit">Is compared with normal.z to suggest up direction</param>
|
||||||
/// <returns>Transformation onto surface point</returns>
|
/// <returns>Transformation onto surface point</returns>
|
||||||
static Transform3d create_transformation_onto_surface(
|
Transform3d create_transformation_onto_surface(
|
||||||
const Vec3f &position, const Vec3f &normal, float up_limit = 0.9f);
|
const Vec3f &position, const Vec3f &normal, float up_limit = 0.9f);
|
||||||
|
|
||||||
class ProjectZ : public IProjection
|
class ProjectZ : public IProjection
|
||||||
@ -345,7 +343,7 @@ public:
|
|||||||
Vec3d project(const Vec3d &point) const override;
|
Vec3d project(const Vec3d &point) const override;
|
||||||
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
|
std::optional<Vec2d> unproject(const Vec3d &p, double * depth = nullptr) const override;
|
||||||
};
|
};
|
||||||
};
|
} // namespace Emboss
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
#endif // slic3r_Emboss_hpp_
|
#endif // slic3r_Emboss_hpp_
|
||||||
|
@ -936,23 +936,8 @@ private:
|
|||||||
// 1 -> is splittable
|
// 1 -> is splittable
|
||||||
mutable int m_is_splittable{ -1 };
|
mutable int m_is_splittable{ -1 };
|
||||||
|
|
||||||
ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object)
|
inline bool check() {
|
||||||
{
|
assert(this->id().valid());
|
||||||
assert(this->id().valid());
|
|
||||||
assert(this->config.id().valid());
|
|
||||||
assert(this->supported_facets.id().valid());
|
|
||||||
assert(this->seam_facets.id().valid());
|
|
||||||
assert(this->mmu_segmentation_facets.id().valid());
|
|
||||||
assert(this->id() != this->config.id());
|
|
||||||
assert(this->id() != this->supported_facets.id());
|
|
||||||
assert(this->id() != this->seam_facets.id());
|
|
||||||
assert(this->id() != this->mmu_segmentation_facets.id());
|
|
||||||
if (mesh.facets_count() > 1)
|
|
||||||
calculate_convex_hull();
|
|
||||||
}
|
|
||||||
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
|
|
||||||
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
|
|
||||||
assert(this->id().valid());
|
|
||||||
assert(this->config.id().valid());
|
assert(this->config.id().valid());
|
||||||
assert(this->supported_facets.id().valid());
|
assert(this->supported_facets.id().valid());
|
||||||
assert(this->seam_facets.id().valid());
|
assert(this->seam_facets.id().valid());
|
||||||
@ -961,6 +946,24 @@ private:
|
|||||||
assert(this->id() != this->supported_facets.id());
|
assert(this->id() != this->supported_facets.id());
|
||||||
assert(this->id() != this->seam_facets.id());
|
assert(this->id() != this->seam_facets.id());
|
||||||
assert(this->id() != this->mmu_segmentation_facets.id());
|
assert(this->id() != this->mmu_segmentation_facets.id());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
|
||||||
|
m_mesh(new TriangleMesh(mesh)), m_type(type), object(object)
|
||||||
|
{
|
||||||
|
assert(check());
|
||||||
|
if (m_mesh->facets_count() > 1) calculate_convex_hull();
|
||||||
|
}
|
||||||
|
ModelVolume(ModelObject *object, TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||||
|
: m_mesh(new TriangleMesh(std::move(mesh))), m_type(type), object(object)
|
||||||
|
{
|
||||||
|
assert(check());
|
||||||
|
if (m_mesh->facets_count() > 1) calculate_convex_hull();
|
||||||
|
}
|
||||||
|
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
|
||||||
|
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
|
||||||
|
assert(check());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
||||||
|
@ -57,7 +57,9 @@
|
|||||||
#endif // ALLOW_DEBUG_MODE
|
#endif // ALLOW_DEBUG_MODE
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
using namespace Slic3r::Emboss;
|
||||||
using namespace Slic3r::GUI;
|
using namespace Slic3r::GUI;
|
||||||
|
using namespace Slic3r::GUI::Emboss;
|
||||||
|
|
||||||
// anonymous namespace for unique names
|
// anonymous namespace for unique names
|
||||||
namespace {
|
namespace {
|
||||||
@ -165,6 +167,18 @@ void GLGizmoEmboss::set_fine_position()
|
|||||||
ImGuiWrapper::draw(rect);
|
ImGuiWrapper::draw(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace priv {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepare data for emboss
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to emboss</param>
|
||||||
|
/// <param name="style_manager">Keep actual selected style</param>
|
||||||
|
/// <returns>Base data for emboss text</returns>
|
||||||
|
static DataBase create_emboss_data_base(const std::string &text, StyleManager &style_manager);
|
||||||
|
|
||||||
|
} // namespace priv
|
||||||
|
|
||||||
void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos)
|
void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos)
|
||||||
{
|
{
|
||||||
assert(volume_type == ModelVolumeType::MODEL_PART ||
|
assert(volume_type == ModelVolumeType::MODEL_PART ||
|
||||||
@ -188,11 +202,17 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous
|
|||||||
Plater* plater = wxGetApp().plater();
|
Plater* plater = wxGetApp().plater();
|
||||||
const Camera &camera = plater->get_camera();
|
const Camera &camera = plater->get_camera();
|
||||||
const Pointfs &bed_shape = plater->build_volume().bed_shape();
|
const Pointfs &bed_shape = plater->build_volume().bed_shape();
|
||||||
EmbossDataCreateObject data{create_emboss_data_base(),
|
// TODO: Fix double creation of base data (first is inside function start_volume_creation)
|
||||||
screen_coor,
|
DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager);
|
||||||
camera,
|
FontProp& prop = emboss_data.text_configuration.style.prop;
|
||||||
bed_shape};
|
|
||||||
auto job = std::make_unique<EmbossCreateObjectJob>(std::move(data));
|
// can't create new object with using surface
|
||||||
|
if (prop.use_surface) prop.use_surface = false;
|
||||||
|
// can't create new object with distance from surface
|
||||||
|
if (prop.distance.has_value()) prop.distance.reset();
|
||||||
|
|
||||||
|
DataCreateObject data{std::move(emboss_data), screen_coor, camera, bed_shape};
|
||||||
|
auto job = std::make_unique<CreateObjectJob>(std::move(data));
|
||||||
Worker &worker = plater->get_ui_job_worker();
|
Worker &worker = plater->get_ui_job_worker();
|
||||||
queue_job(worker, std::move(job));
|
queue_job(worker, std::move(job));
|
||||||
}
|
}
|
||||||
@ -369,9 +389,9 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
|
|||||||
|
|
||||||
// Calculate temporary position
|
// Calculate temporary position
|
||||||
Transform3d object_trmat = m_raycast_manager.get_transformation(hit->tr_key);
|
Transform3d object_trmat = m_raycast_manager.get_transformation(hit->tr_key);
|
||||||
Transform3d trmat = Emboss::create_transformation_onto_surface(hit->position, hit->normal);
|
Transform3d trmat = create_transformation_onto_surface(hit->position, hit->normal);
|
||||||
const FontProp& font_prop = tc.style.prop;
|
const FontProp& font_prop = tc.style.prop;
|
||||||
Emboss::apply_transformation(font_prop, trmat);
|
apply_transformation(font_prop, trmat);
|
||||||
|
|
||||||
// fix baked transformation from .3mf store process
|
// fix baked transformation from .3mf store process
|
||||||
if (tc.fix_3mf_tr.has_value())
|
if (tc.fix_3mf_tr.has_value())
|
||||||
@ -807,8 +827,7 @@ EmbossStyles GLGizmoEmboss::create_default_styles()
|
|||||||
|
|
||||||
void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); }
|
void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); }
|
||||||
|
|
||||||
bool GLGizmoEmboss::start_volume_creation(ModelVolumeType volume_type,
|
bool GLGizmoEmboss::start_volume_creation(ModelVolumeType volume_type, const Vec2d &screen_coor)
|
||||||
const Vec2d &screen_coor)
|
|
||||||
{
|
{
|
||||||
Plater* plater = wxGetApp().plater();
|
Plater* plater = wxGetApp().plater();
|
||||||
|
|
||||||
@ -848,18 +867,33 @@ bool GLGizmoEmboss::start_volume_creation(ModelVolumeType volume_type,
|
|||||||
GLVolume *gl_volume = volumes[hovered_id];
|
GLVolume *gl_volume = volumes[hovered_id];
|
||||||
Transform3d hit_instance_trmat = gl_volume->get_instance_transformation().get_matrix();
|
Transform3d hit_instance_trmat = gl_volume->get_instance_transformation().get_matrix();
|
||||||
|
|
||||||
// create volume
|
DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager);
|
||||||
EmbossDataCreateVolume data{create_emboss_data_base(),
|
|
||||||
volume_type,
|
|
||||||
screen_coor,
|
|
||||||
object_idx_signed,
|
|
||||||
camera,
|
|
||||||
*hit,
|
|
||||||
hit_object_trmat,
|
|
||||||
hit_instance_trmat};
|
|
||||||
|
|
||||||
|
// Create result volume transformation
|
||||||
|
Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal);
|
||||||
|
const FontProp& font_prop = emboss_data.text_configuration.style.prop;
|
||||||
|
apply_transformation(font_prop, surface_trmat);
|
||||||
|
Transform3d volume_trmat = hit_instance_trmat.inverse() * hit_object_trmat * surface_trmat;
|
||||||
|
|
||||||
|
std::unique_ptr<GUI::Job> job;
|
||||||
|
if (!emboss_data.text_configuration.style.prop.use_surface) {
|
||||||
|
// create volume
|
||||||
|
DataCreateVolume data{std::move(emboss_data), volume_type, object_idx_signed, volume_trmat};
|
||||||
|
job = std::make_unique<CreateVolumeJob>(std::move(data));
|
||||||
|
} else {
|
||||||
|
// Model to cut surface from.
|
||||||
|
SurfaceVolumeData::ModelSources sources = create_sources(obj->volumes);
|
||||||
|
if (sources.empty()) return false;
|
||||||
|
|
||||||
|
bool is_outside = volume_type == ModelVolumeType::MODEL_PART;
|
||||||
|
// check that there is not unexpected volume type
|
||||||
|
assert(is_outside ||
|
||||||
|
volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
|
||||||
|
volume_type == ModelVolumeType::PARAMETER_MODIFIER);
|
||||||
|
CreateSurfaceVolumeData surface_data{std::move(emboss_data), volume_trmat, is_outside, std::move(sources), volume_type, object_idx_signed};
|
||||||
|
job = std::make_unique<CreateSurfaceVolumeJob>(std::move(surface_data));
|
||||||
|
}
|
||||||
Worker &worker = plater->get_ui_job_worker();
|
Worker &worker = plater->get_ui_job_worker();
|
||||||
auto job = std::make_unique<EmbossCreateVolumeJob>(std::move(data));
|
|
||||||
queue_job(worker, std::move(job));
|
queue_job(worker, std::move(job));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -957,7 +991,7 @@ bool GLGizmoEmboss::process()
|
|||||||
m_update_job_cancel->store(true);
|
m_update_job_cancel->store(true);
|
||||||
// create new shared ptr to cancel new job
|
// create new shared ptr to cancel new job
|
||||||
m_update_job_cancel = std::make_shared<std::atomic<bool> >(false);
|
m_update_job_cancel = std::make_shared<std::atomic<bool> >(false);
|
||||||
EmbossDataUpdate data{create_emboss_data_base(), m_volume->id(), m_update_job_cancel};
|
DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager), m_volume->id(), m_update_job_cancel};
|
||||||
|
|
||||||
std::unique_ptr<Job> job = nullptr;
|
std::unique_ptr<Job> job = nullptr;
|
||||||
|
|
||||||
@ -965,7 +999,7 @@ bool GLGizmoEmboss::process()
|
|||||||
const TextConfiguration &tc = data.text_configuration;
|
const TextConfiguration &tc = data.text_configuration;
|
||||||
if (tc.style.prop.use_surface) {
|
if (tc.style.prop.use_surface) {
|
||||||
// Model to cut surface from.
|
// Model to cut surface from.
|
||||||
UseSurfaceData::ModelSources sources = UseSurfaceData::create_sources(m_volume);
|
SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume);
|
||||||
if (sources.empty()) return false;
|
if (sources.empty()) return false;
|
||||||
|
|
||||||
Transform3d text_tr = m_volume->get_matrix();
|
Transform3d text_tr = m_volume->get_matrix();
|
||||||
@ -977,11 +1011,10 @@ bool GLGizmoEmboss::process()
|
|||||||
// check that there is not unexpected volume type
|
// check that there is not unexpected volume type
|
||||||
assert(is_outside || m_volume->is_negative_volume() ||
|
assert(is_outside || m_volume->is_negative_volume() ||
|
||||||
m_volume->is_modifier());
|
m_volume->is_modifier());
|
||||||
UseSurfaceData surface_data{std::move(data), text_tr, is_outside,
|
UpdateSurfaceVolumeData surface_data{std::move(data), text_tr, is_outside, std::move(sources)};
|
||||||
std::move(sources)};
|
job = std::make_unique<UpdateSurfaceVolumeJob>(std::move(surface_data));
|
||||||
job = std::make_unique<UseSurfaceJob>(std::move(surface_data));
|
|
||||||
} else {
|
} else {
|
||||||
job = std::make_unique<EmbossUpdateJob>(std::move(data));
|
job = std::make_unique<UpdateJob>(std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
//*
|
//*
|
||||||
@ -1024,7 +1057,7 @@ void GLGizmoEmboss::close()
|
|||||||
void GLGizmoEmboss::discard_and_close() {
|
void GLGizmoEmboss::discard_and_close() {
|
||||||
if (!m_unmodified_volume.has_value()) return;
|
if (!m_unmodified_volume.has_value()) return;
|
||||||
m_volume->set_transformation(m_unmodified_volume->tr);
|
m_volume->set_transformation(m_unmodified_volume->tr);
|
||||||
EmbossUpdateJob::update_volume(m_volume, std::move(m_unmodified_volume->tm), m_unmodified_volume->tc, m_unmodified_volume->name);
|
UpdateJob::update_volume(m_volume, std::move(m_unmodified_volume->tm), m_unmodified_volume->tc, m_unmodified_volume->name);
|
||||||
close();
|
close();
|
||||||
|
|
||||||
//auto plater = wxGetApp().plater();
|
//auto plater = wxGetApp().plater();
|
||||||
@ -1139,18 +1172,18 @@ void GLGizmoEmboss::draw_window()
|
|||||||
|
|
||||||
void GLGizmoEmboss::draw_text_input()
|
void GLGizmoEmboss::draw_text_input()
|
||||||
{
|
{
|
||||||
auto create_range_text = [&mng = m_style_manager, &text = m_text, &exist_unknown = m_text_contain_unknown_glyph]() {
|
auto create_range_text_prep = [&mng = m_style_manager, &text = m_text, &exist_unknown = m_text_contain_unknown_glyph]() {
|
||||||
auto& ff = mng.get_font_file_with_cache();
|
auto& ff = mng.get_font_file_with_cache();
|
||||||
assert(ff.has_value());
|
assert(ff.has_value());
|
||||||
const auto &cn = mng.get_font_prop().collection_number;
|
const auto &cn = mng.get_font_prop().collection_number;
|
||||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||||
return Emboss::create_range_text(text, *ff.font_file, font_index, &exist_unknown);
|
return create_range_text(text, *ff.font_file, font_index, &exist_unknown);
|
||||||
};
|
};
|
||||||
|
|
||||||
ImFont *imgui_font = m_style_manager.get_imgui_font();
|
ImFont *imgui_font = m_style_manager.get_imgui_font();
|
||||||
if (imgui_font == nullptr) {
|
if (imgui_font == nullptr) {
|
||||||
// try create new imgui font
|
// try create new imgui font
|
||||||
m_style_manager.create_imgui_font(create_range_text());
|
m_style_manager.create_imgui_font(create_range_text_prep());
|
||||||
imgui_font = m_style_manager.get_imgui_font();
|
imgui_font = m_style_manager.get_imgui_font();
|
||||||
}
|
}
|
||||||
bool exist_font =
|
bool exist_font =
|
||||||
@ -1190,10 +1223,10 @@ void GLGizmoEmboss::draw_text_input()
|
|||||||
if (prop.line_gap.has_value())
|
if (prop.line_gap.has_value())
|
||||||
append_warning(_u8L("Line gap"), _u8L("Unsupported visualization of gap between lines inside text input."));
|
append_warning(_u8L("Line gap"), _u8L("Unsupported visualization of gap between lines inside text input."));
|
||||||
auto &ff = m_style_manager.get_font_file_with_cache();
|
auto &ff = m_style_manager.get_font_file_with_cache();
|
||||||
float imgui_size = EmbossStyleManager::get_imgui_font_size(prop, *ff.font_file);
|
float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file);
|
||||||
if (imgui_size > EmbossStyleManager::max_imgui_font_size)
|
if (imgui_size > StyleManager::max_imgui_font_size)
|
||||||
append_warning(_u8L("To tall"), _u8L("Diminished font height inside text input."));
|
append_warning(_u8L("To tall"), _u8L("Diminished font height inside text input."));
|
||||||
if (imgui_size < EmbossStyleManager::min_imgui_font_size)
|
if (imgui_size < StyleManager::min_imgui_font_size)
|
||||||
append_warning(_u8L("To small"), _u8L("Enlarged font height inside text input."));
|
append_warning(_u8L("To small"), _u8L("Enlarged font height inside text input."));
|
||||||
if (!who.empty()) warning = GUI::format(_L("%1% is NOT shown."), who);
|
if (!who.empty()) warning = GUI::format(_L("%1% is NOT shown."), who);
|
||||||
}
|
}
|
||||||
@ -1217,7 +1250,7 @@ void GLGizmoEmboss::draw_text_input()
|
|||||||
const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll;
|
const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll;
|
||||||
if (ImGui::InputTextMultiline("##Text", &m_text, text_size, flags)) {
|
if (ImGui::InputTextMultiline("##Text", &m_text, text_size, flags)) {
|
||||||
process();
|
process();
|
||||||
range_text = create_range_text();
|
range_text = create_range_text_prep();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exist_font) ImGui::PopFont();
|
if (exist_font) ImGui::PopFont();
|
||||||
@ -1571,7 +1604,7 @@ void GLGizmoEmboss::draw_font_list()
|
|||||||
// When is unknown font is inside .3mf only font selection is allowed
|
// When is unknown font is inside .3mf only font selection is allowed
|
||||||
// Stop Imgui disable + Guard again start disabling
|
// Stop Imgui disable + Guard again start disabling
|
||||||
ScopeGuard unknown_font_sc;
|
ScopeGuard unknown_font_sc;
|
||||||
if (m_is_unknown_font) {
|
if (m_is_unknown_font) {
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
unknown_font_sc = ScopeGuard([&]() {
|
unknown_font_sc = ScopeGuard([&]() {
|
||||||
m_imgui->disabled_begin(true);
|
m_imgui->disabled_begin(true);
|
||||||
@ -1963,7 +1996,7 @@ void GLGizmoEmboss::draw_delete_style_button() {
|
|||||||
|
|
||||||
void GLGizmoEmboss::draw_revert_all_styles_button() {
|
void GLGizmoEmboss::draw_revert_all_styles_button() {
|
||||||
if (draw_button(IconType::revert_all)) {
|
if (draw_button(IconType::revert_all)) {
|
||||||
m_style_manager = EmbossStyleManager(m_imgui->get_glyph_ranges());
|
m_style_manager = StyleManager(m_imgui->get_glyph_ranges());
|
||||||
m_style_manager.init(nullptr, create_default_styles());
|
m_style_manager.init(nullptr, create_default_styles());
|
||||||
assert(m_style_manager.get_font_file_with_cache().has_value());
|
assert(m_style_manager.get_font_file_with_cache().has_value());
|
||||||
process();
|
process();
|
||||||
@ -2029,7 +2062,7 @@ void GLGizmoEmboss::draw_style_list() {
|
|||||||
m_style_manager.init_style_images(m_gui_cfg->max_style_image_size, m_text);
|
m_style_manager.init_style_images(m_gui_cfg->max_style_image_size, m_text);
|
||||||
m_style_manager.init_trunc_names(max_style_name_width);
|
m_style_manager.init_trunc_names(max_style_name_width);
|
||||||
std::optional<std::pair<size_t,size_t>> swap_indexes;
|
std::optional<std::pair<size_t,size_t>> swap_indexes;
|
||||||
const std::vector<EmbossStyleManager::Item> &styles = m_style_manager.get_styles();
|
const std::vector<StyleManager::Item> &styles = m_style_manager.get_styles();
|
||||||
for (const auto &item : styles) {
|
for (const auto &item : styles) {
|
||||||
size_t index = &item - &styles.front();
|
size_t index = &item - &styles.front();
|
||||||
const EmbossStyle &style = item.style;
|
const EmbossStyle &style = item.style;
|
||||||
@ -2038,7 +2071,7 @@ void GLGizmoEmboss::draw_style_list() {
|
|||||||
bool is_selected = (index == m_style_manager.get_style_index());
|
bool is_selected = (index == m_style_manager.get_style_index());
|
||||||
|
|
||||||
ImVec2 select_size(0,m_gui_cfg->max_style_image_size.y()); // 0,0 --> calculate in draw
|
ImVec2 select_size(0,m_gui_cfg->max_style_image_size.y()); // 0,0 --> calculate in draw
|
||||||
const std::optional<EmbossStyleManager::StyleImage> &img = item.image;
|
const std::optional<StyleManager::StyleImage> &img = item.image;
|
||||||
// allow click delete button
|
// allow click delete button
|
||||||
ImGuiSelectableFlags_ flags = ImGuiSelectableFlags_AllowItemOverlap;
|
ImGuiSelectableFlags_ flags = ImGuiSelectableFlags_AllowItemOverlap;
|
||||||
if (ImGui::Selectable(item.truncated_name.c_str(), is_selected, flags, select_size)) {
|
if (ImGui::Selectable(item.truncated_name.c_str(), is_selected, flags, select_size)) {
|
||||||
@ -2918,9 +2951,9 @@ bool GLGizmoEmboss::choose_svg_file()
|
|||||||
for (const auto &p : polys) bb.merge(p.contour.points);
|
for (const auto &p : polys) bb.merge(p.contour.points);
|
||||||
const FontProp &fp = m_style_manager.get_style().prop;
|
const FontProp &fp = m_style_manager.get_style().prop;
|
||||||
float scale = fp.size_in_mm / std::max(bb.max.x(), bb.max.y());
|
float scale = fp.size_in_mm / std::max(bb.max.x(), bb.max.y());
|
||||||
auto project = std::make_unique<Emboss::ProjectScale>(
|
auto project = std::make_unique<ProjectScale>(
|
||||||
std::make_unique<Emboss::ProjectZ>(fp.emboss / scale), scale);
|
std::make_unique<ProjectZ>(fp.emboss / scale), scale);
|
||||||
indexed_triangle_set its = Emboss::polygons2model(polys, *project);
|
indexed_triangle_set its = polygons2model(polys, *project);
|
||||||
return false;
|
return false;
|
||||||
// test store:
|
// test store:
|
||||||
// for (auto &poly : polys) poly.scale(1e5);
|
// for (auto &poly : polys) poly.scale(1e5);
|
||||||
@ -2929,41 +2962,38 @@ bool GLGizmoEmboss::choose_svg_file()
|
|||||||
//return add_volume(name, its);
|
//return add_volume(name, its);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbossDataBase GLGizmoEmboss::create_emboss_data_base() {
|
DataBase priv::create_emboss_data_base(const std::string &text, StyleManager& style_manager)
|
||||||
|
{
|
||||||
auto create_volume_name = [&]() {
|
auto create_volume_name = [&]() {
|
||||||
bool contain_enter = m_text.find('\n') != std::string::npos;
|
bool contain_enter = text.find('\n') != std::string::npos;
|
||||||
std::string text_fixed;
|
std::string text_fixed;
|
||||||
if (contain_enter) {
|
if (contain_enter) {
|
||||||
// change enters to space
|
// change enters to space
|
||||||
text_fixed = m_text; // copy
|
text_fixed = text; // copy
|
||||||
std::replace(text_fixed.begin(), text_fixed.end(), '\n', ' ');
|
std::replace(text_fixed.begin(), text_fixed.end(), '\n', ' ');
|
||||||
}
|
}
|
||||||
return _u8L("Text") + " - " + ((contain_enter) ? text_fixed : m_text);
|
return _u8L("Text") + " - " + ((contain_enter) ? text_fixed : text);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto create_configuration = [&]() -> TextConfiguration {
|
auto create_configuration = [&]() -> TextConfiguration {
|
||||||
if (!m_style_manager.is_activ_font()) {
|
if (!style_manager.is_activ_font()) {
|
||||||
std::string default_text_for_emboss = _u8L("Embossed text");
|
std::string default_text_for_emboss = _u8L("Embossed text");
|
||||||
EmbossStyle es = m_style_manager.get_style();
|
EmbossStyle es = style_manager.get_style();
|
||||||
TextConfiguration tc{es, default_text_for_emboss};
|
TextConfiguration tc{es, default_text_for_emboss};
|
||||||
// TODO: investigate how to initialize
|
// TODO: investigate how to initialize
|
||||||
return tc;
|
return tc;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbossStyle &es = m_style_manager.get_style();
|
EmbossStyle &es = style_manager.get_style();
|
||||||
// actualize font path - during changes in gui it could be corrupted
|
// actualize font path - during changes in gui it could be corrupted
|
||||||
// volume must store valid path
|
// volume must store valid path
|
||||||
assert(m_style_manager.get_wx_font().has_value());
|
assert(style_manager.get_wx_font().has_value());
|
||||||
assert(es.path.compare(WxFontUtils::store_wxFont(*m_style_manager.get_wx_font())) == 0);
|
assert(es.path.compare(WxFontUtils::store_wxFont(*style_manager.get_wx_font())) == 0);
|
||||||
//style.path = WxFontUtils::store_wxFont(*m_style_manager.get_wx_font());
|
// style.path = WxFontUtils::store_wxFont(*m_style_manager.get_wx_font());
|
||||||
return TextConfiguration{es, m_text};
|
return TextConfiguration{es, text};
|
||||||
};
|
|
||||||
|
|
||||||
return EmbossDataBase{
|
|
||||||
m_style_manager.get_font_file_with_cache(),
|
|
||||||
create_configuration(),
|
|
||||||
create_volume_name()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), create_configuration(), create_volume_name()};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmoEmboss::load_configuration(ModelVolume *volume)
|
bool GLGizmoEmboss::load_configuration(ModelVolume *volume)
|
||||||
@ -2974,7 +3004,7 @@ bool GLGizmoEmboss::load_configuration(ModelVolume *volume)
|
|||||||
const TextConfiguration &tc = *tc_opt;
|
const TextConfiguration &tc = *tc_opt;
|
||||||
const EmbossStyle &style = tc.style;
|
const EmbossStyle &style = tc.style;
|
||||||
|
|
||||||
auto has_same_name = [&style](const EmbossStyleManager::Item &style_item) -> bool {
|
auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool {
|
||||||
const EmbossStyle &es = style_item.style;
|
const EmbossStyle &es = style_item.style;
|
||||||
return es.name == style.name;
|
return es.name == style.name;
|
||||||
};
|
};
|
||||||
|
@ -34,8 +34,6 @@ namespace Slic3r{
|
|||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
class MeshRaycaster;
|
class MeshRaycaster;
|
||||||
struct EmbossDataBase;
|
|
||||||
|
|
||||||
class GLGizmoEmboss : public GLGizmoBase
|
class GLGizmoEmboss : public GLGizmoBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -157,8 +155,6 @@ private:
|
|||||||
bool choose_true_type_file();
|
bool choose_true_type_file();
|
||||||
bool choose_svg_file();
|
bool choose_svg_file();
|
||||||
|
|
||||||
// prepare base data for emboss text
|
|
||||||
EmbossDataBase create_emboss_data_base();
|
|
||||||
bool load_configuration(ModelVolume *volume);
|
bool load_configuration(ModelVolume *volume);
|
||||||
|
|
||||||
// When open text loaded from .3mf it could be written with unknown font
|
// When open text loaded from .3mf it could be written with unknown font
|
||||||
@ -225,7 +221,7 @@ private:
|
|||||||
std::optional<ImVec2> m_set_window_offset;
|
std::optional<ImVec2> m_set_window_offset;
|
||||||
bool m_is_advanced_edit_style = false;
|
bool m_is_advanced_edit_style = false;
|
||||||
|
|
||||||
EmbossStyleManager m_style_manager;
|
Emboss::StyleManager m_style_manager;
|
||||||
|
|
||||||
struct FaceName{
|
struct FaceName{
|
||||||
wxString wx_name;
|
wxString wx_name;
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
using namespace Slic3r::Emboss;
|
||||||
using namespace Slic3r::GUI;
|
using namespace Slic3r::GUI;
|
||||||
|
using namespace Slic3r::GUI::Emboss;
|
||||||
|
|
||||||
|
|
||||||
CreateFontStyleImagesJob::CreateFontStyleImagesJob(
|
CreateFontStyleImagesJob::CreateFontStyleImagesJob(
|
||||||
EmbossStyleManager::StyleImagesData &&input)
|
StyleManager::StyleImagesData &&input)
|
||||||
: m_input(std::move(input))
|
: m_input(std::move(input))
|
||||||
{
|
{
|
||||||
assert(m_input.result != nullptr);
|
assert(m_input.result != nullptr);
|
||||||
@ -34,15 +36,15 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
|
|||||||
// create shapes and calc size (bounding boxes)
|
// create shapes and calc size (bounding boxes)
|
||||||
std::vector<ExPolygons> name_shapes(m_input.styles.size());
|
std::vector<ExPolygons> name_shapes(m_input.styles.size());
|
||||||
std::vector<double> scales(m_input.styles.size());
|
std::vector<double> scales(m_input.styles.size());
|
||||||
images = std::vector<EmbossStyleManager::StyleImage>(m_input.styles.size());
|
images = std::vector<StyleManager::StyleImage>(m_input.styles.size());
|
||||||
|
|
||||||
for (auto &item : m_input.styles) {
|
for (auto &item : m_input.styles) {
|
||||||
size_t index = &item - &m_input.styles.front();
|
size_t index = &item - &m_input.styles.front();
|
||||||
ExPolygons &shapes = name_shapes[index];
|
ExPolygons &shapes = name_shapes[index];
|
||||||
shapes = Emboss::text2shapes(item.font, m_input.text.c_str(), item.prop);
|
shapes = text2shapes(item.font, m_input.text.c_str(), item.prop);
|
||||||
|
|
||||||
// create image description
|
// create image description
|
||||||
EmbossStyleManager::StyleImage &image = images[index];
|
StyleManager::StyleImage &image = images[index];
|
||||||
BoundingBox &bounding_box = image.bounding_box;
|
BoundingBox &bounding_box = image.bounding_box;
|
||||||
for (ExPolygon &shape : shapes)
|
for (ExPolygon &shape : shapes)
|
||||||
bounding_box.merge(BoundingBox(shape.contour.points));
|
bounding_box.merge(BoundingBox(shape.contour.points));
|
||||||
@ -56,7 +58,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
|
|||||||
const auto &cn = item.prop.collection_number;
|
const auto &cn = item.prop.collection_number;
|
||||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||||
double unit_per_em = item.font.font_file->infos[font_index].unit_per_em;
|
double unit_per_em = item.font.font_file->infos[font_index].unit_per_em;
|
||||||
double scale = item.prop.size_in_mm / unit_per_em * Emboss::SHAPE_SCALE * ppm;
|
double scale = item.prop.size_in_mm / unit_per_em * SHAPE_SCALE * ppm;
|
||||||
scales[index] = scale;
|
scales[index] = scale;
|
||||||
|
|
||||||
//double scale = font_prop.size_in_mm * SCALING_FACTOR;
|
//double scale = font_prop.size_in_mm * SCALING_FACTOR;
|
||||||
@ -77,14 +79,14 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
|
|||||||
// arrange bounding boxes
|
// arrange bounding boxes
|
||||||
int offset_y = 0;
|
int offset_y = 0;
|
||||||
width = 0;
|
width = 0;
|
||||||
for (EmbossStyleManager::StyleImage &image : images) {
|
for (StyleManager::StyleImage &image : images) {
|
||||||
image.offset.y() = offset_y;
|
image.offset.y() = offset_y;
|
||||||
offset_y += image.tex_size.y+1;
|
offset_y += image.tex_size.y+1;
|
||||||
if (width < image.tex_size.x)
|
if (width < image.tex_size.x)
|
||||||
width = image.tex_size.x;
|
width = image.tex_size.x;
|
||||||
}
|
}
|
||||||
height = offset_y;
|
height = offset_y;
|
||||||
for (EmbossStyleManager::StyleImage &image : images) {
|
for (StyleManager::StyleImage &image : images) {
|
||||||
const Point &o = image.offset;
|
const Point &o = image.offset;
|
||||||
const ImVec2 &s = image.tex_size;
|
const ImVec2 &s = image.tex_size;
|
||||||
image.uv0 = ImVec2(o.x() / (double) width,
|
image.uv0 = ImVec2(o.x() / (double) width,
|
||||||
@ -97,7 +99,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl)
|
|||||||
pixels = std::vector<unsigned char>(4*width * height, {255});
|
pixels = std::vector<unsigned char>(4*width * height, {255});
|
||||||
|
|
||||||
// upload sub textures
|
// upload sub textures
|
||||||
for (EmbossStyleManager::StyleImage &image : images) {
|
for (StyleManager::StyleImage &image : images) {
|
||||||
sla::Resolution resolution(image.tex_size.x, image.tex_size.y);
|
sla::Resolution resolution(image.tex_size.x, image.tex_size.y);
|
||||||
size_t index = &image - &images.front();
|
size_t index = &image - &images.front();
|
||||||
double pixel_dim = SCALING_FACTOR / scales[index];
|
double pixel_dim = SCALING_FACTOR / scales[index];
|
||||||
@ -146,7 +148,7 @@ void CreateFontStyleImagesJob::finalize(bool canceled, std::exception_ptr &)
|
|||||||
|
|
||||||
// set up texture id
|
// set up texture id
|
||||||
void *texture_id = (void *) (intptr_t) tex_id;
|
void *texture_id = (void *) (intptr_t) tex_id;
|
||||||
for (EmbossStyleManager::StyleImage &image : images)
|
for (StyleManager::StyleImage &image : images)
|
||||||
image.texture_id = texture_id;
|
image.texture_id = texture_id;
|
||||||
|
|
||||||
// move to result
|
// move to result
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#include "slic3r/Utils/EmbossStyleManager.hpp"
|
#include "slic3r/Utils/EmbossStyleManager.hpp"
|
||||||
#include "Job.hpp"
|
#include "Job.hpp"
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI::Emboss {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create texture with name of styles written by its style
|
/// Create texture with name of styles written by its style
|
||||||
@ -15,7 +15,7 @@ namespace Slic3r::GUI {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class CreateFontStyleImagesJob : public Job
|
class CreateFontStyleImagesJob : public Job
|
||||||
{
|
{
|
||||||
EmbossStyleManager::StyleImagesData m_input;
|
StyleManager::StyleImagesData m_input;
|
||||||
|
|
||||||
// Output data
|
// Output data
|
||||||
// texture size
|
// texture size
|
||||||
@ -23,10 +23,10 @@ class CreateFontStyleImagesJob : public Job
|
|||||||
// texture data
|
// texture data
|
||||||
std::vector<unsigned char> pixels;
|
std::vector<unsigned char> pixels;
|
||||||
// descriptors of sub textures
|
// descriptors of sub textures
|
||||||
std::vector<EmbossStyleManager::StyleImage> images;
|
std::vector<StyleManager::StyleImage> images;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CreateFontStyleImagesJob(EmbossStyleManager::StyleImagesData &&input);
|
CreateFontStyleImagesJob(StyleManager::StyleImagesData &&input);
|
||||||
void process(Ctl &ctl) override;
|
void process(Ctl &ctl) override;
|
||||||
void finalize(bool canceled, std::exception_ptr &) override;
|
void finalize(bool canceled, std::exception_ptr &) override;
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,9 @@
|
|||||||
#include "slic3r/Utils/UndoRedo.hpp"
|
#include "slic3r/Utils/UndoRedo.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using namespace GUI;
|
using namespace Slic3r::Emboss;
|
||||||
|
using namespace Slic3r::GUI;
|
||||||
|
using namespace Slic3r::GUI::Emboss;
|
||||||
|
|
||||||
// private namespace
|
// private namespace
|
||||||
namespace priv{
|
namespace priv{
|
||||||
@ -31,11 +33,12 @@ constexpr float safe_extension = 1.0f;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool check(const EmbossDataBase &input, bool check_fontfile = true);
|
bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false);
|
||||||
bool check(const EmbossDataCreateVolume &input, bool is_main_thread = false);
|
bool check(const DataCreateVolume &input, bool is_main_thread = false);
|
||||||
bool check(const EmbossDataCreateObject &input);
|
bool check(const DataCreateObject &input);
|
||||||
bool check(const EmbossDataUpdate &input, bool is_main_thread = false);
|
bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false);
|
||||||
bool check(const UseSurfaceData &input, bool is_main_thread = false);
|
bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||||
|
bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
|
||||||
|
|
||||||
// <summary>
|
// <summary>
|
||||||
/// Try to create mesh from text
|
/// Try to create mesh from text
|
||||||
@ -46,14 +49,8 @@ bool check(const UseSurfaceData &input, bool is_main_thread = false);
|
|||||||
/// NOTE: Cache glyphs is changed</param>
|
/// NOTE: Cache glyphs is changed</param>
|
||||||
/// <param name="was_canceled">To check if process was canceled</param>
|
/// <param name="was_canceled">To check if process was canceled</param>
|
||||||
/// <returns>Triangle mesh model</returns>
|
/// <returns>Triangle mesh model</returns>
|
||||||
template<typename Fnc>
|
template<typename Fnc> static TriangleMesh try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled);
|
||||||
static TriangleMesh try_create_mesh(const EmbossDataBase &input,
|
template<typename Fnc> static TriangleMesh create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl &ctl);
|
||||||
Emboss::FontFileWithCache &font,
|
|
||||||
Fnc was_canceled);
|
|
||||||
template<typename Fnc>
|
|
||||||
static TriangleMesh create_mesh(EmbossDataBase &input,
|
|
||||||
Fnc was_canceled,
|
|
||||||
Job::Ctl &ctl);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create default mesh for embossed text
|
/// Create default mesh for embossed text
|
||||||
@ -66,7 +63,18 @@ static TriangleMesh create_default_mesh();
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mesh">New mesh data</param>
|
/// <param name="mesh">New mesh data</param>
|
||||||
/// <param name="data">Text configuration, ...</param>
|
/// <param name="data">Text configuration, ...</param>
|
||||||
static void update_volume(TriangleMesh &&mesh, const EmbossDataUpdate &data);
|
static void update_volume(TriangleMesh &&mesh, const DataUpdate &data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add new volume to object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mesh">triangles of new volume</param>
|
||||||
|
/// <param name="object_idx">Object where to add volume</param>
|
||||||
|
/// <param name="type">Type of new volume</param>
|
||||||
|
/// <param name="trmat">Transformation of volume inside of object</param>
|
||||||
|
/// <param name="data">Text configuration and New VolumeName</param>
|
||||||
|
static void create_volume(TriangleMesh &&mesh, const size_t object_idx,
|
||||||
|
const ModelVolumeType type, const Transform3d trmat, const DataBase &data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Select Volume from objects
|
/// Select Volume from objects
|
||||||
@ -74,8 +82,7 @@ static void update_volume(TriangleMesh &&mesh, const EmbossDataUpdate &data);
|
|||||||
/// <param name="objects">All objects in scene</param>
|
/// <param name="objects">All objects in scene</param>
|
||||||
/// <param name="volume_id">Identifier of volume in object</param>
|
/// <param name="volume_id">Identifier of volume in object</param>
|
||||||
/// <returns>Pointer to volume when exist otherwise nullptr</returns>
|
/// <returns>Pointer to volume when exist otherwise nullptr</returns>
|
||||||
static ModelVolume *get_volume(ModelObjectPtrs &objects,
|
static ModelVolume *get_volume(ModelObjectPtrs &objects, const ObjectID &volume_id);
|
||||||
const ObjectID &volume_id);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create projection for cut surface from mesh
|
/// Create projection for cut surface from mesh
|
||||||
@ -84,10 +91,7 @@ static ModelVolume *get_volume(ModelObjectPtrs &objects,
|
|||||||
/// <param name="shape_scale">Convert shape to milimeters</param>
|
/// <param name="shape_scale">Convert shape to milimeters</param>
|
||||||
/// <param name="z_range">Bounding box 3d of model volume for projection ranges</param>
|
/// <param name="z_range">Bounding box 3d of model volume for projection ranges</param>
|
||||||
/// <returns>Orthogonal cut_projection</returns>
|
/// <returns>Orthogonal cut_projection</returns>
|
||||||
static Emboss::OrthoProject create_projection_for_cut(
|
static OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const std::pair<float, float> &z_range);
|
||||||
Transform3d tr,
|
|
||||||
double shape_scale,
|
|
||||||
const std::pair<float, float> &z_range);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create tranformation for emboss Cutted surface
|
/// Create tranformation for emboss Cutted surface
|
||||||
@ -97,41 +101,43 @@ static Emboss::OrthoProject create_projection_for_cut(
|
|||||||
/// <param name="tr">Text voliume transformation inside object</param>
|
/// <param name="tr">Text voliume transformation inside object</param>
|
||||||
/// <param name="cut">Cutted surface from model</param>
|
/// <param name="cut">Cutted surface from model</param>
|
||||||
/// <returns>Projection</returns>
|
/// <returns>Projection</returns>
|
||||||
static Emboss::OrthoProject3d create_emboss_projection(
|
static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut);
|
||||||
bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut);
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cut surface into triangle mesh
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input1">(can't be const - cache of font)</param>
|
||||||
|
/// <param name="input2">SurfaceVolume data</param>
|
||||||
|
/// <param name="was_canceled">Check to interupt execution</param>
|
||||||
|
/// <returns>Extruded object from cuted surace</returns>
|
||||||
|
static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, std::function<bool()> was_canceled);
|
||||||
|
|
||||||
static void create_message(const std::string &message); // only in finalize
|
static void create_message(const std::string &message); // only in finalize
|
||||||
static bool process(std::exception_ptr &eptr);
|
static bool process(std::exception_ptr &eptr);
|
||||||
|
|
||||||
class EmbossJobException : public std::runtime_error
|
class JobException : public std::runtime_error {
|
||||||
{ public: EmbossJobException(const char* message):runtime_error(message){}};
|
public: JobException(const char* message):runtime_error(message){}};
|
||||||
|
|
||||||
}// namespace priv
|
}// namespace priv
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
/// Create Volume
|
/// Create Volume
|
||||||
EmbossCreateVolumeJob::EmbossCreateVolumeJob(EmbossDataCreateVolume &&input)
|
CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input)
|
||||||
: m_input(std::move(input))
|
: m_input(std::move(input))
|
||||||
{
|
{
|
||||||
assert(priv::check(m_input, true));
|
assert(priv::check(m_input, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossCreateVolumeJob::process(Ctl &ctl) {
|
void CreateVolumeJob::process(Ctl &ctl) {
|
||||||
if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
|
if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
|
||||||
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
|
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
|
||||||
m_result = priv::create_mesh(m_input, was_canceled, ctl);
|
m_result = priv::create_mesh(m_input, was_canceled, ctl);
|
||||||
if (was_canceled()) return;
|
// center result
|
||||||
|
Vec3f c = m_result.bounding_box().center().cast<float>();
|
||||||
// Create new volume inside of object
|
if (!c.isApprox(Vec3f::Zero())) m_result.translate(-c);
|
||||||
const FontProp &font_prop = m_input.text_configuration.style.prop;
|
|
||||||
Transform3d surface_trmat = Emboss::create_transformation_onto_surface(
|
|
||||||
m_input.hit.position, m_input.hit.normal);
|
|
||||||
Emboss::apply_transformation(font_prop, surface_trmat);
|
|
||||||
m_transformation = m_input.hit_instance_tr.inverse() *
|
|
||||||
m_input.hit_object_tr * surface_trmat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossCreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||||
// doesn't care about exception when process was canceled by user
|
// doesn't care about exception when process was canceled by user
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
eptr = nullptr;
|
eptr = nullptr;
|
||||||
@ -141,74 +147,19 @@ void EmbossCreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
|||||||
if (m_result.its.empty())
|
if (m_result.its.empty())
|
||||||
return priv::create_message(_u8L("Can't create empty volume."));
|
return priv::create_message(_u8L("Can't create empty volume."));
|
||||||
|
|
||||||
GUI_App &app = wxGetApp();
|
priv::create_volume(std::move(m_result), m_input.object_idx, m_input.volume_type, m_input.trmat, m_input);
|
||||||
Plater *plater = app.plater();
|
|
||||||
ObjectList *obj_list = app.obj_list();
|
|
||||||
GLCanvas3D *canvas = plater->canvas3D();
|
|
||||||
ModelObjectPtrs &objects = plater->model().objects;
|
|
||||||
|
|
||||||
// create volume in object
|
|
||||||
size_t object_idx = m_input.object_idx;
|
|
||||||
|
|
||||||
// Parent object for text volume was propably removed.
|
|
||||||
// Assumption: User know what he does, so text volume is no more needed.
|
|
||||||
if (objects.size() <= object_idx)
|
|
||||||
return;
|
|
||||||
|
|
||||||
plater->take_snapshot(_L("Add Emboss text Volume"));
|
|
||||||
|
|
||||||
ModelObject *obj = objects[object_idx];
|
|
||||||
ModelVolumeType type = m_input.volume_type;
|
|
||||||
|
|
||||||
ModelVolume *volume = obj->add_volume(std::move(m_result), type);
|
|
||||||
|
|
||||||
// set a default extruder value, since user can't add it manually
|
|
||||||
volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
|
||||||
|
|
||||||
// do not allow model reload from disk
|
|
||||||
volume->source.is_from_builtin_objects = true;
|
|
||||||
|
|
||||||
volume->name = m_input.volume_name;
|
|
||||||
volume->text_configuration = std::move(m_input.text_configuration);
|
|
||||||
volume->set_transformation(m_transformation);
|
|
||||||
|
|
||||||
// update volume name in object list
|
|
||||||
// updata selection after new volume added
|
|
||||||
// change name of volume in right panel
|
|
||||||
// select only actual volume
|
|
||||||
// when new volume is created change selection to this volume
|
|
||||||
auto add_to_selection = [volume](const ModelVolume *vol) {
|
|
||||||
return vol == volume;
|
|
||||||
};
|
|
||||||
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(
|
|
||||||
m_input.object_idx, add_to_selection);
|
|
||||||
if (!sel.IsEmpty()) obj_list->select_item(sel.front());
|
|
||||||
|
|
||||||
// update printable state on canvas
|
|
||||||
if (type == ModelVolumeType::MODEL_PART)
|
|
||||||
canvas->update_instance_printable_state_for_object(object_idx);
|
|
||||||
|
|
||||||
obj_list->selection_changed();
|
|
||||||
|
|
||||||
// Now is valid text volume selected open emboss gizmo
|
|
||||||
GLGizmosManager &manager = canvas->get_gizmos_manager();
|
|
||||||
if (manager.get_current_type() != GLGizmosManager::Emboss)
|
|
||||||
manager.open_gizmo(GLGizmosManager::Emboss);
|
|
||||||
|
|
||||||
// redraw scene
|
|
||||||
canvas->reload_scene(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
/// Create Object
|
/// Create Object
|
||||||
EmbossCreateObjectJob::EmbossCreateObjectJob(EmbossDataCreateObject &&input)
|
CreateObjectJob::CreateObjectJob(DataCreateObject &&input)
|
||||||
: m_input(std::move(input))
|
: m_input(std::move(input))
|
||||||
{
|
{
|
||||||
assert(priv::check(m_input));
|
assert(priv::check(m_input));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossCreateObjectJob::process(Ctl &ctl)
|
void CreateObjectJob::process(Ctl &ctl)
|
||||||
{
|
{
|
||||||
if (!priv::check(m_input))
|
if (!priv::check(m_input))
|
||||||
throw std::runtime_error("Bad input data for EmbossCreateObjectJob.");
|
throw std::runtime_error("Bad input data for EmbossCreateObjectJob.");
|
||||||
@ -240,7 +191,7 @@ void EmbossCreateObjectJob::process(Ctl &ctl)
|
|||||||
m_transformation = Transform3d(tt);
|
m_transformation = Transform3d(tt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossCreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
|
void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||||
{
|
{
|
||||||
// doesn't care about exception when process was canceled by user
|
// doesn't care about exception when process was canceled by user
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
@ -279,13 +230,13 @@ void EmbossCreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
|
|||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
/// Update Volume
|
/// Update Volume
|
||||||
EmbossUpdateJob::EmbossUpdateJob(EmbossDataUpdate&& input)
|
UpdateJob::UpdateJob(DataUpdate&& input)
|
||||||
: m_input(std::move(input))
|
: m_input(std::move(input))
|
||||||
{
|
{
|
||||||
assert(priv::check(m_input, true));
|
assert(priv::check(m_input, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossUpdateJob::process(Ctl &ctl)
|
void UpdateJob::process(Ctl &ctl)
|
||||||
{
|
{
|
||||||
if (!priv::check(m_input))
|
if (!priv::check(m_input))
|
||||||
throw std::runtime_error("Bad input data for EmbossUpdateJob.");
|
throw std::runtime_error("Bad input data for EmbossUpdateJob.");
|
||||||
@ -297,16 +248,14 @@ void EmbossUpdateJob::process(Ctl &ctl)
|
|||||||
m_result = priv::try_create_mesh(m_input, m_input.font_file, was_canceled);
|
m_result = priv::try_create_mesh(m_input, m_input.font_file, was_canceled);
|
||||||
if (was_canceled()) return;
|
if (was_canceled()) return;
|
||||||
if (m_result.its.empty())
|
if (m_result.its.empty())
|
||||||
throw priv::EmbossJobException(
|
throw priv::JobException(_u8L("Created text volume is empty. Change text or font.").c_str());
|
||||||
_u8L("Created text volume is empty. Change text or "
|
|
||||||
"font.").c_str());
|
|
||||||
|
|
||||||
// center triangle mesh
|
// center triangle mesh
|
||||||
Vec3d shift = m_result.bounding_box().center();
|
Vec3d shift = m_result.bounding_box().center();
|
||||||
m_result.translate(-shift.cast<float>());
|
m_result.translate(-shift.cast<float>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
|
void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||||
{
|
{
|
||||||
// doesn't care about exception when process was canceled by user
|
// doesn't care about exception when process was canceled by user
|
||||||
if (canceled || m_input.cancel->load()) {
|
if (canceled || m_input.cancel->load()) {
|
||||||
@ -317,19 +266,14 @@ void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
|
|||||||
priv::update_volume(std::move(m_result), m_input);
|
priv::update_volume(std::move(m_result), m_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
UseSurfaceData::ModelSources UseSurfaceData::create_sources(
|
namespace Slic3r::GUI::Emboss {
|
||||||
const ModelVolume *text_volume)
|
|
||||||
{
|
|
||||||
if (text_volume == nullptr) return {};
|
|
||||||
if (!text_volume->text_configuration.has_value()) return {};
|
|
||||||
const ModelVolumePtrs &volumes = text_volume->get_object()->volumes;
|
|
||||||
// no other volume in object
|
|
||||||
if (volumes.size() <= 1) return {};
|
|
||||||
|
|
||||||
ModelSources result;
|
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id)
|
||||||
|
{
|
||||||
|
SurfaceVolumeData::ModelSources result;
|
||||||
result.reserve(volumes.size() - 1);
|
result.reserve(volumes.size() - 1);
|
||||||
for (const ModelVolume *v : volumes) {
|
for (const ModelVolume *v : volumes) {
|
||||||
if (v->id() == text_volume->id()) continue;
|
if (text_volume_id.has_value() && v->id().id == *text_volume_id) continue;
|
||||||
// skip modifiers and negative volumes, ...
|
// skip modifiers and negative volumes, ...
|
||||||
if (!v->is_model_part()) continue;
|
if (!v->is_model_part()) continue;
|
||||||
const TriangleMesh &tm = v->mesh();
|
const TriangleMesh &tm = v->mesh();
|
||||||
@ -340,15 +284,60 @@ UseSurfaceData::ModelSources UseSurfaceData::create_sources(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume)
|
||||||
|
{
|
||||||
|
if (text_volume == nullptr) return {};
|
||||||
|
if (!text_volume->text_configuration.has_value()) return {};
|
||||||
|
const ModelVolumePtrs &volumes = text_volume->get_object()->volumes;
|
||||||
|
// no other volume in object
|
||||||
|
if (volumes.size() <= 1) return {};
|
||||||
|
return create_sources(volumes, text_volume->id().id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r::GUI::Emboss
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
/// Cut Surface
|
/// Create Surface volume
|
||||||
UseSurfaceJob::UseSurfaceJob(UseSurfaceData &&input)
|
CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input)
|
||||||
: m_input(std::move(input))
|
: m_input(std::move(input))
|
||||||
{
|
{
|
||||||
assert(priv::check(m_input, true));
|
assert(priv::check(m_input, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UseSurfaceJob::process(Ctl &ctl) {
|
void CreateSurfaceVolumeJob::process(Ctl &ctl) {
|
||||||
|
if (!priv::check(m_input))
|
||||||
|
throw std::runtime_error("Bad input data for CreateSurfaceVolumeJob.");
|
||||||
|
// check cancelation of process
|
||||||
|
auto was_canceled = [&ctl]() -> bool { return ctl.was_canceled(); };
|
||||||
|
m_result = priv::cut_surface(m_input, m_input, was_canceled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
|
||||||
|
// doesn't care about exception when process was canceled by user
|
||||||
|
if (canceled) return;
|
||||||
|
if (priv::process(eptr)) return;
|
||||||
|
|
||||||
|
// TODO: Find better way to Not center volume data when add !!!
|
||||||
|
TriangleMesh mesh = m_result; // Part1: copy
|
||||||
|
|
||||||
|
priv::create_volume(std::move(m_result), m_input.object_idx,
|
||||||
|
m_input.volume_type, m_input.text_tr, m_input);
|
||||||
|
|
||||||
|
// Part2: update volume data
|
||||||
|
//auto vol = wxGetApp().plater()->model().objects[m_input.object_idx]->volumes.back();
|
||||||
|
//UpdateJob::update_volume(vol, std::move(mesh), m_input.text_configuration, m_input.volume_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
/// Cut Surface
|
||||||
|
UpdateSurfaceVolumeJob::UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input)
|
||||||
|
: m_input(std::move(input))
|
||||||
|
{
|
||||||
|
assert(priv::check(m_input, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateSurfaceVolumeJob::process(Ctl &ctl)
|
||||||
|
{
|
||||||
if (!priv::check(m_input))
|
if (!priv::check(m_input))
|
||||||
throw std::runtime_error("Bad input data for UseSurfaceJob.");
|
throw std::runtime_error("Bad input data for UseSurfaceJob.");
|
||||||
|
|
||||||
@ -357,107 +346,25 @@ void UseSurfaceJob::process(Ctl &ctl) {
|
|||||||
if (cancel->load()) return true;
|
if (cancel->load()) return true;
|
||||||
return ctl.was_canceled();
|
return ctl.was_canceled();
|
||||||
};
|
};
|
||||||
|
m_result = priv::cut_surface(m_input, m_input, was_canceled);
|
||||||
const TextConfiguration &tc = m_input.text_configuration;
|
|
||||||
const char *text = tc.text.c_str();
|
|
||||||
const FontProp &fp = tc.style.prop;
|
|
||||||
ExPolygons shapes = Emboss::text2shapes(m_input.font_file, text, fp, was_canceled);
|
|
||||||
if (shapes.empty() || shapes.front().contour.empty())
|
|
||||||
throw priv::EmbossJobException(
|
|
||||||
_u8L("Font doesn't have any shape for given text.").c_str());
|
|
||||||
|
|
||||||
if (was_canceled()) return;
|
|
||||||
|
|
||||||
// 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 Emboss::FontFile &ff = *m_input.font_file.font_file;
|
|
||||||
double shape_scale = Emboss::get_shape_scale(fp, ff);
|
|
||||||
|
|
||||||
size_t biggest_count = 0;
|
|
||||||
const UseSurfaceData::ModelSource *biggest = nullptr;
|
|
||||||
std::vector<size_t> s_to_itss(m_input.sources.size(), std::numeric_limits<size_t>::max());
|
|
||||||
std::vector<indexed_triangle_set> itss;
|
|
||||||
itss.reserve(m_input.sources.size());
|
|
||||||
for (const UseSurfaceData::ModelSource &s : m_input.sources) {
|
|
||||||
Transform3d mesh_tr_inv = s.tr.inverse();
|
|
||||||
Transform3d cut_projection_tr = mesh_tr_inv * m_input.text_tr;
|
|
||||||
std::pair<float, float> z_range{0., 1.};
|
|
||||||
Emboss::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()) continue;
|
|
||||||
if (biggest_count < its.vertices.size()) {
|
|
||||||
biggest_count = its.vertices.size();
|
|
||||||
biggest = &s;
|
|
||||||
}
|
|
||||||
s_to_itss[&s - &m_input.sources.front()] = itss.size();
|
|
||||||
itss.emplace_back(std::move(its));
|
|
||||||
}
|
|
||||||
if (itss.empty())
|
|
||||||
throw priv::EmbossJobException(_u8L("There is no volume in projection direction.").c_str());
|
|
||||||
|
|
||||||
Transform3d tr_inv = biggest->tr.inverse();
|
|
||||||
size_t itss_index = s_to_itss[biggest - &m_input.sources.front()];
|
|
||||||
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
|
|
||||||
for (const UseSurfaceData::ModelSource &s : m_input.sources) {
|
|
||||||
if (&s == biggest) continue;
|
|
||||||
size_t itss_index = s_to_itss[&s - &m_input.sources.front()];
|
|
||||||
if (itss_index == std::numeric_limits<size_t>::max()) continue;
|
|
||||||
Transform3d tr = s.tr * tr_inv;
|
|
||||||
indexed_triangle_set &its = itss[itss_index];
|
|
||||||
its_transform(its, tr);
|
|
||||||
BoundingBoxf3 bb = bounding_box(its);
|
|
||||||
mesh_bb.merge(bb);
|
|
||||||
}
|
|
||||||
|
|
||||||
Transform3d mesh_tr_inv = tr_inv;
|
|
||||||
Transform3d cut_projection_tr = mesh_tr_inv * m_input.text_tr;
|
|
||||||
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()};
|
|
||||||
Emboss::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);
|
|
||||||
|
|
||||||
// Use CGAL to cut surface from triangle mesh
|
|
||||||
SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio);
|
|
||||||
if (cut.empty())
|
|
||||||
throw priv::EmbossJobException(
|
|
||||||
_u8L("There is no valid surface for text projection.").c_str());
|
|
||||||
if (was_canceled()) return;
|
|
||||||
|
|
||||||
// !! Projection needs to transform cut
|
|
||||||
Emboss::OrthoProject3d projection = priv::create_emboss_projection(
|
|
||||||
m_input.is_outside, fp.emboss, emboss_tr, cut);
|
|
||||||
|
|
||||||
indexed_triangle_set new_its = cut2model(cut, projection);
|
|
||||||
assert(!new_its.empty());
|
|
||||||
|
|
||||||
if (was_canceled()) return;
|
|
||||||
//its_write_obj(new_its, "C:/data/temp/projected.obj"); // only debug
|
|
||||||
m_result = TriangleMesh(std::move(new_its));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UseSurfaceJob::finalize(bool canceled, std::exception_ptr &eptr)
|
void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||||
{
|
{
|
||||||
// doesn't care about exception when process was canceled by user
|
// doesn't care about exception when process was canceled by user
|
||||||
if (canceled || m_input.cancel->load()) {
|
if (m_input.cancel->load()) {
|
||||||
eptr = nullptr;
|
eptr = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (canceled) return;
|
||||||
if (priv::process(eptr)) return;
|
if (priv::process(eptr)) return;
|
||||||
priv::update_volume(std::move(m_result), m_input);
|
priv::update_volume(std::move(m_result), m_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////
|
////////////////////////////
|
||||||
/// private namespace implementation
|
/// private namespace implementation
|
||||||
bool priv::check(const EmbossDataBase &input, bool check_fontfile){
|
bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface)
|
||||||
|
{
|
||||||
bool res = true;
|
bool res = true;
|
||||||
if (check_fontfile) {
|
if (check_fontfile) {
|
||||||
assert(input.font_file.has_value());
|
assert(input.font_file.has_value());
|
||||||
@ -469,26 +376,24 @@ bool priv::check(const EmbossDataBase &input, bool check_fontfile){
|
|||||||
res &= !input.text_configuration.text.empty();
|
res &= !input.text_configuration.text.empty();
|
||||||
assert(!input.volume_name.empty());
|
assert(!input.volume_name.empty());
|
||||||
res &= !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;
|
return res;
|
||||||
}
|
}
|
||||||
bool priv::check(const EmbossDataCreateVolume &input, bool is_main_thread) {
|
bool priv::check(const DataCreateVolume &input, bool is_main_thread) {
|
||||||
bool check_fontfile = false;
|
bool check_fontfile = false;
|
||||||
bool res = check((EmbossDataBase) input, check_fontfile);
|
bool res = check((DataBase) input, check_fontfile);
|
||||||
assert(input.volume_type != ModelVolumeType::INVALID);
|
assert(input.volume_type != ModelVolumeType::INVALID);
|
||||||
res &= input.volume_type != ModelVolumeType::INVALID;
|
res &= input.volume_type != ModelVolumeType::INVALID;
|
||||||
assert(input.object_idx >= 0);
|
assert(input.object_idx >= 0);
|
||||||
res &= input.object_idx >= 0;
|
res &= input.object_idx >= 0;
|
||||||
if (is_main_thread)
|
if (is_main_thread)
|
||||||
assert((size_t)input.object_idx < wxGetApp().model().objects.size());
|
assert((size_t)input.object_idx < wxGetApp().model().objects.size());
|
||||||
assert(input.screen_coor.x() >= 0.);
|
|
||||||
res &= input.screen_coor.x() >= 0.;
|
|
||||||
assert(input.screen_coor.y() >= 0.);
|
|
||||||
res &= input.screen_coor.y() >= 0.;
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
bool priv::check(const EmbossDataCreateObject &input) {
|
bool priv::check(const DataCreateObject &input) {
|
||||||
bool check_fontfile = false;
|
bool check_fontfile = false;
|
||||||
bool res = check((EmbossDataBase) input, check_fontfile);
|
bool res = check((DataBase) input, check_fontfile);
|
||||||
assert(input.screen_coor.x() >= 0.);
|
assert(input.screen_coor.x() >= 0.);
|
||||||
res &= input.screen_coor.x() >= 0.;
|
res &= input.screen_coor.x() >= 0.;
|
||||||
assert(input.screen_coor.y() >= 0.);
|
assert(input.screen_coor.y() >= 0.);
|
||||||
@ -497,8 +402,9 @@ bool priv::check(const EmbossDataCreateObject &input) {
|
|||||||
res &= input.bed_shape.size() >= 3;
|
res &= input.bed_shape.size() >= 3;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
bool priv::check(const EmbossDataUpdate &input, bool is_main_thread){
|
bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){
|
||||||
bool res = check((EmbossDataBase) input);
|
bool check_fontfile = true;
|
||||||
|
bool res = check((DataBase) input, check_fontfile, use_surface);
|
||||||
assert(input.volume_id.id >= 0);
|
assert(input.volume_id.id >= 0);
|
||||||
res &= input.volume_id.id >= 0;
|
res &= input.volume_id.id >= 0;
|
||||||
if (is_main_thread)
|
if (is_main_thread)
|
||||||
@ -509,15 +415,24 @@ bool priv::check(const EmbossDataUpdate &input, bool is_main_thread){
|
|||||||
assert(!input.cancel->load());
|
assert(!input.cancel->load());
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
bool priv::check(const UseSurfaceData &input, bool is_main_thread){
|
bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread)
|
||||||
bool res = check((EmbossDataUpdate) input, is_main_thread);
|
{
|
||||||
|
bool use_surface = true;
|
||||||
|
bool res = check((DataBase)input, is_main_thread, use_surface);
|
||||||
|
assert(!input.sources.empty());
|
||||||
|
res &= !input.sources.empty();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){
|
||||||
|
bool use_surface = true;
|
||||||
|
bool res = check((DataUpdate)input, is_main_thread, use_surface);
|
||||||
assert(!input.sources.empty());
|
assert(!input.sources.empty());
|
||||||
res &= !input.sources.empty();
|
res &= !input.sources.empty();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Fnc>
|
template<typename Fnc>
|
||||||
TriangleMesh priv::try_create_mesh(const EmbossDataBase &input, Emboss::FontFileWithCache &font, Fnc was_canceled)
|
TriangleMesh priv::try_create_mesh(const DataBase &input, FontFileWithCache &font, Fnc was_canceled)
|
||||||
{
|
{
|
||||||
const TextConfiguration &tc = input.text_configuration;
|
const TextConfiguration &tc = input.text_configuration;
|
||||||
const char *text = tc.text.c_str();
|
const char *text = tc.text.c_str();
|
||||||
@ -526,7 +441,7 @@ TriangleMesh priv::try_create_mesh(const EmbossDataBase &input, Emboss::FontFile
|
|||||||
assert(font.has_value());
|
assert(font.has_value());
|
||||||
if (!font.has_value()) return {};
|
if (!font.has_value()) return {};
|
||||||
|
|
||||||
ExPolygons shapes = Emboss::text2shapes(font, text, prop, was_canceled);
|
ExPolygons shapes = text2shapes(font, text, prop, was_canceled);
|
||||||
if (shapes.empty()) return {};
|
if (shapes.empty()) return {};
|
||||||
if (was_canceled()) return {};
|
if (was_canceled()) return {};
|
||||||
|
|
||||||
@ -536,14 +451,14 @@ TriangleMesh priv::try_create_mesh(const EmbossDataBase &input, Emboss::FontFile
|
|||||||
int unit_per_em = font.font_file->infos[font_index].unit_per_em;
|
int unit_per_em = font.font_file->infos[font_index].unit_per_em;
|
||||||
float scale = prop.size_in_mm / unit_per_em;
|
float scale = prop.size_in_mm / unit_per_em;
|
||||||
float depth = prop.emboss / scale;
|
float depth = prop.emboss / scale;
|
||||||
auto projectZ = std::make_unique<Emboss::ProjectZ>(depth);
|
auto projectZ = std::make_unique<ProjectZ>(depth);
|
||||||
Emboss::ProjectScale project(std::move(projectZ), scale);
|
ProjectScale project(std::move(projectZ), scale);
|
||||||
if (was_canceled()) return {};
|
if (was_canceled()) return {};
|
||||||
return TriangleMesh(Emboss::polygons2model(shapes, project));
|
return TriangleMesh(polygons2model(shapes, project));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Fnc>
|
template<typename Fnc>
|
||||||
TriangleMesh priv::create_mesh(EmbossDataBase &input, Fnc was_canceled, Job::Ctl& ctl)
|
TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl)
|
||||||
{
|
{
|
||||||
// It is neccessary to create some shape
|
// It is neccessary to create some shape
|
||||||
// Emboss text window is opened by creation new emboss text object
|
// Emboss text window is opened by creation new emboss text object
|
||||||
@ -579,10 +494,10 @@ TriangleMesh priv::create_default_mesh()
|
|||||||
return triangle_mesh;
|
return triangle_mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossUpdateJob::update_volume(ModelVolume *volume,
|
void UpdateJob::update_volume(ModelVolume *volume,
|
||||||
TriangleMesh &&mesh,
|
TriangleMesh &&mesh,
|
||||||
const TextConfiguration &text_configuration,
|
const TextConfiguration &text_configuration,
|
||||||
const std::string &volume_name)
|
const std::string &volume_name)
|
||||||
{
|
{
|
||||||
// check inputs
|
// check inputs
|
||||||
bool is_valid_input =
|
bool is_valid_input =
|
||||||
@ -626,7 +541,7 @@ void EmbossUpdateJob::update_volume(ModelVolume *volume,
|
|||||||
canvas->reload_scene(refresh_immediately);
|
canvas->reload_scene(refresh_immediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
void priv::update_volume(TriangleMesh &&mesh, const EmbossDataUpdate &data)
|
void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data)
|
||||||
{
|
{
|
||||||
// for sure that some object will be created
|
// for sure that some object will be created
|
||||||
if (mesh.its.empty())
|
if (mesh.its.empty())
|
||||||
@ -652,7 +567,72 @@ void priv::update_volume(TriangleMesh &&mesh, const EmbossDataUpdate &data)
|
|||||||
if (tc.has_value() && tc->fix_3mf_tr.has_value())
|
if (tc.has_value() && tc->fix_3mf_tr.has_value())
|
||||||
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
|
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
|
||||||
|
|
||||||
EmbossUpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name);
|
UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void priv::create_volume(
|
||||||
|
TriangleMesh &&mesh, const size_t object_idx,
|
||||||
|
const ModelVolumeType type, const Transform3d trmat, const DataBase &data)
|
||||||
|
{
|
||||||
|
GUI_App &app = wxGetApp();
|
||||||
|
Plater *plater = app.plater();
|
||||||
|
ObjectList *obj_list = app.obj_list();
|
||||||
|
GLCanvas3D *canvas = plater->canvas3D();
|
||||||
|
ModelObjectPtrs &objects = plater->model().objects;
|
||||||
|
|
||||||
|
// Parent object for text volume was propably removed.
|
||||||
|
// Assumption: User know what he does, so text volume is no more needed.
|
||||||
|
if (objects.size() <= object_idx)
|
||||||
|
return priv::create_message(_u8L("Bad object index to create volume."));
|
||||||
|
|
||||||
|
if (mesh.its.empty())
|
||||||
|
return priv::create_message(_u8L("Can't create empty volume."));
|
||||||
|
|
||||||
|
plater->take_snapshot(_L("Add Emboss text Volume"));
|
||||||
|
|
||||||
|
ModelObject *obj = objects[object_idx];
|
||||||
|
|
||||||
|
// NOTE: be carefull add volume also center mesh !!!
|
||||||
|
// So first add simple shape(convex hull is also calculated)
|
||||||
|
ModelVolume *volume = obj->add_volume(make_cube(1., 1., 1.), type);
|
||||||
|
|
||||||
|
// TODO: Refactor to create better way to not set cube at begining
|
||||||
|
// Revert mesh centering by set mesh after add cube
|
||||||
|
volume->set_mesh(std::move(mesh));
|
||||||
|
volume->calculate_convex_hull();
|
||||||
|
|
||||||
|
|
||||||
|
// set a default extruder value, since user can't add it manually
|
||||||
|
volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||||
|
|
||||||
|
// do not allow model reload from disk
|
||||||
|
volume->source.is_from_builtin_objects = true;
|
||||||
|
|
||||||
|
volume->name = data.volume_name; // copy
|
||||||
|
volume->text_configuration = data.text_configuration; // copy
|
||||||
|
volume->set_transformation(trmat);
|
||||||
|
|
||||||
|
// update volume name in object list
|
||||||
|
// updata selection after new volume added
|
||||||
|
// change name of volume in right panel
|
||||||
|
// select only actual volume
|
||||||
|
// when new volume is created change selection to this volume
|
||||||
|
auto add_to_selection = [volume](const ModelVolume *vol) { return vol == volume; };
|
||||||
|
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection);
|
||||||
|
if (!sel.IsEmpty()) obj_list->select_item(sel.front());
|
||||||
|
|
||||||
|
// update printable state on canvas
|
||||||
|
if (type == ModelVolumeType::MODEL_PART) canvas->update_instance_printable_state_for_object(object_idx);
|
||||||
|
|
||||||
|
obj_list->selection_changed();
|
||||||
|
|
||||||
|
// Now is valid text volume selected open emboss gizmo
|
||||||
|
GLGizmosManager &manager = canvas->get_gizmos_manager();
|
||||||
|
if (manager.get_current_type() != GLGizmosManager::Emboss)
|
||||||
|
manager.open_gizmo(GLGizmosManager::Emboss);
|
||||||
|
|
||||||
|
// redraw scene
|
||||||
|
canvas->reload_scene(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelVolume *priv::get_volume(ModelObjectPtrs &objects,
|
ModelVolume *priv::get_volume(ModelObjectPtrs &objects,
|
||||||
@ -664,7 +644,7 @@ ModelVolume *priv::get_volume(ModelObjectPtrs &objects,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
Emboss::OrthoProject priv::create_projection_for_cut(
|
OrthoProject priv::create_projection_for_cut(
|
||||||
Transform3d tr,
|
Transform3d tr,
|
||||||
double shape_scale,
|
double shape_scale,
|
||||||
const std::pair<float, float> &z_range)
|
const std::pair<float, float> &z_range)
|
||||||
@ -686,10 +666,10 @@ Emboss::OrthoProject priv::create_projection_for_cut(
|
|||||||
// Projection is in direction from far plane
|
// Projection is in direction from far plane
|
||||||
tr.translate(Vec3d(0., 0., min_z));
|
tr.translate(Vec3d(0., 0., min_z));
|
||||||
tr.scale(shape_scale);
|
tr.scale(shape_scale);
|
||||||
return Emboss::OrthoProject(tr, project_direction);
|
return OrthoProject(tr, project_direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
Emboss::OrthoProject3d priv::create_emboss_projection(
|
OrthoProject3d priv::create_emboss_projection(
|
||||||
bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut)
|
bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut)
|
||||||
{
|
{
|
||||||
// Offset of clossed side to model
|
// Offset of clossed side to model
|
||||||
@ -699,14 +679,98 @@ Emboss::OrthoProject3d priv::create_emboss_projection(
|
|||||||
back_move = -((is_outside) ? surface_offset : emboss);
|
back_move = -((is_outside) ? surface_offset : emboss);
|
||||||
its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move)));
|
its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move)));
|
||||||
Vec3d from_front_to_back(0., 0., back_move - front_move);
|
Vec3d from_front_to_back(0., 0., back_move - front_move);
|
||||||
return Emboss::OrthoProject3d(from_front_to_back);
|
return OrthoProject3d(from_front_to_back);
|
||||||
|
}
|
||||||
|
|
||||||
|
// input can't be const - cache of font
|
||||||
|
TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
|
||||||
|
{
|
||||||
|
const TextConfiguration &tc = input1.text_configuration;
|
||||||
|
const char *text = tc.text.c_str();
|
||||||
|
const FontProp &fp = tc.style.prop;
|
||||||
|
|
||||||
|
ExPolygons shapes = text2shapes(input1.font_file, text, fp, was_canceled);
|
||||||
|
if (shapes.empty() || shapes.front().contour.empty())
|
||||||
|
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
|
||||||
|
|
||||||
|
if (was_canceled()) return {};
|
||||||
|
|
||||||
|
// 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 FontFile &ff = *input1.font_file.font_file;
|
||||||
|
double shape_scale = get_shape_scale(fp, ff);
|
||||||
|
|
||||||
|
const SurfaceVolumeData::ModelSources &sources = input2.sources;
|
||||||
|
const SurfaceVolumeData::ModelSource *biggest = nullptr;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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.text_tr;
|
||||||
|
std::pair<float, float> z_range{0., 1.};
|
||||||
|
OrthoProject cut_projection = 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()) continue;
|
||||||
|
if (biggest_count < its.vertices.size()) {
|
||||||
|
biggest_count = its.vertices.size();
|
||||||
|
biggest = &s;
|
||||||
|
}
|
||||||
|
s_to_itss[&s - &sources.front()] = itss.size();
|
||||||
|
itss.emplace_back(std::move(its));
|
||||||
|
}
|
||||||
|
if (itss.empty()) throw JobException(_u8L("There is no volume in projection direction.").c_str());
|
||||||
|
|
||||||
|
Transform3d tr_inv = biggest->tr.inverse();
|
||||||
|
size_t itss_index = s_to_itss[biggest - &sources.front()];
|
||||||
|
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
|
||||||
|
for (const SurfaceVolumeData::ModelSource &s : sources) {
|
||||||
|
if (&s == biggest) continue;
|
||||||
|
size_t itss_index = s_to_itss[&s - &sources.front()];
|
||||||
|
if (itss_index == std::numeric_limits<size_t>::max()) continue;
|
||||||
|
Transform3d tr = s.tr * tr_inv;
|
||||||
|
indexed_triangle_set &its = itss[itss_index];
|
||||||
|
its_transform(its, tr);
|
||||||
|
BoundingBoxf3 bb = bounding_box(its);
|
||||||
|
mesh_bb.merge(bb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tr_inv = transformation of mesh inverted
|
||||||
|
Transform3d cut_projection_tr = tr_inv * input2.text_tr;
|
||||||
|
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, shape_scale, z_range);
|
||||||
|
float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension);
|
||||||
|
|
||||||
|
// Use CGAL to cut surface from triangle mesh
|
||||||
|
SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio);
|
||||||
|
if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str());
|
||||||
|
if (was_canceled()) return {};
|
||||||
|
|
||||||
|
// !! Projection needs to transform cut
|
||||||
|
OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool priv::process(std::exception_ptr &eptr) {
|
bool priv::process(std::exception_ptr &eptr) {
|
||||||
if (!eptr) return false;
|
if (!eptr) return false;
|
||||||
try {
|
try {
|
||||||
std::rethrow_exception(eptr);
|
std::rethrow_exception(eptr);
|
||||||
} catch (priv::EmbossJobException &e) {
|
} catch (priv::JobException &e) {
|
||||||
create_message(e.what());
|
create_message(e.what());
|
||||||
eptr = nullptr;
|
eptr = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,15 @@ class ModelVolume;
|
|||||||
class TriangleMesh;
|
class TriangleMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI::Emboss {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base data holder for embossing
|
/// Base data holder for embossing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct EmbossDataBase
|
struct DataBase
|
||||||
{
|
{
|
||||||
// Keep pointer on Data of font (glyph shapes)
|
// Keep pointer on Data of font (glyph shapes)
|
||||||
Emboss::FontFileWithCache font_file;
|
Slic3r::Emboss::FontFileWithCache font_file;
|
||||||
// font item is not used for create object
|
// font item is not used for create object
|
||||||
TextConfiguration text_configuration;
|
TextConfiguration text_configuration;
|
||||||
// new volume name created from text
|
// new volume name created from text
|
||||||
@ -35,24 +35,16 @@ struct EmbossDataBase
|
|||||||
/// Volume is created on the surface of existing volume in object.
|
/// Volume is created on the surface of existing volume in object.
|
||||||
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct EmbossDataCreateVolume : public EmbossDataBase
|
struct DataCreateVolume : public DataBase
|
||||||
{
|
{
|
||||||
// define embossed volume type
|
// define embossed volume type
|
||||||
ModelVolumeType volume_type;
|
ModelVolumeType volume_type;
|
||||||
|
|
||||||
// define position on screen where to create object
|
|
||||||
Vec2d screen_coor;
|
|
||||||
|
|
||||||
// parent ModelObject index where to create volume
|
// parent ModelObject index where to create volume
|
||||||
int object_idx;
|
int object_idx;
|
||||||
|
|
||||||
// projection property
|
// new created volume transformation
|
||||||
Camera camera;
|
Transform3d trmat;
|
||||||
|
|
||||||
// used to find point on surface where to create new object
|
|
||||||
RaycastManager::SurfacePoint hit;
|
|
||||||
Transform3d hit_object_tr;
|
|
||||||
Transform3d hit_instance_tr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -60,14 +52,13 @@ struct EmbossDataCreateVolume : public EmbossDataBase
|
|||||||
/// Should not be stopped
|
/// Should not be stopped
|
||||||
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class EmbossCreateVolumeJob : public Job
|
class CreateVolumeJob : public Job
|
||||||
{
|
{
|
||||||
EmbossDataCreateVolume m_input;
|
DataCreateVolume m_input;
|
||||||
TriangleMesh m_result;
|
TriangleMesh m_result;
|
||||||
Transform3d m_transformation;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EmbossCreateVolumeJob(EmbossDataCreateVolume&& input);
|
CreateVolumeJob(DataCreateVolume&& input);
|
||||||
void process(Ctl &ctl) override;
|
void process(Ctl &ctl) override;
|
||||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||||
};
|
};
|
||||||
@ -77,7 +68,7 @@ public:
|
|||||||
/// Object is placed on bed under screen coor
|
/// Object is placed on bed under screen coor
|
||||||
/// OR to center of scene when it is out of bed shape
|
/// OR to center of scene when it is out of bed shape
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct EmbossDataCreateObject : public EmbossDataBase
|
struct DataCreateObject : public DataBase
|
||||||
{
|
{
|
||||||
// define position on screen where to create object
|
// define position on screen where to create object
|
||||||
Vec2d screen_coor;
|
Vec2d screen_coor;
|
||||||
@ -93,13 +84,13 @@ struct EmbossDataCreateObject : public EmbossDataBase
|
|||||||
/// Create new TextObject on the platter
|
/// Create new TextObject on the platter
|
||||||
/// Should not be stopped
|
/// Should not be stopped
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class EmbossCreateObjectJob : public Job
|
class CreateObjectJob : public Job
|
||||||
{
|
{
|
||||||
EmbossDataCreateObject m_input;
|
DataCreateObject m_input;
|
||||||
TriangleMesh m_result;
|
TriangleMesh m_result;
|
||||||
Transform3d m_transformation;
|
Transform3d m_transformation;
|
||||||
public:
|
public:
|
||||||
EmbossCreateObjectJob(EmbossDataCreateObject&& input);
|
CreateObjectJob(DataCreateObject&& input);
|
||||||
void process(Ctl &ctl) override;
|
void process(Ctl &ctl) override;
|
||||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||||
};
|
};
|
||||||
@ -107,7 +98,7 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hold neccessary data to update embossed text object in job
|
/// Hold neccessary data to update embossed text object in job
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct EmbossDataUpdate : public EmbossDataBase
|
struct DataUpdate : public DataBase
|
||||||
{
|
{
|
||||||
// unique identifier of volume to change
|
// unique identifier of volume to change
|
||||||
ObjectID volume_id;
|
ObjectID volume_id;
|
||||||
@ -121,14 +112,14 @@ struct EmbossDataUpdate : public EmbossDataBase
|
|||||||
/// Update text shape in existing text volume
|
/// Update text shape in existing text volume
|
||||||
/// Predict that there is only one runnig(not canceled) instance of it
|
/// Predict that there is only one runnig(not canceled) instance of it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class EmbossUpdateJob : public Job
|
class UpdateJob : public Job
|
||||||
{
|
{
|
||||||
EmbossDataUpdate m_input;
|
DataUpdate m_input;
|
||||||
TriangleMesh m_result;
|
TriangleMesh m_result;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// move params to private variable
|
// move params to private variable
|
||||||
EmbossUpdateJob(EmbossDataUpdate &&input);
|
UpdateJob(DataUpdate &&input);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create new embossed volume by m_input data and store to m_result
|
/// Create new embossed volume by m_input data and store to m_result
|
||||||
@ -158,10 +149,7 @@ public:
|
|||||||
const std::string &volume_name);
|
const std::string &volume_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
struct SurfaceVolumeData
|
||||||
/// Hold neccessary data to update embossed text object in job
|
|
||||||
/// </summary>
|
|
||||||
struct UseSurfaceData : public EmbossDataUpdate
|
|
||||||
{
|
{
|
||||||
// Transformation of text volume inside of object
|
// Transformation of text volume inside of object
|
||||||
Transform3d text_tr;
|
Transform3d text_tr;
|
||||||
@ -180,26 +168,66 @@ struct UseSurfaceData : public EmbossDataUpdate
|
|||||||
};
|
};
|
||||||
using ModelSources = std::vector<ModelSource>;
|
using ModelSources = std::vector<ModelSource>;
|
||||||
ModelSources sources;
|
ModelSources sources;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copied triangles from object to be able create mesh for cut surface from
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text_volume">Define text in object</param>
|
|
||||||
/// <returns>Source data for cut surface from</returns>
|
|
||||||
static ModelSources create_sources(const ModelVolume *text_volume);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hold neccessary data to create(cut) volume from surface object in job
|
||||||
|
/// </summary>
|
||||||
|
struct CreateSurfaceVolumeData : public DataBase, public SurfaceVolumeData{
|
||||||
|
// define embossed volume type
|
||||||
|
ModelVolumeType volume_type;
|
||||||
|
|
||||||
|
// parent ModelObject index where to create volume
|
||||||
|
int object_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cut surface from object and create cutted volume
|
||||||
|
/// Should not be stopped
|
||||||
|
/// </summary>
|
||||||
|
class CreateSurfaceVolumeJob : public Job
|
||||||
|
{
|
||||||
|
CreateSurfaceVolumeData m_input;
|
||||||
|
TriangleMesh m_result;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input);
|
||||||
|
void process(Ctl &ctl) override;
|
||||||
|
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hold neccessary data to update embossed text object in job
|
||||||
|
/// </summary>
|
||||||
|
struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData{};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copied triangles from object to be able create mesh for cut surface from
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volumes">Source object volumes for cut surface from</param>
|
||||||
|
/// <param name="text_volume_id">Source volume id</param>
|
||||||
|
/// <returns>Source data for cut surface from</returns>
|
||||||
|
SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional<size_t> text_volume_id = {});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copied triangles from object to be able create mesh for cut surface from
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text_volume">Define text in object</param>
|
||||||
|
/// <returns>Source data for cut surface from</returns>
|
||||||
|
SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update text volume to use surface from object
|
/// Update text volume to use surface from object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class UseSurfaceJob : public Job
|
class UpdateSurfaceVolumeJob : public Job
|
||||||
{
|
{
|
||||||
UseSurfaceData m_input;
|
UpdateSurfaceVolumeData m_input;
|
||||||
TriangleMesh m_result;
|
TriangleMesh m_result;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// move params to private variable
|
// move params to private variable
|
||||||
UseSurfaceJob(UseSurfaceData &&input);
|
UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input);
|
||||||
void process(Ctl &ctl) override;
|
void process(Ctl &ctl) override;
|
||||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||||
};
|
};
|
||||||
|
@ -12,9 +12,10 @@
|
|||||||
#include "slic3r/Utils/EmbossStylesSerializable.hpp"
|
#include "slic3r/Utils/EmbossStylesSerializable.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using namespace Slic3r::GUI;
|
using namespace Slic3r::Emboss;
|
||||||
|
using namespace Slic3r::GUI::Emboss;
|
||||||
|
|
||||||
EmbossStyleManager::EmbossStyleManager(const ImWchar *language_glyph_range)
|
StyleManager::StyleManager(const ImWchar *language_glyph_range)
|
||||||
: m_imgui_init_glyph_range(language_glyph_range)
|
: m_imgui_init_glyph_range(language_glyph_range)
|
||||||
, m_exist_style_images(false)
|
, m_exist_style_images(false)
|
||||||
, m_temp_style_images(nullptr)
|
, m_temp_style_images(nullptr)
|
||||||
@ -22,12 +23,12 @@ EmbossStyleManager::EmbossStyleManager(const ImWchar *language_glyph_range)
|
|||||||
, m_last_style_index(std::numeric_limits<size_t>::max())
|
, m_last_style_index(std::numeric_limits<size_t>::max())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
EmbossStyleManager::~EmbossStyleManager() {
|
StyleManager::~StyleManager() {
|
||||||
clear_imgui_font();
|
clear_imgui_font();
|
||||||
free_style_images();
|
free_style_images();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::init(AppConfig *app_config, const EmbossStyles &default_styles)
|
void StyleManager::init(AppConfig *app_config, const EmbossStyles &default_styles)
|
||||||
{
|
{
|
||||||
m_app_config = app_config;
|
m_app_config = app_config;
|
||||||
EmbossStyles styles = (app_config != nullptr) ?
|
EmbossStyles styles = (app_config != nullptr) ?
|
||||||
@ -67,7 +68,7 @@ void EmbossStyleManager::init(AppConfig *app_config, const EmbossStyles &default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::store_styles_to_app_config(bool use_modification,
|
bool StyleManager::store_styles_to_app_config(bool use_modification,
|
||||||
bool store_activ_index)
|
bool store_activ_index)
|
||||||
{
|
{
|
||||||
assert(m_app_config != nullptr);
|
assert(m_app_config != nullptr);
|
||||||
@ -102,7 +103,7 @@ bool EmbossStyleManager::store_styles_to_app_config(bool use_modification,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::add_style(const std::string &name) {
|
void StyleManager::add_style(const std::string &name) {
|
||||||
EmbossStyle& style = m_style_cache.style;
|
EmbossStyle& style = m_style_cache.style;
|
||||||
style.name = name;
|
style.name = name;
|
||||||
make_unique_name(style.name);
|
make_unique_name(style.name);
|
||||||
@ -112,7 +113,7 @@ void EmbossStyleManager::add_style(const std::string &name) {
|
|||||||
m_style_items.push_back({style});
|
m_style_items.push_back({style});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::swap(size_t i1, size_t i2) {
|
void StyleManager::swap(size_t i1, size_t i2) {
|
||||||
if (i1 >= m_style_items.size() ||
|
if (i1 >= m_style_items.size() ||
|
||||||
i2 >= m_style_items.size()) return;
|
i2 >= m_style_items.size()) return;
|
||||||
std::swap(m_style_items[i1], m_style_items[i2]);
|
std::swap(m_style_items[i1], m_style_items[i2]);
|
||||||
@ -125,7 +126,7 @@ void EmbossStyleManager::swap(size_t i1, size_t i2) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::discard_style_changes() {
|
void StyleManager::discard_style_changes() {
|
||||||
if (exist_stored_style()) {
|
if (exist_stored_style()) {
|
||||||
if (load_style(m_style_cache.style_index))
|
if (load_style(m_style_cache.style_index))
|
||||||
return; // correct reload style
|
return; // correct reload style
|
||||||
@ -138,7 +139,7 @@ void EmbossStyleManager::discard_style_changes() {
|
|||||||
load_first_valid_font();
|
load_first_valid_font();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::erase(size_t index) {
|
void StyleManager::erase(size_t index) {
|
||||||
if (index >= m_style_items.size()) return;
|
if (index >= m_style_items.size()) return;
|
||||||
|
|
||||||
// fix selected index
|
// fix selected index
|
||||||
@ -151,7 +152,7 @@ void EmbossStyleManager::erase(size_t index) {
|
|||||||
m_style_items.erase(m_style_items.begin() + index);
|
m_style_items.erase(m_style_items.begin() + index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::rename(const std::string& name) {
|
void StyleManager::rename(const std::string& name) {
|
||||||
m_style_cache.style.name = name;
|
m_style_cache.style.name = name;
|
||||||
m_style_cache.truncated_name.clear();
|
m_style_cache.truncated_name.clear();
|
||||||
if (exist_stored_style()) {
|
if (exist_stored_style()) {
|
||||||
@ -161,7 +162,7 @@ void EmbossStyleManager::rename(const std::string& name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::load_style(size_t style_index)
|
bool StyleManager::load_style(size_t style_index)
|
||||||
{
|
{
|
||||||
if (style_index >= m_style_items.size()) return false;
|
if (style_index >= m_style_items.size()) return false;
|
||||||
if (!load_style(m_style_items[style_index].style)) return false;
|
if (!load_style(m_style_items[style_index].style)) return false;
|
||||||
@ -171,14 +172,14 @@ bool EmbossStyleManager::load_style(size_t style_index)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::load_style(const EmbossStyle &style) {
|
bool StyleManager::load_style(const EmbossStyle &style) {
|
||||||
if (style.type == EmbossStyle::Type::file_path) {
|
if (style.type == EmbossStyle::Type::file_path) {
|
||||||
std::unique_ptr<Emboss::FontFile> font_ptr =
|
std::unique_ptr<FontFile> font_ptr =
|
||||||
Emboss::create_font_file(style.path.c_str());
|
create_font_file(style.path.c_str());
|
||||||
if (font_ptr == nullptr) return false;
|
if (font_ptr == nullptr) return false;
|
||||||
m_style_cache.wx_font = {};
|
m_style_cache.wx_font = {};
|
||||||
m_style_cache.font_file =
|
m_style_cache.font_file =
|
||||||
Emboss::FontFileWithCache(std::move(font_ptr));
|
FontFileWithCache(std::move(font_ptr));
|
||||||
m_style_cache.style = style; // copy
|
m_style_cache.style = style; // copy
|
||||||
m_style_cache.style_index = std::numeric_limits<size_t>::max();
|
m_style_cache.style_index = std::numeric_limits<size_t>::max();
|
||||||
m_style_cache.stored_wx_font = {};
|
m_style_cache.stored_wx_font = {};
|
||||||
@ -190,7 +191,7 @@ bool EmbossStyleManager::load_style(const EmbossStyle &style) {
|
|||||||
return load_style(style, *wx_font_opt);
|
return load_style(style, *wx_font_opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::load_style(const EmbossStyle &style, const wxFont &font)
|
bool StyleManager::load_style(const EmbossStyle &style, const wxFont &font)
|
||||||
{
|
{
|
||||||
if (!set_wx_font(font)) return false;
|
if (!set_wx_font(font)) return false;
|
||||||
m_style_cache.style = style; // copy
|
m_style_cache.style = style; // copy
|
||||||
@ -200,9 +201,9 @@ bool EmbossStyleManager::load_style(const EmbossStyle &style, const wxFont &font
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::is_activ_font() { return m_style_cache.font_file.has_value(); }
|
bool StyleManager::is_activ_font() { return m_style_cache.font_file.has_value(); }
|
||||||
|
|
||||||
bool EmbossStyleManager::load_first_valid_font() {
|
bool StyleManager::load_first_valid_font() {
|
||||||
while (!m_style_items.empty()) {
|
while (!m_style_items.empty()) {
|
||||||
if (load_style(0)) return true;
|
if (load_style(0)) return true;
|
||||||
// can't load so erase it from list
|
// can't load so erase it from list
|
||||||
@ -211,22 +212,22 @@ bool EmbossStyleManager::load_first_valid_font() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmbossStyle* EmbossStyleManager::get_stored_style() const
|
const EmbossStyle* StyleManager::get_stored_style() const
|
||||||
{
|
{
|
||||||
if (m_style_cache.style_index >= m_style_items.size()) return nullptr;
|
if (m_style_cache.style_index >= m_style_items.size()) return nullptr;
|
||||||
return &m_style_items[m_style_cache.style_index].style;
|
return &m_style_items[m_style_cache.style_index].style;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::clear_glyphs_cache()
|
void StyleManager::clear_glyphs_cache()
|
||||||
{
|
{
|
||||||
Emboss::FontFileWithCache &ff = m_style_cache.font_file;
|
FontFileWithCache &ff = m_style_cache.font_file;
|
||||||
if (!ff.has_value()) return;
|
if (!ff.has_value()) return;
|
||||||
ff.cache = std::make_shared<Emboss::Glyphs>();
|
ff.cache = std::make_shared<Glyphs>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); }
|
void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); }
|
||||||
|
|
||||||
ImFont *EmbossStyleManager::get_imgui_font()
|
ImFont *StyleManager::get_imgui_font()
|
||||||
{
|
{
|
||||||
if (!is_activ_font()) return nullptr;
|
if (!is_activ_font()) return nullptr;
|
||||||
|
|
||||||
@ -242,16 +243,16 @@ ImFont *EmbossStyleManager::get_imgui_font()
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<EmbossStyleManager::Item> &EmbossStyleManager::get_styles() const{ return m_style_items; }
|
const std::vector<StyleManager::Item> &StyleManager::get_styles() const{ return m_style_items; }
|
||||||
|
|
||||||
ImFont* EmbossStyleManager::extend_imgui_font_range(size_t index, const std::string& text)
|
ImFont* StyleManager::extend_imgui_font_range(size_t index, const std::string& text)
|
||||||
{
|
{
|
||||||
// TODO: start using merge mode
|
// TODO: start using merge mode
|
||||||
// ImFontConfig::MergeMode = true;
|
// ImFontConfig::MergeMode = true;
|
||||||
return create_imgui_font(text);
|
return create_imgui_font(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::make_unique_name(std::string &name)
|
void StyleManager::make_unique_name(std::string &name)
|
||||||
{
|
{
|
||||||
auto is_unique = [&](const std::string &name) -> bool {
|
auto is_unique = [&](const std::string &name) -> bool {
|
||||||
for (const Item &it : m_style_items)
|
for (const Item &it : m_style_items)
|
||||||
@ -283,7 +284,7 @@ void EmbossStyleManager::make_unique_name(std::string &name)
|
|||||||
name = new_name;
|
name = new_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::init_trunc_names(float max_width) {
|
void StyleManager::init_trunc_names(float max_width) {
|
||||||
for (auto &s : m_style_items)
|
for (auto &s : m_style_items)
|
||||||
if (s.truncated_name.empty()) {
|
if (s.truncated_name.empty()) {
|
||||||
std::string name = s.style.name;
|
std::string name = s.style.name;
|
||||||
@ -298,7 +299,7 @@ void EmbossStyleManager::init_trunc_names(float max_width) {
|
|||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
|
|
||||||
void EmbossStyleManager::init_style_images(const Vec2i &max_size,
|
void StyleManager::init_style_images(const Vec2i &max_size,
|
||||||
const std::string &text)
|
const std::string &text)
|
||||||
{
|
{
|
||||||
// check already initialized
|
// check already initialized
|
||||||
@ -311,7 +312,7 @@ void EmbossStyleManager::init_style_images(const Vec2i &max_size,
|
|||||||
assert(m_temp_style_images->images.size() ==
|
assert(m_temp_style_images->images.size() ==
|
||||||
m_temp_style_images->styles.size());
|
m_temp_style_images->styles.size());
|
||||||
// copy images into styles
|
// copy images into styles
|
||||||
for (EmbossStyleManager::StyleImage &image : m_temp_style_images->images){
|
for (StyleManager::StyleImage &image : m_temp_style_images->images){
|
||||||
size_t index = &image - &m_temp_style_images->images.front();
|
size_t index = &image - &m_temp_style_images->images.front();
|
||||||
StyleImagesData::Item &style = m_temp_style_images->styles[index];
|
StyleImagesData::Item &style = m_temp_style_images->styles[index];
|
||||||
|
|
||||||
@ -340,11 +341,11 @@ void EmbossStyleManager::init_style_images(const Vec2i &max_size,
|
|||||||
const EmbossStyle &style = item.style;
|
const EmbossStyle &style = item.style;
|
||||||
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
|
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
|
||||||
if (!wx_font_opt.has_value()) continue;
|
if (!wx_font_opt.has_value()) continue;
|
||||||
std::unique_ptr<Emboss::FontFile> font_file =
|
std::unique_ptr<FontFile> font_file =
|
||||||
WxFontUtils::create_font_file(*wx_font_opt);
|
WxFontUtils::create_font_file(*wx_font_opt);
|
||||||
if (font_file == nullptr) continue;
|
if (font_file == nullptr) continue;
|
||||||
styles.push_back({
|
styles.push_back({
|
||||||
Emboss::FontFileWithCache(std::move(font_file)),
|
FontFileWithCache(std::move(font_file)),
|
||||||
style.name,
|
style.name,
|
||||||
style.prop
|
style.prop
|
||||||
});
|
});
|
||||||
@ -354,7 +355,7 @@ void EmbossStyleManager::init_style_images(const Vec2i &max_size,
|
|||||||
queue_job(worker, std::make_unique<CreateFontStyleImagesJob>(std::move(data)));
|
queue_job(worker, std::make_unique<CreateFontStyleImagesJob>(std::move(data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmbossStyleManager::free_style_images() {
|
void StyleManager::free_style_images() {
|
||||||
if (!m_exist_style_images) return;
|
if (!m_exist_style_images) return;
|
||||||
GLuint tex_id = 0;
|
GLuint tex_id = 0;
|
||||||
for (Item &it : m_style_items) {
|
for (Item &it : m_style_items) {
|
||||||
@ -367,10 +368,10 @@ void EmbossStyleManager::free_style_images() {
|
|||||||
m_exist_style_images = false;
|
m_exist_style_images = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float EmbossStyleManager::min_imgui_font_size = 18.f;
|
float StyleManager::min_imgui_font_size = 18.f;
|
||||||
float EmbossStyleManager::max_imgui_font_size = 60.f;
|
float StyleManager::max_imgui_font_size = 60.f;
|
||||||
float EmbossStyleManager::get_imgui_font_size(const FontProp &prop,
|
float StyleManager::get_imgui_font_size(const FontProp &prop,
|
||||||
const Emboss::FontFile &file)
|
const FontFile &file)
|
||||||
{
|
{
|
||||||
const auto &cn = prop.collection_number;
|
const auto &cn = prop.collection_number;
|
||||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||||
@ -384,12 +385,12 @@ float EmbossStyleManager::get_imgui_font_size(const FontProp &prop,
|
|||||||
return c1 * std::abs(prop.size_in_mm) / 0.3528f;
|
return c1 * std::abs(prop.size_in_mm) / 0.3528f;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImFont *EmbossStyleManager::create_imgui_font(const std::string &text)
|
ImFont *StyleManager::create_imgui_font(const std::string &text)
|
||||||
{
|
{
|
||||||
// inspiration inside of ImGuiWrapper::init_font
|
// inspiration inside of ImGuiWrapper::init_font
|
||||||
auto& ff = m_style_cache.font_file;
|
auto& ff = m_style_cache.font_file;
|
||||||
if (!ff.has_value()) return nullptr;
|
if (!ff.has_value()) return nullptr;
|
||||||
const Emboss::FontFile &font_file = *ff.font_file;
|
const FontFile &font_file = *ff.font_file;
|
||||||
|
|
||||||
ImFontGlyphRangesBuilder builder;
|
ImFontGlyphRangesBuilder builder;
|
||||||
builder.AddRanges(m_imgui_init_glyph_range);
|
builder.AddRanges(m_imgui_init_glyph_range);
|
||||||
@ -464,18 +465,18 @@ ImFont *EmbossStyleManager::create_imgui_font(const std::string &text)
|
|||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::set_wx_font(const wxFont &wx_font) {
|
bool StyleManager::set_wx_font(const wxFont &wx_font) {
|
||||||
std::unique_ptr<Emboss::FontFile> font_file =
|
std::unique_ptr<FontFile> font_file =
|
||||||
WxFontUtils::create_font_file(wx_font);
|
WxFontUtils::create_font_file(wx_font);
|
||||||
return set_wx_font(wx_font, std::move(font_file));
|
return set_wx_font(wx_font, std::move(font_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbossStyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr<Emboss::FontFile> font_file)
|
bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr<FontFile> font_file)
|
||||||
{
|
{
|
||||||
if (font_file == nullptr) return false;
|
if (font_file == nullptr) return false;
|
||||||
m_style_cache.wx_font = wx_font; // copy
|
m_style_cache.wx_font = wx_font; // copy
|
||||||
m_style_cache.font_file =
|
m_style_cache.font_file =
|
||||||
Emboss::FontFileWithCache(std::move(font_file));
|
FontFileWithCache(std::move(font_file));
|
||||||
|
|
||||||
EmbossStyle &style = m_style_cache.style;
|
EmbossStyle &style = m_style_cache.style;
|
||||||
style.type = WxFontUtils::get_actual_type();
|
style.type = WxFontUtils::get_actual_type();
|
||||||
|
@ -11,24 +11,24 @@
|
|||||||
|
|
||||||
class wxFont;
|
class wxFont;
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI::Emboss {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manage Emboss text styles
|
/// Manage Emboss text styles
|
||||||
/// Cache actual state of style
|
/// Cache actual state of style
|
||||||
/// + imgui font
|
/// + imgui font
|
||||||
/// + wx font
|
/// + wx font
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class EmbossStyleManager
|
class StyleManager
|
||||||
{
|
{
|
||||||
friend class CreateFontStyleImagesJob; // access to StyleImagesData
|
friend class CreateFontStyleImagesJob; // access to StyleImagesData
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EmbossStyleManager(const ImWchar *language_glyph_range);
|
StyleManager(const ImWchar *language_glyph_range);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Release imgui font and style images from GPU
|
/// Release imgui font and style images from GPU
|
||||||
/// </summary>
|
/// </summary>
|
||||||
~EmbossStyleManager();
|
~StyleManager();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load font style list from config
|
/// Load font style list from config
|
||||||
@ -111,7 +111,7 @@ public:
|
|||||||
FontProp &get_font_prop() { return get_style().prop; }
|
FontProp &get_font_prop() { return get_style().prop; }
|
||||||
const std::optional<wxFont> &get_wx_font() const { return m_style_cache.wx_font; }
|
const std::optional<wxFont> &get_wx_font() const { return m_style_cache.wx_font; }
|
||||||
const std::optional<wxFont> &get_stored_wx_font() const { return m_style_cache.stored_wx_font; }
|
const std::optional<wxFont> &get_stored_wx_font() const { return m_style_cache.stored_wx_font; }
|
||||||
Emboss::FontFileWithCache &get_font_file_with_cache() { return m_style_cache.font_file; }
|
Slic3r::Emboss::FontFileWithCache &get_font_file_with_cache() { return m_style_cache.font_file; }
|
||||||
bool has_collections() const { return m_style_cache.font_file.font_file != nullptr &&
|
bool has_collections() const { return m_style_cache.font_file.font_file != nullptr &&
|
||||||
m_style_cache.font_file.font_file->infos.size() > 1; }
|
m_style_cache.font_file.font_file->infos.size() > 1; }
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ public:
|
|||||||
/// <param name="wx_font">Must be source of font file</param>
|
/// <param name="wx_font">Must be source of font file</param>
|
||||||
/// <param name="font_file">font file created by WxFontUtils::create_font_file(wx_font)</param>
|
/// <param name="font_file">font file created by WxFontUtils::create_font_file(wx_font)</param>
|
||||||
/// <returns>True on success otherwise false</returns>
|
/// <returns>True on success otherwise false</returns>
|
||||||
bool set_wx_font(const wxFont &wx_font, std::unique_ptr<Emboss::FontFile> font_file );
|
bool set_wx_font(const wxFont &wx_font, std::unique_ptr<Slic3r::Emboss::FontFile> font_file);
|
||||||
|
|
||||||
// Getter on acitve font pointer for imgui
|
// Getter on acitve font pointer for imgui
|
||||||
// Initialize imgui font(generate texture) when doesn't exist yet.
|
// Initialize imgui font(generate texture) when doesn't exist yet.
|
||||||
@ -191,7 +191,7 @@ public:
|
|||||||
// Value out of limits is crop
|
// Value out of limits is crop
|
||||||
static float min_imgui_font_size;
|
static float min_imgui_font_size;
|
||||||
static float max_imgui_font_size;
|
static float max_imgui_font_size;
|
||||||
static float get_imgui_font_size(const FontProp& prop, const Emboss::FontFile& file);
|
static float get_imgui_font_size(const FontProp &prop, const Slic3r::Emboss::FontFile &file);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// erase font when not possible to load
|
// erase font when not possible to load
|
||||||
@ -207,7 +207,7 @@ private:
|
|||||||
struct StyleCache
|
struct StyleCache
|
||||||
{
|
{
|
||||||
// share font file data with emboss job thread
|
// share font file data with emboss job thread
|
||||||
Emboss::FontFileWithCache font_file = {};
|
Slic3r::Emboss::FontFileWithCache font_file = {};
|
||||||
|
|
||||||
// must live same as imgui_font inside of atlas
|
// must live same as imgui_font inside of atlas
|
||||||
ImVector<ImWchar> ranges = {};
|
ImVector<ImWchar> ranges = {};
|
||||||
@ -251,7 +251,7 @@ private:
|
|||||||
{
|
{
|
||||||
struct Item
|
struct Item
|
||||||
{
|
{
|
||||||
Emboss::FontFileWithCache font;
|
Slic3r::Emboss::FontFileWithCache font;
|
||||||
std::string text;
|
std::string text;
|
||||||
FontProp prop;
|
FontProp prop;
|
||||||
};
|
};
|
||||||
|
@ -51,7 +51,7 @@ public:
|
|||||||
/// <param name="font">wx descriptor of font</param>
|
/// <param name="font">wx descriptor of font</param>
|
||||||
/// <param name="font_file">file described in wx font</param>
|
/// <param name="font_file">file described in wx font</param>
|
||||||
/// <returns>New created font fileon success otherwise nullptr</returns>
|
/// <returns>New created font fileon success otherwise nullptr</returns>
|
||||||
static std::unique_ptr<Emboss::FontFile> set_italic(wxFont &font, const Emboss::FontFile& prev_font_file);
|
static std::unique_ptr<Slic3r::Emboss::FontFile> set_italic(wxFont &font, const Slic3r::Emboss::FontFile &prev_font_file);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set boldness into wx font
|
/// Set boldness into wx font
|
||||||
@ -61,7 +61,7 @@ public:
|
|||||||
/// <param name="font">wx descriptor of font</param>
|
/// <param name="font">wx descriptor of font</param>
|
||||||
/// <param name="font_file">file described in wx font</param>
|
/// <param name="font_file">file described in wx font</param>
|
||||||
/// <returns>New created font fileon success otherwise nullptr</returns>
|
/// <returns>New created font fileon success otherwise nullptr</returns>
|
||||||
static std::unique_ptr<Emboss::FontFile> set_bold(wxFont &font, const Emboss::FontFile& font_file);
|
static std::unique_ptr<Slic3r::Emboss::FontFile> set_bold(wxFont &font, const Slic3r::Emboss::FontFile &font_file);
|
||||||
|
|
||||||
// convert wxFont types to string and vice versa
|
// convert wxFont types to string and vice versa
|
||||||
static const boost::bimap<wxFontFamily, std::string_view> type_to_family;
|
static const boost::bimap<wxFontFamily, std::string_view> type_to_family;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user