Cut surface in backend job
Store flag about use surface(into 3mf and style) GUI: Add checkbox to start using model surface
This commit is contained in:
parent
e5bf946008
commit
864d1e5830
@ -152,7 +152,7 @@ struct IntersectingElement
|
||||
/// <returns>CGAL mesh - half edge mesh</returns>
|
||||
CutMesh to_cgal(const indexed_triangle_set &its);
|
||||
|
||||
using Project = Emboss::IProject;
|
||||
using Project = Emboss::IProjection;
|
||||
/// <summary>
|
||||
/// Covert 2d shape (e.g. Glyph) to CGAL model
|
||||
/// </summary>
|
||||
@ -434,9 +434,9 @@ void store(const SurfaceCuts &cut, const std::string &dir);
|
||||
} // namespace privat
|
||||
|
||||
|
||||
SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
SurfaceCut Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
const ExPolygons &shapes,
|
||||
const Emboss::IProject &projection)
|
||||
const Emboss::IProjection &projection)
|
||||
{
|
||||
priv::CutMesh cgal_model = priv::to_cgal(model);
|
||||
|
||||
@ -519,12 +519,11 @@ SurfaceCuts Slic3r::cut_surface(const indexed_triangle_set &model,
|
||||
priv::store(result, DEBUG_OUTPUT_DIR + "cuts/"); // only debug
|
||||
#endif // DEBUG_OUTPUT_DIR
|
||||
|
||||
// TODO: Filter surfaceCuts to only the top most.
|
||||
return result;
|
||||
return merge(std::move(result));
|
||||
}
|
||||
|
||||
indexed_triangle_set Slic3r::cuts2model(const SurfaceCuts &cuts,
|
||||
const Emboss::IProject &projection)
|
||||
const Emboss::IProject3f &projection)
|
||||
{
|
||||
indexed_triangle_set result;
|
||||
size_t count_vertices = 0;
|
||||
@ -605,7 +604,7 @@ indexed_triangle_set Slic3r::cuts2model(const SurfaceCuts &cuts,
|
||||
}
|
||||
|
||||
indexed_triangle_set Slic3r::cut2model(const SurfaceCut &cut,
|
||||
const Emboss::IProject &projection)
|
||||
const Emboss::IProject3f &projection)
|
||||
{
|
||||
assert(!cut.empty());
|
||||
size_t count_vertices = cut.vertices.size() * 2;
|
||||
@ -707,7 +706,7 @@ priv::CutMesh priv::to_cgal(const ExPolygons &shapes,
|
||||
indices.reserve(polygon.points.size() * 2);
|
||||
size_t num_vertices_old = result.number_of_vertices();
|
||||
for (const Point &p2 : polygon.points) {
|
||||
auto p = projection.project(p2);
|
||||
auto p = projection.create_front_back(p2);
|
||||
CutMesh::Point v_first{p.first.x(), p.first.y(), p.first.z()};
|
||||
CutMesh::Point v_second{p.second.x(), p.second.y(), p.second.z()};
|
||||
|
||||
@ -1388,7 +1387,7 @@ void priv::filter_cuts(CutAOIs &cuts,
|
||||
|
||||
// compare distances of vertices
|
||||
Point p = get_point(*i);
|
||||
Vec3f source_point = projection.project(p).first;
|
||||
Vec3f source_point = projection.create_front_back(p).first;
|
||||
const auto &prev = mesh.point(ci.vi);
|
||||
Vec3f prev_point(prev.x(), prev.y(), prev.z());
|
||||
float prev_sq_norm = (source_point - prev_point).squaredNorm();
|
||||
|
@ -57,9 +57,9 @@ SurfaceCut merge(SurfaceCuts&& cuts);
|
||||
/// <param name="shapes">Multiple shape to cut from model</param>
|
||||
/// <param name="projection">Define transformation from 2d coordinate of shape to 3d</param>
|
||||
/// <returns>Cutted surface from model</returns>
|
||||
SurfaceCuts cut_surface(const indexed_triangle_set &model,
|
||||
SurfaceCut cut_surface(const indexed_triangle_set &model,
|
||||
const ExPolygons &shapes,
|
||||
const Emboss::IProject &projection);
|
||||
const Emboss::IProjection &projection);
|
||||
|
||||
/// <summary>
|
||||
/// Create model from surface cuts by projection
|
||||
@ -68,7 +68,7 @@ SurfaceCuts cut_surface(const indexed_triangle_set &model,
|
||||
/// <param name="projection">Way of emboss</param>
|
||||
/// <returns>Mesh</returns>
|
||||
indexed_triangle_set cuts2model(const SurfaceCuts &cuts,
|
||||
const Emboss::IProject &projection);
|
||||
const Emboss::IProject3f &projection);
|
||||
/// <summary>
|
||||
/// Create model from surface cuts by projection
|
||||
/// </summary>
|
||||
@ -76,7 +76,7 @@ indexed_triangle_set cuts2model(const SurfaceCuts &cuts,
|
||||
/// <param name="projection">Way of emboss</param>
|
||||
/// <returns>Mesh</returns>
|
||||
indexed_triangle_set cut2model(const SurfaceCut &cut,
|
||||
const Emboss::IProject &projection);
|
||||
const Emboss::IProject3f &projection);
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -769,7 +769,7 @@ std::string Emboss::create_range_text(const std::string &text,
|
||||
|
||||
|
||||
indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
|
||||
const IProject &projection)
|
||||
const IProjection &projection)
|
||||
{
|
||||
indexed_triangle_set result;
|
||||
size_t count_point = count_points(shape2d);
|
||||
@ -782,7 +782,7 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
|
||||
auto insert_point = [&projection, &front_points,
|
||||
&back_points](const Polygon& polygon) {
|
||||
for (const Point& p : polygon.points) {
|
||||
auto p2 = projection.project(p);
|
||||
auto p2 = projection.create_front_back(p);
|
||||
front_points.emplace_back(p2.first);
|
||||
back_points.emplace_back(p2.second);
|
||||
}
|
||||
@ -832,7 +832,7 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<Vec3f, Vec3f> Emboss::ProjectZ::project(const Point &p) const
|
||||
std::pair<Vec3f, Vec3f> Emboss::ProjectZ::create_front_back(const Point &p) const
|
||||
{
|
||||
Vec3f front(
|
||||
static_cast<float>(p.x() * SHAPE_SCALE),
|
||||
@ -902,7 +902,7 @@ Transform3d Emboss::create_transformation_onto_surface(const Vec3f &position,
|
||||
|
||||
// OrthoProject
|
||||
|
||||
std::pair<Vec3f, Vec3f> Emboss::OrthoProject::project(const Point &p) const {
|
||||
std::pair<Vec3f, Vec3f> Emboss::OrthoProject::create_front_back(const Point &p) const {
|
||||
Vec3d front(p.x(), p.y(), 0.);
|
||||
Vec3f front_tr = (m_matrix * front).cast<float>();
|
||||
return std::make_pair(front_tr, project(front_tr));
|
||||
|
@ -183,23 +183,12 @@ public:
|
||||
static std::string create_range_text(const std::string &text, const FontFile &font, unsigned int font_index, bool* exist_unknown = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Project 2d point into space
|
||||
/// Could be plane, sphere, cylindric, ...
|
||||
/// Project spatial point
|
||||
/// </summary>
|
||||
class IProject
|
||||
class IProject3f
|
||||
{
|
||||
public:
|
||||
virtual ~IProject() = default;
|
||||
/// <summary>
|
||||
/// convert 2d point to 3d point
|
||||
/// </summary>
|
||||
/// <param name="p">2d coordinate</param>
|
||||
/// <returns>
|
||||
/// first - front spatial point
|
||||
/// second - back spatial point
|
||||
/// </returns>
|
||||
virtual std::pair<Vec3f, Vec3f> project(const Point &p) const = 0;
|
||||
|
||||
virtual ~IProject3f() = default;
|
||||
/// <summary>
|
||||
/// Move point with respect to projection direction
|
||||
/// e.g. Orthogonal projection will move with point by direction
|
||||
@ -210,13 +199,33 @@ public:
|
||||
virtual Vec3f project(const Vec3f &point) const = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Project 2d point into space
|
||||
/// Could be plane, sphere, cylindric, ...
|
||||
/// </summary>
|
||||
class IProjection : public IProject3f
|
||||
{
|
||||
public:
|
||||
virtual ~IProjection() = default;
|
||||
|
||||
/// <summary>
|
||||
/// convert 2d point to 3d points
|
||||
/// </summary>
|
||||
/// <param name="p">2d coordinate</param>
|
||||
/// <returns>
|
||||
/// first - front spatial point
|
||||
/// second - back spatial point
|
||||
/// </returns>
|
||||
virtual std::pair<Vec3f, Vec3f> create_front_back(const Point &p) const = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create triangle model for text
|
||||
/// </summary>
|
||||
/// <param name="shape2d">text or image</param>
|
||||
/// <param name="projection">Define transformation from 2d to 3d(orientation, position, scale, ...)</param>
|
||||
/// <returns>Projected shape into space</returns>
|
||||
static indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProject& projection);
|
||||
static indexed_triangle_set polygons2model(const ExPolygons &shape2d, const IProjection& projection);
|
||||
|
||||
/// <summary>
|
||||
/// Create transformation for emboss text object to lay on surface point
|
||||
@ -228,29 +237,29 @@ public:
|
||||
static Transform3d create_transformation_onto_surface(
|
||||
const Vec3f &position, const Vec3f &normal, float up_limit = 0.9f);
|
||||
|
||||
class ProjectZ : public IProject
|
||||
class ProjectZ : public IProjection
|
||||
{
|
||||
public:
|
||||
ProjectZ(float depth) : m_depth(depth) {}
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3f, Vec3f> project(const Point &p) const override;
|
||||
std::pair<Vec3f, Vec3f> create_front_back(const Point &p) const override;
|
||||
Vec3f project(const Vec3f &point) const override;
|
||||
float m_depth;
|
||||
};
|
||||
|
||||
class ProjectScale : public IProject
|
||||
class ProjectScale : public IProjection
|
||||
{
|
||||
std::unique_ptr<IProject> core;
|
||||
std::unique_ptr<IProjection> core;
|
||||
float m_scale;
|
||||
public:
|
||||
ProjectScale(std::unique_ptr<IProject> core, float scale)
|
||||
ProjectScale(std::unique_ptr<IProjection> core, float scale)
|
||||
: core(std::move(core)), m_scale(scale)
|
||||
{}
|
||||
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3f, Vec3f> project(const Point &p) const override
|
||||
std::pair<Vec3f, Vec3f> create_front_back(const Point &p) const override
|
||||
{
|
||||
auto res = core->project(p);
|
||||
auto res = core->create_front_back(p);
|
||||
return std::make_pair(res.first * m_scale, res.second * m_scale);
|
||||
}
|
||||
Vec3f project(const Vec3f &point) const override{
|
||||
@ -258,7 +267,16 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class OrthoProject: public Emboss::IProject {
|
||||
class OrthoProject3f : public Emboss::IProject3f
|
||||
{
|
||||
// size and direction of emboss for ortho projection
|
||||
Vec3f m_direction;
|
||||
public:
|
||||
OrthoProject3f(Vec3f direction) : m_direction(direction) {}
|
||||
Vec3f project(const Vec3f &point) const override{ return point + m_direction;}
|
||||
};
|
||||
|
||||
class OrthoProject: public Emboss::IProjection {
|
||||
Transform3d m_matrix;
|
||||
// size and direction of emboss for ortho projection
|
||||
Vec3f m_direction;
|
||||
@ -267,7 +285,7 @@ public:
|
||||
: m_matrix(matrix), m_direction(direction)
|
||||
{}
|
||||
// Inherited via IProject
|
||||
std::pair<Vec3f, Vec3f> project(const Point &p) const override;
|
||||
std::pair<Vec3f, Vec3f> create_front_back(const Point &p) const override;
|
||||
Vec3f project(const Vec3f &point) const override;
|
||||
};
|
||||
};
|
||||
|
@ -157,6 +157,7 @@ static constexpr const char *CHAR_GAP_ATTR = "char_gap";
|
||||
static constexpr const char *LINE_GAP_ATTR = "line_gap";
|
||||
static constexpr const char *LINE_HEIGHT_ATTR = "line_height";
|
||||
static constexpr const char *DEPTH_ATTR = "depth";
|
||||
static constexpr const char *USE_SURFACE_ATTR = "use_surface";
|
||||
static constexpr const char *BOLDNESS_ATTR = "boldness";
|
||||
static constexpr const char *SKEW_ATTR = "skew";
|
||||
static constexpr const char *DISTANCE_ATTR = "distance";
|
||||
@ -3335,6 +3336,8 @@ void TextConfigurationSerialization::to_xml(std::stringstream &stream, const Tex
|
||||
|
||||
stream << LINE_HEIGHT_ATTR << "=\"" << fp.size_in_mm << "\" ";
|
||||
stream << DEPTH_ATTR << "=\"" << fp.emboss << "\" ";
|
||||
if (fp.use_surface)
|
||||
stream << USE_SURFACE_ATTR << "=\"" << 1 << "\" ";
|
||||
if (fp.boldness.has_value())
|
||||
stream << BOLDNESS_ATTR << "=\"" << *fp.boldness << "\" ";
|
||||
if (fp.skew.has_value())
|
||||
@ -3417,6 +3420,8 @@ std::optional<TextConfiguration> TextConfigurationSerialization::read(const char
|
||||
float distance = get_attribute_value_float(attributes, num_attributes, DISTANCE_ATTR);
|
||||
if (std::fabs(distance) > std::numeric_limits<float>::epsilon())
|
||||
fp.distance = distance;
|
||||
std::string use_surface = get_attribute_value_string(attributes, num_attributes, USE_SURFACE_ATTR);
|
||||
if (!use_surface.empty()) fp.use_surface = true;
|
||||
float angle = get_attribute_value_float(attributes, num_attributes, ANGLE_ATTR);
|
||||
if (std::fabs(angle) > std::numeric_limits<float>::epsilon())
|
||||
fp.angle = angle;
|
||||
|
@ -25,6 +25,12 @@ struct FontProp
|
||||
// Z depth of text
|
||||
float emboss; // [in mm]
|
||||
|
||||
// Flag that text should use surface cutted from object
|
||||
// FontProp::distance should without value
|
||||
// FontProp::emboss should be positive number
|
||||
// Note: default value is false
|
||||
bool use_surface;
|
||||
|
||||
// positive value mean wider character shape
|
||||
// negative value mean tiner character shape
|
||||
// When not set value is zero and is not stored
|
||||
@ -85,13 +91,14 @@ struct FontProp
|
||||
/// <param name="line_height">Y size of text [in mm]</param>
|
||||
/// <param name="depth">Z size of text [in mm]</param>
|
||||
FontProp(float line_height = 10.f, float depth = 2.f)
|
||||
: emboss(depth), size_in_mm(line_height)
|
||||
: emboss(depth), size_in_mm(line_height), use_surface(false)
|
||||
{}
|
||||
|
||||
bool operator==(const FontProp& other) const {
|
||||
return
|
||||
char_gap == other.char_gap &&
|
||||
line_gap == other.line_gap &&
|
||||
use_surface == other.use_surface &&
|
||||
is_approx(emboss, other.emboss) &&
|
||||
is_approx(size_in_mm, other.size_in_mm) &&
|
||||
is_approx(boldness, other.boldness) &&
|
||||
|
@ -606,10 +606,12 @@ void GLGizmoEmboss::initialize()
|
||||
tr.font = _u8L("Font");
|
||||
tr.size = _u8L("Height");
|
||||
tr.depth = _u8L("Depth");
|
||||
tr.use_surface = _u8L("Use surface");
|
||||
float max_edit_text_width = std::max(
|
||||
{ImGui::CalcTextSize(tr.font.c_str()).x,
|
||||
ImGui::CalcTextSize(tr.size.c_str()).x,
|
||||
ImGui::CalcTextSize(tr.depth.c_str()).x });
|
||||
ImGui::CalcTextSize(tr.depth.c_str()).x,
|
||||
ImGui::CalcTextSize(tr.use_surface.c_str()).x });
|
||||
cfg.edit_input_offset =
|
||||
3 * space + ImGui::GetTreeNodeToLabelSpacing() +
|
||||
max_edit_text_width;
|
||||
@ -646,7 +648,7 @@ void GLGizmoEmboss::initialize()
|
||||
float window_width = cfg.style_combobox_width + style.WindowPadding.x * 2;
|
||||
cfg.minimal_window_size = ImVec2(window_width, window_height);
|
||||
|
||||
float addition_edit_height = input_height * 3 + tree_header;
|
||||
float addition_edit_height = input_height * 4 + tree_header;
|
||||
cfg.minimal_window_size_with_edit = ImVec2(cfg.minimal_window_size.x,
|
||||
cfg.minimal_window_size.y +
|
||||
addition_edit_height);
|
||||
@ -869,11 +871,40 @@ bool GLGizmoEmboss::process()
|
||||
m_update_job_cancel = std::make_shared<std::atomic<bool> >(false);
|
||||
EmbossDataBase base{font, create_configuration(), create_volume_name()};
|
||||
EmbossDataUpdate data{std::move(base), m_volume->id(), m_update_job_cancel};
|
||||
|
||||
std::unique_ptr<Job> job = nullptr;
|
||||
|
||||
// check cutting from source mesh
|
||||
const TextConfiguration &tc = data.text_configuration;
|
||||
if (tc.font_item.prop.use_surface) {
|
||||
// Model to cut surface from.
|
||||
const ModelVolume *mesh = get_volume_to_cut_surface_from();
|
||||
if (mesh == nullptr) return false;
|
||||
|
||||
Transform3d text_tr = m_volume->get_matrix();
|
||||
if (tc.fix_3mf_tr.has_value())
|
||||
text_tr = text_tr * tc.fix_3mf_tr->inverse();
|
||||
|
||||
bool is_outside = m_volume->is_model_part();
|
||||
// check that there is not unexpected volume type
|
||||
assert(is_outside || m_volume->is_negative_volume() ||
|
||||
m_volume->is_modifier());
|
||||
UseSurfaceData surface_data{std::move(data),
|
||||
text_tr,
|
||||
is_outside,
|
||||
mesh->mesh().its /*copy*/,
|
||||
mesh->get_matrix(),
|
||||
mesh->mesh().bounding_box()};
|
||||
job = std::make_unique<UseSurfaceJob>(std::move(surface_data));
|
||||
} else {
|
||||
job = std::make_unique<EmbossUpdateJob>(std::move(data));
|
||||
}
|
||||
|
||||
//*
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
queue_job(worker, std::make_unique<EmbossUpdateJob>(std::move(data)));
|
||||
queue_job(worker, std::move(job));
|
||||
/*/ // Run Job on main thread (blocking) - ONLY DEBUG
|
||||
execute_job(std::make_shared<EmbossUpdateJob>(std::move(data)));
|
||||
execute_job(std::move(job));
|
||||
// */
|
||||
|
||||
// notification is removed befor object is changed by job
|
||||
@ -913,49 +944,18 @@ void GLGizmoEmboss::select_stored_font_item()
|
||||
m_stored_font_item = it->second;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// choose valid source object for cut surface
|
||||
/// </summary>
|
||||
/// <param name="text_volume">Source model volume</param>
|
||||
/// <returns>triangle set OR nullptr</returns>
|
||||
const indexed_triangle_set *get_source_object(const ModelVolume* mv) {
|
||||
if (mv == nullptr) return nullptr;
|
||||
if (!mv->text_configuration.has_value()) return nullptr;
|
||||
const auto &volumes = mv->get_object()->volumes;
|
||||
// no other volume in object
|
||||
if (volumes.size() <= 1) return nullptr;
|
||||
|
||||
// Improve create object from part or use gl_volume
|
||||
// Get first model part in object
|
||||
for (const ModelVolume *v : volumes) {
|
||||
if (v->id() == mv->id()) continue;
|
||||
if (!v->is_model_part()) continue;
|
||||
const TriangleMesh &tm = v->mesh();
|
||||
if (tm.empty()) continue;
|
||||
return &tm.its;
|
||||
}
|
||||
|
||||
// No valid source volume in objct volumes
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose valid source Volume to project on(cut surface from).
|
||||
/// </summary>
|
||||
/// <param name="text_volume">Volume with text</param>
|
||||
/// <returns>ModelVolume to project on</returns>
|
||||
const ModelVolume *get_source_volume(const ModelVolume *text_volume)
|
||||
const ModelVolume * GLGizmoEmboss::get_volume_to_cut_surface_from()
|
||||
{
|
||||
if (text_volume == nullptr) return nullptr;
|
||||
if (!text_volume->text_configuration.has_value()) return nullptr;
|
||||
const auto &volumes = text_volume->get_object()->volumes;
|
||||
if (m_volume == nullptr) return nullptr;
|
||||
if (!m_volume->text_configuration.has_value()) return nullptr;
|
||||
const auto &volumes = m_volume->get_object()->volumes;
|
||||
// no other volume in object
|
||||
if (volumes.size() <= 1) return nullptr;
|
||||
|
||||
// Improve create object from part or use gl_volume
|
||||
// Get first model part in object
|
||||
for (const ModelVolume *v : volumes) {
|
||||
if (v->id() == text_volume->id()) continue;
|
||||
if (v->id() == m_volume->id()) continue;
|
||||
if (!v->is_model_part()) continue;
|
||||
const TriangleMesh &tm = v->mesh();
|
||||
if (tm.empty()) continue;
|
||||
@ -967,169 +967,36 @@ const ModelVolume *get_source_volume(const ModelVolume *text_volume)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
double get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff)
|
||||
{
|
||||
const auto &cn = fp.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
int unit_per_em = ff.infos[font_index].unit_per_em;
|
||||
double scale = fp.size_in_mm / unit_per_em;
|
||||
// Shape is scaled for store point coordinate as integer
|
||||
return scale * Emboss::SHAPE_SCALE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create cut_projection for cut surface
|
||||
/// </summary>
|
||||
/// <param name="tr">Volume transformation in object</param>
|
||||
/// <param name="tc">Configuration of embossig</param>
|
||||
/// <param name="ff">Font file for size --> unit per em</param>
|
||||
/// <param name="shape_bb">Bounding box 2d of shape to center result volume</param>
|
||||
/// <param name="z_range">Bounding box 3d of model volume for projection ranges</param>
|
||||
/// <returns>Orthogonal cut_projection</returns>
|
||||
std::unique_ptr<Emboss::IProject> create_projection_for_cut(
|
||||
Transform3d tr,
|
||||
const TextConfiguration &tc,
|
||||
const Emboss::FontFile &ff,
|
||||
const BoundingBox &shape_bb,
|
||||
const std::pair<float, float>& z_range)
|
||||
{
|
||||
// create sure that emboss object is bigger than source object
|
||||
const float safe_extension = 1.0f;
|
||||
float min_z = z_range.first - safe_extension;
|
||||
float max_z = z_range.second + safe_extension;
|
||||
assert(min_z < max_z);
|
||||
// range between min and max value
|
||||
double projection_size = max_z - min_z;
|
||||
Matrix3d transformation_for_vector = tr.linear();
|
||||
// Projection must be negative value.
|
||||
// System of text coordinate
|
||||
// X .. from left to right
|
||||
// Y .. from bottom to top
|
||||
// Z .. from text to eye
|
||||
Vec3d untransformed_direction(0., 0., projection_size);
|
||||
Vec3f project_direction =
|
||||
(transformation_for_vector * untransformed_direction).cast<float>();
|
||||
|
||||
// Projection is in direction from far plane
|
||||
tr.translate(Vec3d(0., 0., min_z));
|
||||
|
||||
tr.scale(get_shape_scale(tc.font_item.prop, ff));
|
||||
// Text alignemnt to center 2D
|
||||
Vec2d move = -(shape_bb.max + shape_bb.min).cast<double>() / 2.;
|
||||
tr.translate(Vec3d(move.x(), move.y(), 0.));
|
||||
return std::make_unique<Emboss::OrthoProject>(tr, project_direction);
|
||||
}
|
||||
|
||||
#include "libslic3r/CutSurface.hpp"
|
||||
/// <summary>
|
||||
/// Create tranformation for emboss
|
||||
/// </summary>
|
||||
/// <param name="is_outside">True .. raise, False .. engrave</param>
|
||||
/// <param name="tc">Text configuration</param>
|
||||
/// <param name="tr">Text voliume transformation inside object</param>
|
||||
/// <param name="cut">Cutted surface from model</param>
|
||||
/// <returns>Projection</returns>
|
||||
static std::unique_ptr<Emboss::IProject> create_emboss_projection(
|
||||
bool is_outside,
|
||||
const TextConfiguration &tc,
|
||||
Transform3d tr,
|
||||
SurfaceCut &cut)
|
||||
{
|
||||
// Offset of clossed side to model
|
||||
const float surface_offset = 1e-3f; // [in mm]
|
||||
|
||||
const FontProp &fp = tc.font_item.prop;
|
||||
float front_move, back_move;
|
||||
if (is_outside) {
|
||||
front_move = fp.emboss;
|
||||
back_move = -surface_offset;
|
||||
} else {
|
||||
front_move = surface_offset;
|
||||
back_move = -fp.emboss;
|
||||
}
|
||||
Matrix3d rot_i = tr.linear().inverse();
|
||||
its_transform(cut, rot_i);
|
||||
|
||||
BoundingBoxf3 bb = Slic3r::bounding_box(cut);
|
||||
float z_move = -bb.max.z();
|
||||
float x_center = (bb.max.x() + bb.min.x()) / 2;
|
||||
float y_center = (bb.max.y() + bb.min.y()) / 2;
|
||||
// move to front distance
|
||||
Vec3f move(x_center, y_center, front_move + z_move);
|
||||
|
||||
its_translate(cut, move);
|
||||
|
||||
Vec3f from_front_to_back(0.f, 0.f, back_move - front_move);
|
||||
return std::make_unique<Emboss::OrthoProject>(Transform3d::Identity() /*not used*/,
|
||||
from_front_to_back);
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::use_surface() {
|
||||
// Model to cut surface from.
|
||||
const ModelVolume *source = get_source_volume(m_volume);
|
||||
if (source == nullptr) return;
|
||||
|
||||
// font face with glyph cache
|
||||
auto ffc = m_font_manager.get_font().font_file_with_cache;
|
||||
if (!ffc.has_value()) return;
|
||||
|
||||
// Model to cut surface from.
|
||||
const ModelVolume *mesh = get_volume_to_cut_surface_from();
|
||||
if (mesh == nullptr) return;
|
||||
|
||||
// Cancel previous job
|
||||
if (m_update_job_cancel != nullptr) m_update_job_cancel->store(true);
|
||||
// create new shared ptr to cancel new job
|
||||
m_update_job_cancel = std::make_shared<std::atomic<bool>>(false);
|
||||
EmbossDataBase base{ffc, create_configuration(), create_volume_name()};
|
||||
EmbossDataUpdate data{std::move(base), m_volume->id(), m_update_job_cancel};
|
||||
|
||||
assert(m_volume->text_configuration.has_value());
|
||||
if (!m_volume->text_configuration.has_value()) return;
|
||||
const TextConfiguration &tc = *m_volume->text_configuration;
|
||||
const char *text = tc.text.c_str();
|
||||
const FontProp& fp = tc.font_item.prop;
|
||||
ExPolygons shapes = Emboss::text2shapes(ffc, text, fp);
|
||||
|
||||
if (shapes.empty()) return;
|
||||
if (shapes.front().contour.empty()) return;
|
||||
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
|
||||
Transform3d text_tr = m_volume->get_matrix();
|
||||
if (tc.fix_3mf_tr.has_value())
|
||||
text_tr = text_tr * tc.fix_3mf_tr->inverse();
|
||||
|
||||
Transform3d source_tr = source->get_matrix();
|
||||
Transform3d source_tr_inv = source_tr.inverse();
|
||||
Transform3d cut_projection_tr = source_tr_inv * text_tr;
|
||||
//Transform3d cut_projection_tr = source_tr * text_tr.inverse();
|
||||
|
||||
BoundingBoxf3 source_bb = source->mesh().bounding_box();
|
||||
BoundingBoxf3 source_bb_tr = source_bb.transformed(cut_projection_tr.inverse());
|
||||
std::pair<float, float> z_range{source_bb_tr.min.z(), source_bb_tr.max.z()};
|
||||
|
||||
const Emboss::FontFile &ff = *ffc.font_file;
|
||||
auto cut_projection = create_projection_for_cut(cut_projection_tr, tc, ff, bb, z_range);
|
||||
if (cut_projection == nullptr) return;
|
||||
|
||||
SurfaceCuts cuts = cut_surface(source->mesh().its, shapes, *cut_projection);
|
||||
if (cuts.empty()) return;
|
||||
|
||||
bool is_outside = m_volume->is_model_part();
|
||||
// check that there is not unexpected volume type
|
||||
assert(is_outside || m_volume->is_negative_volume() || m_volume->is_modifier());
|
||||
UseSurfaceData surface_data{std::move(data), text_tr, is_outside,
|
||||
mesh->mesh().its /*copy*/, mesh->get_matrix(), mesh->mesh().bounding_box()};
|
||||
|
||||
SurfaceCut cut = merge(std::move(cuts));
|
||||
// !! Projection needs to transform cut
|
||||
auto projection = create_emboss_projection(is_outside, tc, cut_projection_tr, cut);
|
||||
if (projection == nullptr) return;
|
||||
|
||||
indexed_triangle_set new_its = cut2model(cut, *projection);
|
||||
its_write_obj(new_its, "C:/data/temp/projected.obj"); // only debug
|
||||
|
||||
TriangleMesh tm(std::move(new_its));
|
||||
// center triangle mesh
|
||||
Vec3d shift = tm.bounding_box().center();
|
||||
// do not center in emboss direction
|
||||
shift.z() = 0;
|
||||
tm.translate(-shift.cast<float>());
|
||||
|
||||
// discard fix transformation when exists
|
||||
if (tc.fix_3mf_tr.has_value()) {
|
||||
m_volume->set_transformation(text_tr);
|
||||
m_volume->text_configuration->fix_3mf_tr = {};
|
||||
}
|
||||
|
||||
m_volume->set_mesh(std::move(tm));
|
||||
m_volume->set_new_unique_id();
|
||||
wxGetApp().plater()->canvas3D()->reload_scene(true);
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
queue_job(worker, std::make_unique<UseSurfaceJob>(std::move(surface_data)));
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::draw_window()
|
||||
@ -1907,6 +1774,25 @@ bool GLGizmoEmboss::rev_input(const std::string &name,
|
||||
return revertible(name, value, default_value, exist_change, undo_tooltip, undo_offset, draw_offseted_input);
|
||||
}
|
||||
|
||||
bool GLGizmoEmboss::rev_checkbox(const std::string &name,
|
||||
bool &value,
|
||||
bool *default_value,
|
||||
const std::string &undo_tooltip)
|
||||
{
|
||||
// draw offseted input
|
||||
auto draw_offseted_input = [&]() -> bool {
|
||||
float input_offset = m_gui_cfg->edit_input_offset;
|
||||
ImGui::SameLine(input_offset);
|
||||
return ImGui::Checkbox(("##" + name).c_str(), &value);
|
||||
};
|
||||
float undo_offset = ImGui::GetStyle().FramePadding.x;
|
||||
bool exist_change = default_value != nullptr ?
|
||||
(value != *default_value) :
|
||||
false;
|
||||
return revertible(name, value, default_value, exist_change, undo_tooltip,
|
||||
undo_offset, draw_offseted_input);
|
||||
}
|
||||
|
||||
void GLGizmoEmboss::draw_style_edit() {
|
||||
const GuiCfg::Translations &tr = m_gui_cfg->translations;
|
||||
|
||||
@ -2006,6 +1892,18 @@ void GLGizmoEmboss::draw_style_edit() {
|
||||
_u8L("Revert embossed depth."), 0.1f, 0.25, "%.2f mm"))
|
||||
process();
|
||||
|
||||
bool *def_use_surface = m_stored_font_item.has_value() ?
|
||||
&m_stored_font_item->prop.use_surface : nullptr;
|
||||
if (rev_checkbox(tr.use_surface, font_prop.use_surface, def_use_surface,
|
||||
_u8L("Revert using of model surface."))) {
|
||||
if (font_prop.use_surface) {
|
||||
font_prop.distance.reset();
|
||||
if (font_prop.emboss < 0.1)
|
||||
font_prop.emboss = 1;
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode(_u8L("advanced").c_str())) {
|
||||
if (!m_is_advanced_edit_style) {
|
||||
set_minimal_window_size(true, true);
|
||||
|
@ -115,6 +115,12 @@ private:
|
||||
// TODO: only for developing - remove it
|
||||
void use_surface();
|
||||
|
||||
/// <summary>
|
||||
/// Choose valid source Volume to project on(cut surface from).
|
||||
/// </summary>
|
||||
/// <returns>ModelVolume to project on</returns>
|
||||
const ModelVolume *get_volume_to_cut_surface_from();
|
||||
|
||||
/// <summary>
|
||||
/// Reversible input float with option to restor default value
|
||||
/// TODO: make more general, static and move to ImGuiWrapper
|
||||
@ -123,6 +129,7 @@ private:
|
||||
bool rev_input(const std::string &name, float &value, float *default_value,
|
||||
const std::string &undo_tooltip, float step, float step_fast, const char *format,
|
||||
ImGuiInputTextFlags flags = 0);
|
||||
bool rev_checkbox(const std::string &name, bool &value, bool* default_value, const std::string &undo_tooltip);
|
||||
bool rev_slider(const std::string &name, std::optional<int>& value, std::optional<int> *default_value,
|
||||
const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip);
|
||||
bool rev_slider(const std::string &name, std::optional<float>& value, std::optional<float> *default_value,
|
||||
@ -185,6 +192,7 @@ private:
|
||||
std::string font;
|
||||
std::string size;
|
||||
std::string depth;
|
||||
std::string use_surface;
|
||||
|
||||
// advanced
|
||||
std::string char_gap;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/Format/OBJ.hpp> // load_obj for default mesh
|
||||
#include <libslic3r/CutSurface.hpp> // use surface cuts
|
||||
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
@ -40,105 +41,46 @@ static TriangleMesh create_mesh(const char *text,
|
||||
/// </summary>
|
||||
/// <returns>Not empty model(index trinagle set - its)</returns>
|
||||
static TriangleMesh create_default_mesh();
|
||||
|
||||
/// <summary>
|
||||
/// Must be called on main thread
|
||||
/// </summary>
|
||||
/// <param name="mesh">New mesh data</param>
|
||||
/// <param name="data">Text configuration, ...</param>
|
||||
static void update_volume(TriangleMesh &&mesh, const EmbossDataUpdate &data);
|
||||
|
||||
/// <summary>
|
||||
/// Create projection for cut surface from mesh
|
||||
/// </summary>
|
||||
/// <param name="tr">Volume transformation in object</param>
|
||||
/// <param name="tc">Configuration of embossig</param>
|
||||
/// <param name="ff">Font file for size --> unit per em</param>
|
||||
/// <param name="shape_bb">Bounding box 2d of shape to center result
|
||||
/// volume</param> <param name="z_range">Bounding box 3d of model volume for
|
||||
/// projection ranges</param> <returns>Orthogonal cut_projection</returns>
|
||||
static std::unique_ptr<Emboss::IProjection> create_projection_for_cut(
|
||||
Transform3d tr,
|
||||
const TextConfiguration &tc,
|
||||
const Emboss::FontFile &ff,
|
||||
const BoundingBox &shape_bb,
|
||||
const std::pair<float, float> &z_range);
|
||||
|
||||
/// <summary>
|
||||
/// Create tranformation for emboss Cutted surface
|
||||
/// </summary>
|
||||
/// <param name="is_outside">True .. raise, False .. engrave</param>
|
||||
/// <param name="tc">Text configuration</param>
|
||||
/// <param name="tr">Text voliume transformation inside object</param>
|
||||
/// <param name="cut">Cutted surface from model</param>
|
||||
/// <returns>Projection</returns>
|
||||
static std::unique_ptr<Emboss::IProject3f> create_emboss_projection(
|
||||
bool is_outside,
|
||||
const TextConfiguration &tc,
|
||||
Transform3d tr,
|
||||
SurfaceCut &cut);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/////////////////
|
||||
/// Update Volume
|
||||
EmbossUpdateJob::EmbossUpdateJob(EmbossDataUpdate&& input)
|
||||
: m_input(std::move(input))
|
||||
{
|
||||
assert(m_input.cancel != nullptr);
|
||||
assert(m_input.font_file.has_value());
|
||||
assert(!m_input.text_configuration.text.empty());
|
||||
assert(!m_input.text_configuration.fix_3mf_tr.has_value());
|
||||
}
|
||||
|
||||
void EmbossUpdateJob::process(Ctl &ctl)
|
||||
{
|
||||
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
|
||||
if (cancel->load()) return true;
|
||||
return ctl.was_canceled();
|
||||
};
|
||||
|
||||
// check if exist valid font
|
||||
if (!m_input.font_file.has_value()) return;
|
||||
|
||||
const TextConfiguration &cfg = m_input.text_configuration;
|
||||
m_result = priv::create_mesh(cfg.text.c_str(), m_input.font_file,
|
||||
cfg.font_item.prop, was_canceled);
|
||||
if (m_result.its.empty()) return;
|
||||
if (was_canceled()) return;
|
||||
|
||||
// center triangle mesh
|
||||
Vec3d shift = m_result.bounding_box().center();
|
||||
m_result.translate(-shift.cast<float>());
|
||||
}
|
||||
|
||||
void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &)
|
||||
{
|
||||
if (canceled || m_input.cancel->load()) return;
|
||||
|
||||
// for sure that some object is created from shape
|
||||
if (m_result.its.indices.empty()) return;
|
||||
|
||||
GUI_App & app = wxGetApp(); // may be move to input
|
||||
Plater * plater = app.plater();
|
||||
GLCanvas3D * canvas = plater->canvas3D();
|
||||
|
||||
// Check emboss gizmo is still open
|
||||
GLGizmosManager &manager = canvas->get_gizmos_manager();
|
||||
if (manager.get_current_type() != GLGizmosManager::Emboss) return;
|
||||
|
||||
std::string snap_name = GUI::format(_L("Text: %1%"), m_input.text_configuration.text);
|
||||
Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction);
|
||||
|
||||
ModelVolume *volume = nullptr;
|
||||
Model &model = plater->model();
|
||||
for (auto obj : model.objects)
|
||||
for (auto vol : obj->volumes)
|
||||
if (vol->id() == m_input.volume_id) {
|
||||
volume = vol;
|
||||
break;
|
||||
}
|
||||
|
||||
// could appear when user delete edited volume
|
||||
if (volume == nullptr)
|
||||
return;
|
||||
|
||||
// apply fix matrix made by store to .3mf
|
||||
const auto &tc = volume->text_configuration;
|
||||
assert(tc.has_value());
|
||||
if (tc.has_value() && tc->fix_3mf_tr.has_value())
|
||||
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
|
||||
|
||||
// update volume
|
||||
volume->set_mesh(std::move(m_result));
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
volume->get_object()->invalidate_bounding_box();
|
||||
volume->name = m_input.volume_name;
|
||||
volume->text_configuration = m_input.text_configuration;
|
||||
|
||||
// update volume in right panel( volume / object name)
|
||||
const Selection &selection = canvas->get_selection();
|
||||
const GLVolume * gl_volume = selection.get_volume(
|
||||
*selection.get_volume_idxs().begin());
|
||||
int object_idx = gl_volume->object_idx();
|
||||
int volume_idx = gl_volume->volume_idx();
|
||||
ObjectList *obj_list = app.obj_list();
|
||||
obj_list->update_name_in_list(object_idx, volume_idx);
|
||||
|
||||
// update printable state on canvas
|
||||
if (volume->type() == ModelVolumeType::MODEL_PART)
|
||||
canvas->update_instance_printable_state_for_object((size_t) object_idx);
|
||||
|
||||
// redraw scene
|
||||
bool refresh_immediately = false;
|
||||
canvas->reload_scene(refresh_immediately);
|
||||
}
|
||||
|
||||
|
||||
/////////////////
|
||||
/// Create Volume
|
||||
EmbossCreateVolumeJob::EmbossCreateVolumeJob(EmbossDataCreateVolume &&input)
|
||||
@ -296,6 +238,104 @@ void EmbossCreateObjectJob::finalize(bool canceled, std::exception_ptr &)
|
||||
canvas->reload_scene(true);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
/// Update Volume
|
||||
EmbossUpdateJob::EmbossUpdateJob(EmbossDataUpdate&& input)
|
||||
: m_input(std::move(input))
|
||||
{
|
||||
assert(m_input.cancel != nullptr);
|
||||
assert(m_input.font_file.has_value());
|
||||
assert(!m_input.text_configuration.text.empty());
|
||||
assert(!m_input.text_configuration.fix_3mf_tr.has_value());
|
||||
}
|
||||
|
||||
void EmbossUpdateJob::process(Ctl &ctl)
|
||||
{
|
||||
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
|
||||
if (cancel->load()) return true;
|
||||
return ctl.was_canceled();
|
||||
};
|
||||
|
||||
// check if exist valid font
|
||||
if (!m_input.font_file.has_value()) return;
|
||||
|
||||
const TextConfiguration &cfg = m_input.text_configuration;
|
||||
m_result = priv::create_mesh(cfg.text.c_str(), m_input.font_file,
|
||||
cfg.font_item.prop, was_canceled);
|
||||
if (m_result.its.empty()) return;
|
||||
if (was_canceled()) return;
|
||||
|
||||
// center triangle mesh
|
||||
Vec3d shift = m_result.bounding_box().center();
|
||||
m_result.translate(-shift.cast<float>());
|
||||
}
|
||||
|
||||
void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &)
|
||||
{
|
||||
if (canceled || m_input.cancel->load()) return;
|
||||
priv::update_volume(std::move(m_result), m_input);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
/// Cut Surface
|
||||
UseSurfaceJob::UseSurfaceJob(UseSurfaceData &&input)
|
||||
: m_input(std::move(input))
|
||||
{}
|
||||
|
||||
void UseSurfaceJob::process(Ctl &ctl) {
|
||||
// font face with glyph cache
|
||||
if (!m_input.font_file.has_value()) return;
|
||||
|
||||
// check cancelation of process
|
||||
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
|
||||
if (cancel->load()) return true;
|
||||
return ctl.was_canceled();
|
||||
};
|
||||
|
||||
const TextConfiguration &tc = m_input.text_configuration;
|
||||
const char *text = tc.text.c_str();
|
||||
const FontProp &fp = tc.font_item.prop;
|
||||
ExPolygons shapes = Emboss::text2shapes(m_input.font_file, text, fp);
|
||||
if (shapes.empty()) return;
|
||||
if (shapes.front().contour.empty()) return;
|
||||
if (was_canceled()) return;
|
||||
|
||||
BoundingBox bb = get_extents(shapes);
|
||||
|
||||
Transform3d mesh_tr_inv = m_input.mesh_tr.inverse();
|
||||
Transform3d cut_projection_tr = mesh_tr_inv * m_input.text_tr;
|
||||
BoundingBoxf3 mesh_bb_tr = m_input.mesh_bb.transformed(cut_projection_tr.inverse());
|
||||
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
|
||||
|
||||
const Emboss::FontFile &ff = *m_input.font_file.font_file;
|
||||
auto cut_projection = priv::create_projection_for_cut(cut_projection_tr, tc, ff, bb, z_range);
|
||||
if (cut_projection == nullptr) return;
|
||||
|
||||
// Use CGAL to cut surface from triangle mesh
|
||||
SurfaceCut cut = cut_surface(m_input.mesh_its, shapes, *cut_projection);
|
||||
if (cut.empty()) return;
|
||||
if (was_canceled()) return;
|
||||
|
||||
// !! Projection needs to transform cut
|
||||
auto projection = priv::create_emboss_projection(m_input.is_outside, tc, cut_projection_tr, cut);
|
||||
if (projection == nullptr) return;
|
||||
|
||||
indexed_triangle_set new_its = cut2model(cut, *projection);
|
||||
if (was_canceled()) return;
|
||||
//its_write_obj(new_its, "C:/data/temp/projected.obj"); // only debug
|
||||
|
||||
m_result = TriangleMesh(std::move(new_its));
|
||||
Vec3d shift = -m_result.bounding_box().center();
|
||||
// do not center in emboss direction
|
||||
shift.z() = 0;
|
||||
m_result.translate(shift.cast<float>());
|
||||
}
|
||||
|
||||
void UseSurfaceJob::finalize(bool canceled, std::exception_ptr &)
|
||||
{
|
||||
if (canceled || m_input.cancel->load()) return;
|
||||
priv::update_volume(std::move(m_result), m_input);
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
/// private namespace implementation
|
||||
@ -335,3 +375,148 @@ TriangleMesh priv::create_default_mesh()
|
||||
}
|
||||
return triangle_mesh;
|
||||
}
|
||||
|
||||
static ModelVolume *get_volume(const ObjectID &volume_id, Model &model)
|
||||
{
|
||||
for (auto obj : model.objects)
|
||||
for (auto vol : obj->volumes)
|
||||
if (vol->id() == volume_id)
|
||||
return vol;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ModelVolume *get_volume(const ObjectID &volume_id) {
|
||||
return get_volume(volume_id, wxGetApp().plater()->model());
|
||||
}
|
||||
|
||||
void priv::update_volume(TriangleMesh &&mesh,
|
||||
const EmbossDataUpdate &data)
|
||||
{
|
||||
// for sure that some object will be created
|
||||
if (mesh.its.empty()) return;
|
||||
|
||||
GUI_App & app = wxGetApp(); // may be move to input
|
||||
Plater * plater = app.plater();
|
||||
GLCanvas3D * canvas = plater->canvas3D();
|
||||
|
||||
// Check emboss gizmo is still open
|
||||
GLGizmosManager &manager = canvas->get_gizmos_manager();
|
||||
if (manager.get_current_type() != GLGizmosManager::Emboss) return;
|
||||
|
||||
std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text);
|
||||
Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction);
|
||||
|
||||
ModelVolume *volume = get_volume(data.volume_id, plater->model());
|
||||
// could appear when user delete edited volume
|
||||
if (volume == nullptr)
|
||||
return;
|
||||
|
||||
// apply fix matrix made by store to .3mf
|
||||
const auto &tc = volume->text_configuration;
|
||||
assert(tc.has_value());
|
||||
if (tc.has_value() && tc->fix_3mf_tr.has_value())
|
||||
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
|
||||
|
||||
// update volume
|
||||
volume->set_mesh(std::move(mesh));
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
volume->get_object()->invalidate_bounding_box();
|
||||
volume->name = data.volume_name;
|
||||
volume->text_configuration = data.text_configuration;
|
||||
|
||||
// update volume in right panel( volume / object name)
|
||||
const Selection &selection = canvas->get_selection();
|
||||
const GLVolume * gl_volume = selection.get_volume(
|
||||
*selection.get_volume_idxs().begin());
|
||||
int object_idx = gl_volume->object_idx();
|
||||
int volume_idx = gl_volume->volume_idx();
|
||||
ObjectList *obj_list = app.obj_list();
|
||||
obj_list->update_name_in_list(object_idx, volume_idx);
|
||||
|
||||
// update printable state on canvas
|
||||
if (volume->type() == ModelVolumeType::MODEL_PART)
|
||||
canvas->update_instance_printable_state_for_object((size_t) object_idx);
|
||||
|
||||
// redraw scene
|
||||
bool refresh_immediately = false;
|
||||
canvas->reload_scene(refresh_immediately);
|
||||
}
|
||||
|
||||
static double get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff)
|
||||
{
|
||||
const auto &cn = fp.collection_number;
|
||||
unsigned int font_index = (cn.has_value()) ? *cn : 0;
|
||||
int unit_per_em = ff.infos[font_index].unit_per_em;
|
||||
double scale = fp.size_in_mm / unit_per_em;
|
||||
// Shape is scaled for store point coordinate as integer
|
||||
return scale * Emboss::SHAPE_SCALE;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::IProjection> priv::create_projection_for_cut(
|
||||
Transform3d tr,
|
||||
const TextConfiguration &tc,
|
||||
const Emboss::FontFile &ff,
|
||||
const BoundingBox &shape_bb,
|
||||
const std::pair<float, float> &z_range)
|
||||
{
|
||||
// create sure that emboss object is bigger than source object
|
||||
const float safe_extension = 1.0f;
|
||||
float min_z = z_range.first - safe_extension;
|
||||
float max_z = z_range.second + safe_extension;
|
||||
assert(min_z < max_z);
|
||||
// range between min and max value
|
||||
double projection_size = max_z - min_z;
|
||||
Matrix3d transformation_for_vector = tr.linear();
|
||||
// Projection must be negative value.
|
||||
// System of text coordinate
|
||||
// X .. from left to right
|
||||
// Y .. from bottom to top
|
||||
// Z .. from text to eye
|
||||
Vec3d untransformed_direction(0., 0., projection_size);
|
||||
Vec3f project_direction =
|
||||
(transformation_for_vector * untransformed_direction).cast<float>();
|
||||
|
||||
// Projection is in direction from far plane
|
||||
tr.translate(Vec3d(0., 0., min_z));
|
||||
|
||||
tr.scale(get_shape_scale(tc.font_item.prop, ff));
|
||||
// Text alignemnt to center 2D
|
||||
Vec2d move = -(shape_bb.max + shape_bb.min).cast<double>() / 2.;
|
||||
tr.translate(Vec3d(move.x(), move.y(), 0.));
|
||||
return std::make_unique<Emboss::OrthoProject>(tr, project_direction);
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::IProject3f> priv::create_emboss_projection(
|
||||
bool is_outside,
|
||||
const TextConfiguration &tc,
|
||||
Transform3d tr,
|
||||
SurfaceCut &cut)
|
||||
{
|
||||
// Offset of clossed side to model
|
||||
const float surface_offset = 1e-3f; // [in mm]
|
||||
|
||||
const FontProp &fp = tc.font_item.prop;
|
||||
float front_move, back_move;
|
||||
if (is_outside) {
|
||||
front_move = fp.emboss;
|
||||
back_move = -surface_offset;
|
||||
} else {
|
||||
front_move = surface_offset;
|
||||
back_move = -fp.emboss;
|
||||
}
|
||||
Matrix3d rot_i = tr.linear().inverse();
|
||||
its_transform(cut, rot_i);
|
||||
|
||||
BoundingBoxf3 bb = Slic3r::bounding_box(cut);
|
||||
float z_move = -bb.max.z();
|
||||
float x_center = (bb.max.x() + bb.min.x()) / 2;
|
||||
float y_center = (bb.max.y() + bb.min.y()) / 2;
|
||||
// move to front distance
|
||||
Vec3f move(x_center, y_center, front_move + z_move);
|
||||
|
||||
its_translate(cut, move);
|
||||
|
||||
Vec3f from_front_to_back(0.f, 0.f, back_move - front_move);
|
||||
return std::make_unique<Emboss::OrthoProject3f>(from_front_to_back);
|
||||
}
|
||||
|
@ -30,19 +30,6 @@ struct EmbossDataBase
|
||||
std::string volume_name;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to update embossed text object in job
|
||||
/// </summary>
|
||||
struct EmbossDataUpdate : public EmbossDataBase
|
||||
{
|
||||
// unique identifier of volume to change
|
||||
ObjectID volume_id;
|
||||
|
||||
// flag that job is canceled
|
||||
// for time after process.
|
||||
std::shared_ptr<std::atomic<bool> > cancel;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to create ModelVolume in job
|
||||
/// Volume is created on the surface of existing volume in object.
|
||||
@ -67,6 +54,22 @@ struct EmbossDataCreateVolume : public EmbossDataBase
|
||||
Transform3d hit_instance_tr;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create new TextVolume on the surface of ModelObject
|
||||
/// Should not be stopped
|
||||
/// </summary>
|
||||
class EmbossCreateVolumeJob : public Job
|
||||
{
|
||||
EmbossDataCreateVolume m_input;
|
||||
TriangleMesh m_result;
|
||||
Transform3d m_transformation;
|
||||
|
||||
public:
|
||||
EmbossCreateVolumeJob(EmbossDataCreateVolume&& input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to create ModelObject in job
|
||||
/// Object is placed on bed under screen coor
|
||||
@ -84,6 +87,34 @@ struct EmbossDataCreateObject : public EmbossDataBase
|
||||
std::vector<Vec2d> bed_shape;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create new TextObject on the platter
|
||||
/// Should not be stopped
|
||||
/// </summary>
|
||||
class EmbossCreateObjectJob : public Job
|
||||
{
|
||||
EmbossDataCreateObject m_input;
|
||||
TriangleMesh m_result;
|
||||
Transform3d m_transformation;
|
||||
public:
|
||||
EmbossCreateObjectJob(EmbossDataCreateObject&& input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Hold neccessary data to update embossed text object in job
|
||||
/// </summary>
|
||||
struct EmbossDataUpdate : public EmbossDataBase
|
||||
{
|
||||
// unique identifier of volume to change
|
||||
ObjectID volume_id;
|
||||
|
||||
// flag that job is canceled
|
||||
// for time after process.
|
||||
std::shared_ptr<std::atomic<bool>> cancel;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Update text shape in existing text volume
|
||||
/// Predict that there is only one runnig(not canceled) instance of it
|
||||
@ -92,12 +123,13 @@ class EmbossUpdateJob : public Job
|
||||
{
|
||||
EmbossDataUpdate m_input;
|
||||
TriangleMesh m_result;
|
||||
|
||||
public:
|
||||
// only move params to private variable
|
||||
// move params to private variable
|
||||
EmbossUpdateJob(EmbossDataUpdate &&input);
|
||||
|
||||
/// <summary>
|
||||
/// Create volume movel by input data
|
||||
/// Create new embossed volume by m_input data and store to m_result
|
||||
/// </summary>
|
||||
/// <param name="ctl">Control containing cancel flag</param>
|
||||
void process(Ctl &ctl) override;
|
||||
@ -112,35 +144,39 @@ public:
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create new TextVolume on the surface of ModelObject
|
||||
/// Should not be stopped
|
||||
/// Hold neccessary data to update embossed text object in job
|
||||
/// </summary>
|
||||
class EmbossCreateVolumeJob : public Job
|
||||
struct UseSurfaceData : public EmbossDataUpdate
|
||||
{
|
||||
EmbossDataCreateVolume m_input;
|
||||
TriangleMesh m_result;
|
||||
Transform3d m_transformation;
|
||||
// Transformation of text volume inside of object
|
||||
Transform3d text_tr;
|
||||
|
||||
public:
|
||||
EmbossCreateVolumeJob(EmbossDataCreateVolume&& input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
// Define projection move
|
||||
// True (raised) .. move outside from surface
|
||||
// False (engraved).. move into object
|
||||
bool is_outside;
|
||||
|
||||
// IMPROVE: copy of source mesh tringles
|
||||
// copy could slow down on big meshes
|
||||
indexed_triangle_set mesh_its;
|
||||
// Transformation of volume inside of object
|
||||
Transform3d mesh_tr;
|
||||
// extract bounds for projection
|
||||
BoundingBoxf3 mesh_bb;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create new TextObject on the platter
|
||||
/// Should not be stopped
|
||||
/// Update text volume to use surface from object
|
||||
/// </summary>
|
||||
class EmbossCreateObjectJob : public Job
|
||||
class UseSurfaceJob : public Job
|
||||
{
|
||||
EmbossDataCreateObject m_input;
|
||||
UseSurfaceData m_input;
|
||||
TriangleMesh m_result;
|
||||
Transform3d m_transformation;
|
||||
|
||||
public:
|
||||
EmbossCreateObjectJob(EmbossDataCreateObject&& input);
|
||||
// move params to private variable
|
||||
UseSurfaceJob(UseSurfaceData &&input);
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &) override;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ const std::string FontListSerializable::APP_CONFIG_FONT_NAME = "name";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_DESCRIPTOR = "descriptor";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_LINE_HEIGHT = "line_height";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_DEPTH = "depth";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_USE_SURFACE = "use_surface";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_BOLDNESS = "boldness";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_SKEW = "skew";
|
||||
const std::string FontListSerializable::APP_CONFIG_FONT_DISTANCE = "distance";
|
||||
@ -23,6 +24,15 @@ std::string FontListSerializable::create_section_name(unsigned index)
|
||||
return AppConfig::SECTION_FONT + ':' + std::to_string(index);
|
||||
}
|
||||
|
||||
// check only existence of flag
|
||||
bool FontListSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, bool& value){
|
||||
auto item = section.find(key);
|
||||
if (item == section.end()) return false;
|
||||
|
||||
value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
#include "fast_float/fast_float.h"
|
||||
bool FontListSerializable::read(const std::map<std::string, std::string>& section, const std::string& key, float& value){
|
||||
auto item = section.find(key);
|
||||
@ -92,6 +102,7 @@ std::optional<FontItem> FontListSerializable::load_font_item(
|
||||
FontProp fp;
|
||||
read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, fp.use_surface);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, fp.distance);
|
||||
@ -115,6 +126,8 @@ void FontListSerializable::store_font_item(AppConfig & cfg,
|
||||
const FontProp &fp = fi.prop;
|
||||
cfg.set(section_name, APP_CONFIG_FONT_LINE_HEIGHT, std::to_string(fp.size_in_mm));
|
||||
cfg.set(section_name, APP_CONFIG_FONT_DEPTH, std::to_string(fp.emboss));
|
||||
if (fp.use_surface)
|
||||
cfg.set(section_name, APP_CONFIG_FONT_USE_SURFACE, "true");
|
||||
if (fp.boldness.has_value())
|
||||
cfg.set(section_name, APP_CONFIG_FONT_BOLDNESS, std::to_string(*fp.boldness));
|
||||
if (fp.skew.has_value())
|
||||
|
@ -21,6 +21,7 @@ class FontListSerializable
|
||||
static const std::string APP_CONFIG_FONT_DESCRIPTOR;
|
||||
static const std::string APP_CONFIG_FONT_LINE_HEIGHT;
|
||||
static const std::string APP_CONFIG_FONT_DEPTH;
|
||||
static const std::string APP_CONFIG_FONT_USE_SURFACE;
|
||||
static const std::string APP_CONFIG_FONT_BOLDNESS;
|
||||
static const std::string APP_CONFIG_FONT_SKEW;
|
||||
static const std::string APP_CONFIG_FONT_DISTANCE;
|
||||
@ -37,6 +38,7 @@ public:
|
||||
|
||||
private:
|
||||
// TODO: move to app config like read from section
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, bool& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, float& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<int>& value);
|
||||
static bool read(const std::map<std::string, std::string>& section, const std::string& key, std::optional<unsigned int>& value);
|
||||
|
@ -330,10 +330,9 @@ TEST_CASE("Cut surface", "[]")
|
||||
CHECK(!surfaces.empty());
|
||||
|
||||
Emboss::OrthoProject projection(Transform3d::Identity(), Vec3f(0.f, 0.f, 10.f));
|
||||
for (auto &surface : surfaces)
|
||||
its_translate(surface, Vec3f(0.f, 0.f, 10));
|
||||
its_translate(surfaces, Vec3f(0.f, 0.f, 10));
|
||||
|
||||
indexed_triangle_set its = cuts2model(surfaces, projection);
|
||||
indexed_triangle_set its = cut2model(surfaces, projection);
|
||||
CHECK(!its.empty());
|
||||
//its_write_obj(its, "C:/data/temp/projected.obj");
|
||||
}
|
||||
@ -480,7 +479,7 @@ namespace Slic3r::MeshBoolean::cgal2 {
|
||||
/// <param name="contour_indices">Identify point on shape contour</param>
|
||||
/// <returns>CGAL model of extruded shape</returns>
|
||||
CGALMesh to_cgal(const ExPolygons &shape,
|
||||
const Slic3r::Emboss::IProject &projection,
|
||||
const Slic3r::Emboss::IProjection &projection,
|
||||
int32_t shape_id,
|
||||
const std::string &edge_shape_map_name,
|
||||
const std::string &face_shape_map_name,
|
||||
@ -500,7 +499,7 @@ namespace Slic3r::MeshBoolean::cgal2 {
|
||||
int32_t vertex_index = static_cast<int32_t>(contour_indices.size());
|
||||
contour_indices.push_back({iexpoly, id, int32_t(num_vertices_old) });
|
||||
for (const Point& p2 : polygon.points) {
|
||||
auto p = projection.project(p2);
|
||||
auto p = projection.create_front_back(p2);
|
||||
auto vi = result.add_vertex(typename CGALMesh::Point{ p.first.x(), p.first.y(), p.first.z() });
|
||||
assert((size_t)vi == indices.size() + num_vertices_old);
|
||||
indices.emplace_back(vi);
|
||||
@ -565,7 +564,7 @@ namespace Slic3r::MeshBoolean::cgal2 {
|
||||
/// <returns>Cutted surface, Its do not represent Volume</returns>
|
||||
indexed_triangle_set cut_shape(const indexed_triangle_set &source,
|
||||
const ExPolygon &shape,
|
||||
const Emboss::IProject &projection)
|
||||
const Emboss::IProjection &projection)
|
||||
{
|
||||
// NOT implemented yet
|
||||
return {};
|
||||
@ -580,7 +579,7 @@ indexed_triangle_set cut_shape(const indexed_triangle_set &source,
|
||||
/// <returns>Cutted surface, Its do not represent Volume</returns>
|
||||
indexed_triangle_set cut_shape(const indexed_triangle_set &source,
|
||||
const ExPolygons &shapes,
|
||||
const Emboss::IProject &projection)
|
||||
const Emboss::IProjection &projection)
|
||||
{
|
||||
indexed_triangle_set result;
|
||||
for (const ExPolygon &shape : shapes)
|
||||
|
Loading…
Reference in New Issue
Block a user