Inform user about unsuccessfull cut surface from job

This commit is contained in:
Filip Sykala 2022-06-02 09:38:47 +02:00
parent ff604785f6
commit 4af976e19c
4 changed files with 328 additions and 154 deletions

View File

@ -116,7 +116,6 @@ static bool is_text_empty(const std::string &text){
return text.empty() || return text.empty() ||
text.find_first_not_of(" \n\t\r") == std::string::npos; text.find_first_not_of(" \n\t\r") == std::string::npos;
} }
} // namespace } // namespace
GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
@ -909,6 +908,32 @@ static inline void execute_job(std::shared_ptr<Job> j)
}); });
} }
static UseSurfaceData::ModelSources get_sources_to_cut_surface_from(
const ModelVolume *text_volume)
{
if (text_volume == nullptr) return {};
if (!text_volume->text_configuration.has_value()) return {};
const auto &volumes = text_volume->get_object()->volumes;
// no other volume in object
if (volumes.size() <= 1) return {};
UseSurfaceData::ModelSources result;
// 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->is_model_part()) continue;
const TriangleMesh &tm = v->mesh();
if (tm.empty()) continue;
if (tm.its.empty()) continue;
UseSurfaceData::ModelSource ms = {tm.its,
v->get_transformation().get_matrix(),
tm.bounding_box()};
result.push_back(std::move(ms));
}
return result;
}
bool GLGizmoEmboss::process() bool GLGizmoEmboss::process()
{ {
// no volume is selected -> selection from right panel // no volume is selected -> selection from right panel
@ -940,8 +965,8 @@ bool GLGizmoEmboss::process()
const TextConfiguration &tc = data.text_configuration; const TextConfiguration &tc = data.text_configuration;
if (tc.font_item.prop.use_surface) { if (tc.font_item.prop.use_surface) {
// Model to cut surface from. // Model to cut surface from.
const ModelVolume *mesh = get_volume_to_cut_surface_from(); auto sources = get_sources_to_cut_surface_from(m_volume);
if (mesh == nullptr) return false; if (sources.empty()) return false;
Transform3d text_tr = m_volume->get_matrix(); Transform3d text_tr = m_volume->get_matrix();
auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr; auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr;
@ -952,12 +977,8 @@ 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), UseSurfaceData surface_data{std::move(data), text_tr, is_outside,
text_tr, std::move(sources)};
is_outside,
mesh->mesh().its /*copy*/,
mesh->get_matrix(),
mesh->mesh().bounding_box()};
job = std::make_unique<UseSurfaceJob>(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<EmbossUpdateJob>(std::move(data));
@ -1024,29 +1045,6 @@ void GLGizmoEmboss::select_stored_font_item()
m_stored_wx_font = WxFontUtils::load_wxFont(m_stored_font_item->path); m_stored_wx_font = WxFontUtils::load_wxFont(m_stored_font_item->path);
} }
const ModelVolume * GLGizmoEmboss::get_volume_to_cut_surface_from()
{
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() == m_volume->id()) continue;
if (!v->is_model_part()) continue;
const TriangleMesh &tm = v->mesh();
if (tm.empty()) continue;
if (tm.its.empty()) continue;
return v;
}
// No valid source volume in objct volumes
return nullptr;
}
void GLGizmoEmboss::draw_window() void GLGizmoEmboss::draw_window()
{ {
#ifdef ALLOW_DEBUG_MODE #ifdef ALLOW_DEBUG_MODE

View File

@ -116,12 +116,6 @@ private:
void do_translate(const Vec3d& relative_move); void do_translate(const Vec3d& relative_move);
void do_rotate(float relative_z_angle); void do_rotate(float relative_z_angle);
/// <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> /// <summary>
/// Reversible input float with option to restor default value /// Reversible input float with option to restor default value
/// TODO: make more general, static and move to ImGuiWrapper /// TODO: make more general, static and move to ImGuiWrapper

View File

@ -22,20 +22,35 @@ using namespace GUI;
// private namespace // private namespace
namespace priv{ namespace priv{
// <summary> /// <summary>
/// Create mesh from text /// Assert check of inputs data
/// </summary> /// </summary>
/// <param name="text">Text to convert on mesh</param> /// <param name="input"></param>
/// <param name="font">Define shape of characters. /// <returns></returns>
/// NOTE: Can't be const cache glyphs</param> bool check(const EmbossDataBase &input, bool check_fontfile = true);
/// <param name="font_prop">Property of font</param> bool check(const EmbossDataCreateVolume &input, bool is_main_thread = false);
/// <param name="was_canceled">Lambda returning bool to check if process was canceled</param> bool check(const EmbossDataCreateObject &input);
bool check(const EmbossDataUpdate &input, bool is_main_thread = false);
bool check(const UseSurfaceData &input, bool is_main_thread = false);
// <summary>
/// Try to create mesh from text
/// </summary>
/// <param name="input">Text to convert on mesh
/// + Shape of characters + Property of font</param>
/// <param name="font">Font file with cache
/// NOTE: Cache glyphs is changed</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 create_mesh(const char *text, static TriangleMesh try_create_mesh(const EmbossDataBase &input,
Emboss::FontFileWithCache &font, Emboss::FontFileWithCache &font,
const FontProp &font_prop, Fnc was_canceled);
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
/// </summary> /// </summary>
@ -49,6 +64,14 @@ static TriangleMesh create_default_mesh();
/// <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 EmbossDataUpdate &data);
/// <summary>
/// Select Volume from objects
/// </summary>
/// <param name="objects">All objects in scene</param>
/// <param name="volume_id">Identifier of volume in object</param>
/// <returns>Pointer to volume when exist otherwise nullptr</returns>
static ModelVolume *get_volume(ModelObjectPtrs &objects,
const ObjectID &volume_id);
/// <summary> /// <summary>
/// extract scale in 2d /// extract scale in 2d
/// </summary> /// </summary>
@ -65,7 +88,7 @@ static double get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff);
/// <param name="shape_bb">Bounding box 2d of shape to center result</param> /// <param name="shape_bb">Bounding box 2d of shape to center result</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 std::unique_ptr<Emboss::IProjection> create_projection_for_cut( static Emboss::OrthoProject create_projection_for_cut(
Transform3d tr, Transform3d tr,
double shape_scale, double shape_scale,
const BoundingBox &shape_bb, const BoundingBox &shape_bb,
@ -79,28 +102,33 @@ static std::unique_ptr<Emboss::IProjection> 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 std::unique_ptr<Emboss::IProject3f> create_emboss_projection( static Emboss::OrthoProject3f create_emboss_projection(
bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut); bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut);
static void create_message(const std::string &message); // only in finalize
static bool process(std::exception_ptr &eptr);
class EmbossJobException: public std::exception {
public: EmbossJobException(char const *const message)
: std::exception(message)
{}
};
} }
///////////////// /////////////////
/// Create Volume /// Create Volume
EmbossCreateVolumeJob::EmbossCreateVolumeJob(EmbossDataCreateVolume &&input) EmbossCreateVolumeJob::EmbossCreateVolumeJob(EmbossDataCreateVolume &&input)
: m_input(std::move(input)) : m_input(std::move(input))
{} {
assert(priv::check(m_input, true));
}
void EmbossCreateVolumeJob::process(Ctl &ctl) { void EmbossCreateVolumeJob::process(Ctl &ctl) {
// It is neccessary to create some shape if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob.");
// Emboss text window is opened by creation new emboss text object
const char *text = m_input.text_configuration.text.c_str();
FontProp &prop = m_input.text_configuration.font_item.prop;
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); }; auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
m_result = priv::create_mesh(text, m_input.font_file, prop, was_canceled); m_result = priv::create_mesh(m_input, was_canceled, ctl);
if (m_result.its.empty()) m_result = priv::create_default_mesh();
if (was_canceled()) return; if (was_canceled()) return;
// Create new volume inside of object // Create new volume inside of object
const FontProp &font_prop = m_input.text_configuration.font_item.prop; const FontProp &font_prop = m_input.text_configuration.font_item.prop;
Transform3d surface_trmat = Emboss::create_transformation_onto_surface( Transform3d surface_trmat = Emboss::create_transformation_onto_surface(
@ -110,25 +138,36 @@ void EmbossCreateVolumeJob::process(Ctl &ctl) {
m_input.hit_object_tr * surface_trmat; m_input.hit_object_tr * surface_trmat;
} }
void EmbossCreateVolumeJob::finalize(bool canceled, std::exception_ptr &) { void EmbossCreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
if (canceled) return; // doesn't care about exception when process was canceled by user
if (canceled) {
eptr = nullptr;
return;
}
if (priv::process(eptr)) return;
if (m_result.its.empty())
return priv::create_message(_u8L("Can't create empty volume."));
GUI_App &app = wxGetApp(); GUI_App &app = wxGetApp();
Plater *plater = app.plater(); Plater *plater = app.plater();
ObjectList *obj_list = app.obj_list(); ObjectList *obj_list = app.obj_list();
GLCanvas3D *canvas = plater->canvas3D(); GLCanvas3D *canvas = plater->canvas3D();
Model &model = plater->model(); ModelObjectPtrs &objects = plater->model().objects;
// create volume in object // create volume in object
size_t object_idx = m_input.object_idx; size_t object_idx = m_input.object_idx;
assert(model.objects.size() > object_idx);
if (model.objects.size() <= object_idx) return; // 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::TakeSnapshot snapshot(plater, _L("Add Emboss text Volume")); Plater::TakeSnapshot snapshot(plater, _L("Add Emboss text Volume"));
ModelObject *obj = model.objects[object_idx]; ModelObject *obj = objects[object_idx];
ModelVolumeType type = m_input.volume_type; ModelVolumeType type = m_input.volume_type;
ModelVolume *volume = obj->add_volume(std::move(m_result), type);
ModelVolume *volume = obj->add_volume(std::move(m_result), type);
// set a default extruder value, since user can't add it manually // set a default extruder value, since user can't add it manually
volume->config.set_key_value("extruder", new ConfigOptionInt(0)); volume->config.set_key_value("extruder", new ConfigOptionInt(0));
@ -149,7 +188,7 @@ void EmbossCreateVolumeJob::finalize(bool canceled, std::exception_ptr &) {
return vol == volume; return vol == volume;
}; };
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection( wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(
(int) object_idx, add_to_selection); m_input.object_idx, add_to_selection);
if (!sel.IsEmpty()) obj_list->select_item(sel.front()); if (!sel.IsEmpty()) obj_list->select_item(sel.front());
// update printable state on canvas // update printable state on canvas
@ -172,22 +211,17 @@ void EmbossCreateVolumeJob::finalize(bool canceled, std::exception_ptr &) {
/// Create Object /// Create Object
EmbossCreateObjectJob::EmbossCreateObjectJob(EmbossDataCreateObject &&input) EmbossCreateObjectJob::EmbossCreateObjectJob(EmbossDataCreateObject &&input)
: m_input(std::move(input)) : m_input(std::move(input))
{} {
assert(priv::check(m_input));
}
void EmbossCreateObjectJob::process(Ctl &ctl) void EmbossCreateObjectJob::process(Ctl &ctl)
{ {
// It is neccessary to create some shape if (!priv::check(m_input))
// Emboss text window is opened by creation new emboss text object throw std::runtime_error("Bad input data for EmbossCreateObjectJob.");
const char *text = m_input.text_configuration.text.c_str();
FontProp &prop = m_input.text_configuration.font_item.prop;
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
if (!m_input.font_file.has_value())
m_result = priv::create_default_mesh();
else
m_result = priv::create_mesh(text, m_input.font_file, prop, was_canceled);
if (m_result.its.empty())
m_result = priv::create_default_mesh();
auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); };
m_result = priv::create_mesh(m_input, was_canceled, ctl);
if (was_canceled()) return; if (was_canceled()) return;
// Create new object // Create new object
@ -213,9 +247,18 @@ void EmbossCreateObjectJob::process(Ctl &ctl)
m_transformation = Transform3d(tt); m_transformation = Transform3d(tt);
} }
void EmbossCreateObjectJob::finalize(bool canceled, std::exception_ptr &) void EmbossCreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr)
{ {
if (canceled) return; // doesn't care about exception when process was canceled by user
if (canceled) {
eptr = nullptr;
return;
}
if (priv::process(eptr)) return;
// only for sure
if (m_result.empty())
return priv::create_message(_u8L("Can't create empty object."));
GUI_App &app = wxGetApp(); GUI_App &app = wxGetApp();
Plater *plater = app.plater(); Plater *plater = app.plater();
@ -246,36 +289,38 @@ void EmbossCreateObjectJob::finalize(bool canceled, std::exception_ptr &)
EmbossUpdateJob::EmbossUpdateJob(EmbossDataUpdate&& input) EmbossUpdateJob::EmbossUpdateJob(EmbossDataUpdate&& input)
: m_input(std::move(input)) : m_input(std::move(input))
{ {
assert(m_input.cancel != nullptr); assert(priv::check(m_input, true));
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) void EmbossUpdateJob::process(Ctl &ctl)
{ {
if (!priv::check(m_input))
throw std::runtime_error("Bad input data for EmbossUpdateJob.");
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool { auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
if (cancel->load()) return true; if (cancel->load()) return true;
return ctl.was_canceled(); return ctl.was_canceled();
}; };
m_result = priv::try_create_mesh(m_input, m_input.font_file, 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; if (was_canceled()) return;
if (m_result.its.empty())
throw priv::EmbossJobException(
_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 &) void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &eptr)
{ {
if (canceled || m_input.cancel->load()) return; // doesn't care about exception when process was canceled by user
if (canceled || m_input.cancel->load()) {
eptr = nullptr;
return;
}
if (priv::process(eptr)) return;
priv::update_volume(std::move(m_result), m_input); priv::update_volume(std::move(m_result), m_input);
} }
@ -283,12 +328,14 @@ void EmbossUpdateJob::finalize(bool canceled, std::exception_ptr &)
/// Cut Surface /// Cut Surface
UseSurfaceJob::UseSurfaceJob(UseSurfaceData &&input) UseSurfaceJob::UseSurfaceJob(UseSurfaceData &&input)
: m_input(std::move(input)) : m_input(std::move(input))
{} {
assert(priv::check(m_input, true));
}
void UseSurfaceJob::process(Ctl &ctl) { void UseSurfaceJob::process(Ctl &ctl) {
// font face with glyph cache if (!priv::check(m_input))
if (!m_input.font_file.has_value()) return; throw std::runtime_error("Bad input data for UseSurfaceJob.");
// check cancelation of process // check cancelation of process
auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool { auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool {
if (cancel->load()) return true; if (cancel->load()) return true;
@ -299,73 +346,169 @@ void UseSurfaceJob::process(Ctl &ctl) {
const char *text = tc.text.c_str(); const char *text = tc.text.c_str();
const FontProp &fp = tc.font_item.prop; const FontProp &fp = tc.font_item.prop;
ExPolygons shapes = Emboss::text2shapes(m_input.font_file, text, fp); ExPolygons shapes = Emboss::text2shapes(m_input.font_file, text, fp);
if (shapes.empty()) return; if (shapes.empty() || shapes.front().contour.empty())
if (shapes.front().contour.empty()) return; throw priv::EmbossJobException(
_u8L("Font doesn't have any shape for given text.").c_str());
if (was_canceled()) return; if (was_canceled()) return;
BoundingBox bb = get_extents(shapes); BoundingBox bb = get_extents(shapes);
// TODO: merge input sources somehow
const UseSurfaceData::ModelSource &source = m_input.sources[0];
Transform3d mesh_tr_inv = m_input.mesh_tr.inverse(); Transform3d mesh_tr_inv = source.tr.inverse();
Transform3d cut_projection_tr = mesh_tr_inv * m_input.text_tr; Transform3d cut_projection_tr = mesh_tr_inv * m_input.text_tr;
Transform3d emboss_tr = cut_projection_tr.inverse(); Transform3d emboss_tr = cut_projection_tr.inverse();
BoundingBoxf3 mesh_bb_tr = m_input.mesh_bb.transformed(emboss_tr); BoundingBoxf3 mesh_bb_tr = source.bb.transformed(emboss_tr);
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()}; 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; const Emboss::FontFile &ff = *m_input.font_file.font_file;
double shape_scale = priv::get_shape_scale(fp, ff); double shape_scale = priv::get_shape_scale(fp, ff);
auto cut_projection = priv::create_projection_for_cut(cut_projection_tr, Emboss::OrthoProject cut_projection = priv::create_projection_for_cut(
shape_scale, bb, cut_projection_tr, shape_scale, bb, z_range);
z_range);
if (cut_projection == nullptr) return;
// Use CGAL to cut surface from triangle mesh // Use CGAL to cut surface from triangle mesh
SurfaceCut cut = cut_surface(m_input.mesh_its, shapes, *cut_projection); SurfaceCut cut = cut_surface(source.its, shapes, cut_projection);
if (cut.empty()) return; if (cut.empty())
throw priv::EmbossJobException(
_u8L("There is no valid surface for text projection.").c_str());
if (was_canceled()) return; if (was_canceled()) return;
// !! Projection needs to transform cut // !! Projection needs to transform cut
auto projection = priv::create_emboss_projection(m_input.is_outside, fp.emboss, emboss_tr, cut); Emboss::OrthoProject3f projection = priv::create_emboss_projection(
if (projection == nullptr) return; m_input.is_outside, fp.emboss, emboss_tr, cut);
indexed_triangle_set new_its = cut2model(cut, projection);
assert(!new_its.empty());
indexed_triangle_set new_its = cut2model(cut, *projection);
if (was_canceled()) return; if (was_canceled()) return;
//its_write_obj(new_its, "C:/data/temp/projected.obj"); // only debug //its_write_obj(new_its, "C:/data/temp/projected.obj"); // only debug
m_result = TriangleMesh(std::move(new_its)); m_result = TriangleMesh(std::move(new_its));
} }
void UseSurfaceJob::finalize(bool canceled, std::exception_ptr &) void UseSurfaceJob::finalize(bool canceled, std::exception_ptr &eptr)
{ {
if (canceled || m_input.cancel->load()) return; // doesn't care about exception when process was canceled by user
if (canceled || m_input.cancel->load()) {
eptr = nullptr;
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 res = true;
if (check_fontfile) {
assert(input.font_file.has_value());
res &= input.font_file.has_value();
}
assert(!input.text_configuration.fix_3mf_tr.has_value());
res &= !input.text_configuration.fix_3mf_tr.has_value();
assert(!input.text_configuration.text.empty());
res &= !input.text_configuration.text.empty();
assert(!input.volume_name.empty());
res &= !input.volume_name.empty();
return res;
}
bool priv::check(const EmbossDataCreateVolume &input, bool is_main_thread) {
bool check_fontfile = false;
bool res = check((EmbossDataBase) input, check_fontfile);
assert(input.volume_type != ModelVolumeType::INVALID);
res &= input.volume_type != ModelVolumeType::INVALID;
assert(input.object_idx >= 0);
res &= input.object_idx >= 0;
if (is_main_thread)
assert(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;
}
bool priv::check(const EmbossDataCreateObject &input) {
bool check_fontfile = false;
bool res = check((EmbossDataBase) input, check_fontfile);
assert(input.screen_coor.x() >= 0.);
res &= input.screen_coor.x() >= 0.;
assert(input.screen_coor.y() >= 0.);
res &= input.screen_coor.y() >= 0.;
assert(input.bed_shape.size() >= 3); // at least triangle
res &= input.bed_shape.size() >= 3;
return res;
}
bool priv::check(const EmbossDataUpdate &input, bool is_main_thread){
bool res = check((EmbossDataBase) input);
assert(input.volume_id.id >= 0);
res &= input.volume_id.id >= 0;
if (is_main_thread)
assert(get_volume(wxGetApp().model().objects, input.volume_id) != nullptr);
assert(input.cancel != nullptr);
res &= input.cancel != nullptr;
if (is_main_thread)
assert(!input.cancel->load());
return res;
}
bool priv::check(const UseSurfaceData &input, bool is_main_thread){
bool res = check((EmbossDataUpdate) input, is_main_thread);
assert(!input.sources.empty());
res &= !input.sources.empty();
return res;
}
template<typename Fnc> template<typename Fnc>
TriangleMesh priv::create_mesh(const char *text, TriangleMesh priv::try_create_mesh(const EmbossDataBase &input, Emboss::FontFileWithCache &font, Fnc was_canceled)
Emboss::FontFileWithCache &font,
const FontProp &font_prop,
Fnc was_canceled)
{ {
const TextConfiguration &tc = input.text_configuration;
const char *text = tc.text.c_str();
const FontProp &prop = tc.font_item.prop;
assert(font.has_value()); assert(font.has_value());
if (!font.has_value()) return {}; if (!font.has_value()) return {};
ExPolygons shapes = Emboss::text2shapes(font, text, font_prop); ExPolygons shapes = Emboss::text2shapes(font, text, prop);
if (shapes.empty()) return {}; if (shapes.empty()) return {};
if (was_canceled()) return {}; if (was_canceled()) return {};
const auto &cn = font_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;
assert(font_index < font.font_file->infos.size()); assert(font_index < font.font_file->infos.size());
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 = font_prop.size_in_mm / unit_per_em; float scale = prop.size_in_mm / unit_per_em;
float depth = font_prop.emboss / scale; float depth = prop.emboss / scale;
auto projectZ = std::make_unique<Emboss::ProjectZ>(depth); auto projectZ = std::make_unique<Emboss::ProjectZ>(depth);
Emboss::ProjectScale project(std::move(projectZ), scale); Emboss::ProjectScale project(std::move(projectZ), scale);
if (was_canceled()) return {}; if (was_canceled()) return {};
return TriangleMesh(Emboss::polygons2model(shapes, project)); return TriangleMesh(Emboss::polygons2model(shapes, project));
} }
template<typename Fnc>
TriangleMesh priv::create_mesh(EmbossDataBase &input, Fnc was_canceled, Job::Ctl& ctl)
{
// It is neccessary to create some shape
// Emboss text window is opened by creation new emboss text object
TriangleMesh result;
if (input.font_file.has_value()) {
result = try_create_mesh(input, input.font_file, was_canceled);
if (was_canceled()) return {};
}
if (result.its.empty()) {
result = priv::create_default_mesh();
if (was_canceled()) return {};
// only info
ctl.call_on_main_thread([]() {
create_message(_u8L("It is used default volume for embossed "
"text, try to change text or font for fix it."));
});
}
assert(!result.its.empty());
return result;
}
TriangleMesh priv::create_default_mesh() TriangleMesh priv::create_default_mesh()
{ {
// When cant load any font use default object loaded from file // When cant load any font use default object loaded from file
@ -382,7 +525,8 @@ void priv::update_volume(TriangleMesh &&mesh,
const EmbossDataUpdate &data) const EmbossDataUpdate &data)
{ {
// for sure that some object will be created // for sure that some object will be created
if (mesh.its.empty()) return; if (mesh.its.empty())
return priv::create_message("Empty mesh can't be created.");
GUI_App & app = wxGetApp(); // may be move to input GUI_App & app = wxGetApp(); // may be move to input
Plater * plater = app.plater(); Plater * plater = app.plater();
@ -394,14 +538,7 @@ void priv::update_volume(TriangleMesh &&mesh,
std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text); std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text);
Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction); Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction);
ModelVolume *volume = get_volume(plater->model().objects, data.volume_id);
auto get_volume = [&model = plater->model()](const ObjectID &volume_id)->ModelVolume *{
for (ModelObject* obj : model.objects)
for (ModelVolume* vol : obj->volumes)
if (vol->id() == volume_id) return vol;
return nullptr;
};
ModelVolume *volume = get_volume(data.volume_id);
// could appear when user delete edited volume // could appear when user delete edited volume
if (volume == nullptr) if (volume == nullptr)
return; return;
@ -446,7 +583,17 @@ void priv::update_volume(TriangleMesh &&mesh,
canvas->reload_scene(refresh_immediately); canvas->reload_scene(refresh_immediately);
} }
static double priv::get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff) ModelVolume *priv::get_volume(ModelObjectPtrs &objects,
const ObjectID &volume_id)
{
for (ModelObject *obj : objects)
for (ModelVolume *vol : obj->volumes)
if (vol->id() == volume_id) return vol;
return nullptr;
};
double priv::get_shape_scale(const FontProp &fp, const Emboss::FontFile &ff)
{ {
const auto &cn = fp.collection_number; const auto &cn = fp.collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0; unsigned int font_index = (cn.has_value()) ? *cn : 0;
@ -456,7 +603,7 @@ static double priv::get_shape_scale(const FontProp &fp, const Emboss::FontFile &
return scale * Emboss::SHAPE_SCALE; return scale * Emboss::SHAPE_SCALE;
} }
std::unique_ptr<Emboss::IProjection> priv::create_projection_for_cut( Emboss::OrthoProject priv::create_projection_for_cut(
Transform3d tr, Transform3d tr,
double shape_scale, double shape_scale,
const BoundingBox &shape_bb, const BoundingBox &shape_bb,
@ -487,10 +634,10 @@ std::unique_ptr<Emboss::IProjection> priv::create_projection_for_cut(
Vec2d move = -(shape_bb.max + shape_bb.min).cast<double>() / 2.; Vec2d move = -(shape_bb.max + shape_bb.min).cast<double>() / 2.;
//Vec2d move = -shape_bb.center().cast<double>(); // not precisse //Vec2d move = -shape_bb.center().cast<double>(); // not precisse
tr.translate(Vec3d(move.x(), move.y(), 0.)); tr.translate(Vec3d(move.x(), move.y(), 0.));
return std::make_unique<Emboss::OrthoProject>(tr, project_direction); return Emboss::OrthoProject(tr, project_direction);
} }
std::unique_ptr<Emboss::IProject3f> priv::create_emboss_projection( Emboss::OrthoProject3f 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
@ -500,5 +647,23 @@ std::unique_ptr<Emboss::IProject3f> 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)));
Vec3f from_front_to_back(0.f, 0.f, back_move - front_move); Vec3f from_front_to_back(0.f, 0.f, back_move - front_move);
return std::make_unique<Emboss::OrthoProject3f>(from_front_to_back); return Emboss::OrthoProject3f(from_front_to_back);
}
bool priv::process(std::exception_ptr &eptr) {
if (!eptr) return false;
try {
std::rethrow_exception(eptr);
} catch (priv::EmbossJobException &e) {
create_message(e.what());
eptr = nullptr;
}
return true;
}
#include <wx/msgdlg.h>
void priv::create_message(const std::string &message) {
wxMessageBox(wxString(message), _L("Issue during embossing the text."),
wxOK | wxICON_WARNING);
} }

View File

@ -33,6 +33,7 @@ struct EmbossDataBase
/// <summary> /// <summary>
/// Hold neccessary data to create ModelVolume in job /// Hold neccessary data to create ModelVolume in job
/// 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 !!!
/// </summary> /// </summary>
struct EmbossDataCreateVolume : public EmbossDataBase struct EmbossDataCreateVolume : public EmbossDataBase
{ {
@ -57,6 +58,7 @@ struct EmbossDataCreateVolume : public EmbossDataBase
/// <summary> /// <summary>
/// Create new TextVolume on the surface of ModelObject /// Create new TextVolume on the surface of ModelObject
/// Should not be stopped /// Should not be stopped
/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!!
/// </summary> /// </summary>
class EmbossCreateVolumeJob : public Job class EmbossCreateVolumeJob : public Job
{ {
@ -67,7 +69,7 @@ class EmbossCreateVolumeJob : public Job
public: public:
EmbossCreateVolumeJob(EmbossDataCreateVolume&& input); EmbossCreateVolumeJob(EmbossDataCreateVolume&& 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 &eptr) override;
}; };
/// <summary> /// <summary>
@ -99,7 +101,7 @@ class EmbossCreateObjectJob : public Job
public: public:
EmbossCreateObjectJob(EmbossDataCreateObject&& input); EmbossCreateObjectJob(EmbossDataCreateObject&& 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 &eptr) override;
}; };
/// <summary> /// <summary>
@ -141,7 +143,7 @@ public:
/// NOTE: Be carefull it doesn't care about /// NOTE: Be carefull it doesn't care about
/// time between finished process and started finalize part.</param> /// time between finished process and started finalize part.</param>
/// <param name="">unused</param> /// <param name="">unused</param>
void finalize(bool canceled, std::exception_ptr &) override; void finalize(bool canceled, std::exception_ptr &eptr) override;
}; };
/// <summary> /// <summary>
@ -157,13 +159,28 @@ struct UseSurfaceData : public EmbossDataUpdate
// False (engraved).. move into object // False (engraved).. move into object
bool is_outside; bool is_outside;
// IMPROVE: copy of source mesh tringles struct ModelSource
// copy could slow down on big meshes {
indexed_triangle_set mesh_its; // IMPROVE: copy of source mesh tringles
// Transformation of volume inside of object // copy could slow down on big meshes
Transform3d mesh_tr; // but proccessing on thread need it
// extract bounds for projection indexed_triangle_set its;
BoundingBoxf3 mesh_bb; // Transformation of volume inside of object
Transform3d tr;
// extract bounds for projection
BoundingBoxf3 bb;
};
using ModelSources = std::vector<ModelSource>;
ModelSources sources;
//// IMPROVE: copy of source mesh tringles
//// copy could slow down on big meshes
//// but proccess on thread need it
//indexed_triangle_set object_volumes;
//// Transformation of volume inside of object
//Transform3d mesh_tr;
//// extract bounds for projection
//BoundingBoxf3 mesh_bb;
}; };
/// <summary> /// <summary>
@ -178,7 +195,7 @@ public:
// move params to private variable // move params to private variable
UseSurfaceJob(UseSurfaceData &&input); UseSurfaceJob(UseSurfaceData &&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 &eptr) override;
}; };
} // namespace Slic3r::GUI } // namespace Slic3r::GUI