From c2f348ee191d1c6a270a39eae6d6128e98adcd89 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 10 Mar 2023 15:22:29 +0100 Subject: [PATCH] ReWork DataBase for embossing to prepare for SVG. Remove dependency on Emboss inside of Object list Hide variable SHAPE_SCALE to one .cpp file only --- src/libslic3r/CutSurface.cpp | 38 +- src/libslic3r/Emboss.cpp | 20 +- src/libslic3r/Emboss.hpp | 6 +- src/libslic3r/EmbossShape.hpp | 15 +- src/slic3r/GUI/GUI_ObjectList.cpp | 33 +- src/slic3r/GUI/GUI_ObjectList.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 156 ++++++-- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 95 ++++- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 26 +- .../GUI/Jobs/CreateFontStyleImagesJob.cpp | 5 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 337 +++++++++--------- src/slic3r/GUI/Jobs/EmbossJob.hpp | 72 ++-- src/slic3r/GUI/Selection.cpp | 25 ++ src/slic3r/GUI/Selection.hpp | 6 +- tests/libslic3r/test_cut_surface.cpp | 5 +- tests/libslic3r/test_emboss.cpp | 15 +- 16 files changed, 527 insertions(+), 331 deletions(-) diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index a8db9e4183..7b95fe8c16 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -449,16 +449,21 @@ ProjectionDistances choose_best_distance( /// /// For each point selected closest distance /// All patches -/// All patches +/// Shape to cut +/// Bound of shapes +/// +/// +/// +/// /// Mask of used patch std::vector select_patches(const ProjectionDistances &best_distances, const SurfacePatches &patches, - - const ExPolygons &shapes, - const ExPolygonsIndices &s2i, - const VCutAOIs &cutAOIs, - const CutMeshes &meshes, - const Project &projection); + const ExPolygons &shapes, + const BoundingBox &shapes_bb, + const ExPolygonsIndices &s2i, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes, + const Project &projection); /// /// Merge two surface cuts together @@ -601,8 +606,7 @@ SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, // Use only outline points // for each point select best projection priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches); - std::vector use_patch = priv::select_patches(best_projection, patches, - shapes, s2i,model_cuts, cgal_models, projection); + std::vector use_patch = priv::select_patches(best_projection, patches, shapes, shapes_bb, s2i, model_cuts, cgal_models, projection); SurfaceCut result = merge_patches(patches, use_patch); //*/ @@ -3194,15 +3198,17 @@ bool priv::is_over_whole_expoly(const CutAOI &cutAOI, std::vector priv::select_patches(const ProjectionDistances &best_distances, const SurfacePatches &patches, - - const ExPolygons &shapes, - const ExPolygonsIndices &s2i, - const VCutAOIs &cutAOIs, - const CutMeshes &meshes, - const Project &projection) + const ExPolygons &shapes, + const BoundingBox &shapes_bb, + const ExPolygonsIndices &s2i, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes, + const Project &projection) { // extension to cover numerical mistake made by back projection patch from 3d to 2d - const float extend_delta = 5.f / Emboss::SHAPE_SCALE; // [Font points scaled by Emboss::SHAPE_SCALE] + // Calculated as one percent of average size(width and height) + Point s = shapes_bb.size(); + const float extend_delta = (s.x() + s.y())/ float(2 * 100); // vector of patches for shape std::vector> used_shapes_patches(shapes.size()); diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 65aa5a333b..ed8ad12a8c 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -19,6 +19,10 @@ #include "libslic3r/Line.hpp" #include "libslic3r/BoundingBox.hpp" +// every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value +// 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 + using namespace Slic3r; using namespace Emboss; using fontinfo_opt = std::optional; @@ -1358,12 +1362,12 @@ std::string Emboss::create_range_text(const std::string &text, return boost::nowide::narrow(ws); } -double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff) +double Emboss::get_text_shape_scale(const FontProp &fp, const FontFile &ff) { - const auto &cn = fp.collection_number; + const std::optional &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; + 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 * SHAPE_SCALE; } @@ -1523,10 +1527,7 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, std::pair Emboss::ProjectZ::create_front_back(const Point &p) const { - Vec3d front( - p.x() * SHAPE_SCALE, - p.y() * SHAPE_SCALE, - 0.); + Vec3d front(p.x(), p.y(), 0.); return std::make_pair(front, project(front)); } @@ -1538,8 +1539,7 @@ Vec3d Emboss::ProjectZ::project(const Vec3d &point) const } std::optional Emboss::ProjectZ::unproject(const Vec3d &p, double *depth) const { - if (depth != nullptr) *depth /= SHAPE_SCALE; - return Vec2d(p.x() / SHAPE_SCALE, p.y() / SHAPE_SCALE); + return Vec2d(p.x(), p.y()); } diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index fc0f0a0a3c..2e0eae7322 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -18,10 +18,6 @@ namespace Slic3r { /// namespace Emboss { - // every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value - // 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 - /// /// Collect fonts registred inside OS /// @@ -220,7 +216,7 @@ namespace Emboss /// Property of font /// Font data /// Conversion to mm - double get_shape_scale(const FontProp &fp, const FontFile &ff); + double get_text_shape_scale(const FontProp &fp, const FontFile &ff); /// /// Project spatial point diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index bbb37d39a7..97a0f8a943 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -18,13 +18,14 @@ namespace Slic3r { struct EmbossShape { // shape defined by integer point consist only by lines not curves - ExPolygons shape; + ExPolygons shapes; // scale of shape, multiplier to get 3d point in mm from integer shape - double shape_scale; + double scale; - // Emboss depth - double depth; // [in world mm] + // Emboss depth, Size in local Z direction + double depth; // [in loacal mm] + // NOTE: User should see and modify mainly world size not local // Flag that result volume use surface cutted from source objects bool use_surface = false; @@ -32,6 +33,7 @@ struct EmbossShape // distance from surface point // used for move over model surface // When not set value is zero and is not stored + // NOTE: Can't be used together with use_surface std::optional distance; // [in mm] // !!! Volume stored in .3mf has transformed vertices. @@ -42,18 +44,19 @@ struct EmbossShape std::optional fix_3mf_tr; // file(.svg) path to source of shape + // When empty can't reload from disk std::string svg_file_path; // undo / redo stack recovery template void save(Archive &ar) const { - ar(shape, shape_scale, depth, use_surface, svg_file_path); + ar(shapes, scale, depth, use_surface, svg_file_path); cereal::save(ar, distance); cereal::save(ar, fix_3mf_tr); } template void load(Archive &ar) { - ar(shape, shape_scale, depth, use_surface, svg_file_path); + ar(shapes, scale, depth, use_surface, svg_file_path); cereal::load(ar, distance); cereal::load(ar, fix_3mf_tr); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index ae0e54b456..5aa9943900 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1833,12 +1833,7 @@ void ObjectList::load_shape_object_from_gallery(const wxArrayString& input_files wxGetApp().mainframe->update_title(); } -void ObjectList::load_mesh_object( - const TriangleMesh & mesh, - const std::string & name, - bool center, - const TextConfiguration *text_config /* = nullptr*/, - const Transform3d * transformation /* = nullptr*/) +void ObjectList::load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center) { PlaterAfterLoadAutoArrange plater_after_load_auto_arrange; // Add mesh to model as a new object @@ -1848,7 +1843,6 @@ void ObjectList::load_mesh_object( check_model_ids_validity(model); #endif /* _DEBUG */ - std::vector object_idxs; ModelObject* new_object = model.add_object(); new_object->name = name; new_object->add_instance(); // each object should have at list one instance @@ -1856,31 +1850,24 @@ void ObjectList::load_mesh_object( ModelVolume* new_volume = new_object->add_volume(mesh); new_object->sort_volumes(wxGetApp().app_config->get_bool("order_volumes")); new_volume->name = name; - if (text_config) - new_volume->text_configuration = *text_config; + // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_object->invalidate_bounding_box(); - if (transformation) { - assert(!center); - Slic3r::Geometry::Transformation tr(*transformation); - new_object->instances[0]->set_transformation(tr); - } else { - auto bb = mesh.bounding_box(); - new_object->translate(-bb.center()); - new_object->instances[0]->set_offset( - center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) : - bb.center()); - } + + auto bb = mesh.bounding_box(); + new_object->translate(-bb.center()); + new_object->instances[0]->set_offset( + center ? to_3d(wxGetApp().plater()->build_volume().bounding_volume2d().center(), -new_object->origin_translation.z()) : + bb.center()); new_object->ensure_on_bed(); - object_idxs.push_back(model.objects.size() - 1); #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - - paste_objects_into_list(object_idxs); + + paste_objects_into_list({model.objects.size() - 1}); #ifdef _DEBUG check_model_ids_validity(model); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index d2965c77e3..9f2a10e2b6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -27,7 +27,6 @@ class ModelConfig; class ModelObject; class ModelVolume; class TriangleMesh; -struct TextConfiguration; enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: @@ -258,8 +257,7 @@ public: void load_shape_object(const std::string &type_name); void load_shape_object_from_gallery(); void load_shape_object_from_gallery(const wxArrayString& input_files); - void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true, - const TextConfiguration* text_config = nullptr, const Transform3d* transformation = nullptr); + void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true); bool del_object(const int obj_idx); bool del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 034a563d2e..457471af54 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -107,6 +107,67 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) // Private namespace with helper function for create volume namespace priv { +/// +/// Data for emboss job to create shape +/// +struct TextDataBase : public DataBase +{ + TextDataBase(DataBase &&parent, const FontFileWithCache& font_file, TextConfiguration &&text_configuration) + : DataBase(std::move(parent)), font_file(font_file) /* copy */, text_configuration(std::move(text_configuration)) + { + assert(this->font_file.has_value()); + + // partialy fill shape from text configuration + const FontProp &fp = this->text_configuration.style.prop; + shape.depth = fp.emboss; + shape.use_surface = fp.use_surface; + shape.distance = fp.distance; + + const FontFile &ff = *this->font_file.font_file; + shape.scale = get_text_shape_scale(fp, ff); + } + /// + /// Create shape from text and font + /// + /// Text shape defined by configuration and font file + EmbossShape &create_shape() override { + if (!shape.shapes.empty()) + return shape; + + // create shape by configuration + const char *text = text_configuration.text.c_str(); + const FontProp &fp = text_configuration.style.prop; + auto was_canceled = [&c = cancel]() -> bool { return c->load(); }; + shape.shapes = text2shapes(font_file, text, fp, was_canceled); + + // TEST + const FontProp &prop = text_configuration.style.prop; + const std::optional &cn = prop.collection_number; + unsigned int font_index = (cn.has_value()) ? *cn : 0; + const FontFileWithCache &font = font_file; + assert(font_index < font.font_file->infos.size()); + int unit_per_em = font.font_file->infos[font_index].unit_per_em; + float scale = prop.size_in_mm / unit_per_em; + float depth = prop.emboss / scale; + + return shape; + } + + void write(ModelVolume &volume) const override + { + volume.text_configuration = text_configuration; // copy + + // discard information about rotation, should not be stored in volume + volume.text_configuration->style.prop.angle.reset(); + + DataBase::write(volume); + } + + // Keep pointer on Data of font (glyph shapes) + FontFileWithCache font_file; + // font item is not used for create object + TextConfiguration text_configuration; +}; /// /// Check if volume type is possible use for new text volume @@ -122,7 +183,8 @@ static bool is_valid(ModelVolumeType volume_type); /// Keep actual selected style /// Cancel for previous job /// Base data for emboss text -static DataBase create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr> &cancel); +static std::unique_ptr create_emboss_data_base( + const std::string &text, StyleManager &style_manager, std::shared_ptr> &cancel); /// /// Start job for add new volume to object with given transformation @@ -133,7 +195,7 @@ static DataBase create_emboss_data_base(const std::string &text, StyleManager &s /// Type of volume static void start_create_volume_job(const ModelObject *object, const Transform3d volume_trmat, - DataBase &emboss_data, + std::unique_ptr emboss_data, ModelVolumeType volume_type); /// @@ -146,7 +208,7 @@ static void start_create_volume_job(const ModelObject *object, /// Ability to ray cast to model /// Contain already used scene RayCasters /// True when start creation, False when there is no hit surface by screen coor -static bool start_create_volume_on_surface_job(DataBase &emboss_data, +static bool start_create_volume_on_surface_job(std::unique_ptr emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, @@ -174,7 +236,7 @@ static void find_closest_volume(const Selection &selection, /// /// Define params of text /// Screen coordinat, where to create new object laying on bed -static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor); +static void start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor); // Loaded icons enum // Have to match order of files in function GLGizmoEmboss::init_icons() @@ -209,17 +271,17 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous set_default_text(); GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); if (gl_volume != nullptr) { // Try to cast ray into scene and find object for add volume - if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) { + if (!priv::start_create_volume_on_surface_job(std::move(emboss_data), volume_type, mouse_pos, gl_volume, m_raycast_manager, m_parent)) { // When model is broken. It could appear that hit miss the object. // So add part near by in simmilar manner as right panel do create_volume(volume_type); } } else { // object is not under mouse position soo create object on plater - priv::start_create_object_job(emboss_data, mouse_pos); + priv::start_create_object_job(std::move(emboss_data), mouse_pos); } } @@ -236,13 +298,13 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) Size s = m_parent.get_canvas_size(); Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); const ModelObjectPtrs &objects = selection.get_model()->objects; // No selected object so create new object if (selection.is_empty() || object_idx < 0 || static_cast(object_idx) >= objects.size()) { // create Object on center of screen // when ray throw center of screen not hit bed it create object on center of bed - priv::start_create_object_job(emboss_data, screen_center); + priv::start_create_object_job(std::move(emboss_data), screen_center); return; } @@ -252,15 +314,18 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) const Camera &camera = wxGetApp().plater()->get_camera(); priv::find_closest_volume(selection, screen_center, camera, objects, &coor, &vol); if (vol == nullptr) { - priv::start_create_object_job(emboss_data, screen_center); - } else if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager, m_parent)) { + priv::start_create_object_job(std::move(emboss_data), screen_center); + } else if (!priv::start_create_volume_on_surface_job(std::move(emboss_data), volume_type, coor, vol, m_raycast_manager, m_parent)) { // in centroid of convex hull is not hit with object // soo create transfomation on border of object + // unique pointer is already destoyed need to create again + std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + // TODO: do it better way + FontProp &fp = static_cast(emboss_data.get())->text_configuration.style.prop; // there is no point on surface so no use of surface will be applied - FontProp &prop = emboss_data.text_configuration.style.prop; - if (prop.use_surface) - prop.use_surface = false; + if (fp.use_surface) + fp.use_surface = false; // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject const ModelObject *obj = objects[vol->object_idx()]; @@ -268,11 +333,11 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Transform3d tr = vol->get_instance_transformation().get_matrix_no_offset().inverse(); Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created - - instance_bb.size().y() / 2 - prop.size_in_mm / 2, // under - prop.emboss / 2 - instance_bb.size().z() / 2 // lay on bed + - instance_bb.size().y() / 2 - fp.size_in_mm / 2, // under + fp.emboss / 2 - instance_bb.size().z() / 2 // lay on bed ); Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); - priv::start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); + priv::start_create_volume_job(obj, volume_trmat, std::move(emboss_data), volume_type); } } @@ -1064,8 +1129,10 @@ bool GLGizmoEmboss::process() std::unique_ptr job = nullptr; - // check cutting from source mesh - bool &use_surface = data.text_configuration.style.prop.use_surface; + // check cutting from source mesh + // TODO: do it better way + FontProp &fp = static_cast(data.base.get())->text_configuration.style.prop; + bool &use_surface = fp.use_surface; bool is_object = m_volume->get_object()->volumes.size() == 1; if (use_surface && is_object) use_surface = false; @@ -3271,15 +3338,16 @@ bool GLGizmoEmboss::is_text_object(const ModelVolume *text) { bool priv::is_valid(ModelVolumeType volume_type) { - if (volume_type == ModelVolumeType::MODEL_PART || volume_type == ModelVolumeType::NEGATIVE_VOLUME || + if (volume_type == ModelVolumeType::MODEL_PART || + volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER) return true; - BOOST_LOG_TRIVIAL(error) << "Can't create embossed volume with this type: " << (int) volume_type; + BOOST_LOG_TRIVIAL(error) << "Can't create embossed text with this type: " << (int) volume_type; return false; } -DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr>& cancel) +std::unique_ptr priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr>& cancel) { // create volume_name std::string volume_name = text; // copy @@ -3297,7 +3365,6 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st // volume must store valid path assert(style_manager.get_wx_font().IsOk()); assert(es.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0); - TextConfiguration tc{es, text}; // Cancel previous Job, when it is in process // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs @@ -3306,10 +3373,14 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager &st cancel->store(true); // create new shared ptr to cancel new job cancel = std::make_shared>(false); - return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), tc, volume_name, cancel}; + + DataBase base(volume_name, cancel); + FontFileWithCache &font = style_manager.get_font_file_with_cache(); + TextConfiguration tc{es, text}; + return std::make_unique(std::move(base), font, std::move(tc)); } -void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) +void priv::start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor) { // start creation of new object Plater *plater = wxGetApp().plater(); @@ -3317,12 +3388,13 @@ void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) const Pointfs &bed_shape = plater->build_volume().bed_shape(); // can't create new object with distance from surface - FontProp &prop = emboss_data.text_configuration.style.prop; - if (prop.distance.has_value()) prop.distance.reset(); + // TODO: do it better way + FontProp &fp = static_cast(emboss_data.get())->text_configuration.style.prop; + if (fp.distance.has_value()) fp.distance.reset(); // can't create new object with using surface - if (prop.use_surface) - prop.use_surface = false; + if (fp.use_surface) + fp.use_surface = false; // Transform3d volume_tr = priv::create_transformation_on_bed(mouse_pos, camera, bed_shape, prop.emboss / 2); DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape}; @@ -3333,10 +3405,13 @@ void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) void priv::start_create_volume_job(const ModelObject *object, const Transform3d volume_trmat, - DataBase &emboss_data, + std::unique_ptr emboss_data, ModelVolumeType volume_type) { - bool &use_surface = emboss_data.text_configuration.style.prop.use_surface; + // TODO: do it better way + FontProp &fp = static_cast(emboss_data.get())->text_configuration.style.prop; + bool &use_surface = fp.use_surface; + std::unique_ptr job; if (use_surface) { // Model to cut surface from. @@ -3348,7 +3423,7 @@ void priv::start_create_volume_job(const ModelObject *object, // check that there is not unexpected volume type assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); SurfaceVolumeData sfvd{volume_trmat, is_outside, std::move(sources)}; - CreateSurfaceVolumeData surface_data{std::move(emboss_data), std::move(sfvd), volume_type, object->id()}; + CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(emboss_data), volume_type, object->id()}; job = std::make_unique(std::move(surface_data)); } } @@ -3363,8 +3438,12 @@ void priv::start_create_volume_job(const ModelObject *object, queue_job(worker, std::move(job)); } -bool priv::start_create_volume_on_surface_job( - DataBase &emboss_data, ModelVolumeType volume_type, const Vec2d &screen_coor, const GLVolume *gl_volume, RaycastManager &raycaster, GLCanvas3D& canvas) +bool priv::start_create_volume_on_surface_job(std::unique_ptr emboss_data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume *gl_volume, + RaycastManager &raycaster, + GLCanvas3D &canvas) { assert(gl_volume != nullptr); if (gl_volume == nullptr) return false; @@ -3395,11 +3474,14 @@ bool priv::start_create_volume_on_surface_job( // Create result volume transformation Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); - const FontProp &font_prop = emboss_data.text_configuration.style.prop; - apply_transformation(font_prop, surface_trmat); + + // TODO: find better way !!! + const FontProp &fp = static_cast(emboss_data.get())->text_configuration.style.prop; + + apply_transformation(fp, surface_trmat); // new transformation in world coor is surface_trmat Transform3d volume_trmat = instance.inverse() * surface_trmat; - start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); + start_create_volume_job(obj, volume_trmat, std::move(emboss_data), volume_type); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 4d788ba54b..30a3540f3d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -41,9 +41,6 @@ static const struct Limits // distance text object from surface MinMax angle{-180.f, 180.f}; // in degrees } limits; - -static std::string choose_svg_file(); -static std::string get_file_name(const std::string &file_path); } // namespace priv GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) @@ -56,6 +53,34 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) m_rotate_gizmo.set_force_local_coordinate(true); } +// Private functions to create emboss volume +namespace priv { + +/// +/// Check if volume type is possible use for new text volume +/// +/// Type +/// True when allowed otherwise false +static bool is_valid(ModelVolumeType volume_type); + +/// +/// Open file dialog with svg files +/// +/// File path to svg +static std::string choose_svg_file(); + +/// +/// Separate file name from file path. +/// String after last delimiter and before last point +/// +/// path return by file dialog +/// File name without directory path +static std::string get_file_name(const std::string &file_path); + + +} // namespace priv + + void GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos){ std::string path = priv::choose_svg_file(); if (path.empty()) return; @@ -594,30 +619,70 @@ void GLGizmoSVG::draw_model_type() } +///////////// +// priv namespace implementation +/////////////// + +bool priv::is_valid(ModelVolumeType volume_type) +{ + if (volume_type == ModelVolumeType::MODEL_PART || + volume_type == ModelVolumeType::NEGATIVE_VOLUME || + volume_type == ModelVolumeType::PARAMETER_MODIFIER ) + return true; + + BOOST_LOG_TRIVIAL(error) << "Can't create embossed SVG with this type: " << (int) volume_type; + return false; +} + std::string priv::get_file_name(const std::string &file_path) { + if (file_path.empty()) + return file_path; + size_t pos_last_delimiter = file_path.find_last_of("/\\"); - size_t pos_point = file_path.find_last_of('.'); - size_t offset = pos_last_delimiter + 1; - size_t count = pos_point - pos_last_delimiter - 1; + if (pos_last_delimiter == std::string::npos) { + // should not happend that in path is not delimiter + assert(false); + pos_last_delimiter = 0; + } + + size_t pos_point = file_path.find_last_of('.'); + if (pos_point == std::string::npos || + pos_point < pos_last_delimiter // last point is inside of directory path + ) { + // there is no extension + assert(false); + pos_point = file_path.size(); + } + + size_t offset = pos_last_delimiter + 1; // result should not contain last delimiter ( +1 ) + size_t count = pos_point - pos_last_delimiter - 1; // result should not contain extension point ( -1 ) return file_path.substr(offset, count); } std::string priv::choose_svg_file() { - wxArrayString input_files; + wxWindow* parent = nullptr; + wxString message = _L("Choose SVG file for emboss:"); wxString defaultDir = wxEmptyString; wxString selectedFile = wxEmptyString; - - wxFileDialog dialog(nullptr, _L("Choose SVG file:"), defaultDir, - selectedFile, file_wildcards(FT_SVG), - wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files); - if (input_files.IsEmpty()) + wxString wildcard = file_wildcards(FT_SVG); + long style = wxFD_OPEN | wxFD_FILE_MUST_EXIST; + wxFileDialog dialog(parent, message, defaultDir, selectedFile, wildcard, style); + if (dialog.ShowModal() != wxID_OK) { + BOOST_LOG_TRIVIAL(warning) << "SVG file for emboss was NOT selected."; return {}; + } + + wxArrayString input_files; + dialog.GetPaths(input_files); + if (input_files.IsEmpty()) { + BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result is empty."; + return {}; + } + if (input_files.size() != 1) - return {}; + BOOST_LOG_TRIVIAL(warning) << "SVG file dialog result contain multiple files but only first is used."; auto &input_file = input_files.front(); std::string path = std::string(input_file.c_str()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 90552c0a5d..6cbf96799d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -37,7 +37,7 @@ static void call_after_if_active(std::function fn, GUI_App* app = &wxGet }); } -static std::set get_volume_ids(const Selection &selection) +static std::set get_selected_volume_ids(const Selection &selection) { const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); const ModelObjectPtrs &model_objects = selection.get_model()->objects; @@ -64,20 +64,6 @@ static std::set get_volume_ids(const Selection &selection) return result; } -// return ModelVolume from selection by object id -static ModelVolume *get_volume(const ObjectID &id, const Selection &selection) { - const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); - const ModelObjectPtrs &model_objects = selection.get_model()->objects; - for (auto volume_id : volume_ids) { - const GLVolume *selected_volume = selection.get_volume(volume_id); - const GLVolume::CompositeID &cid = selected_volume->composite_id; - ModelObject *obj = model_objects[cid.object_id]; - ModelVolume *volume = obj->volumes[cid.volume_id]; - if (id == volume->id()) return volume; - } - return nullptr; -} - static std::string create_volumes_name(const std::set& ids, const Selection &selection){ assert(!ids.empty()); std::string name; @@ -88,7 +74,7 @@ static std::string create_volumes_name(const std::set& ids, const Sele else name += " + "; - const ModelVolume *volume = get_volume(id, selection); + const ModelVolume *volume = get_selected_volume(id, selection); assert(volume != nullptr); name += volume->name; } @@ -181,7 +167,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi { create_gui_cfg(); const Selection &selection = m_parent.get_selection(); - auto act_volume_ids = get_volume_ids(selection); + auto act_volume_ids = get_selected_volume_ids(selection); if (act_volume_ids.empty()) { stop_worker_thread_request(); close(); @@ -472,7 +458,7 @@ void GLGizmoSimplify::process() const Selection& selection = m_parent.get_selection(); State::Data its; for (const auto &id : m_volume_ids) { - const ModelVolume *volume = get_volume(id, selection); + const ModelVolume *volume = get_selected_volume(id, selection); its[id] = std::make_unique(volume->mesh().its); // copy } @@ -549,7 +535,7 @@ void GLGizmoSimplify::apply_simplify() { for (const auto &item: m_state.result) { const ObjectID &id = item.first; const indexed_triangle_set &its = *item.second; - ModelVolume *volume = get_volume(id, selection); + ModelVolume *volume = get_selected_volume(id, selection); assert(volume != nullptr); ModelObject *obj = volume->get_object(); @@ -725,7 +711,7 @@ void GLGizmoSimplify::on_render() const Selection & selection = m_parent.get_selection(); // Check that the GLVolume still belongs to the ModelObject we work on. - if (m_volume_ids != get_volume_ids(selection)) return; + if (m_volume_ids != get_selected_volume_ids(selection)) return; const ModelObjectPtrs &model_objects = selection.get_model()->objects; const Selection::IndicesList &volume_idxs = selection.get_volume_idxs(); diff --git a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp index 8aa9e23cb9..e0e19ad52d 100644 --- a/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontStyleImagesJob.cpp @@ -46,10 +46,7 @@ void CreateFontStyleImagesJob::process(Ctl &ctl) for (ExPolygon &shape : shapes) shape.translate(-bounding_box.min); // calculate conversion from FontPoint to screen pixels by size of font - const auto &cn = item.prop.collection_number; - unsigned int font_index = (cn.has_value()) ? *cn : 0; - double unit_per_em = item.font.font_file->infos[font_index].unit_per_em; - double scale = item.prop.size_in_mm / unit_per_em * SHAPE_SCALE * m_input.ppm; + double scale = get_text_shape_scale(item.prop, *item.font.font_file); scales[index] = scale; //double scale = font_prop.size_in_mm * SCALING_FACTOR; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index f5c315b30e..88f2624919 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -40,8 +40,6 @@ bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surfac bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); -template static ExPolygons create_shape(DataBase &input, Fnc was_canceled); - // /// Try to create mesh from text /// @@ -68,6 +66,13 @@ static TriangleMesh create_default_mesh(); /// Transformation of volume static void update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d *tr = nullptr); +/// +/// Update name in right panel +/// +/// Right panel data +/// Volume with just changed name +static void update_name_in_list(const ObjectList &object_list, const ModelVolume &volume); + /// /// Add new volume to object /// @@ -109,7 +114,7 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr /// /// Cut surface into triangle mesh /// -/// (can't be const - cache of font) +/// (can't be const - cache of font) /// SurfaceVolume data /// Check to interupt execution /// Extruded object from cuted surace @@ -122,50 +127,49 @@ static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &in class JobException : public std::runtime_error { public: JobException(const char* message):runtime_error(message){}}; +auto was_canceled(Job::Ctl &ctl, DataBase &base){ + return [&ctl, &cancel = base.cancel]() -> bool { + if (cancel->load()) + return true; + return ctl.was_canceled(); + }; +} + }// namespace priv ///////////////// /// Create Volume -CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input) - : m_input(std::move(input)) -{ - assert(priv::check(m_input, true)); -} +CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input): m_input(std::move(input)){ assert(priv::check(m_input, true)); } void CreateVolumeJob::process(Ctl &ctl) { - if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateVolumeJob."); - auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); }; - m_result = priv::create_mesh(m_input, was_canceled, ctl); + if (!priv::check(m_input)) + throw std::runtime_error("Bad input data for EmbossCreateVolumeJob."); + + m_result = priv::create_mesh(*m_input.base, priv::was_canceled(ctl, *m_input.base), ctl); // center result Vec3f c = m_result.bounding_box().center().cast(); if (!c.isApprox(Vec3f::Zero())) m_result.translate(-c); } - void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, m_input)) + if (!priv::finalize(canceled, eptr, *m_input.base)) return; if (m_result.its.empty()) return priv::create_message(_u8L("Can't create empty volume.")); - - priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, m_input); + priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base); } ///////////////// /// Create Object -CreateObjectJob::CreateObjectJob(DataCreateObject &&input) - : m_input(std::move(input)) -{ - assert(priv::check(m_input)); -} +CreateObjectJob::CreateObjectJob(DataCreateObject &&input): m_input(std::move(input)){ assert(priv::check(m_input)); } void CreateObjectJob::process(Ctl &ctl) { if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateObjectJob."); - auto was_canceled = [&ctl]()->bool { return ctl.was_canceled(); }; - m_result = priv::create_mesh(m_input, was_canceled, ctl); + auto was_canceled = priv::was_canceled(ctl, *m_input.base); + m_result = priv::create_mesh(*m_input.base, was_canceled, ctl); if (was_canceled()) return; // Create new object @@ -184,7 +188,9 @@ void CreateObjectJob::process(Ctl &ctl) // mouse pose is out of build plate so create object in center of plate bed_coor = bed.centroid().cast(); - double z = m_input.text_configuration.style.prop.emboss / 2; + // TODO: need TextConfiguration refactor to work !!! + double z = m_input.base->shape.depth / 2; + Vec3d offset(bed_coor.x(), bed_coor.y(), z); offset -= m_result.center(); Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z()); @@ -193,29 +199,49 @@ void CreateObjectJob::process(Ctl &ctl) void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, m_input)) + if (!priv::finalize(canceled, eptr, *m_input.base)) return; // only for sure if (m_result.empty()) return priv::create_message(_u8L("Can't create empty object.")); - GUI_App &app = wxGetApp(); - Plater *plater = app.plater(); - ObjectList *obj_list = app.obj_list(); - GLCanvas3D *canvas = plater->canvas3D(); - + GUI_App &app = wxGetApp(); + Plater *plater = app.plater(); plater->take_snapshot(_L("Add Emboss text object")); - // Create new object and change selection - bool center = false; - obj_list->load_mesh_object(std::move(m_result), m_input.volume_name, - center, &m_input.text_configuration, - &m_transformation); + Model& model = plater->model(); +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + { + // INFO: inspiration for create object is from ObjectList::load_mesh_object() + ModelObject *new_object = model.add_object(); + new_object->name = m_input.base->volume_name; + new_object->add_instance(); // each object should have at list one instance + + ModelVolume *new_volume = new_object->add_volume(std::move(m_result)); + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + // write emboss data into volume + m_input.base->write(*new_volume); + + // set transformation + Slic3r::Geometry::Transformation tr(m_transformation); + new_object->instances.front()->set_transformation(tr); + new_object->ensure_on_bed(); + + // Actualize right panel and set inside of selection + app.obj_list()->paste_objects_into_list({model.objects.size() - 1}); + } +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ // When add new object selection is empty. // When cursor move and no one object is selected than // Manager::reset_all() So Gizmo could be closed before end of creation object + GLCanvas3D *canvas = plater->canvas3D(); GLGizmosManager &manager = canvas->get_gizmos_manager(); if (manager.get_current_type() != GLGizmosManager::Emboss) manager.open_gizmo(GLGizmosManager::Emboss); @@ -226,22 +252,15 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) ///////////////// /// Update Volume -UpdateJob::UpdateJob(DataUpdate&& input) - : m_input(std::move(input)) -{ - assert(priv::check(m_input, true)); -} +UpdateJob::UpdateJob(DataUpdate&& input): m_input(std::move(input)){ assert(priv::check(m_input, true)); } void UpdateJob::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 { - if (cancel->load()) return true; - return ctl.was_canceled(); - }; - m_result = priv::try_create_mesh(m_input, was_canceled); + auto was_canceled = priv::was_canceled(ctl, *m_input.base); + m_result = priv::try_create_mesh(*m_input.base, was_canceled); if (was_canceled()) return; if (m_result.its.empty()) throw priv::JobException(_u8L("Created text volume is empty. Change text or font.").c_str()); @@ -253,7 +272,7 @@ void UpdateJob::process(Ctl &ctl) void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, m_input)) + if (!priv::finalize(canceled, eptr, *m_input.base)) return; priv::update_volume(std::move(m_result), m_input); } @@ -299,16 +318,14 @@ CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input) 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); + m_result = priv::cut_surface(*m_input.base, m_input, priv::was_canceled(ctl, *m_input.base)); } void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, m_input)) + if (!priv::finalize(canceled, eptr, *m_input.base)) return; priv::create_volume(std::move(m_result), m_input.object_id, - m_input.volume_type, m_input.text_tr, m_input); + m_input.volume_type, m_input.transform, *m_input.base); } ///////////////// @@ -323,23 +340,17 @@ void UpdateSurfaceVolumeJob::process(Ctl &ctl) { if (!priv::check(m_input)) throw std::runtime_error("Bad input data for UseSurfaceJob."); - - // check cancelation of process - auto was_canceled = [&ctl, &cancel = m_input.cancel]()->bool { - if (cancel->load()) return true; - return ctl.was_canceled(); - }; - m_result = priv::cut_surface(m_input, m_input, was_canceled); + m_result = priv::cut_surface(*m_input.base, m_input, priv::was_canceled(ctl, *m_input.base)); } void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, m_input)) + if (!priv::finalize(canceled, eptr, *m_input.base)) return; // when start using surface it is wanted to move text origin on surface of model // also when repeteadly move above surface result position should match - Transform3d *tr = &m_input.text_tr; + Transform3d *tr = &m_input.transform; priv::update_volume(std::move(m_result), m_input, tr); } @@ -348,23 +359,25 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface) { 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(); + //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(); - assert(input.text_configuration.style.prop.use_surface == use_surface); - res &= input.text_configuration.style.prop.use_surface == use_surface; + //assert(input.text_configuration.style.prop.use_surface == use_surface); + //res &= input.text_configuration.style.prop.use_surface == use_surface; return res; } bool priv::check(const DataCreateVolume &input, bool is_main_thread) { bool check_fontfile = false; - bool res = check((DataBase) input, check_fontfile); + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile); assert(input.volume_type != ModelVolumeType::INVALID); res &= input.volume_type != ModelVolumeType::INVALID; assert(input.object_id.id >= 0); @@ -373,7 +386,9 @@ bool priv::check(const DataCreateVolume &input, bool is_main_thread) { } bool priv::check(const DataCreateObject &input) { bool check_fontfile = false; - bool res = check((DataBase) input, check_fontfile); + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile); assert(input.screen_coor.x() >= 0.); res &= input.screen_coor.x() >= 0.; assert(input.screen_coor.y() >= 0.); @@ -384,66 +399,49 @@ bool priv::check(const DataCreateObject &input) { } bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){ bool check_fontfile = true; - bool res = check((DataBase) input, check_fontfile, use_surface); + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile, use_surface); 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; + assert(input.base->cancel != nullptr); + res &= input.base->cancel != nullptr; if (is_main_thread) - assert(!input.cancel->load()); + assert(!input.base->cancel->load()); return res; } bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread) { bool use_surface = true; - bool res = check((DataBase)input, is_main_thread, use_surface); + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, 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.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, is_main_thread, use_surface); assert(!input.sources.empty()); res &= !input.sources.empty(); return res; } -template -ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) { - FontFileWithCache &font = input.font_file; - const TextConfiguration &tc = input.text_configuration; - const char *text = tc.text.c_str(); - const FontProp &prop = tc.style.prop; - - assert(font.has_value()); - if (!font.has_value()) - return {}; - - return text2shapes(font, text, prop, was_canceled); -} - template -TriangleMesh priv::try_create_mesh(DataBase &input, Fnc was_canceled) +TriangleMesh priv::try_create_mesh(DataBase &base, Fnc was_canceled) { - ExPolygons shapes = priv::create_shape(input, was_canceled); - if (shapes.empty()) return {}; - if (was_canceled()) return {}; - - const FontProp &prop = input.text_configuration.style.prop; - const std::optional &cn = prop.collection_number; - unsigned int font_index = (cn.has_value()) ? *cn : 0; - const FontFileWithCache &font = input.font_file; - assert(font_index < font.font_file->infos.size()); - int unit_per_em = font.font_file->infos[font_index].unit_per_em; - float scale = prop.size_in_mm / unit_per_em; - float depth = prop.emboss / scale; + const EmbossShape& shape = base.create_shape(); + if (shape.shapes.empty()) return {}; + float depth = shape.depth / shape.scale; auto projectZ = std::make_unique(depth); - ProjectScale project(std::move(projectZ), scale); + ProjectScale project(std::move(projectZ), shape.scale); if (was_canceled()) return {}; - return TriangleMesh(polygons2model(shapes, project)); + return TriangleMesh(polygons2model(shape.shapes, project)); } template @@ -451,11 +449,8 @@ TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl) { // It is neccessary to create some shape // Emboss text window is opened by creation new emboss text object - TriangleMesh result; - if (input.font_file.has_value()) { - result = try_create_mesh(input, was_canceled); - if (was_canceled()) return {}; - } + TriangleMesh result = try_create_mesh(input, was_canceled); + if (was_canceled()) return {}; if (result.its.empty()) { result = priv::create_default_mesh(); @@ -483,16 +478,13 @@ TriangleMesh priv::create_default_mesh() return triangle_mesh; } -void UpdateJob::update_volume(ModelVolume *volume, - TriangleMesh &&mesh, - const TextConfiguration &text_configuration, - const std::string &volume_name) +void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base) { // check inputs bool is_valid_input = volume != nullptr && !mesh.empty() && - !volume_name.empty(); + !base.volume_name.empty(); assert(is_valid_input); if (!is_valid_input) return; @@ -501,24 +493,17 @@ void UpdateJob::update_volume(ModelVolume *volume, volume->set_new_unique_id(); volume->calculate_convex_hull(); volume->get_object()->invalidate_bounding_box(); - volume->text_configuration = text_configuration; - // discard information about rotation, should not be stored in volume - volume->text_configuration->style.prop.angle.reset(); + // write data from base into volume + base.write(*volume); - GUI_App &app = wxGetApp(); // may be move to input - GLCanvas3D *canvas = app.plater()->canvas3D(); - 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(); - - if (volume->name != volume_name) { - volume->name = volume_name; - - // update volume name in right panel( volume / object name) - int volume_idx = gl_volume->volume_idx(); - ObjectList *obj_list = app.obj_list(); - obj_list->update_name_in_list(object_idx, volume_idx); + GUI_App &app = wxGetApp(); // may be move to input + if (volume->name != base.volume_name) { + volume->name = base.volume_name; + + ObjectList *obj_list = app.obj_list(); + if (obj_list != nullptr) + priv::update_name_in_list(*obj_list, *volume); } // When text is object. @@ -528,6 +513,8 @@ void UpdateJob::update_volume(ModelVolume *volume, volume->get_object()->ensure_on_bed(); // redraw scene + GLCanvas3D *canvas = app.plater()->canvas3D(); + bool refresh_immediately = false; canvas->reload_scene(refresh_immediately); @@ -535,21 +522,53 @@ void UpdateJob::update_volume(ModelVolume *volume, canvas->post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } +void priv::update_name_in_list(const ObjectList &object_list, const ModelVolume &volume) +{ + const ModelObjectPtrs *objects_ptr = object_list.objects(); + if (objects_ptr == nullptr) + return; + + const ModelObjectPtrs &objects = *objects_ptr; + const ModelObject *object = volume.get_object(); + const ObjectID& object_id = object->id(); + + // search for index of object + int object_index = -1; + for (size_t i = 0; i < objects.size(); ++i) + if (objects[i]->id() == object_id) { + object_index = i; + break; + } + + const ModelVolumePtrs volumes = object->volumes; + const ObjectID& volume_id = volume.id(); + + // search for index of volume + int volume_index = -1; + for (size_t i = 0; i < volumes.size(); ++i) + if (volumes[i]->id() == volume_id) { + volume_index = i; + break; + } + + if (object_index < 0 || volume_index < 0) + return; + + object_list.update_name_in_list(object_index, volume_index); +} + void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d* tr) { // for sure that some object will be created if (mesh.its.empty()) return create_message("Empty mesh can't be created."); - Plater *plater = wxGetApp().plater(); - GLCanvas3D *canvas = plater->canvas3D(); + Plater *plater = wxGetApp().plater(); + // Check gizmo is still open otherwise job should be canceled + assert(plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss || + plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Svg); - // 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); + std::string snap_name = GUI::format(_L("Change: %1%"), data.base->volume_name); Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction); ModelVolume *volume = get_volume(plater->model().objects, data.volume_id); @@ -567,7 +586,7 @@ void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3 volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse()); } - UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name); + UpdateJob::update_volume(volume, std::move(mesh), *data.base); } void priv::create_volume( @@ -609,20 +628,15 @@ void priv::create_volume( 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 - - // discard information about rotation, should not be stored in volume - volume->text_configuration->style.prop.angle.reset(); - + volume->name = data.volume_name; // copy volume->set_transformation(trmat); + data.write(*volume); // update printable state on canvas if (type == ModelVolumeType::MODEL_PART) { @@ -635,7 +649,7 @@ void priv::create_volume( // 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; }; + 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()); @@ -698,24 +712,21 @@ OrthoProject3d priv::create_emboss_projection( } // input can't be const - cache of font -TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function was_canceled) +TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, std::function was_canceled) { - ExPolygons shapes = create_shape(input1, was_canceled); + EmbossShape& emboss_shape = base.create_shape(); + ExPolygons& shapes = emboss_shape.shapes; if (shapes.empty()) throw JobException(_u8L("Font doesn't have any shape for given text.").c_str()); if (was_canceled()) return {}; // Define alignment of text - left, right, center, top bottom, .... - BoundingBox bb = get_extents(shapes); + 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; - const FontProp &fp = input1.text_configuration.style.prop; - double shape_scale = get_shape_scale(fp, ff); - const SurfaceVolumeData::ModelSources &sources = input2.sources; const SurfaceVolumeData::ModelSource *biggest = nullptr; @@ -726,9 +737,9 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 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; + Transform3d cut_projection_tr = mesh_tr_inv * input2.transform; std::pair z_range{0., 1.}; - OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); + OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_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; @@ -745,7 +756,7 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 throw JobException(_u8L("There is no volume in projection direction.").c_str()); Transform3d tr_inv = biggest->tr.inverse(); - Transform3d cut_projection_tr = tr_inv * input2.text_tr; + Transform3d cut_projection_tr = tr_inv * input2.transform; size_t itss_index = s_to_itss[biggest - &sources.front()]; BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]); @@ -767,10 +778,10 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 Transform3d emboss_tr = cut_projection_tr.inverse(); BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); std::pair 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); + OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_shape.scale, z_range); float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension); - bool is_text_reflected = Slic3r::has_reflection(input2.text_tr); + bool is_text_reflected = Slic3r::has_reflection(input2.transform); if (is_text_reflected) { // revert order of points in expolygons // CW --> CCW @@ -794,8 +805,8 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 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); + // !! Projection needs to transform cut + OrthoProject3d projection = create_emboss_projection(input2.is_outside, emboss_shape.depth, emboss_tr, cut); indexed_triangle_set new_its = cut2model(cut, projection); assert(!new_its.empty()); if (was_canceled()) return {}; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index d3896c0a65..d909291368 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include "libslic3r/Emboss.hpp" +#include "libslic3r/EmbossShape.hpp" #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/GUI/Camera.hpp" #include "Job.hpp" @@ -17,20 +18,43 @@ class TriangleMesh; namespace Slic3r::GUI::Emboss { /// -/// Base data holder for embossing +/// Base data hold data for create emboss shape /// -struct DataBase +class DataBase { - // Keep pointer on Data of font (glyph shapes) - Slic3r::Emboss::FontFileWithCache font_file; - // font item is not used for create object - TextConfiguration text_configuration; - // new volume name created from text +public: + DataBase(std::string volume_name, std::shared_ptr> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {} + DataBase(std::string volume_name, std::shared_ptr> cancel, EmbossShape shape) + : volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)) + {} + virtual ~DataBase() {} + + /// + /// Create shape + /// e.g. Text extract glyphs from font + /// Not 'const' function because it could modify shape + /// + virtual EmbossShape &create_shape() { return shape; }; + + /// + /// Write data how to reconstruct shape to volume + /// + /// Data object for store emboss params + virtual void write(ModelVolume &volume) const + { + volume.name = volume_name; + volume.emboss_shape = shape; + }; + + // new volume name std::string volume_name; // flag that job is canceled // for time after process. std::shared_ptr> cancel; + + // shape to emboss + EmbossShape shape; }; /// @@ -38,8 +62,11 @@ struct DataBase /// Volume is created on the surface of existing volume in object. /// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! /// -struct DataCreateVolume : public DataBase +struct DataCreateVolume { + // Hold data about shape + std::unique_ptr base; + // define embossed volume type ModelVolumeType volume_type; @@ -71,8 +98,11 @@ public: /// Object is placed on bed under screen coor /// OR to center of scene when it is out of bed shape /// -struct DataCreateObject : public DataBase +struct DataCreateObject { + // Hold data about shape + std::unique_ptr base; + // define position on screen where to create object Vec2d screen_coor; @@ -101,8 +131,11 @@ public: /// /// Hold neccessary data to update embossed text object in job /// -struct DataUpdate : public DataBase +struct DataUpdate { + // Hold data about shape + std::unique_ptr base; + // unique identifier of volume to change ObjectID volume_id; }; @@ -140,18 +173,14 @@ public: /// /// Volume to be updated /// New Triangle mesh for volume - /// Parametric description of volume - /// Name of volume - static void update_volume(ModelVolume *volume, - TriangleMesh &&mesh, - const TextConfiguration &text_configuration, - const std::string &volume_name); + /// Data to write into volume + static void update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base); }; struct SurfaceVolumeData { - // Transformation of text volume inside of object - Transform3d text_tr; + // Transformation of volume inside of object + Transform3d transform; // Define projection move // True (raised) .. move outside from surface @@ -172,7 +201,10 @@ struct SurfaceVolumeData /// /// Hold neccessary data to create(cut) volume from surface object in job /// -struct CreateSurfaceVolumeData : public DataBase, public SurfaceVolumeData{ +struct CreateSurfaceVolumeData : public SurfaceVolumeData{ + // Hold data about shape + std::unique_ptr base; + // define embossed volume type ModelVolumeType volume_type; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2b77fe7ff4..e55a80a107 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3435,5 +3435,30 @@ const GLVolume *get_selected_gl_volume(const Selection &selection) return selection.get_volume(volume_idx); } +ModelVolume *get_selected_volume(const ObjectID &volume_id, const Selection &selection) { + const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); + const ModelObjectPtrs &model_objects = selection.get_model()->objects; + for (auto id : volume_ids) { + const GLVolume *selected_volume = selection.get_volume(id); + const GLVolume::CompositeID &cid = selected_volume->composite_id; + ModelObject *obj = model_objects[cid.object_id]; + ModelVolume *volume = obj->volumes[cid.volume_id]; + if (volume_id == volume->id()) + return volume; + } + return nullptr; +} + +ModelVolume *get_volume(const ObjectID &volume_id, const Selection &selection) { + const ModelObjectPtrs &objects = selection.get_model()->objects; + for (const ModelObject *object : objects) { + for (ModelVolume *volume : object->volumes) { + if (volume->id() == volume_id) + return volume; + } + } + return nullptr; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index d3f4567027..e34e0e7732 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -18,6 +18,7 @@ class Shader; class Model; class ModelObject; class ModelVolume; +class ObjectID; class GLVolume; class GLArrow; class GLCurvedArrow; @@ -524,9 +525,12 @@ private: #endif // ENABLE_WORLD_COORDINATE }; -ModelVolume *get_selected_volume(const Selection &selection); +ModelVolume *get_selected_volume (const Selection &selection); const GLVolume *get_selected_gl_volume(const Selection &selection); +ModelVolume *get_selected_volume (const ObjectID &volume_id, const Selection &selection); +ModelVolume *get_volume (const ObjectID &volume_id, const Selection &selection); + } // namespace GUI } // namespace Slic3r diff --git a/tests/libslic3r/test_cut_surface.cpp b/tests/libslic3r/test_cut_surface.cpp index ffc762b865..d9376195b5 100644 --- a/tests/libslic3r/test_cut_surface.cpp +++ b/tests/libslic3r/test_cut_surface.cpp @@ -23,7 +23,8 @@ TEST_CASE("Cut character from surface", "[]") Transform3d tr = Transform3d::Identity(); tr.translate(Vec3d(0., 0., -z_depth)); - tr.scale(Emboss::SHAPE_SCALE); + double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE + tr.scale(text_shape_scale); Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth)); auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5); @@ -158,7 +159,7 @@ TEST_CASE("CutSurface in 3mf", "[Emboss]") FontProp fp = tc.style.prop; ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp); - double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file); + double shape_scale = Emboss::get_text_shape_scale(fp, *ff.font_file); Emboss::OrthoProject projection = create_projection_for_cut( cut_projection_tr, shape_scale, get_extents(shapes), z_range); diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 8ab482f9ce..f087dbf3a2 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -258,7 +258,9 @@ TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]") auto a = heal_and_check(polygons); Polygons scaled_shape = polygons; // copy - scale(scaled_shape, 1 / Emboss::SHAPE_SCALE); + + double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE + scale(scaled_shape, 1 / text_shape_scale); auto b = heal_and_check(scaled_shape); // different scale @@ -468,7 +470,8 @@ TEST_CASE("Cut surface", "[]") Transform3d tr = Transform3d::Identity(); tr.translate(Vec3d(0., 0., -z_depth)); - tr.scale(Emboss::SHAPE_SCALE); + double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE + tr.scale(text_shape_scale); Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth)); auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5); @@ -527,8 +530,8 @@ TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]") TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]") { EmbossShape emboss; - emboss.shape = {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}}; - emboss.shape_scale = 2.; + emboss.shapes = {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}}; + emboss.scale = 2.; emboss.depth = 5.; emboss.use_surface = true; emboss.distance = 3.f; @@ -549,8 +552,8 @@ TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]") cereal::BinaryInputArchive iarchive(ss); // Create an input archive iarchive(emboss_loaded); } - CHECK(emboss.shape == emboss_loaded.shape); - CHECK(emboss.shape_scale == emboss_loaded.shape_scale); + CHECK(emboss.shapes == emboss_loaded.shapes); + CHECK(emboss.scale == emboss_loaded.scale); CHECK(emboss.depth == emboss_loaded.depth); CHECK(emboss.use_surface == emboss_loaded.use_surface); CHECK(emboss.distance == emboss_loaded.distance);