From ae0f4a7c8bbcff8c47ca15be836e852bcc55b8bb Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 7 Mar 2023 09:20:52 +0100 Subject: [PATCH 001/120] Add emboss SVG configuration to model add Cereal serialization of Polygons and ExPolygons --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/EmbossShape.hpp | 64 ++++++++++++++++++++++++++++ src/libslic3r/ExPolygonSerialize.hpp | 28 ++++++++++++ src/libslic3r/Model.hpp | 13 ++++-- tests/libslic3r/test_emboss.cpp | 35 ++++++++++++++- tests/libslic3r/test_expolygon.cpp | 29 +++++++++++++ 6 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 src/libslic3r/EmbossShape.hpp create mode 100644 src/libslic3r/ExPolygonSerialize.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 098131e008..c551b22898 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -56,9 +56,11 @@ set(SLIC3R_SOURCES ElephantFootCompensation.hpp Emboss.cpp Emboss.hpp + EmbossShape.hpp enum_bitmask.hpp ExPolygon.cpp ExPolygon.hpp + ExPolygonSerialize.hpp ExPolygonsIndex.cpp ExPolygonsIndex.hpp Extruder.cpp diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp new file mode 100644 index 0000000000..bbb37d39a7 --- /dev/null +++ b/src/libslic3r/EmbossShape.hpp @@ -0,0 +1,64 @@ +#ifndef slic3r_EmbossShape_hpp_ +#define slic3r_EmbossShape_hpp_ + +#include +#include +#include +#include +#include +#include "Point.hpp" // Transform3d +#include "ExPolygon.hpp" +#include "ExPolygonSerialize.hpp" + +namespace Slic3r { + +/// +/// Contain plane shape information to be able emboss it and edit it +/// +struct EmbossShape +{ + // shape defined by integer point consist only by lines not curves + ExPolygons shape; + + // scale of shape, multiplier to get 3d point in mm from integer shape + double shape_scale; + + // Emboss depth + double depth; // [in world mm] + + // Flag that result volume use surface cutted from source objects + bool use_surface = false; + + // distance from surface point + // used for move over model surface + // When not set value is zero and is not stored + std::optional distance; // [in mm] + + // !!! Volume stored in .3mf has transformed vertices. + // (baked transformation into vertices position) + // Only place for fill this is when load from .3mf + // This is correction for volume transformation + // Stored_Transform3d * fix_3mf_tr = Transform3d_before_store_to_3mf + std::optional fix_3mf_tr; + + // file(.svg) path to source of shape + 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); + 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); + cereal::load(ar, distance); + cereal::load(ar, fix_3mf_tr); + } +}; + +} // namespace Slic3r + +#endif // slic3r_EmbossShape_hpp_ diff --git a/src/libslic3r/ExPolygonSerialize.hpp b/src/libslic3r/ExPolygonSerialize.hpp new file mode 100644 index 0000000000..712d4706d5 --- /dev/null +++ b/src/libslic3r/ExPolygonSerialize.hpp @@ -0,0 +1,28 @@ +#ifndef slic3r_ExPolygonSerialize_hpp_ +#define slic3r_ExPolygonSerialize_hpp_ + +#include "ExPolygon.hpp" +#include "Point.hpp" // Cereal serialization of Point +#include +#include + +/// +/// External Cereal serialization of ExPolygons +/// + +// Serialization through the Cereal library +#include +namespace cereal { + +template +void serialize(Archive &archive, Slic3r::Polygon &polygon) { + archive(polygon.points); +} + +template +void serialize(Archive &archive, Slic3r::ExPolygon &expoly) { + archive(expoly.contour, expoly.holes); +} + +} // namespace Slic3r +#endif // slic3r_ExPolygonSerialize_hpp_ diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 08fa794810..5056ccf696 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -15,6 +15,7 @@ #include "CustomGCode.hpp" #include "enum_bitmask.hpp" #include "TextConfiguration.hpp" +#include "EmbossShape.hpp" #include #include @@ -824,6 +825,10 @@ public: // Contain information how to re-create volume std::optional text_configuration; + // Is set only when volume is Embossed Shape + // Contain 2d information about embossed shape to be editabled + std::optional emboss_shape; + // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; } ModelVolumeType type() const { return m_type; } @@ -1011,8 +1016,7 @@ private: name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), - cut_info(other.cut_info), - text_configuration(other.text_configuration) + cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -1033,8 +1037,7 @@ private: // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation), - cut_info(other.cut_info), - text_configuration(other.text_configuration) + cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) { assert(this->id().valid()); assert(this->config.id().valid()); @@ -1082,6 +1085,7 @@ private: cereal::load_by_value(ar, mmu_segmentation_facets); cereal::load_by_value(ar, config); cereal::load(ar, text_configuration); + cereal::load(ar, emboss_shape); assert(m_mesh); if (has_convex_hull) { cereal::load_optional(ar, m_convex_hull); @@ -1099,6 +1103,7 @@ private: cereal::save_by_value(ar, mmu_segmentation_facets); cereal::save_by_value(ar, config); cereal::save(ar, text_configuration); + cereal::save(ar, emboss_shape); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); } diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 2d98b1d4d4..90eaa01447 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -491,7 +491,7 @@ TEST_CASE("Cut surface", "[]") #include #include #include -TEST_CASE("UndoRedo serialization", "[Emboss]") +TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]") { TextConfiguration tc; tc.text = "Dovede-li se člověk zasmát sám sobě, nevyjde ze smíchu po celý život."; @@ -523,6 +523,39 @@ TEST_CASE("UndoRedo serialization", "[Emboss]") CHECK(tc.fix_3mf_tr.has_value() == tc_loaded.fix_3mf_tr.has_value()); } +#include "libslic3r/EmbossShape.hpp" +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.depth = 5.; + emboss.use_surface = true; + emboss.distance = 3.f; + emboss.fix_3mf_tr = Transform3d::Identity(); + emboss.svg_file_path = "Everything starts somewhere, though many physicists disagree.\ + But people have always been dimly aware of the problem with the start of things.\ + They wonder how the snowplough driver gets to work,\ + or how the makers of dictionaries look up the spelling of words."; + std::stringstream ss; // any stream can be used + { + cereal::BinaryOutputArchive oarchive(ss); // Create an output archive + + oarchive(emboss); + } // archive goes out of scope, ensuring all contents are flushed + + EmbossShape emboss_loaded; + { + 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.depth == emboss_loaded.depth); + CHECK(emboss.use_surface == emboss_loaded.use_surface); + CHECK(emboss.distance == emboss_loaded.distance); +} + #include #include diff --git a/tests/libslic3r/test_expolygon.cpp b/tests/libslic3r/test_expolygon.cpp index 63d763e2f6..3810af5ed5 100644 --- a/tests/libslic3r/test_expolygon.cpp +++ b/tests/libslic3r/test_expolygon.cpp @@ -65,3 +65,32 @@ SCENARIO("Basics", "[ExPolygon]") { } } } + +#include +#include +#include +#include "libslic3r/ExPolygonSerialize.hpp" +TEST_CASE("Serialization of expolygons", "[ExPolygon, Cereal, serialization]") +{ + ExPolygons expolys{{ + // expolygon 1 - without holes + {{0,0}, {10,0}, {10,10}, {0,10}}, // contour + // expolygon 2 - with rect 1px hole + {{{0,0}, {10,0}, {10,10}, {0,10}}, + {{5, 5}, {6, 5}, {6, 6}, {5, 6}}} + }}; + + std::stringstream ss; // any stream can be used + { + cereal::BinaryOutputArchive oarchive(ss); // Create an output archive + oarchive(expolys); + } // archive goes out of scope, ensuring all contents are flushed + + ExPolygons expolys_loaded; + { + cereal::BinaryInputArchive iarchive(ss); // Create an input archive + iarchive(expolys_loaded); + } + + CHECK(expolys == expolys_loaded); +} From a5ea46a83abaf4d114f2591a8a89e27b47acc4e2 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 7 Mar 2023 16:47:12 +0100 Subject: [PATCH 002/120] Add SVG gizmo - buildable --- src/libslic3r/Geometry.hpp | 6 + src/libslic3r/Point.hpp | 21 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 214 +-------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 613 ++++++++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 172 +++++++ src/slic3r/GUI/ImGuiWrapper.cpp | 42 ++ src/slic3r/GUI/ImGuiWrapper.hpp | 9 + src/slic3r/GUI/SurfaceDrag.cpp | 79 +++ src/slic3r/GUI/SurfaceDrag.hpp | 26 + 11 files changed, 995 insertions(+), 193 deletions(-) create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index ba7e7a4b2a..4ccf0f86b2 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -306,6 +306,12 @@ template T angle_to_0_2PI(T angle) return angle; } +template void to_range_pi_pi(T &angle){ + if (angle > T(PI) || angle < -T(PI)) { + int count = static_cast(std::round(angle / (2 * PI))); + angle -= static_cast(count * 2 * PI); + } +} void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d53352f28e..d97f1b32d0 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -539,6 +539,27 @@ inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) inline Point align_to_grid(Point coord, Point spacing, Point base) { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } +// MinMaxLimits +template struct MinMax { T min; T max;}; +template +static bool apply(std::optional &val, const MinMax &limit) { + if (!val.has_value()) return false; + return apply(*val, limit); +} +template +static bool apply(T &val, const MinMax &limit) +{ + if (val > limit.max) { + val = limit.max; + return true; + } + if (val < limit.min) { + val = limit.min; + return true; + } + return false; +} + } // namespace Slic3r // start Boost diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 2ca1998e31..6db1456e3d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -65,6 +65,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoSeam.hpp GUI/Gizmos/GLGizmoSimplify.cpp GUI/Gizmos/GLGizmoSimplify.hpp + GUI/Gizmos/GLGizmoSVG.cpp + GUI/Gizmos/GLGizmoSVG.hpp GUI/Gizmos/GLGizmoMmuSegmentation.cpp GUI/Gizmos/GLGizmoMmuSegmentation.hpp GUI/Gizmos/GLGizmoMeasure.cpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 6deda777e5..82b83dec6d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -67,7 +67,6 @@ using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; namespace priv { -template struct MinMax { T min; T max;}; template struct Limit { // Limitation for view slider range in GUI MinMax gui; @@ -86,40 +85,8 @@ static const struct Limits MinMax line_gap{-20000, 20000}; // in font points // distance text object from surface MinMax angle{-180.f, 180.f}; // in degrees - - template - static bool apply(std::optional &val, const MinMax &limit) { - if (val.has_value()) - return apply(*val, limit); - return false; - } - template - static bool apply(T &val, const MinMax &limit) - { - if (val > limit.max) { - val = limit.max; - return true; - } - if (val < limit.min) { - val = limit.min; - return true; - } - return false; - } - } limits; -// Define where is up vector on model -constexpr double up_limit = 0.9; - -// Normalize radian angle from -PI to PI -template void to_range_pi_pi(T& angle) -{ - if (angle > PI || angle < -PI) { - int count = static_cast(std::round(angle / (2 * PI))); - angle -= static_cast(count * 2 * PI); - } -} } // namespace priv using namespace priv; @@ -232,15 +199,6 @@ enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ } const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType type, IconState state); // short call of Slic3r::GUI::button static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); - -/// -/// Apply camera direction for emboss direction -/// -/// Define view vector -/// Containe Selected Model to modify -/// True when apply change otherwise false -static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); - } // namespace priv void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) @@ -342,7 +300,7 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle += *m_rotate_start_angle; // move to range <-M_PI, M_PI> - priv::to_range_pi_pi(angle); + Geometry::to_range_pi_pi(angle); // set into activ style assert(m_style_manager.is_active_font()); @@ -363,7 +321,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) return false; std::optional up_limit; - if (m_keep_up) up_limit = priv::up_limit; + if (m_keep_up) up_limit = Slic3r::GUI::up_limit; const Camera &camera = wxGetApp().plater()->get_camera(); bool was_dragging = m_surface_drag.has_value(); bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); @@ -399,7 +357,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) if (gl_volume == nullptr || !m_style_manager.is_active_font()) return res; - m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); } } return res; @@ -622,12 +580,6 @@ namespace priv { /// static ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size); -/// -/// Change position of emboss window -/// -/// -/// When True Only move to be full visible otherwise reset position -static void change_window_position(std::optional &output_window_offset, bool try_to_fix); } // namespace priv void GLGizmoEmboss::on_set_state() @@ -679,7 +631,7 @@ void GLGizmoEmboss::on_set_state() m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); } else { if (m_gui_cfg.has_value()) - priv::change_window_position(m_set_window_offset, false); + m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), false); else m_set_window_offset = ImVec2(-1, -1); } @@ -712,7 +664,7 @@ void GLGizmoEmboss::on_stop_dragging() assert(m_style_manager.is_active_font()); assert(gl_volume != nullptr); if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); m_rotate_start_angle.reset(); @@ -1029,7 +981,7 @@ void GLGizmoEmboss::set_volume_by_selection() // Calculate current angle of up vector assert(m_style_manager.is_active_font()); if (m_style_manager.is_active_font()) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); // calculate scale for height and depth inside of scaled object instance calculate_scale(); @@ -2228,7 +2180,7 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, // fix rotation float f_angle = f_angle_opt.has_value() ? *f_angle_opt : .0f; float t_angle = t_angle_opt.has_value() ? *t_angle_opt : .0f; - do_rotate(t_angle - f_angle); + do_local_z_rotate(m_parent, t_angle - f_angle); } // fix distance (Z move) when exists difference in styles @@ -2237,7 +2189,7 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, if (!is_approx(f_move_opt, t_move_opt)) { float f_move = f_move_opt.has_value() ? *f_move_opt : .0f; float t_move = t_move_opt.has_value() ? *t_move_opt : .0f; - do_translate(Vec3d::UnitZ() * (t_move - f_move)); + do_local_z_move(m_parent, t_move - f_move); } } @@ -2669,7 +2621,7 @@ bool GLGizmoEmboss::set_height() { float &value = m_style_manager.get_style().prop.size_in_mm; // size can't be zero or negative - priv::Limits::apply(value, priv::limits.size_in_mm); + apply(value, priv::limits.size_in_mm); if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { assert(false); @@ -2710,7 +2662,7 @@ bool GLGizmoEmboss::set_depth() float &value = m_style_manager.get_style().prop.emboss; // size can't be zero or negative - priv::Limits::apply(value, priv::limits.emboss); + apply(value, priv::limits.emboss); // only different value need process return !is_approx(value, m_volume->text_configuration->style.prop.emboss); @@ -2795,39 +2747,6 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, undo_tooltip, undo_offset, draw_slider_float); } -void GLGizmoEmboss::do_translate(const Vec3d &relative_move) -{ - assert(m_volume != nullptr); - assert(m_volume->text_configuration.has_value()); - Selection &selection = m_parent.get_selection(); - assert(!selection.is_empty()); - selection.setup_cache(); - selection.translate(relative_move, TransformationType::Local); - - std::string snapshot_name; // empty mean no store undo / redo - // NOTE: it use L instead of _L macro because prefix _ is appended inside - // function do_move - // snapshot_name = L("Set surface distance"); - m_parent.do_move(snapshot_name); -} - -void GLGizmoEmboss::do_rotate(float relative_z_angle) -{ - assert(m_volume != nullptr); - assert(m_volume->text_configuration.has_value()); - Selection &selection = m_parent.get_selection(); - assert(!selection.is_empty()); - selection.setup_cache(); - TransformationType transformation_type = TransformationType::Local_Relative_Joint; - selection.rotate(Vec3d(0., 0., relative_z_angle), transformation_type); - - std::string snapshot_name; // empty meand no store undo / redo - // NOTE: it use L instead of _L macro because prefix _ is appended - // inside function do_move - // snapshot_name = L("Set text rotation"); - m_parent.do_rotate(snapshot_name); -} - void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); @@ -2898,7 +2817,7 @@ void GLGizmoEmboss::draw_advanced() if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!priv::Limits::apply(font_prop.char_gap, priv::limits.char_gap) || + if (!apply(font_prop.char_gap, priv::limits.char_gap) || !m_volume->text_configuration->style.prop.char_gap.has_value() || m_volume->text_configuration->style.prop.char_gap != font_prop.char_gap) { // char gap is stored inside of imgui font atlas @@ -2914,7 +2833,7 @@ void GLGizmoEmboss::draw_advanced() if (rev_slider(tr.line_gap, font_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!priv::Limits::apply(font_prop.line_gap, priv::limits.line_gap) || + if (!apply(font_prop.line_gap, priv::limits.line_gap) || !m_volume->text_configuration->style.prop.line_gap.has_value() || m_volume->text_configuration->style.prop.line_gap != font_prop.line_gap) { // line gap is planed to be stored inside of imgui font atlas @@ -2928,7 +2847,7 @@ void GLGizmoEmboss::draw_advanced() &stored_style->prop.boldness : nullptr; if (rev_slider(tr.boldness, font_prop.boldness, def_boldness, _u8L("Undo boldness"), priv::limits.boldness.gui.min, priv::limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){ - if (!priv::Limits::apply(font_prop.boldness, priv::limits.boldness.values) || + if (!apply(font_prop.boldness, priv::limits.boldness.values) || !m_volume->text_configuration->style.prop.boldness.has_value() || m_volume->text_configuration->style.prop.boldness != font_prop.boldness) exist_change = true; @@ -2939,7 +2858,7 @@ void GLGizmoEmboss::draw_advanced() &stored_style->prop.skew : nullptr; if (rev_slider(tr.skew_ration, font_prop.skew, def_skew, _u8L("Undo letter's skew"), priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ - if (!priv::Limits::apply(font_prop.skew, priv::limits.skew.values) || + if (!apply(font_prop.skew, priv::limits.skew.values) || !m_volume->text_configuration->style.prop.skew.has_value() || m_volume->text_configuration->style.prop.skew != font_prop.skew) exist_change = true; @@ -2987,7 +2906,7 @@ void GLGizmoEmboss::draw_advanced() if (is_moved){ m_volume->text_configuration->style.prop.distance = font_prop.distance; float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f; - do_translate(Vec3d::UnitZ() * (act_distance - prev_distance)); + do_local_z_move(m_parent, act_distance - prev_distance); } m_imgui->disabled_end(); @@ -3007,19 +2926,18 @@ void GLGizmoEmboss::draw_advanced() priv::limits.angle.min, priv::limits.angle.max, u8"%.2f °", _L("Rotate text Clock-wise."))) { // convert back to radians and CCW - float angle_rad = static_cast(-angle_deg * M_PI / 180.0); - priv::to_range_pi_pi(angle_rad); - + double angle_rad = -angle_deg * M_PI / 180.0; + Geometry::to_range_pi_pi(angle_rad); - float diff_angle = angle_rad - angle; - do_rotate(diff_angle); + double diff_angle = angle_rad - angle; + do_local_z_rotate(m_parent, diff_angle); // calc angle after rotation const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); assert(gl_volume != nullptr); assert(m_style_manager.is_active_font()); if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), priv::up_limit); + m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); // recalculate for surface cut if (font_prop.use_surface) @@ -3070,7 +2988,7 @@ void GLGizmoEmboss::draw_advanced() assert(get_selected_volume(m_parent.get_selection()) == m_volume); const Camera &cam = wxGetApp().plater()->get_camera(); bool use_surface = m_style_manager.get_style().prop.use_surface; - if (priv::apply_camera_dir(cam, m_parent) && use_surface) + if (face_selected_volume_to_camera(cam, m_parent) && use_surface) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); @@ -3103,9 +3021,9 @@ void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style) float diff_y = window_size.y - min_win_size_prev.y; m_is_advanced_edit_style = is_advance_edit_style; const ImVec2 &min_win_size = get_minimal_window_size(); - ImGui::SetWindowSize(ImVec2(0.f, min_win_size.y + diff_y), - ImGuiCond_Always); - priv::change_window_position(m_set_window_offset, true); + ImVec2 new_window_size(0.f, min_win_size.y + diff_y); + ImGui::SetWindowSize(new_window_size, ImGuiCond_Always); + m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), true); } ImVec2 GLGizmoEmboss::get_minimal_window_size() const @@ -3478,7 +3396,7 @@ bool priv::start_create_volume_on_surface_job( Transform3d instance = gl_volume->get_instance_transformation().get_matrix(); // Create result volume transformation - Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, priv::up_limit); + 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); // new transformation in world coor is surface_trmat @@ -3540,87 +3458,5 @@ ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &window return offset; } -// Need internals to get window -#include "imgui/imgui_internal.h" -void priv::change_window_position(std::optional& output_window_offset, bool try_to_fix) { - const char* name = "Emboss"; - ImGuiWindow *window = ImGui::FindWindowByName(name); - // is window just created - if (window == NULL) - return; - - // position of window on screen - ImVec2 position = window->Pos; - ImVec2 size = window->SizeFull; - - // screen size - ImVec2 screen = ImGui::GetMainViewport()->Size; - - if (position.x < 0) { - if (position.y < 0) - output_window_offset = ImVec2(0, 0); - else - output_window_offset = ImVec2(0, position.y); - } else if (position.y < 0) { - output_window_offset = ImVec2(position.x, 0); - } else if (screen.x < (position.x + size.x)) { - if (screen.y < (position.y + size.y)) - output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); - else - output_window_offset = ImVec2(screen.x - size.x, position.y); - } else if (screen.y < (position.y + size.y)) { - output_window_offset = ImVec2(position.x, screen.y - size.y); - } - - if (!try_to_fix && output_window_offset.has_value()) - output_window_offset = ImVec2(-1, -1); // Cannot -} - - -bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { - const Vec3d &cam_dir = camera.get_dir_forward(); - - Selection &sel = canvas.get_selection(); - if (sel.is_empty()) return false; - - // camera direction transformed into volume coordinate system - Transform3d to_world = world_matrix_fixed(sel); - Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; - cam_dir_tr.normalize(); - - Vec3d emboss_dir(0., 0., -1.); - - // check wether cam_dir is already used - if (is_approx(cam_dir_tr, emboss_dir)) return false; - - assert(sel.get_volume_idxs().size() == 1); - GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin()); - - Transform3d vol_rot; - Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix(); - // check whether cam_dir is opposit to emboss dir - if (is_approx(cam_dir_tr, -emboss_dir)) { - // rotate 180 DEG by y - vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); - } else { - // calc params for rotation - Vec3d axe = emboss_dir.cross(cam_dir_tr); - axe.normalize(); - double angle = std::acos(emboss_dir.dot(cam_dir_tr)); - vol_rot = Eigen::AngleAxis(angle, axe); - } - - Vec3d offset = vol_tr * Vec3d::Zero(); - Vec3d offset_inv = vol_rot.inverse() * offset; - Transform3d res = vol_tr * - Eigen::Translation(-offset) * - vol_rot * - Eigen::Translation(offset_inv); - //Transform3d res = vol_tr * vol_rot; - gl_volume->set_volume_transformation(Geometry::Transformation(res)); - get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res); - return true; -} - // any existing icon filename to not influence GUI const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 7e7c2aa161..af92e58f54 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -117,10 +117,6 @@ private: void draw_advanced(); bool select_facename(const wxString& facename); - - void do_translate(const Vec3d& relative_move); - void do_rotate(float relative_z_angle); - bool rev_input_mm(const std::string &name, float &value, const float *default_value, const std::string &undo_tooltip, float step, float step_fast, const char *format, bool use_inch, const std::optional& scale); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp new file mode 100644 index 0000000000..20ff2a4435 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -0,0 +1,613 @@ +#include "GLGizmoSVG.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "slic3r/GUI/MainFrame.hpp" // to update title when add text +#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/CameraUtils.hpp" +#include "slic3r/GUI/Jobs/EmbossJob.hpp" +#include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include "libslic3r/SVG.hpp" // debug store +#include "libslic3r/Geometry.hpp" // covex hull 2d +#include "libslic3r/Timer.hpp" // covex hull 2d + +#include "libslic3r/NSVGUtils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ClipperUtils.hpp" // union_ex + +#include "imgui/imgui_stdlib.h" // using std::string for inputs +#include "nanosvg/nanosvg.h" // load SVG file + +#include // detection of change DPI +#include + +#include +#include // measure enumeration of fonts + +using namespace Slic3r; +using namespace Slic3r::Emboss; +using namespace Slic3r::GUI; +using namespace Slic3r::GUI::Emboss; + +namespace priv { +// Variable keep limits for variables +static const struct Limits +{ + MinMax emboss{0.01f, 1e4f}; // in mm + // distance text object from surface + MinMax angle{-180.f, 180.f}; // in degrees +} limits; +} // namespace priv + +GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) + : GLGizmoBase(parent, M_ICON_FILENAME, -3) + , m_volume(nullptr) + , m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis) + , m_job_cancel(nullptr) +{ + m_rotate_gizmo.set_group_id(0); + m_rotate_gizmo.set_force_local_coordinate(true); +} + +void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos) { +} + +void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type) { +} + +bool GLGizmoSVG::is_svg(const ModelVolume *volume) { + if (volume == nullptr) + return false; + return volume->emboss_shape.has_value(); +} + +bool GLGizmoSVG::is_svg_object(const ModelVolume *volume) { + if (volume == nullptr) return false; + if (!volume->emboss_shape.has_value()) return false; + if (volume->type() != ModelVolumeType::MODEL_PART) return false; + for (const ModelVolume *v : volume->get_object()->volumes) { + if (v == volume) continue; + if (v->type() == ModelVolumeType::MODEL_PART) return false; + } + return true; +} + +bool GLGizmoSVG::on_mouse_for_rotation(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) return false; + + bool used = use_grabbers(mouse_event); + if (!m_dragging) return used; + + if (mouse_event.Dragging()) { + auto &angle_opt = m_volume->text_configuration->style.prop.angle; + if (!m_rotate_start_angle.has_value()) + m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f; + double angle = m_rotate_gizmo.get_angle(); + angle -= PI / 2; // Grabber is upward + + // temporary rotation + const TransformationType transformation_type = m_parent.get_selection().is_single_text() ? + TransformationType::Local_Relative_Joint : TransformationType::World_Relative_Joint; + m_parent.get_selection().rotate(Vec3d(0., 0., angle), transformation_type); + + angle += *m_rotate_start_angle; + // move to range <-M_PI, M_PI> + Geometry::to_range_pi_pi(angle); + // propagate angle into property + angle_opt = static_cast(angle); + + // do not store zero + if (is_approx(*angle_opt, 0.f)) + angle_opt.reset(); + } + return used; +} + +bool GLGizmoSVG::on_mouse_for_translate(const wxMouseEvent &mouse_event) +{ + // exist selected volume? + if (m_volume == nullptr) + return false; + + std::optional up_limit; + if (m_keep_up) + up_limit = Slic3r::GUI::up_limit; + const Camera &camera = wxGetApp().plater()->get_camera(); + bool was_dragging = m_surface_drag.has_value(); + bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); + bool is_dragging = m_surface_drag.has_value(); + + // End with surface dragging? + if (was_dragging && !is_dragging) { + // Update surface by new position + if (m_volume->text_configuration->style.prop.use_surface) + process(); + + // Show correct value of height & depth inside of inputs + calculate_scale(); + } + + // Start with dragging + else if (!was_dragging && is_dragging) { + // Cancel job to prevent interuption of dragging (duplicit result) + if (m_job_cancel != nullptr) + m_job_cancel->store(true); + } + + // during drag + else if (was_dragging && is_dragging) { + // update scale of selected volume --> should be approx the same + calculate_scale(); + + // Recalculate angle for GUI + if (!m_keep_up) { + const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection()); + assert(gl_volume != nullptr); + m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + } + } + return res; +} + +bool GLGizmoSVG::on_mouse(const wxMouseEvent &mouse_event) +{ + // not selected volume + if (m_volume == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr || + !m_volume->text_configuration.has_value()) return false; + + if (on_mouse_for_rotation(mouse_event)) return true; + if (on_mouse_for_translate(mouse_event)) return true; + + return false; +} + +bool GLGizmoSVG::on_init() +{ + m_rotate_gizmo.init(); + ColorRGBA gray_color(.6f, .6f, .6f, .3f); + m_rotate_gizmo.set_highlight_color(gray_color); + + // No shortCut + // m_shortcut_key = WXK_CONTROL_T; + + // Set rotation gizmo upwardrotate + m_rotate_gizmo.set_angle(PI / 2); + return true; +} + +std::string GLGizmoSVG::on_get_name() const { return _u8L("SVG"); } + +void GLGizmoSVG::on_render() { + // no volume selected + if (m_volume == nullptr || + get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr) + return; + + Selection &selection = m_parent.get_selection(); + if (selection.is_empty()) return; + + // prevent get local coordinate system on multi volumes + if (!selection.is_single_volume_or_modifier() && + !selection.is_single_volume_instance()) return; + bool is_surface_dragging = m_surface_drag.has_value(); + bool is_parent_dragging = m_parent.is_mouse_dragging(); + // Do NOT render rotation grabbers when dragging object + bool is_rotate_by_grabbers = m_dragging; + if (is_rotate_by_grabbers || + (!is_surface_dragging && !is_parent_dragging)) { + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + m_rotate_gizmo.render(); + } +} + +void GLGizmoSVG::on_register_raycasters_for_picking(){ + m_rotate_gizmo.register_raycasters_for_picking(); +} +void GLGizmoSVG::on_unregister_raycasters_for_picking(){ + m_rotate_gizmo.unregister_raycasters_for_picking(); +} + +void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit) +{ + set_volume_by_selection(); + + // Configuration creation + double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor(); + float main_toolbar_height = m_parent.get_main_toolbar_height(); + if (!m_gui_cfg.has_value() || // Exist configuration - first run + m_gui_cfg->screen_scale != screen_scale || // change of DPI + m_gui_cfg->main_toolbar_height != main_toolbar_height // change size of view port + ) { + // Create cache for gui offsets + GuiCfg cfg = create_gui_configuration(); + cfg.screen_scale = screen_scale; + cfg.main_toolbar_height = main_toolbar_height; + m_gui_cfg.emplace(std::move(cfg)); + // set position near toolbar + m_set_window_offset = ImVec2(-1.f, -1.f); + } + + const ImVec2 &min_window_size = m_gui_cfg->minimal_window_size; + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size); + + // Draw origin position of text during dragging + if (m_surface_drag.has_value()) { + ImVec2 mouse_pos = ImGui::GetMousePos(); + ImVec2 center( + mouse_pos.x + m_surface_drag->mouse_offset.x(), + mouse_pos.y + m_surface_drag->mouse_offset.y()); + ImU32 color = ImGui::GetColorU32( + m_surface_drag->exist_hit ? + ImVec4(1.f, 1.f, 1.f, .75f) : // transparent white + ImVec4(1.f, .3f, .3f, .75f) + ); // Warning color + const float radius = 16.f; + ImGuiWrapper::draw_cross_hair(center, radius, color); + } + + // check if is set window offset + if (m_set_window_offset.has_value()) { + if (m_set_window_offset->y < 0) + // position near toolbar + m_set_window_offset = ImVec2(x, std::min(y, bottom_limit - min_window_size.y)); + + ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always); + m_set_window_offset.reset(); + } + + bool is_opened = true; + ImGuiWindowFlags flag = ImGuiWindowFlags_NoCollapse; + if (ImGui::Begin(on_get_name().c_str(), &is_opened, flag)) { + // Need to pop var before draw window + ImGui::PopStyleVar(); // WindowMinSize + draw_window(); + } else { + ImGui::PopStyleVar(); // WindowMinSize + } + + ImGui::End(); + if (!is_opened) + close(); +} + +void GLGizmoSVG::on_set_state() +{ + // enable / disable bed from picking + // Rotation gizmo must work through bed + m_parent.set_raycaster_gizmos_on_top(GLGizmoBase::m_state == GLGizmoBase::On); + + m_rotate_gizmo.set_state(GLGizmoBase::m_state); + + // Closing gizmo. e.g. selecting another one + if (GLGizmoBase::m_state == GLGizmoBase::Off) { + // refuse outgoing during text preview + if (false) { + GLGizmoBase::m_state = GLGizmoBase::On; + auto notification_manager = wxGetApp().plater()->get_notification_manager(); + notification_manager->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, + _u8L("ERROR: Wait until ends or Cancel process.")); + return; + } + reset_volume(); + } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + // Try(when exist) set text configuration by volume + set_volume_by_selection(); + + if (!m_gui_cfg.has_value()) + m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), false); + else + m_set_window_offset = ImVec2(-1, -1); + + // when open by hyperlink it needs to show up + // or after key 'T' windows doesn't appear + // m_parent.set_as_dirty(); + } +} + +void GLGizmoSVG::data_changed() { + set_volume_by_selection(); +} + +void GLGizmoSVG::on_start_dragging() { m_rotate_gizmo.start_dragging(); } +void GLGizmoSVG::on_stop_dragging() +{ + m_rotate_gizmo.stop_dragging(); + + // TODO: when start second rotatiton previous rotation rotate draggers + // This is fast fix for second try to rotate + // When fixing, move grabber above text (not on side) + m_rotate_gizmo.set_angle(PI/2); + + // apply rotation + m_parent.do_rotate(L("Text-Rotate")); + + m_rotate_start_angle.reset(); + + // recalculate for surface cut + if (m_volume != nullptr && + m_volume->emboss_shape.has_value() && + m_volume->emboss_shape->use_surface) + process(); +} +void GLGizmoSVG::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } + +GLGizmoSVG::GuiCfg GLGizmoSVG::create_gui_configuration() +{ + GuiCfg cfg; // initialize by default values; + + + return cfg; +} + +void GLGizmoSVG::set_volume_by_selection() +{ + const Selection &selection = m_parent.get_selection(); + const GLVolume *gl_volume = get_selected_gl_volume(selection); + if (gl_volume == nullptr) + return reset_volume(); + + const ModelObjectPtrs &objects = selection.get_model()->objects; + ModelVolume *volume =get_model_volume(*gl_volume, objects); + if (volume == nullptr) + return reset_volume(); + + // is same volume as actual selected? + if (volume->id() == m_volume_id) + return; + + // Do not use focused input value when switch volume(it must swith value) + if (m_volume != nullptr && + m_volume != volume) // when update volume it changed id BUT not pointer + ImGuiWrapper::left_inputs(); + + // is valid svg volume? + if (!is_svg(volume)) + return reset_volume(); + + // cancel previous job + if (m_job_cancel != nullptr) { + m_job_cancel->store(true); + m_job_cancel = nullptr; + } + + m_volume = volume; + m_volume_id = volume->id(); + + // Calculate current angle of up vector + m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + + // calculate scale for height and depth inside of scaled object instance + calculate_scale(); +} + +void GLGizmoSVG::reset_volume() +{ + if (m_volume == nullptr) + return; // already reseted + + m_volume = nullptr; + m_volume_id.id = 0; +} + +void GLGizmoSVG::calculate_scale() { + Transform3d to_world = m_parent.get_selection().get_first_volume()->world_matrix(); + auto to_world_linear = to_world.linear(); + auto calc = [&to_world_linear](const Vec3d &axe, std::optional& scale)->bool { + Vec3d axe_world = to_world_linear * axe; + double norm_sq = axe_world.squaredNorm(); + if (is_approx(norm_sq, 1.)) { + if (scale.has_value()) + scale.reset(); + else + return false; + } else { + scale = sqrt(norm_sq); + } + return true; + }; + + calc(Vec3d::UnitY(), m_scale_height); + calc(Vec3d::UnitZ(), m_scale_depth); +} + +bool GLGizmoSVG::process() +{ + // no volume is selected -> selection from right panel + assert(m_volume != nullptr); + if (m_volume == nullptr) return false; + + //DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel), m_volume->id()}; + + //std::unique_ptr job = nullptr; + + //// check cutting from source mesh + //bool &use_surface = data.text_configuration.style.prop.use_surface; + //bool is_object = m_volume->get_object()->volumes.size() == 1; + //if (use_surface && is_object) + // use_surface = false; + // + //if (use_surface) { + // // Model to cut surface from. + // SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); + // if (sources.empty()) return false; + + // Transform3d text_tr = m_volume->get_matrix(); + // auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr; + // if (fix_3mf.has_value()) + // text_tr = text_tr * fix_3mf->inverse(); + + // // when it is new applying of use surface than move origin onto surfaca + // if (!m_volume->text_configuration->style.prop.use_surface) { + // auto offset = priv::calc_surface_offset(*m_volume, m_raycast_manager, m_parent.get_selection()); + // if (offset.has_value()) + // text_tr *= Eigen::Translation(*offset); + // } + + // bool is_outside = m_volume->is_model_part(); + // // check that there is not unexpected volume type + // assert(is_outside || m_volume->is_negative_volume() || + // m_volume->is_modifier()); + // UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, is_outside, std::move(sources)}}; + // job = std::make_unique(std::move(surface_data)); + //} else { + // job = std::make_unique(std::move(data)); + //} + + //auto &worker = wxGetApp().plater()->get_ui_job_worker(); + //queue_job(worker, std::move(job)); + + //// notification is removed befor object is changed by job + //remove_notification_not_valid_font(); + return true; +} + +void GLGizmoSVG::close() +{ + // close gizmo == open it again + auto& mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() == GLGizmosManager::Emboss) + mng.open_gizmo(GLGizmosManager::Emboss); +} + +void GLGizmoSVG::draw_window() +{ + ImGui::Text("Preview of svg image."); + + if (ImGui::Button("choose svg file")) choose_svg_file(); + } + +void GLGizmoSVG::draw_model_type() +{ + bool is_last_solid_part = is_svg_object(m_volume); + std::string title = _u8L("SVG is to object"); + if (is_last_solid_part) { + ImVec4 color{.5f, .5f, .5f, 1.f}; + m_imgui->text_colored(color, title.c_str()); + } else { + ImGui::Text("%s", title.c_str()); + } + + std::optional new_type; + ModelVolumeType modifier = ModelVolumeType::PARAMETER_MODIFIER; + ModelVolumeType negative = ModelVolumeType::NEGATIVE_VOLUME; + ModelVolumeType part = ModelVolumeType::MODEL_PART; + ModelVolumeType type = m_volume->type(); + + if (ImGui::RadioButton(_u8L("Added").c_str(), type == part)) + new_type = part; + else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); + ImGui::SameLine(); + + std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); + if (ImGui::RadioButton(_u8L("Subtracted").c_str(), type == negative)) + new_type = negative; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); + else if (type != negative) + ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); + } + + // In simple mode are not modifiers + if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + ImGui::SameLine(); + if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) + new_type = modifier; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); + else if (type != modifier) + ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); + } + } + + if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) { + GUI_App &app = wxGetApp(); + Plater * plater = app.plater(); + Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction); + m_volume->set_type(*new_type); + + // Update volume position when switch from part or into part + if (m_volume->text_configuration->style.prop.use_surface) { + // move inside + bool is_volume_move_inside = (type == part); + bool is_volume_move_outside = (*new_type == part); + if (is_volume_move_inside || is_volume_move_outside) process(); + } + + // inspiration in ObjectList::change_part_type() + // how to view correct side panel with objects + ObjectList *obj_list = app.obj_list(); + wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection( + obj_list->get_selected_obj_idx(), + [volume = m_volume](const ModelVolume *vol) { return vol == volume; }); + if (!sel.IsEmpty()) obj_list->select_item(sel.front()); + + // NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false) + // which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed + auto &mng = m_parent.get_gizmos_manager(); + if (mng.get_current_type() != GLGizmosManager::Emboss) + mng.open_gizmo(GLGizmosManager::Emboss); + // TODO: select volume back - Ask @Sasa + } +} +namespace priv { + +std::string get_file_name(const std::string &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; + return file_path.substr(offset, count); +} + +} + +bool GLGizmoSVG::choose_svg_file() +{ + wxArrayString input_files; + wxString fontDir = wxEmptyString; + wxString selectedFile = wxEmptyString; + wxFileDialog dialog(nullptr, _L("Choose SVG file:"), fontDir, + selectedFile, file_wildcards(FT_SVG), + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files); + if (input_files.IsEmpty()) return false; + if (input_files.size() != 1) return false; + auto & input_file = input_files.front(); + std::string path = std::string(input_file.c_str()); + std::string name = priv::get_file_name(path); + + NSVGimage *image = nsvgParseFromFile(path.c_str(), "mm", 96.0f); + ExPolygons polys = NSVGUtils::to_ExPolygons(image); + nsvgDelete(image); + + BoundingBox bb; + for (const auto &p : polys) bb.merge(p.contour.points); + + double scale = 1e-4; + auto project = std::make_unique( + std::make_unique(10 / scale), scale); + indexed_triangle_set its = polygons2model(polys, *project); + return false; + // test store: + // for (auto &poly : polys) poly.scale(1e5); + // SVG svg("converted.svg", BoundingBox(polys.front().contour.points)); + // svg.draw(polys); + //return add_volume(name, its); +} + +// any existing icon filename to not influence GUI +const std::string GLGizmoSVG::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp new file mode 100644 index 0000000000..96519daa5e --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -0,0 +1,172 @@ +#ifndef slic3r_GLGizmoSVG_hpp_ +#define slic3r_GLGizmoSVG_hpp_ + +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, +// which overrides our localization "L" macro. +#include "GLGizmoBase.hpp" +#include "GLGizmoRotate.hpp" +#include "slic3r/GUI/SurfaceDrag.hpp" +#include "slic3r/Utils/RaycastManager.hpp" + +#include +#include +#include + +#include "libslic3r/Emboss.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Model.hpp" + +#include +#include + +namespace Slic3r{ + //class AppConfig; + class GLVolume; + class ModelVolume; + enum class ModelVolumeType : int; +} + +namespace Slic3r::GUI { +class GLGizmoSVG : public GLGizmoBase +{ +public: + GLGizmoSVG(GLCanvas3D &parent); + + /// + /// Create new embossed text volume by type on position of mouse + /// + /// Object part / Negative volume / Modifier + /// Define position of new volume + void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos); + + /// + /// Create new text without given position + /// + /// Object part / Negative volume / Modifier + void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type); + + /// + /// Check whether volume is object containing only emboss volume + /// + /// Pointer to volume + /// True when object otherwise False + static bool is_svg_object(const ModelVolume *volume); + + /// + /// Check whether volume has emboss data + /// + /// Pointer to volume + /// True when constain emboss data otherwise False + static bool is_svg(const ModelVolume *volume); + +protected: + bool on_init() override; + std::string on_get_name() const override; + void on_render() override; + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; + void on_render_input_window(float x, float y, float bottom_limit) override; + bool on_is_activable() const override { return true; } + bool on_is_selectable() const override { return false; } + void on_set_state() override; + void data_changed() override; // selection changed + void on_set_hover_id() override{ m_rotate_gizmo.set_hover_id(m_hover_id); } + void on_enable_grabber(unsigned int id) override { m_rotate_gizmo.enable_grabber(); } + void on_disable_grabber(unsigned int id) override { m_rotate_gizmo.disable_grabber(); } + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData &data) override; + + /// + /// Rotate by text on dragging rotate grabers + /// + /// Information about mouse + /// Propagete normaly return false. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return _u8L("Enter SVG gizmo"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leave SVG gizmo"); } + std::string get_action_snapshot_name() override { return _u8L("SVG actions"); } +private: + void set_volume_by_selection(); + void reset_volume(); + + // create volume from text - main functionality + bool process(); + void close(); + void draw_window(); + void draw_model_type(); + + // process mouse event + bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); + bool on_mouse_for_translate(const wxMouseEvent &mouse_event); + + bool choose_svg_file(); + + // This configs holds GUI layout size given by translated texts. + // etc. When language changes, GUI is recreated and this class constructed again, + // so the change takes effect. (info by GLGizmoFdmSupports.hpp) + struct GuiCfg + { + // Detect invalid config values when change monitor DPI + double screen_scale; + float main_toolbar_height; + + // Zero means it is calculated in init function + ImVec2 minimal_window_size = ImVec2(0, 0); + + float input_width = 0.f; + float input_offset = 0.f; + + // Only translations needed for calc GUI size + struct Translations + { + std::string font; + }; + Translations translations; + }; + std::optional m_gui_cfg; + static GuiCfg create_gui_configuration(); + + // actual selected only one volume - with emboss data + ModelVolume *m_volume; + + // When work with undo redo stack there could be situation that + // m_volume point to unexisting volume so One need also objectID + ObjectID m_volume_id; + + // cancel for previous update of volume to cancel finalize part + std::shared_ptr> m_job_cancel; + + // Rotation gizmo + GLGizmoRotate m_rotate_gizmo; + std::optional m_angle; + // Value is set only when dragging rotation to calculate actual angle + std::optional m_rotate_start_angle; + + // TODO: it should be accessible by other gizmo too. + // May be move to plater? + RaycastManager m_raycast_manager; + + // When true keep up vector otherwise relative rotation + bool m_keep_up = true; + + // setted only when wanted to use - not all the time + std::optional m_set_window_offset; + + // Keep data about dragging only during drag&drop + std::optional m_surface_drag; + + // For volume on scaled objects + std::optional m_scale_height; + std::optional m_scale_depth; + void calculate_scale(); + + // only temporary solution + static const std::string M_ICON_FILENAME; +}; + +} // namespace Slic3r::GUI + +#endif // slic3r_GLGizmoSVG_hpp_ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 55bf576028..e22c45940e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1369,6 +1369,48 @@ bool ImGuiWrapper::slider_optional_int(const char *label, } else return false; } +std::optional ImGuiWrapper::change_window_position(const char *window_name, bool try_to_fix) { + ImGuiWindow *window = ImGui::FindWindowByName(window_name); + // is window just created + if (window == NULL) + return {}; + + // position of window on screen + ImVec2 position = window->Pos; + ImVec2 size = window->SizeFull; + + // screen size + ImVec2 screen = ImGui::GetMainViewport()->Size; + + std::optional output_window_offset; + if (position.x < 0) { + if (position.y < 0) + // top left + output_window_offset = ImVec2(0, 0); + else + // only left + output_window_offset = ImVec2(0, position.y); + } else if (position.y < 0) { + // only top + output_window_offset = ImVec2(position.x, 0); + } else if (screen.x < (position.x + size.x)) { + if (screen.y < (position.y + size.y)) + // right bottom + output_window_offset = ImVec2(screen.x - size.x, screen.y - size.y); + else + // only right + output_window_offset = ImVec2(screen.x - size.x, position.y); + } else if (screen.y < (position.y + size.y)) { + // only bottom + output_window_offset = ImVec2(position.x, screen.y - size.y); + } + + if (!try_to_fix && output_window_offset.has_value()) + output_window_offset = ImVec2(-1, -1); // Put on default position + + return output_window_offset; +} + void ImGuiWrapper::left_inputs() { ImGui::ClearActiveID(); } diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 077bf568de..8ece7b3ea0 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -146,6 +146,15 @@ public: // Extended function ImGuiWrapper::slider_float to work with std::optional, when value == def_val than optional release its value bool slider_optional_int(const char* label, std::optional &v, int v_min, int v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true, int def_val = 0); + /// + /// Change position of imgui window + /// + /// ImGui identifier of window + /// [output] optional + /// When True Only move to be full visible otherwise reset position + /// New offset of window for function ImGui::SetNextWindowPos + static std::optional change_window_position(const char *window_name, bool try_to_fix); + /// /// Use ImGui internals to unactivate (lose focus) in input. /// When input is activ it can't change value by application. diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp index 0f2f1706ae..8e07adbea9 100644 --- a/src/slic3r/GUI/SurfaceDrag.cpp +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -337,4 +337,83 @@ Transform3d world_matrix_fixed(const Selection &selection) return world_matrix_fixed(*gl_volume, selection.get_model()->objects); } +bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas) +{ + const Vec3d &cam_dir = camera.get_dir_forward(); + Selection &sel = canvas.get_selection(); + if (sel.is_empty()) + return false; + + // camera direction transformed into volume coordinate system + Transform3d to_world = world_matrix_fixed(sel); + Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; + cam_dir_tr.normalize(); + + Vec3d emboss_dir(0., 0., -1.); + + // check wether cam_dir is already used + if (is_approx(cam_dir_tr, emboss_dir)) + return false; + + assert(sel.get_volume_idxs().size() == 1); + GLVolume *gl_volume = sel.get_volume(*sel.get_volume_idxs().begin()); + + Transform3d vol_rot; + Transform3d vol_tr = gl_volume->get_volume_transformation().get_matrix(); + // check whether cam_dir is opposit to emboss dir + if (is_approx(cam_dir_tr, -emboss_dir)) { + // rotate 180 DEG by y + vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); + } else { + // calc params for rotation + Vec3d axe = emboss_dir.cross(cam_dir_tr); + axe.normalize(); + double angle = std::acos(emboss_dir.dot(cam_dir_tr)); + vol_rot = Eigen::AngleAxis(angle, axe); + } + + Vec3d offset = vol_tr * Vec3d::Zero(); + Vec3d offset_inv = vol_rot.inverse() * offset; + Transform3d res = vol_tr * Eigen::Translation(-offset) * vol_rot * Eigen::Translation(offset_inv); + // Transform3d res = vol_tr * vol_rot; + gl_volume->set_volume_transformation(Geometry::Transformation(res)); + get_model_volume(*gl_volume, sel.get_model()->objects)->set_transformation(res); + return true; +} + +void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle) +{ + Selection &selection = canvas.get_selection(); + + assert(!selection.is_empty()); + if(selection.is_empty()) return; + + selection.setup_cache(); + TransformationType transformation_type = TransformationType::Local_Relative_Joint; + selection.rotate(Vec3d(0., 0., relative_angle), transformation_type); + + std::string snapshot_name; // empty meand no store undo / redo + // NOTE: it use L instead of _L macro because prefix _ is appended + // inside function do_move + // snapshot_name = L("Set text rotation"); + canvas.do_rotate(snapshot_name); +} + +void do_local_z_move(GLCanvas3D &canvas, double relative_move) { + + Selection &selection = canvas.get_selection(); + assert(!selection.is_empty()); + if (selection.is_empty()) return; + + selection.setup_cache(); + Vec3d translate = Vec3d::UnitZ() * relative_move; + selection.translate(translate, TransformationType::Local); + + std::string snapshot_name; // empty mean no store undo / redo + // NOTE: it use L instead of _L macro because prefix _ is appended inside + // function do_move + // snapshot_name = L("Set surface distance"); + canvas.do_move(snapshot_name); +} + } // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/SurfaceDrag.hpp b/src/slic3r/GUI/SurfaceDrag.hpp index bb2600c28f..19fc1c795f 100644 --- a/src/slic3r/GUI/SurfaceDrag.hpp +++ b/src/slic3r/GUI/SurfaceDrag.hpp @@ -41,6 +41,10 @@ struct SurfaceDrag bool exist_hit = true; }; +// Limit direction of up vector on model +// Between side and top surface +constexpr double up_limit = 0.9; + /// /// Mouse event handler, when move(drag&drop) volume over model surface /// NOTE: Dragged volume has to be selected. And also has to be hovered on start of dragging. @@ -86,5 +90,27 @@ Transform3d world_matrix_fixed(const GLVolume &gl_volume, const ModelObjectPtrs& /// Fixed Transformation of selected volume in selection Transform3d world_matrix_fixed(const Selection &selection); +/// +/// Apply camera direction for emboss direction +/// +/// Define view vector +/// Containe Selected ModelVolume to modify orientation +/// True when apply change otherwise false +bool face_selected_volume_to_camera(const Camera &camera, GLCanvas3D &canvas); + +/// +/// Rotation around z Axis(emboss direction) +/// +/// Selected volume for rotation +/// Relative angle to rotate around emboss direction +void do_local_z_rotate(GLCanvas3D &canvas, double relative_angle); + +/// +/// Translation along local z Axis (emboss direction) +/// +/// Selected volume for translate +/// Relative move along emboss direction +void do_local_z_move(GLCanvas3D &canvas, double relative_move); + } // namespace Slic3r::GUI #endif // slic3r_SurfaceDrag_hpp_ \ No newline at end of file From 9ae91c291a9bfbc5cf7844118f0aa7088ca39925 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 8 Mar 2023 09:33:02 +0100 Subject: [PATCH 003/120] misc --- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 20ff2a4435..546ef66d98 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -119,10 +119,11 @@ bool GLGizmoSVG::on_mouse_for_translate(const wxMouseEvent &mouse_event) std::optional up_limit; if (m_keep_up) up_limit = Slic3r::GUI::up_limit; - const Camera &camera = wxGetApp().plater()->get_camera(); - bool was_dragging = m_surface_drag.has_value(); - bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); - bool is_dragging = m_surface_drag.has_value(); + const Camera &camera = wxGetApp().plater()->get_camera(); + + bool was_dragging = m_surface_drag.has_value(); + bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit); + bool is_dragging = m_surface_drag.has_value(); // End with surface dragging? if (was_dragging && !is_dragging) { From 24c791b29dcfabdf6497fe5e6bbc1532cc2072b4 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 8 Mar 2023 16:40:03 +0100 Subject: [PATCH 004/120] Add svg to context menu --- src/slic3r/GUI/GLCanvas3D.cpp | 22 ++++-- src/slic3r/GUI/GUI_Factories.cpp | 38 ++++++++++ src/slic3r/GUI/GUI_Factories.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 84 +++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 + src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 + 7 files changed, 108 insertions(+), 44 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d1d44709e6..e0c1f8ee2f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3392,7 +3392,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) type == GLGizmosManager::EType::Move || type == GLGizmosManager::EType::Rotate || type == GLGizmosManager::EType::Scale || - type == GLGizmosManager::EType::Emboss) ) { + type == GLGizmosManager::EType::Emboss|| + type == GLGizmosManager::EType::Svg) ) { for (int hover_volume_id : m_hover_volume_idxs) { const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id]; int object_idx = hover_gl_volume.object_idx(); @@ -3401,12 +3402,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int hover_volume_idx = hover_gl_volume.volume_idx(); if (hover_volume_idx < 0 || static_cast(hover_volume_idx) >= hover_object->volumes.size()) continue; const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx]; - if (!hover_volume->text_configuration.has_value()) continue; - m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); - if (type != GLGizmosManager::EType::Emboss) - m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); - wxGetApp().obj_list()->update_selections(); - return; + + if (hover_volume->text_configuration.has_value()) { + m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (type != GLGizmosManager::EType::Emboss) + m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); + wxGetApp().obj_list()->update_selections(); + return; + } else if (hover_volume->emboss_shape.has_value()) { + if (type != GLGizmosManager::EType::Svg) + m_gizmos.open_gizmo(GLGizmosManager::EType::Svg); + wxGetApp().obj_list()->update_selections(); + return; + } } } diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index df8ace79a3..daab5fafc9 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -14,6 +14,7 @@ #include "Selection.hpp" #include "format.hpp" #include "Gizmos/GLGizmoEmboss.hpp" +#include "Gizmos/GLGizmoSVG.hpp" #include #include "slic3r/Utils/FixModelByWin10.hpp" @@ -487,6 +488,7 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty } append_menu_item_add_text(sub_menu, type); + append_menu_item_add_svg(sub_menu, type); if (mode >= comAdvanced) { sub_menu->AppendSeparator(); @@ -533,6 +535,42 @@ void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, } } +void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/) +{ + auto open_svg = [type](wxCommandEvent &evt) { + GLCanvas3D* canvas = plater()->canvas3D(); + GLGizmosManager& mng = canvas->get_gizmos_manager(); + GLGizmoBase* gizmo = mng.get_gizmo(GLGizmosManager::Svg); + GLGizmoSVG* svg = dynamic_cast(gizmo); + assert(svg != nullptr); + if (svg == nullptr) return; + + ModelVolumeType volume_type = type; + // no selected object means create new object + if (volume_type == ModelVolumeType::INVALID) + volume_type = ModelVolumeType::MODEL_PART; + + auto screen_position = canvas->get_popup_menu_position(); + if (screen_position.has_value()) { + svg->create_volume(volume_type, *screen_position); + } else { + svg->create_volume(volume_type); + } + }; + + if ( type == ModelVolumeType::MODEL_PART + || type == ModelVolumeType::NEGATIVE_VOLUME + || type == ModelVolumeType::PARAMETER_MODIFIER + || type == ModelVolumeType::INVALID // cannot use gizmo without selected object + ) { + wxString item_name = is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": "; + item_name += _L("SVG"); + menu->AppendSeparator(); + const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second; + append_menu_item(menu, wxID_ANY, item_name, "", open_svg, icon_name, menu); + } +} + void MenuFactory::append_menu_items_add_volume(MenuType menu_type) { wxMenu* menu = menu_type == mtObjectFFF ? &m_object_menu : menu_type == mtObjectSLA ? &m_sla_object_menu : nullptr; diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 515311d0de..f6e84517d1 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -87,6 +87,7 @@ private: wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type); void append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item = true); + void append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item = true); void append_menu_items_add_volume(MenuType type); wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 546ef66d98..4d788ba54b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -9,10 +9,9 @@ #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/CameraUtils.hpp" -#include "slic3r/GUI/Jobs/EmbossJob.hpp" -#include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "libslic3r/Point.hpp" #include "libslic3r/SVG.hpp" // debug store #include "libslic3r/Geometry.hpp" // covex hull 2d #include "libslic3r/Timer.hpp" // covex hull 2d @@ -33,7 +32,6 @@ using namespace Slic3r; using namespace Slic3r::Emboss; using namespace Slic3r::GUI; -using namespace Slic3r::GUI::Emboss; namespace priv { // Variable keep limits for variables @@ -43,6 +41,9 @@ 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) @@ -55,10 +56,35 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) m_rotate_gizmo.set_force_local_coordinate(true); } +void GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos){ + std::string path = priv::choose_svg_file(); + if (path.empty()) return; + create_volume(path, volume_type, mouse_pos); +} +void GLGizmoSVG::create_volume(ModelVolumeType volume_type) { + std::string path = priv::choose_svg_file(); + if (path.empty()) return; + create_volume(path, volume_type); +} + void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos) { + std::string name = priv::get_file_name(svg_file_path); + NSVGimage *image = nsvgParseFromFile(svg_file_path.c_str(), "mm", 96.0f); + ExPolygons polys = NSVGUtils::to_ExPolygons(image); + nsvgDelete(image); + + BoundingBox bb; + for (const auto &p : polys) + bb.merge(p.contour.points); + + double scale = 1e-4; + auto project = std::make_unique(std::make_unique(10 / scale), scale); + indexed_triangle_set its = polygons2model(polys, *project); + // add volume } void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type) { + } bool GLGizmoSVG::is_svg(const ModelVolume *volume) { @@ -484,7 +510,10 @@ void GLGizmoSVG::draw_window() { ImGui::Text("Preview of svg image."); - if (ImGui::Button("choose svg file")) choose_svg_file(); + if (ImGui::Button("choose svg file")) + return; + + //choose_svg_file(); } void GLGizmoSVG::draw_model_type() @@ -563,9 +592,9 @@ void GLGizmoSVG::draw_model_type() // TODO: select volume back - Ask @Sasa } } -namespace priv { - -std::string get_file_name(const std::string &file_path) + + +std::string priv::get_file_name(const std::string &file_path) { size_t pos_last_delimiter = file_path.find_last_of("/\\"); size_t pos_point = file_path.find_last_of('.'); @@ -574,40 +603,25 @@ std::string get_file_name(const std::string &file_path) return file_path.substr(offset, count); } -} - -bool GLGizmoSVG::choose_svg_file() +std::string priv::choose_svg_file() { wxArrayString input_files; - wxString fontDir = wxEmptyString; - wxString selectedFile = wxEmptyString; - wxFileDialog dialog(nullptr, _L("Choose SVG file:"), fontDir, + 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()) return false; - if (input_files.size() != 1) return false; - auto & input_file = input_files.front(); - std::string path = std::string(input_file.c_str()); - std::string name = priv::get_file_name(path); + if (input_files.IsEmpty()) + return {}; + if (input_files.size() != 1) + return {}; - NSVGimage *image = nsvgParseFromFile(path.c_str(), "mm", 96.0f); - ExPolygons polys = NSVGUtils::to_ExPolygons(image); - nsvgDelete(image); - - BoundingBox bb; - for (const auto &p : polys) bb.merge(p.contour.points); - - double scale = 1e-4; - auto project = std::make_unique( - std::make_unique(10 / scale), scale); - indexed_triangle_set its = polygons2model(polys, *project); - return false; - // test store: - // for (auto &poly : polys) poly.scale(1e5); - // SVG svg("converted.svg", BoundingBox(polys.front().contour.points)); - // svg.draw(polys); - //return add_volume(name, its); + auto &input_file = input_files.front(); + std::string path = std::string(input_file.c_str()); + return path; } // any existing icon filename to not influence GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 96519daa5e..7ca9637393 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -38,12 +38,14 @@ public: /// Object part / Negative volume / Modifier /// Define position of new volume void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos); + void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog /// /// Create new text without given position /// /// Object part / Negative volume / Modifier void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type); + void create_volume(ModelVolumeType volume_type); // first open file dialog /// /// Check whether volume is object containing only emboss volume @@ -102,8 +104,6 @@ private: bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); bool on_mouse_for_translate(const wxMouseEvent &mouse_event); - bool choose_svg_file(); - // This configs holds GUI layout size given by translated texts. // etc. When language changes, GUI is recreated and this class constructed again, // so the change takes effect. (info by GLGizmoFdmSupports.hpp) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index af93c07df3..5b30729b2f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -22,6 +22,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" #include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp" #include "libslic3r/format.hpp" @@ -109,6 +110,7 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9)); m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 10)); m_gizmos.emplace_back(new GLGizmoEmboss(m_parent)); + m_gizmos.emplace_back(new GLGizmoSVG(m_parent)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent)); m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 8c98596bfd..509a433fd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -79,6 +79,7 @@ public: MmuSegmentation, Measure, Emboss, + Svg, Simplify, Undefined }; 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 005/120] 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); From 07181b4f3745950a5d9e18d0532e4232bde3ce6c Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 13 Mar 2023 20:03:03 +0100 Subject: [PATCH 006/120] Refactor for easier use of emboss shape --- src/libslic3r/EmbossShape.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 394 ++++++++---------------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 1 - src/slic3r/GUI/Jobs/EmbossJob.cpp | 182 +++++++++-- src/slic3r/GUI/Jobs/EmbossJob.hpp | 91 +++++- 5 files changed, 368 insertions(+), 304 deletions(-) diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index 97a0f8a943..0946235d87 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -21,10 +21,10 @@ struct EmbossShape ExPolygons shapes; // scale of shape, multiplier to get 3d point in mm from integer shape - double scale; + double scale = 1.; // Emboss depth, Size in local Z direction - double depth; // [in loacal mm] + double depth = 1.; // [in loacal mm] // NOTE: User should see and modify mainly world size not local // Flag that result volume use surface cutted from source objects diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 457471af54..56f5e433d7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -14,6 +14,7 @@ #include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" #include "slic3r/Utils/WxFontUtils.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "slic3r/Utils/EmbossGui.hpp" // TODO: remove include #include "libslic3r/SVG.hpp" // debug store @@ -107,67 +108,6 @@ 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 @@ -187,16 +127,28 @@ 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 +/// Data for emboss job to create shape /// -/// Define where to add -/// Volume transformation -/// Define text -/// Type of volume -static void start_create_volume_job(const ModelObject *object, - const Transform3d volume_trmat, - std::unique_ptr emboss_data, - ModelVolumeType volume_type); +struct TextDataBase : public DataBase +{ + TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration); + // Create shape from text + font configuration + EmbossShape &create_shape() override; + void write(ModelVolume &volume) const override; + +// private: + // Keep pointer on Data of font (glyph shapes) + FontFileWithCache font_file; + // font item is not used for create object + TextConfiguration text_configuration; +}; + +/// +/// Start job for add object with text into scene +/// +/// Define params of text +/// Screen coordinat, where to create new object laying on bed +static void start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor); /// /// Start job for add new volume on surface of object defined by screen coor @@ -208,35 +160,13 @@ 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(std::unique_ptr emboss_data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume *gl_volume, - RaycastManager &raycaster, - GLCanvas3D &canvas); - -/// -/// Find volume in selected object with closest convex hull to screen center. -/// Return -/// -/// Define where to search for closest -/// Canvas center(dependent on camera settings) -/// Actual objects -/// OUT: coordinate of controid of closest volume -/// OUT: closest volume -static void find_closest_volume(const Selection &selection, - const Vec2d &screen_center, - const Camera &camera, - const ModelObjectPtrs &objects, - Vec2d *closest_center, - const GLVolume **closest_volume); - -/// -/// Start job for add object with text into scene -/// -/// Define params of text -/// Screen coordinat, where to create new object laying on bed -static void start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor); +static bool start_create_volume_on_surface_job(const FontProp &fp, + std::unique_ptr emboss_data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume &gl_volume, + RaycastManager &raycaster, + GLCanvas3D &canvas); // Loaded icons enum // Have to match order of files in function GLGizmoEmboss::init_icons() @@ -270,19 +200,15 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous m_style_manager.discard_style_changes(); set_default_text(); - GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); 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(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 { + GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); + if (gl_volume == nullptr) // object is not under mouse position soo create object on plater - priv::start_create_object_job(std::move(emboss_data), mouse_pos); - } + return priv::start_create_object_job(std::move(emboss_data), mouse_pos); + + const FontProp &fp = m_style_manager.get_style().prop; + if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, mouse_pos, *gl_volume, m_raycast_manager, m_parent)) + return create_volume(volume_type); } // Designed for create volume without information of mouse in scene @@ -310,34 +236,26 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) // create volume inside of selected object Vec2d coor; - const GLVolume *vol = nullptr; 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(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 + const GLVolume *gl_volume = find_closest(selection, screen_center, camera, objects, &coor); + + FontProp &fp = m_style_manager.get_style().prop; + if (gl_volume == nullptr) { + return priv::start_create_object_job(std::move(emboss_data), screen_center); + } else if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, coor, *gl_volume, m_raycast_manager, m_parent)){ + // In centroid of convex hull is not hit with object. e.g. torid // 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 - 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()]; - BoundingBoxf3 instance_bb = obj->instance_bounding_box(vol->instance_idx()); - // 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 - 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, std::move(emboss_data), volume_type); + if (emboss_data->shape.use_surface) + emboss_data->shape.use_surface = false; + + const ModelObject *object = get_model_object(*gl_volume, objects); + Worker &worker = wxGetApp().plater()->get_ui_job_worker(); + Transform3d volume_trmat = create_volume_transformation(*gl_volume, objects, fp.size_in_mm, fp.emboss); + start_create_volume_job(worker, *object, volume_trmat, std::move(emboss_data), volume_type); } } @@ -3347,6 +3265,59 @@ bool priv::is_valid(ModelVolumeType volume_type) return false; } +priv::TextDataBase::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); +} + +EmbossShape &priv::TextDataBase::create_shape() +{ + 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 priv::TextDataBase::write(ModelVolume &volume) const +{ + volume.text_configuration = text_configuration; // copy + + // discard information about rotation, should not be stored in volume + volume.text_configuration->style.prop.angle.reset(); + + // only temporary solution + volume.text_configuration->style.prop.use_surface = shape.use_surface; + volume.text_configuration->style.prop.distance = shape.distance; + + DataBase::write(volume); +} + std::unique_ptr priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr>& cancel) { // create volume_name @@ -3380,143 +3351,48 @@ std::unique_ptr priv::create_emboss_data_base(const std::string &text, return std::make_unique(std::move(base), font, std::move(tc)); } -void priv::start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor) -{ - // start creation of new object +void priv::start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor) { Plater *plater = wxGetApp().plater(); const Camera &camera = plater->get_camera(); const Pointfs &bed_shape = plater->build_volume().bed_shape(); - - // can't create new object with distance from surface - // 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 (fp.use_surface) - fp.use_surface = false; - - // Transform3d volume_tr = priv::create_transformation_on_bed(mouse_pos, camera, bed_shape, prop.emboss / 2); + Worker &worker = plater->get_ui_job_worker(); DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape}; - auto job = std::make_unique(std::move(data)); - Worker &worker = plater->get_ui_job_worker(); + auto job = std::make_unique(std::move(data)); queue_job(worker, std::move(job)); } -void priv::start_create_volume_job(const ModelObject *object, - const Transform3d volume_trmat, - std::unique_ptr emboss_data, - ModelVolumeType volume_type) +bool priv::start_create_volume_on_surface_job(const FontProp &fp, + std::unique_ptr emboss_data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume &gl_volume, + RaycastManager &raycaster, + GLCanvas3D &canvas) { - // 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. - SurfaceVolumeData::ModelSources sources = create_sources(object->volumes); - if (sources.empty()) { - use_surface = false; - } else { - bool is_outside = volume_type == ModelVolumeType::MODEL_PART; - // check that there is not unexpected volume type - assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); - SurfaceVolumeData sfvd{volume_trmat, is_outside, std::move(sources)}; - CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(emboss_data), volume_type, object->id()}; - job = std::make_unique(std::move(surface_data)); - } - } - if (!use_surface) { - // create volume - DataCreateVolume data{std::move(emboss_data), volume_type, object->id(), volume_trmat}; - job = std::make_unique(std::move(data)); - } - - Plater *plater = wxGetApp().plater(); - Worker &worker = plater->get_ui_job_worker(); - queue_job(worker, std::move(job)); -} - -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; - - Plater *plater = wxGetApp().plater(); - const ModelObjectPtrs &objects = plater->model().objects; - - int object_idx = gl_volume->object_idx(); - if (object_idx < 0 || static_cast(object_idx) >= objects.size()) return false; - ModelObject *obj = objects[object_idx]; - size_t vol_id = obj->volumes[gl_volume->volume_idx()]->id().id; - auto cond = RaycastManager::AllowVolumes({vol_id}); - - RaycastManager::Meshes meshes = create_meshes(canvas, cond); - raycaster.actualize(*obj, &cond, &meshes); - - const Camera &camera = plater->get_camera(); - std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); - - // context menu for add text could be open only by right click on an - // object. After right click, object is selected and object_idx is set - // also hit must exist. But there is options to add text by object list - if (!hit.has_value()) + const ModelObjectPtrs &objects = canvas.get_model()->objects; + const ModelVolume* volume = get_model_volume(gl_volume, objects); + const ModelInstance *instance = get_model_instance(gl_volume, objects); + if (volume == nullptr || instance == nullptr || + volume->get_object() == nullptr) { + // weird situation + assert(false); return false; - - // priv::reset_skew(hit_to_world); - Transform3d instance = gl_volume->get_instance_transformation().get_matrix(); - - // Create result volume transformation - Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); - - // 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, std::move(emboss_data), volume_type); - return true; -} - -void priv::find_closest_volume(const Selection &selection, - const Vec2d &screen_center, - const Camera &camera, - const ModelObjectPtrs &objects, - Vec2d *closest_center, - const GLVolume **closest_volume) -{ - assert(closest_center != nullptr); - assert(closest_volume != nullptr); - assert(*closest_volume == nullptr); - const Selection::IndicesList &indices = selection.get_volume_idxs(); - assert(!indices.empty()); // no selected volume - if (indices.empty()) return; - - double center_sq_distance = std::numeric_limits::max(); - for (unsigned int id : indices) { - const GLVolume *gl_volume = selection.get_volume(id); - const ModelVolume *volume = get_model_volume(*gl_volume, objects); - if (!volume->is_model_part()) continue; - Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); - Vec2d c = hull.centroid().cast(); - Vec2d d = c - screen_center; - bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); - if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || - (!is_bigger_x && d.y() * d.y() > center_sq_distance)) continue; - - double distance = d.squaredNorm(); - if (center_sq_distance < distance) continue; - center_sq_distance = distance; - *closest_center = c; - *closest_volume = gl_volume; } + + Plater* plater = wxGetApp().plater(); + const Camera &camera = plater->get_camera(); + std::optional transform = create_volume_transformation_on_surface( + screen_coor, camera, *volume, *instance, raycaster, canvas, fp.distance, fp.angle); + + if (!transform.has_value()) { + // When model is broken. It could appear that hit miss the object. + // So add part near by in simmilar manner as right panel do + return false; + } + + // Try to cast ray into scene and find object for add volume + Worker &worker = plater->get_ui_job_worker(); + return start_create_volume_job(worker, *volume->get_object(), *transform, std::move(emboss_data), volume_type); } ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index af92e58f54..bf78fc3eb6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -16,7 +16,6 @@ #include "libslic3r/Emboss.hpp" #include "libslic3r/Point.hpp" -#include "libslic3r/Model.hpp" #include "libslic3r/TextConfiguration.hpp" #include diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 88f2624919..c56b347b9b 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -14,9 +14,13 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp" +#include "slic3r/GUI/Selection.hpp" #include "slic3r/GUI/CameraUtils.hpp" #include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/Jobs/Worker.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "slic3r/Utils/RaycastManager.hpp" using namespace Slic3r; using namespace Slic3r::Emboss; @@ -168,6 +172,13 @@ void CreateObjectJob::process(Ctl &ctl) if (!priv::check(m_input)) throw std::runtime_error("Bad input data for EmbossCreateObjectJob."); + if (m_input.base->shape.distance.has_value()) + m_input.base->shape.distance.reset(); + + // can't create new object with using surface + if (m_input.base->shape.use_surface) + m_input.base->shape.use_surface = false; + 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; @@ -277,36 +288,6 @@ void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) priv::update_volume(std::move(m_result), m_input); } -namespace Slic3r::GUI::Emboss { - -SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) -{ - SurfaceVolumeData::ModelSources result; - result.reserve(volumes.size() - 1); - for (const ModelVolume *v : volumes) { - if (text_volume_id.has_value() && v->id().id == *text_volume_id) continue; - // skip modifiers and negative volumes, ... - if (!v->is_model_part()) continue; - const TriangleMesh &tm = v->mesh(); - if (tm.empty()) continue; - if (tm.its.empty()) continue; - result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); - } - return result; -} - -SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume) -{ - if (text_volume == nullptr) return {}; - if (!text_volume->text_configuration.has_value()) return {}; - const ModelVolumePtrs &volumes = text_volume->get_object()->volumes; - // no other volume in object - if (volumes.size() <= 1) return {}; - return create_sources(volumes, text_volume->id().id); -} - -} // namespace Slic3r::GUI::Emboss - ///////////////// /// Create Surface volume CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input) @@ -354,6 +335,147 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) priv::update_volume(std::move(m_result), m_input, tr); } +namespace Slic3r::GUI::Emboss { + +SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) +{ + SurfaceVolumeData::ModelSources result; + result.reserve(volumes.size() - 1); + for (const ModelVolume *v : volumes) { + if (text_volume_id.has_value() && v->id().id == *text_volume_id) + continue; + // skip modifiers and negative volumes, ... + if (!v->is_model_part()) + continue; + const TriangleMesh &tm = v->mesh(); + if (tm.empty()) + continue; + if (tm.its.empty()) + continue; + result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); + } + return result; +} + +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume) +{ + if (text_volume == nullptr) + return {}; + if (!text_volume->text_configuration.has_value()) + return {}; + const ModelVolumePtrs &volumes = text_volume->get_object()->volumes; + // no other volume in object + if (volumes.size() <= 1) + return {}; + return create_sources(volumes, text_volume->id().id); +} + +bool start_create_volume_job( + Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type) +{ + bool &use_surface = data->shape.use_surface; + std::unique_ptr job; + if (use_surface) { + // Model to cut surface from. + SurfaceVolumeData::ModelSources sources = create_sources(object.volumes); + if (sources.empty()) { + use_surface = false; + } else { + bool is_outside = volume_type == ModelVolumeType::MODEL_PART; + // check that there is not unexpected volume type + assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); + SurfaceVolumeData sfvd{volume_tr, is_outside, std::move(sources)}; + CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id()}; + job = std::make_unique(std::move(surface_data)); + } + } + if (!use_surface) { + // create volume + DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr}; + job = std::make_unique(std::move(create_volume_data)); + } + return queue_job(worker, std::move(job)); +} + +std::optional create_volume_transformation_on_surface(const Vec2d &screen_coor, + const Camera &camera, + const ModelVolume &volume, + const ModelInstance &instance, + RaycastManager &raycaster, + GLCanvas3D &canvas, + const std::optional &distance, + const std::optional &angle) +{ + auto cond = RaycastManager::AllowVolumes({volume.id().id}); + RaycastManager::Meshes meshes = create_meshes(canvas, cond); + raycaster.actualize(instance, &cond, &meshes); + + std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); + + // context menu for add text could be open only by right click on an + // object. After right click, object is selected and object_idx is set + // also hit must exist. But there is options to add text by object list + if (!hit.has_value()) + return {}; + + // Create result volume transformation + Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); + + apply_transformation(angle, distance, surface_trmat); + + return instance.get_matrix().inverse() * surface_trmat; +} + +Transform3d create_volume_transformation(const GLVolume &gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth) +{ + // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject + const ModelObject *obj = objects[gl_volume.object_idx()]; + BoundingBoxf3 instance_bb = obj->instance_bounding_box(gl_volume.instance_idx()); + // 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 = gl_volume.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 - volume_height / 2, // under + volume_depth / 2 - instance_bb.size().z() / 2 // lay on bed + ); + return tr * Eigen::Translation3d(offset_tr); +} + +const GLVolume *find_closest( + const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) +{ + assert(closest_center != nullptr); + const GLVolume * closest = nullptr; + const Selection::IndicesList &indices = selection.get_volume_idxs(); + assert(!indices.empty()); // no selected volume + if (indices.empty()) + return closest; + + double center_sq_distance = std::numeric_limits::max(); + for (unsigned int id : indices) { + const GLVolume *gl_volume = selection.get_volume(id); + const ModelVolume *volume = get_model_volume(*gl_volume, objects); + if (!volume->is_model_part()) + continue; + Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); + Vec2d c = hull.centroid().cast(); + Vec2d d = c - screen_center; + bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); + if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) + continue; + + double distance = d.squaredNorm(); + if (center_sq_distance < distance) + continue; + center_sq_distance = distance; + + *closest_center = c; + closest = gl_volume; + } + return closest; +} + +} // namespace Slic3r::GUI::Emboss + //////////////////////////// /// private namespace implementation bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface) diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index d909291368..b1b43f6f83 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -6,14 +6,28 @@ #include #include "libslic3r/Emboss.hpp" #include "libslic3r/EmbossShape.hpp" +#include "libslic3r/Point.hpp" // Transform3d + #include "slic3r/Utils/RaycastManager.hpp" + +#include "slic3r/GUI/Jobs/EmbossJob.hpp" // Emboss::DataBase #include "slic3r/GUI/Camera.hpp" + #include "Job.hpp" +// forward declarations namespace Slic3r { +class GLVolume; class ModelVolume; +class ModelObject; class TriangleMesh; -} +typedef std::vector ModelObjectPtrs; +typedef std::vector ModelVolumePtrs; +namespace GUI { +class Selection; +class RaycastManager; +class Worker; +}} namespace Slic3r::GUI::Emboss { @@ -232,6 +246,21 @@ public: /// struct UpdateSurfaceVolumeData : public DataUpdate, public SurfaceVolumeData{}; +/// +/// Update text volume to use surface from object +/// +class UpdateSurfaceVolumeJob : public Job +{ + UpdateSurfaceVolumeData m_input; + TriangleMesh m_result; + +public: + // move params to private variable + UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + /// /// Copied triangles from object to be able create mesh for cut surface from /// @@ -247,21 +276,59 @@ SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, s /// Source data for cut surface from SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume); +using DataBasePtr = std::unique_ptr; /// -/// Update text volume to use surface from object +/// Start job for add new volume to object with given transformation /// -class UpdateSurfaceVolumeJob : public Job -{ - UpdateSurfaceVolumeData m_input; - TriangleMesh m_result; +/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() +/// Define where to add +/// Wanted volume transformation +/// Define what to emboss - shape +/// Type of volume: Part, negative, modifier +/// True on success otherwise false +bool start_create_volume_job(Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type); -public: - // move params to private variable - UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; +/// +/// Start job for add new volume on surface of object defined by screen coor +/// +/// Mouse position which define position +/// Volume to find surface for create +/// Instance to find surface for create +/// Ability to ray cast to model +/// Contain already used scene RayCasters +/// Initial z move +/// Initial z rotation +/// Volume transformation otherwise there is no hit surface by screen coor +std::optional create_volume_transformation_on_surface(const Vec2d &screen_coor, + const Camera &camera, + const ModelVolume &volume, + const ModelInstance &instance, + RaycastManager &raycaster, + GLCanvas3D &canvas, + const std::optional &distance = {}, + const std::optional &angle = {}); + +/// +/// Create transformation for volume near from object(defined by glVolume) +/// +/// Define object +/// All objects +/// Y Size of embossed volume [mm in instance] +/// Z size of embossed volume - emboss depth[mm in instance] +/// Transformation for new created volume +Transform3d create_volume_transformation(const GLVolume& gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth); + +/// +/// Find volume in selected objects with closest convex hull to screen center. +/// +/// Define where to search for closest +/// Canvas center(dependent on camera settings) +/// Actual objects +/// OUT: coordinate of controid of closest volume +/// closest volume when exists otherwise nullptr +const GLVolume *find_closest( + const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); } // namespace Slic3r::GUI From b6d84fb276ff109e074b6938e2e1c3b55d48065f Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 14 Mar 2023 19:56:52 +0100 Subject: [PATCH 007/120] Emboss on job SVG and Text together --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 156 +----- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 196 +++++--- src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 12 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 644 +++++++++++++++++++----- src/slic3r/GUI/Jobs/EmbossJob.hpp | 211 ++------ 6 files changed, 695 insertions(+), 528 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 56f5e433d7..dc94546859 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -14,7 +14,6 @@ #include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp" #include "slic3r/Utils/WxFontUtils.hpp" #include "slic3r/Utils/UndoRedo.hpp" -#include "slic3r/Utils/EmbossGui.hpp" // TODO: remove include #include "libslic3r/SVG.hpp" // debug store @@ -108,14 +107,6 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) // Private namespace with helper function for create 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); - /// /// Prepare data for emboss /// @@ -134,7 +125,7 @@ struct TextDataBase : public DataBase TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration); // Create shape from text + font configuration EmbossShape &create_shape() override; - void write(ModelVolume &volume) const override; + void write(ModelVolume &volume) const override; // private: // Keep pointer on Data of font (glyph shapes) @@ -143,31 +134,6 @@ struct TextDataBase : public DataBase TextConfiguration text_configuration; }; -/// -/// Start job for add object with text into scene -/// -/// Define params of text -/// Screen coordinat, where to create new object laying on bed -static void start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor); - -/// -/// Start job for add new volume on surface of object defined by screen coor -/// -/// Define params of text -/// Emboss / engrave -/// Mouse position which define position -/// Volume to find surface for create -/// 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(const FontProp &fp, - std::unique_ptr emboss_data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume &gl_volume, - RaycastManager &raycaster, - GLCanvas3D &canvas); - // Loaded icons enum // Have to match order of files in function GLGizmoEmboss::init_icons() enum class IconType : unsigned { @@ -194,69 +160,26 @@ const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType typ static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); } // namespace priv -void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) +bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) { - if (!priv::is_valid(volume_type)) return; m_style_manager.discard_style_changes(); - set_default_text(); - - std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); - if (gl_volume == nullptr) - // object is not under mouse position soo create object on plater - return priv::start_create_object_job(std::move(emboss_data), mouse_pos); - + set_default_text(); + DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + Plater *plater_ptr = wxGetApp().plater(); const FontProp &fp = m_style_manager.get_style().prop; - if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, mouse_pos, *gl_volume, m_raycast_manager, m_parent)) - return create_volume(volume_type); + return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Emboss, mouse_pos, fp.distance, fp.angle); } // Designed for create volume without information of mouse in scene -void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) +bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type) { - if (!priv::is_valid(volume_type)) return; m_style_manager.discard_style_changes(); set_default_text(); - - // select position by camera position and view direction - const Selection &selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - - Size s = m_parent.get_canvas_size(); - Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); 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(std::move(emboss_data), screen_center); - return; - } - - // create volume inside of selected object - Vec2d coor; - const Camera &camera = wxGetApp().plater()->get_camera(); - const GLVolume *gl_volume = find_closest(selection, screen_center, camera, objects, &coor); - - FontProp &fp = m_style_manager.get_style().prop; - if (gl_volume == nullptr) { - return priv::start_create_object_job(std::move(emboss_data), screen_center); - } else if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, coor, *gl_volume, m_raycast_manager, m_parent)){ - // In centroid of convex hull is not hit with object. e.g. torid - // 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); - // there is no point on surface so no use of surface will be applied - if (emboss_data->shape.use_surface) - emboss_data->shape.use_surface = false; - - const ModelObject *object = get_model_object(*gl_volume, objects); - Worker &worker = wxGetApp().plater()->get_ui_job_worker(); - Transform3d volume_trmat = create_volume_transformation(*gl_volume, objects, fp.size_in_mm, fp.emboss); - start_create_volume_job(worker, *object, volume_trmat, std::move(emboss_data), volume_type); - } + Plater *plater_ptr = wxGetApp().plater(); + const FontProp &fp = m_style_manager.get_style().prop; + return start_create_volume_without_position(plater_ptr, std::move(emboss_data), + volume_type, m_raycast_manager, GLGizmosManager::Emboss, fp.distance, fp.angle); } bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) @@ -1057,7 +980,7 @@ bool GLGizmoEmboss::process() if (use_surface) { // Model to cut surface from. - SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); + SurfaceVolumeData::ModelSources sources = create_volume_sources(*m_volume); if (sources.empty()) return false; Transform3d text_tr = m_volume->get_matrix(); @@ -3254,17 +3177,6 @@ bool GLGizmoEmboss::is_text_object(const ModelVolume *text) { // 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 text with this type: " << (int) volume_type; - return false; -} - priv::TextDataBase::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)) { @@ -3351,50 +3263,6 @@ std::unique_ptr priv::create_emboss_data_base(const std::string &text, return std::make_unique(std::move(base), font, std::move(tc)); } -void priv::start_create_object_job(std::unique_ptr emboss_data, const Vec2d &coor) { - Plater *plater = wxGetApp().plater(); - const Camera &camera = plater->get_camera(); - const Pointfs &bed_shape = plater->build_volume().bed_shape(); - Worker &worker = plater->get_ui_job_worker(); - DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape}; - auto job = std::make_unique(std::move(data)); - queue_job(worker, std::move(job)); -} - -bool priv::start_create_volume_on_surface_job(const FontProp &fp, - std::unique_ptr emboss_data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume &gl_volume, - RaycastManager &raycaster, - GLCanvas3D &canvas) -{ - const ModelObjectPtrs &objects = canvas.get_model()->objects; - const ModelVolume* volume = get_model_volume(gl_volume, objects); - const ModelInstance *instance = get_model_instance(gl_volume, objects); - if (volume == nullptr || instance == nullptr || - volume->get_object() == nullptr) { - // weird situation - assert(false); - return false; - } - - Plater* plater = wxGetApp().plater(); - const Camera &camera = plater->get_camera(); - std::optional transform = create_volume_transformation_on_surface( - screen_coor, camera, *volume, *instance, raycaster, canvas, fp.distance, fp.angle); - - if (!transform.has_value()) { - // When model is broken. It could appear that hit miss the object. - // So add part near by in simmilar manner as right panel do - return false; - } - - // Try to cast ray into scene and find object for add volume - Worker &worker = plater->get_ui_job_worker(); - return start_create_volume_job(worker, *volume->get_object(), *transform, std::move(emboss_data), volume_type); -} - ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) { const Selection::IndicesList indices = selection.get_volume_idxs(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index bf78fc3eb6..3b9b167ef8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -39,13 +39,13 @@ public: /// /// Object part / Negative volume / Modifier /// Define position of new volume - void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); + bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); /// /// Create new text without given position /// /// Object part / Negative volume / Modifier - void create_volume(ModelVolumeType volume_type); + bool create_volume(ModelVolumeType volume_type); protected: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 30a3540f3d..c8eb662b55 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/CameraUtils.hpp" +#include "slic3r/GUI/Jobs/EmbossJob.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/Point.hpp" @@ -32,11 +33,11 @@ using namespace Slic3r; using namespace Slic3r::Emboss; using namespace Slic3r::GUI; +using namespace Slic3r::GUI::Emboss; namespace priv { // Variable keep limits for variables -static const struct Limits -{ +static const struct Limits{ MinMax emboss{0.01f, 1e4f}; // in mm // distance text object from surface MinMax angle{-180.f, 180.f}; // in degrees @@ -55,20 +56,34 @@ GLGizmoSVG::GLGizmoSVG(GLCanvas3D &parent) // 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(); +/// +/// Let user to choose file with (S)calable (V)ector (G)raphics - SVG. +/// Than let select contour +/// +/// EmbossShape to create +static EmbossShape select_shape(); + +/// +/// Create new embos data +/// +/// Cancel for previous job +/// Base data for emboss SVG +static DataBasePtr create_emboss_data_base(std::shared_ptr> &cancel); + +/// +/// Create symbol '?' as default shape +/// without source file +/// with size 2cm +/// +/// Default shape to emboss +static ExPolygons default_shape(); + /// /// Separate file name from file path. /// String after last delimiter and before last point @@ -77,53 +92,39 @@ static std::string choose_svg_file(); /// File name without directory path static std::string get_file_name(const std::string &file_path); +/// +/// Create volume name from shape information +/// +/// File path +/// Name for volume +static std::string volume_name(const EmbossShape& shape); } // namespace priv -void GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos){ - std::string path = priv::choose_svg_file(); - if (path.empty()) return; - create_volume(path, volume_type, mouse_pos); -} -void GLGizmoSVG::create_volume(ModelVolumeType volume_type) { - std::string path = priv::choose_svg_file(); - if (path.empty()) return; - create_volume(path, volume_type); +bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) +{ + DataBasePtr base = priv::create_emboss_data_base(m_job_cancel); + Plater *plater_ptr = wxGetApp().plater(); + return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg, mouse_pos); } -void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos) { - std::string name = priv::get_file_name(svg_file_path); - NSVGimage *image = nsvgParseFromFile(svg_file_path.c_str(), "mm", 96.0f); - ExPolygons polys = NSVGUtils::to_ExPolygons(image); - nsvgDelete(image); - - BoundingBox bb; - for (const auto &p : polys) - bb.merge(p.contour.points); - - double scale = 1e-4; - auto project = std::make_unique(std::make_unique(10 / scale), scale); - indexed_triangle_set its = polygons2model(polys, *project); - // add volume +bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) +{ + DataBasePtr base = priv::create_emboss_data_base(m_job_cancel); + Plater *plater_ptr = wxGetApp().plater(); + return start_create_volume_without_position(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg); } -void GLGizmoSVG::create_volume(const std::string &svg_file_path, ModelVolumeType volume_type) { - +bool GLGizmoSVG::is_svg(const ModelVolume &volume) { + return volume.emboss_shape.has_value(); } -bool GLGizmoSVG::is_svg(const ModelVolume *volume) { - if (volume == nullptr) - return false; - return volume->emboss_shape.has_value(); -} - -bool GLGizmoSVG::is_svg_object(const ModelVolume *volume) { - if (volume == nullptr) return false; - if (!volume->emboss_shape.has_value()) return false; - if (volume->type() != ModelVolumeType::MODEL_PART) return false; - for (const ModelVolume *v : volume->get_object()->volumes) { - if (v == volume) continue; +bool GLGizmoSVG::is_svg_object(const ModelVolume &volume) { + if (!volume.emboss_shape.has_value()) return false; + if (volume.type() != ModelVolumeType::MODEL_PART) return false; + for (const ModelVolume *v : volume.get_object()->volumes) { + if (v->id() == volume.id()) continue; if (v->type() == ModelVolumeType::MODEL_PART) return false; } return true; @@ -423,7 +424,7 @@ void GLGizmoSVG::set_volume_by_selection() ImGuiWrapper::left_inputs(); // is valid svg volume? - if (!is_svg(volume)) + if (!is_svg(*volume)) return reset_volume(); // cancel previous job @@ -527,8 +528,8 @@ void GLGizmoSVG::close() { // close gizmo == open it again auto& mng = m_parent.get_gizmos_manager(); - if (mng.get_current_type() == GLGizmosManager::Emboss) - mng.open_gizmo(GLGizmosManager::Emboss); + if (mng.get_current_type() == GLGizmosManager::Svg) + mng.open_gizmo(GLGizmosManager::Svg); } void GLGizmoSVG::draw_window() @@ -543,7 +544,8 @@ void GLGizmoSVG::draw_window() void GLGizmoSVG::draw_model_type() { - bool is_last_solid_part = is_svg_object(m_volume); + assert(m_volume != nullptr); + bool is_last_solid_part = is_svg_object(*m_volume); std::string title = _u8L("SVG is to object"); if (is_last_solid_part) { ImVec4 color{.5f, .5f, .5f, 1.f}; @@ -561,7 +563,7 @@ void GLGizmoSVG::draw_model_type() if (ImGui::RadioButton(_u8L("Added").c_str(), type == part)) new_type = part; else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); + ImGui::SetTooltip("%s", _u8L("Change to object's part.").c_str()); ImGui::SameLine(); std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); @@ -571,11 +573,16 @@ void GLGizmoSVG::draw_model_type() if (is_last_solid_part) ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); else if (type != negative) - ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); + ImGui::SetTooltip("%s", _u8L("Change to negative volume.").c_str()); } - // In simple mode are not modifiers - if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { + GUI_App &app = wxGetApp(); + Plater *plater = app.plater(); + // SLA printers do not use modifiers + bool is_sla = plater->printer_technology() == ptSLA; + // In simple mode are not modifiers ?!? + bool is_simple = app.get_mode() == ConfigOptionMode::comSimple; + if (!is_sla && !is_simple) { ImGui::SameLine(); if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) new_type = modifier; @@ -583,13 +590,11 @@ void GLGizmoSVG::draw_model_type() if (is_last_solid_part) ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); else if (type != modifier) - ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); + ImGui::SetTooltip("%s", _u8L("Change to modifier.").c_str()); } } if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) { - GUI_App &app = wxGetApp(); - Plater * plater = app.plater(); Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction); m_volume->set_type(*new_type); @@ -612,8 +617,8 @@ void GLGizmoSVG::draw_model_type() // NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false) // which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed auto &mng = m_parent.get_gizmos_manager(); - if (mng.get_current_type() != GLGizmosManager::Emboss) - mng.open_gizmo(GLGizmosManager::Emboss); + if (mng.get_current_type() != GLGizmosManager::Svg) + mng.open_gizmo(GLGizmosManager::Svg); // TODO: select volume back - Ask @Sasa } } @@ -623,17 +628,6 @@ 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()) @@ -660,6 +654,14 @@ std::string priv::get_file_name(const std::string &file_path) return file_path.substr(offset, count); } +std::string priv::volume_name(const EmbossShape &shape) +{ + std::string file_name = get_file_name(shape.svg_file_path); + if (!file_name.empty()) + return file_name; + return "SVG shape"; +} + std::string priv::choose_svg_file() { wxWindow* parent = nullptr; @@ -689,5 +691,57 @@ std::string priv::choose_svg_file() return path; } +ExPolygons priv::default_shape() { + std::string file = Slic3r::resources_dir() + "/icons/question.svg"; + NSVGimage *image = nsvgParseFromFile(file.c_str(), "px", 96.0f); + ExPolygons shape = NSVGUtils::to_ExPolygons(image); + nsvgDelete(image); + return shape; +} + +EmbossShape priv::select_shape() { + EmbossShape shape; + shape.depth = 10.; + shape.distance = 0; + shape.use_surface = false; + + shape.svg_file_path = choose_svg_file(); + if (shape.svg_file_path.empty()) + return {}; + + NSVGimage *image = nsvgParseFromFile(shape.svg_file_path.c_str(), "mm", 96.0f); + ScopeGuard sg([image]() { nsvgDelete(image); }); + shape.shapes = NSVGUtils::to_ExPolygons(image); + + // TODO: get scale from file + shape.scale = 1.; + + // Must contain some shapes !!! + if (shape.shapes.empty()) + shape.shapes = default_shape(); + + return shape; +} + +DataBasePtr priv::create_emboss_data_base(std::shared_ptr> &cancel) { + EmbossShape shape = priv::select_shape(); + + if (shape.shapes.empty()) + // canceled selection of SVG file + return nullptr; + + // Cancel previous Job, when it is in process + // worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs + // Cancel only EmbossUpdateJob no others + if (cancel != nullptr) + cancel->store(true); + // create new shared ptr to cancel new job + cancel = std::make_shared>(false); + + std::string name = priv::volume_name(shape); + + return std::make_unique(name, cancel /*copy*/, std::move(shape)); +} + // any existing icon filename to not influence GUI const std::string GLGizmoSVG::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 7ca9637393..9d810cdde9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -37,29 +37,29 @@ public: /// /// Object part / Negative volume / Modifier /// Define position of new volume - void create_volume(const std::string& svg_file_path, ModelVolumeType volume_type, const Vec2d &mouse_pos); - void create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog + /// True on succesfull start creation otherwise False + bool create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos); // first open file dialog /// /// Create new text without given position /// /// Object part / Negative volume / Modifier - void create_volume(const std::string &svg_file_path, ModelVolumeType volume_type); - void create_volume(ModelVolumeType volume_type); // first open file dialog + /// True on succesfull start creation otherwise False + bool create_volume(ModelVolumeType volume_type); // first open file dialog /// /// Check whether volume is object containing only emboss volume /// /// Pointer to volume /// True when object otherwise False - static bool is_svg_object(const ModelVolume *volume); + static bool is_svg_object(const ModelVolume &volume); /// /// Check whether volume has emboss data /// /// Pointer to volume /// True when constain emboss data otherwise False - static bool is_svg(const ModelVolume *volume); + static bool is_svg(const ModelVolume &volume); protected: bool on_init() override; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index c56b347b9b..136399bc64 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -5,6 +5,7 @@ #include #include // load_obj for default mesh #include // use surface cuts +#include // create object #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -27,6 +28,120 @@ using namespace Slic3r::Emboss; using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; +// Private implementation for create volume and objects jobs +namespace Slic3r::GUI::Emboss { +/// +/// Hold neccessary data to create ModelVolume in job +/// Volume is created on the surface of existing volume in object. +/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! +/// +struct DataCreateVolume +{ + // Hold data about shape + DataBasePtr base; + + // define embossed volume type + ModelVolumeType volume_type; + + // parent ModelObject index where to create volume + ObjectID object_id; + + // new created volume transformation + std::optional trmat; + + // Define which gizmo open on the success + GLGizmosManager::EType gizmo; +}; + +/// +/// Create new TextVolume on the surface of ModelObject +/// Should not be stopped +/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! +/// +class CreateVolumeJob : public Job +{ + DataCreateVolume m_input; + TriangleMesh m_result; + +public: + CreateVolumeJob(DataCreateVolume &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + +/// +/// Hold neccessary data to create ModelObject in job +/// Object is placed on bed under screen coor +/// OR to center of scene when it is out of bed shape +/// +struct DataCreateObject +{ + // Hold data about shape + DataBasePtr base; + + // define position on screen where to create object + Vec2d screen_coor; + + // projection property + Camera camera; + + // shape of bed in case of create volume on bed + std::vector bed_shape; + + // Define which gizmo open on the success + GLGizmosManager::EType gizmo; +}; + +/// +/// Create new TextObject on the platter +/// Should not be stopped +/// +class CreateObjectJob : public Job +{ + DataCreateObject m_input; + TriangleMesh m_result; + Transform3d m_transformation; + +public: + CreateObjectJob(DataCreateObject &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; + +/// +/// Hold neccessary data to create(cut) volume from surface object in job +/// +struct CreateSurfaceVolumeData : public SurfaceVolumeData +{ + // Hold data about shape + DataBasePtr base; + + // define embossed volume type + ModelVolumeType volume_type; + + // parent ModelObject index where to create volume + ObjectID object_id; + + // Define which gizmo open on the success + GLGizmosManager::EType gizmo; +}; + +/// +/// Cut surface from object and create cutted volume +/// Should not be stopped +/// +class CreateSurfaceVolumeJob : public Job +{ + CreateSurfaceVolumeData m_input; + TriangleMesh m_result; + +public: + CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); + void process(Ctl &ctl) override; + void finalize(bool canceled, std::exception_ptr &eptr) override; +}; +} // namespace Slic3r::GUI::Emboss + // private namespace namespace priv{ // create sure that emboss object is bigger than source object [in mm] @@ -37,12 +152,13 @@ constexpr float safe_extension = 1.0f; /// /// /// -bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false); -bool check(const DataCreateVolume &input, bool is_main_thread = false); -bool check(const DataCreateObject &input); -bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); -bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); -bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); +static bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false); +static bool check(GLGizmosManager::EType gizmo); +static bool check(const DataCreateVolume &input, bool is_main_thread = false); +static bool check(const DataCreateObject &input); +static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); +static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); +static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); // /// Try to create mesh from text @@ -85,8 +201,9 @@ static void update_name_in_list(const ObjectList &object_list, const ModelVolume /// Type of new volume /// Transformation of volume inside of object /// Text configuration and New VolumeName -static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, - const ModelVolumeType type, const Transform3d trmat, const DataBase &data); +/// Gizmo to open +static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, const ModelVolumeType type, + const std::optional& trmat, const DataBase &data, GLGizmosManager::EType gizmo); /// /// Select Volume from objects @@ -124,6 +241,14 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr /// Extruded object from cuted surace static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, std::function was_canceled); +/// +/// Copied triangles from object to be able create mesh for cut surface from +/// +/// Source object volumes for cut surface from +/// Source volume id +/// Source data for cut surface from +static SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); + static void create_message(const std::string &message); // only in finalize static bool process(std::exception_ptr &eptr); static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input); @@ -141,6 +266,11 @@ auto was_canceled(Job::Ctl &ctl, DataBase &base){ }// namespace priv +void Slic3r::GUI::Emboss::DataBase::write(ModelVolume &volume) const{ + volume.name = volume_name; + volume.emboss_shape = shape; +} + ///////////////// /// Create Volume CreateVolumeJob::CreateVolumeJob(DataCreateVolume &&input): m_input(std::move(input)){ assert(priv::check(m_input, true)); } @@ -159,7 +289,7 @@ void CreateVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { 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.base); + priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base, m_input.gizmo); } @@ -194,7 +324,7 @@ void CreateObjectJob::process(Ctl &ctl) bed_shape_.reserve(m_input.bed_shape.size()); for (const Vec2d &p : m_input.bed_shape) bed_shape_.emplace_back(p.cast()); - Polygon bed(bed_shape_); + Slic3r::Polygon bed(bed_shape_); if (!bed.contains(bed_coor.cast())) // mouse pose is out of build plate so create object in center of plate bed_coor = bed.centroid().cast(); @@ -254,8 +384,8 @@ void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) // 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); + if (manager.get_current_type() != m_input.gizmo) + manager.open_gizmo(m_input.gizmo); // redraw scene canvas->reload_scene(true); @@ -306,7 +436,7 @@ void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { 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.transform, *m_input.base); + m_input.volume_type, m_input.transform, *m_input.base, m_input.gizmo); } ///////////////// @@ -335,143 +465,203 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) priv::update_volume(std::move(m_result), m_input, tr); } -namespace Slic3r::GUI::Emboss { +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); + +/// +/// Start job for add new volume to object with given transformation +/// +/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() +/// Define where to add +/// Wanted volume transformation, when not set will be calculated after creation to be near the object +/// Define what to emboss - shape +/// Type of volume: Part, negative, modifier +/// Define which gizmo open on the success +/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way +static bool start_create_volume_job(Worker &worker, const ModelObject &object, const std::optional& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo); + +/// +/// Find volume in selected objects with closest convex hull to screen center. +/// +/// Define where to search for closest +/// Canvas center(dependent on camera settings) +/// Actual objects +/// OUT: coordinate of controid of closest volume +/// closest volume when exists otherwise nullptr +static const GLVolume *find_closest( + const Selection &selection, const Vec2d &screen_center, + const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); + +/// +/// Start job for add object with text into scene +/// +/// Contain worker and build shape +/// Define params of text +/// Screen coordinat, where to create new object laying on bed +/// Define which gizmo open on the success +/// True when can add job to worker otherwise FALSE +static bool start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo); + +/// +/// Start job to create volume on the surface of object +/// +/// scene RayCasters + Objects + Camera + worker +/// Describe what to emboss +/// Type of new volume +/// Where to add +/// Surface point is belonge to +/// For project on surface +/// Define which gizmo open on the success +/// Distance from surface +/// Angle around emboss direction +/// True on add job to worker otherwise false +/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way +static DataBasePtr start_create_volume_on_surface_job(Plater &plater, + DataBasePtr data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume &gl_volume, + RaycastManager &raycaster, + GLGizmosManager::EType gizmo, + const std::optional &distance, + const std::optional &angle, + bool &success); -SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) -{ - SurfaceVolumeData::ModelSources result; - result.reserve(volumes.size() - 1); - for (const ModelVolume *v : volumes) { - if (text_volume_id.has_value() && v->id().id == *text_volume_id) - continue; - // skip modifiers and negative volumes, ... - if (!v->is_model_part()) - continue; - const TriangleMesh &tm = v->mesh(); - if (tm.empty()) - continue; - if (tm.its.empty()) - continue; - result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); - } - return result; } -SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume) +namespace Slic3r::GUI::Emboss { + +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_volume) { - if (text_volume == nullptr) - return {}; - if (!text_volume->text_configuration.has_value()) - return {}; - const ModelVolumePtrs &volumes = text_volume->get_object()->volumes; + const ModelVolumePtrs &volumes = text_volume.get_object()->volumes; // no other volume in object if (volumes.size() <= 1) return {}; - return create_sources(volumes, text_volume->id().id); + return priv::create_sources(volumes, text_volume.id().id); } -bool start_create_volume_job( - Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type) +bool start_create_volume(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const Vec2d &mouse_pos, + const std::optional &distance, + const std::optional &angle) { - bool &use_surface = data->shape.use_surface; - std::unique_ptr job; - if (use_surface) { - // Model to cut surface from. - SurfaceVolumeData::ModelSources sources = create_sources(object.volumes); - if (sources.empty()) { - use_surface = false; - } else { - bool is_outside = volume_type == ModelVolumeType::MODEL_PART; - // check that there is not unexpected volume type - assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); - SurfaceVolumeData sfvd{volume_tr, is_outside, std::move(sources)}; - CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id()}; - job = std::make_unique(std::move(surface_data)); - } - } - if (!use_surface) { - // create volume - DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr}; - job = std::make_unique(std::move(create_volume_data)); - } - return queue_job(worker, std::move(job)); + if (!priv::is_valid(volume_type)) + return false; + + assert(plater_ptr); + if (plater_ptr == nullptr) + return false; + Plater &plater = *plater_ptr; + + GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); + assert(canvas_ptr); + if (canvas_ptr == nullptr) + return false; + + GLGizmosManager::EType gizmo_type = static_cast(gizmo); + + GLVolume *gl_volume = get_first_hovered_gl_volume(*canvas_ptr); + if (gl_volume == nullptr) + // object is not under mouse position soo create object on plater + return priv::start_create_object_job(plater, std::move(data), mouse_pos, gizmo_type); + + bool success = true; + DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data), + volume_type, mouse_pos, *gl_volume, raycaster, gizmo_type, distance, angle, success); + + // Is successfull created job for add volume on surface? + if (success) + return true; + + // not success and consume data + if (data2 == nullptr) + return false; + + // Can't create on coordinate try to create somewhere + return start_create_volume_without_position(plater_ptr, std::move(data2), volume_type, raycaster, gizmo, distance, angle); } -std::optional create_volume_transformation_on_surface(const Vec2d &screen_coor, - const Camera &camera, - const ModelVolume &volume, - const ModelInstance &instance, - RaycastManager &raycaster, - GLCanvas3D &canvas, - const std::optional &distance, - const std::optional &angle) +bool start_create_volume_without_position(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const std::optional &distance, + const std::optional &angle) { - auto cond = RaycastManager::AllowVolumes({volume.id().id}); - RaycastManager::Meshes meshes = create_meshes(canvas, cond); - raycaster.actualize(instance, &cond, &meshes); + if (!priv::is_valid(volume_type)) + return false; - std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); + assert(plater_ptr); + if (plater_ptr == nullptr) + return false; + Plater &plater = *plater_ptr; - // context menu for add text could be open only by right click on an - // object. After right click, object is selected and object_idx is set - // also hit must exist. But there is options to add text by object list - if (!hit.has_value()) - return {}; + GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); + assert(canvas_ptr); + if (canvas_ptr == nullptr) + return false; + const GLCanvas3D &canvas = *canvas_ptr; - // Create result volume transformation - Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); + // select position by camera position and view direction + const Selection &selection = canvas.get_selection(); + int object_idx = selection.get_object_idx(); - apply_transformation(angle, distance, surface_trmat); + Size s = canvas.get_canvas_size(); + Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); + const ModelObjectPtrs &objects = selection.get_model()->objects; - return instance.get_matrix().inverse() * surface_trmat; -} + GLGizmosManager::EType gizmo_type = static_cast(gizmo); -Transform3d create_volume_transformation(const GLVolume &gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth) -{ - // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject - const ModelObject *obj = objects[gl_volume.object_idx()]; - BoundingBoxf3 instance_bb = obj->instance_bounding_box(gl_volume.instance_idx()); - // 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 = gl_volume.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 - volume_height / 2, // under - volume_depth / 2 - instance_bb.size().z() / 2 // lay on bed - ); - return tr * Eigen::Translation3d(offset_tr); -} + // 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 + return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type); -const GLVolume *find_closest( - const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) -{ - assert(closest_center != nullptr); - const GLVolume * closest = nullptr; - const Selection::IndicesList &indices = selection.get_volume_idxs(); - assert(!indices.empty()); // no selected volume - if (indices.empty()) - return closest; + // create volume inside of selected object + Vec2d coor; + const Camera &camera = wxGetApp().plater()->get_camera(); + const GLVolume *gl_volume = priv::find_closest(selection, screen_center, camera, objects, &coor); - double center_sq_distance = std::numeric_limits::max(); - for (unsigned int id : indices) { - const GLVolume *gl_volume = selection.get_volume(id); - const ModelVolume *volume = get_model_volume(*gl_volume, objects); - if (!volume->is_model_part()) - continue; - Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); - Vec2d c = hull.centroid().cast(); - Vec2d d = c - screen_center; - bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); - if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) - continue; + if (gl_volume == nullptr) + return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type); + + bool success = true; + DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data), + volume_type, coor, *gl_volume, raycaster, gizmo_type, distance, angle, success); - double distance = d.squaredNorm(); - if (center_sq_distance < distance) - continue; - center_sq_distance = distance; + // Is successfull created job for add volume on surface? + if (success) + return true; - *closest_center = c; - closest = gl_volume; - } - return closest; + // not success and consume data + if (data2 == nullptr) + return false; + + // In centroid of convex hull is not hit with object. e.g. torid + // soo create transfomation on border of object + + // there is no point on surface so no use of surface will be applied + if (data2->shape.use_surface) + data2->shape.use_surface = false; + + Worker &worker = plater.get_ui_job_worker(); + const ModelObject *object = get_model_object(*gl_volume, objects); + if (object == nullptr) + return false; + + return priv::start_create_volume_job(worker, *object, {}, std::move(data2), volume_type, gizmo_type); } } // namespace Slic3r::GUI::Emboss @@ -495,6 +685,13 @@ bool priv::check(const DataBase &input, bool check_fontfile, bool use_surface) //res &= input.text_configuration.style.prop.use_surface == use_surface; return res; } + +bool priv::check(GLGizmosManager::EType gizmo) +{ + assert(gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg); + return gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg; +} + bool priv::check(const DataCreateVolume &input, bool is_main_thread) { bool check_fontfile = false; assert(input.base != nullptr); @@ -504,6 +701,7 @@ bool priv::check(const DataCreateVolume &input, bool is_main_thread) { res &= input.volume_type != ModelVolumeType::INVALID; assert(input.object_id.id >= 0); res &= input.object_id.id >= 0; + res &= check(input.gizmo); return res; } bool priv::check(const DataCreateObject &input) { @@ -517,6 +715,7 @@ bool priv::check(const DataCreateObject &input) { res &= input.screen_coor.y() >= 0.; assert(input.bed_shape.size() >= 3); // at least triangle res &= input.bed_shape.size() >= 3; + res &= check(input.gizmo); return res; } bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){ @@ -542,6 +741,7 @@ bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread) res &= check(*input.base, is_main_thread, use_surface); assert(!input.sources.empty()); res &= !input.sources.empty(); + res &= check(input.gizmo); return res; } bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){ @@ -711,9 +911,12 @@ void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3 UpdateJob::update_volume(volume, std::move(mesh), *data.base); } -void priv::create_volume( - TriangleMesh &&mesh, const ObjectID& object_id, - const ModelVolumeType type, const Transform3d trmat, const DataBase &data) +void priv::create_volume(TriangleMesh &&mesh, + const ObjectID &object_id, + const ModelVolumeType type, + const std::optional &trmat, + const DataBase &data, + GLGizmosManager::EType gizmo) { GUI_App &app = wxGetApp(); Plater *plater = app.plater(); @@ -741,6 +944,13 @@ void priv::create_volume( plater->take_snapshot(_L("Add Emboss text Volume")); + BoundingBoxf3 instance_bb; + if (!trmat.has_value()) { + // used for align to instance + size_t instance_index = 0; // must exist + instance_bb = obj->instance_bounding_box(instance_index); + } + // NOTE: be carefull add volume also center mesh !!! // So first add simple shape(convex hull is also calculated) ModelVolume *volume = obj->add_volume(make_cube(1., 1., 1.), type); @@ -757,7 +967,24 @@ void priv::create_volume( volume->source.is_from_builtin_objects = true; volume->name = data.volume_name; // copy - volume->set_transformation(trmat); + + if (trmat.has_value()) { + volume->set_transformation(*trmat); + } else { + assert(!data.shape.use_surface); + // Create transformation for volume near from object(defined by glVolume) + // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject + Vec3d volume_size = volume->mesh().bounding_box().size(); + // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. + Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created + -instance_bb.size().y() / 2 - volume_size.y() / 2, // under + volume_size.z() / 2 - instance_bb.size().z() / 2); // lay on bed + // use same instance as for calculation of instance_bounding_box + Transform3d tr = obj->instances.front()->get_transformation().get_matrix_no_offset().inverse(); + Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); + volume->set_transformation(volume_trmat); + } + data.write(*volume); // update printable state on canvas @@ -935,6 +1162,26 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, return TriangleMesh(std::move(new_its)); } +SurfaceVolumeData::ModelSources priv::create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) +{ + SurfaceVolumeData::ModelSources result; + result.reserve(volumes.size() - 1); + for (const ModelVolume *v : volumes) { + if (text_volume_id.has_value() && v->id().id == *text_volume_id) + continue; + // skip modifiers and negative volumes, ... + if (!v->is_model_part()) + continue; + const TriangleMesh &tm = v->mesh(); + if (tm.empty()) + continue; + if (tm.its.empty()) + continue; + result.push_back({v->get_mesh_shared_ptr(), v->get_matrix()}); + } + return result; +} + bool priv::process(std::exception_ptr &eptr) { if (!eptr) return false; try { @@ -956,6 +1203,141 @@ bool priv::finalize(bool canceled, std::exception_ptr &eptr, const DataBase &inp return !process(eptr); } +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 volume with this type: " << (int) volume_type; + return false; +} + +bool priv::start_create_volume_job( + Worker &worker, const ModelObject &object, const std::optional& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo) +{ + bool &use_surface = data->shape.use_surface; + std::unique_ptr job; + if (use_surface) { + // Model to cut surface from. + SurfaceVolumeData::ModelSources sources = create_sources(object.volumes); + if (sources.empty() || !volume_tr.has_value()) { + use_surface = false; + } else { + bool is_outside = volume_type == ModelVolumeType::MODEL_PART; + // check that there is not unexpected volume type + assert(is_outside || volume_type == ModelVolumeType::NEGATIVE_VOLUME || volume_type == ModelVolumeType::PARAMETER_MODIFIER); + SurfaceVolumeData sfvd{*volume_tr, is_outside, std::move(sources)}; + CreateSurfaceVolumeData surface_data{std::move(sfvd), std::move(data), volume_type, object.id(), gizmo}; + job = std::make_unique(std::move(surface_data)); + } + } + if (!use_surface) { + // create volume + DataCreateVolume create_volume_data{std::move(data), volume_type, object.id(), volume_tr, gizmo}; + job = std::make_unique(std::move(create_volume_data)); + } + return queue_job(worker, std::move(job)); +} + +const GLVolume * priv::find_closest( + const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) +{ + assert(closest_center != nullptr); + const GLVolume *closest = nullptr; + const Selection::IndicesList &indices = selection.get_volume_idxs(); + assert(!indices.empty()); // no selected volume + if (indices.empty()) + return closest; + + double center_sq_distance = std::numeric_limits::max(); + for (unsigned int id : indices) { + const GLVolume *gl_volume = selection.get_volume(id); + const ModelVolume *volume = get_model_volume(*gl_volume, objects); + if (!volume->is_model_part()) + continue; + Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); + Vec2d c = hull.centroid().cast(); + Vec2d d = c - screen_center; + bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); + if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) + continue; + + double distance = d.squaredNorm(); + if (center_sq_distance < distance) + continue; + center_sq_distance = distance; + + *closest_center = c; + closest = gl_volume; + } + return closest; +} + +bool priv::start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo) +{ + const Camera &camera = plater.get_camera(); + const Pointfs &bed_shape = plater.build_volume().bed_shape(); + + DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape, gizmo}; + auto job = std::make_unique(std::move(data)); + + Worker &worker = plater.get_ui_job_worker(); + return queue_job(worker, std::move(job)); +} + +DataBasePtr priv::start_create_volume_on_surface_job(Plater &plater, + DataBasePtr data, + ModelVolumeType volume_type, + const Vec2d &screen_coor, + const GLVolume &gl_volume, + RaycastManager &raycaster, + GLGizmosManager::EType gizmo, + const std::optional &distance, + const std::optional &angle, + bool &success) +{ + success = false; + const ModelObjectPtrs &objects = plater.model().objects; + const ModelVolume* volume = get_model_volume(gl_volume, objects); + const ModelInstance *instance = get_model_instance(gl_volume, objects); + if (volume == nullptr || instance == nullptr || + volume->get_object() == nullptr) { + // weird situation + assert(false); + return data; + } + + const Camera &camera = plater.get_camera(); + GLCanvas3D &canvas = *plater.get_current_canvas3D(); + + auto cond = RaycastManager::AllowVolumes({volume->id().id}); + RaycastManager::Meshes meshes = create_meshes(canvas, cond); + raycaster.actualize(*instance, &cond, &meshes); + std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); + + // context menu for add text could be open only by right click on an + // object. After right click, object is selected and object_idx is set + // also hit must exist. But there is options to add text by object list + if (!hit.has_value()) + // When model is broken. It could appear that hit miss the object. + // So add part near by in simmilar manner as right panel do + return data; + + // Create result volume transformation + Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); + + apply_transformation(angle, distance, surface_trmat); + + Transform3d transform = instance->get_matrix().inverse() * surface_trmat; + + // Try to cast ray into scene and find object for add volume + Worker &worker = plater.get_ui_job_worker(); + + success = priv::start_create_volume_job(worker, *volume->get_object(), transform, std::move(data), volume_type, gizmo); + return nullptr; +} #include diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index b1b43f6f83..00de9b69f9 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -7,8 +7,7 @@ #include "libslic3r/Emboss.hpp" #include "libslic3r/EmbossShape.hpp" #include "libslic3r/Point.hpp" // Transform3d - -#include "slic3r/Utils/RaycastManager.hpp" +#include "libslic3r/ObjectID.hpp" #include "slic3r/GUI/Jobs/EmbossJob.hpp" // Emboss::DataBase #include "slic3r/GUI/Camera.hpp" @@ -17,16 +16,12 @@ // forward declarations namespace Slic3r { -class GLVolume; -class ModelVolume; -class ModelObject; class TriangleMesh; -typedef std::vector ModelObjectPtrs; -typedef std::vector ModelVolumePtrs; +class ModelVolume; +enum class ModelVolumeType : int; namespace GUI { -class Selection; class RaycastManager; -class Worker; +class Plater; }} namespace Slic3r::GUI::Emboss { @@ -38,7 +33,7 @@ class DataBase { 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) + 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() {} @@ -54,11 +49,7 @@ public: /// 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; - }; + virtual void write(ModelVolume &volume) const; // new volume name std::string volume_name; @@ -70,77 +61,7 @@ public: // shape to emboss EmbossShape shape; }; - -/// -/// Hold neccessary data to create ModelVolume in job -/// Volume is created on the surface of existing volume in object. -/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! -/// -struct DataCreateVolume -{ - // Hold data about shape - std::unique_ptr base; - - // define embossed volume type - ModelVolumeType volume_type; - - // parent ModelObject index where to create volume - ObjectID object_id; - - // new created volume transformation - Transform3d trmat; -}; - -/// -/// Create new TextVolume on the surface of ModelObject -/// Should not be stopped -/// NOTE: EmbossDataBase::font_file doesn't have to be valid !!! -/// -class CreateVolumeJob : public Job -{ - DataCreateVolume m_input; - TriangleMesh m_result; - -public: - CreateVolumeJob(DataCreateVolume&& input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; - -/// -/// Hold neccessary data to create ModelObject in job -/// Object is placed on bed under screen coor -/// OR to center of scene when it is out of bed shape -/// -struct DataCreateObject -{ - // Hold data about shape - std::unique_ptr base; - - // define position on screen where to create object - Vec2d screen_coor; - - // projection property - Camera camera; - - // shape of bed in case of create volume on bed - std::vector bed_shape; -}; - -/// -/// Create new TextObject on the platter -/// Should not be stopped -/// -class CreateObjectJob : public Job -{ - DataCreateObject m_input; - TriangleMesh m_result; - Transform3d m_transformation; -public: - CreateObjectJob(DataCreateObject&& input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; +using DataBasePtr = std::unique_ptr; /// /// Hold neccessary data to update embossed text object in job @@ -148,7 +69,7 @@ public: struct DataUpdate { // Hold data about shape - std::unique_ptr base; + DataBasePtr base; // unique identifier of volume to change ObjectID volume_id; @@ -212,35 +133,6 @@ struct SurfaceVolumeData ModelSources sources; }; -/// -/// Hold neccessary data to create(cut) volume from surface object in job -/// -struct CreateSurfaceVolumeData : public SurfaceVolumeData{ - // Hold data about shape - std::unique_ptr base; - - // define embossed volume type - ModelVolumeType volume_type; - - // parent ModelObject index where to create volume - ObjectID object_id; -}; - -/// -/// Cut surface from object and create cutted volume -/// Should not be stopped -/// -class CreateSurfaceVolumeJob : public Job -{ - CreateSurfaceVolumeData m_input; - TriangleMesh m_result; - -public: - CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); - void process(Ctl &ctl) override; - void finalize(bool canceled, std::exception_ptr &eptr) override; -}; - /// /// Hold neccessary data to update embossed text object in job /// @@ -264,72 +156,43 @@ public: /// /// Copied triangles from object to be able create mesh for cut surface from /// -/// Source object volumes for cut surface from -/// Source volume id +/// Define embossed volume /// Source data for cut surface from -SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume); /// -/// Copied triangles from object to be able create mesh for cut surface from +/// Create new volume on position of mouse cursor /// -/// Define text in object -/// Source data for cut surface from -SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume *text_volume); +/// canvas + camera + bed shape + +/// Shape of emboss +/// New created volume type +/// Knows object in scene +/// Define which gizmo open on the success - enum GLGizmosManager::EType +/// Define position where to create volume +/// Wanted additionl move in Z(emboss) direction of new created volume +/// Wanted additionl rotation around Z of new created volume +/// True on success otherwise False +bool start_create_volume(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const Vec2d &mouse_pos, + const std::optional &distance = {}, + const std::optional &angle = {}); -using DataBasePtr = std::unique_ptr; /// -/// Start job for add new volume to object with given transformation +/// Same as previous function but without mouse position +/// Need to suggest position or put near the selection /// -/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() -/// Define where to add -/// Wanted volume transformation -/// Define what to emboss - shape -/// Type of volume: Part, negative, modifier -/// True on success otherwise false -bool start_create_volume_job(Worker &worker, const ModelObject &object, const Transform3d volume_tr, DataBasePtr data, ModelVolumeType volume_type); - -/// -/// Start job for add new volume on surface of object defined by screen coor -/// -/// Mouse position which define position -/// Volume to find surface for create -/// Instance to find surface for create -/// Ability to ray cast to model -/// Contain already used scene RayCasters -/// Initial z move -/// Initial z rotation -/// Volume transformation otherwise there is no hit surface by screen coor -std::optional create_volume_transformation_on_surface(const Vec2d &screen_coor, - const Camera &camera, - const ModelVolume &volume, - const ModelInstance &instance, - RaycastManager &raycaster, - GLCanvas3D &canvas, - const std::optional &distance = {}, - const std::optional &angle = {}); - -/// -/// Create transformation for volume near from object(defined by glVolume) -/// -/// Define object -/// All objects -/// Y Size of embossed volume [mm in instance] -/// Z size of embossed volume - emboss depth[mm in instance] -/// Transformation for new created volume -Transform3d create_volume_transformation(const GLVolume& gl_volume, const ModelObjectPtrs &objects, float volume_height, float volume_depth); - -/// -/// Find volume in selected objects with closest convex hull to screen center. -/// -/// Define where to search for closest -/// Canvas center(dependent on camera settings) -/// Actual objects -/// OUT: coordinate of controid of closest volume -/// closest volume when exists otherwise nullptr -const GLVolume *find_closest( - const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); - +bool start_create_volume_without_position(Plater *plater_ptr, + DataBasePtr data, + ModelVolumeType volume_type, + RaycastManager &raycaster, + unsigned char gizmo, + const std::optional &distance = {}, + const std::optional &angle = {}); } // namespace Slic3r::GUI #endif // slic3r_EmbossJob_hpp_ From 733ad16feb660788a58af44c0878e987045400ff Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 15 Mar 2023 12:31:19 +0100 Subject: [PATCH 008/120] misc --- src/slic3r/Utils/EmbossStyleManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 100a532b89..5740ac13aa 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -59,8 +59,7 @@ void StyleManager::init(AppConfig *app_config) load_valid_style(); } -bool StyleManager::store_styles_to_app_config(bool use_modification, - bool store_active_index) +bool StyleManager::store_styles_to_app_config(bool use_modification, bool store_active_index) { assert(m_app_config != nullptr); if (m_app_config == nullptr) return false; From 2a106c7e6d81cd394d9aef895fd7a52c85055899 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 15 Mar 2023 13:43:04 +0100 Subject: [PATCH 009/120] Fix for SonarLint warns --- src/slic3r/GUI/Jobs/EmbossJob.cpp | 46 +++++++++++++++---------------- src/slic3r/GUI/Jobs/EmbossJob.hpp | 10 +++---- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 136399bc64..d855e84065 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -64,7 +64,7 @@ class CreateVolumeJob : public Job TriangleMesh m_result; public: - CreateVolumeJob(DataCreateVolume &&input); + explicit CreateVolumeJob(DataCreateVolume &&input); void process(Ctl &ctl) override; void finalize(bool canceled, std::exception_ptr &eptr) override; }; @@ -103,7 +103,7 @@ class CreateObjectJob : public Job Transform3d m_transformation; public: - CreateObjectJob(DataCreateObject &&input); + explicit CreateObjectJob(DataCreateObject &&input); void process(Ctl &ctl) override; void finalize(bool canceled, std::exception_ptr &eptr) override; }; @@ -136,7 +136,7 @@ class CreateSurfaceVolumeJob : public Job TriangleMesh m_result; public: - CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); + explicit CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input); void process(Ctl &ctl) override; void finalize(bool canceled, std::exception_ptr &eptr) override; }; @@ -169,8 +169,8 @@ static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = fa /// NOTE: Cache glyphs is changed /// To check if process was canceled /// Triangle mesh model -template static TriangleMesh try_create_mesh(DataBase &input, Fnc was_canceled); -template static TriangleMesh create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl &ctl); +template static TriangleMesh try_create_mesh(DataBase &input, const Fnc& was_canceled); +template static TriangleMesh create_mesh(DataBase &input, const Fnc& was_canceled, Job::Ctl &ctl); /// /// Create default mesh for embossed text @@ -239,7 +239,7 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr /// SurfaceVolume data /// Check to interupt execution /// Extruded object from cuted surace -static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, std::function was_canceled); +static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, const std::function& was_canceled); /// /// Copied triangles from object to be able create mesh for cut surface from @@ -254,9 +254,8 @@ static bool process(std::exception_ptr &eptr); static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input); class JobException : public std::runtime_error { -public: JobException(const char* message):runtime_error(message){}}; - -auto was_canceled(Job::Ctl &ctl, DataBase &base){ +public: JobException(const char* message):runtime_error(message){}}; +static auto was_canceled(Job::Ctl &ctl, DataBase &base){ return [&ctl, &cancel = base.cancel]() -> bool { if (cancel->load()) return true; @@ -554,6 +553,8 @@ bool start_create_volume(Plater *plater_ptr, const std::optional &distance, const std::optional &angle) { + if (data == nullptr) + return false; if (!priv::is_valid(volume_type)) return false; @@ -562,14 +563,13 @@ bool start_create_volume(Plater *plater_ptr, return false; Plater &plater = *plater_ptr; - GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); + const GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); assert(canvas_ptr); if (canvas_ptr == nullptr) return false; - GLGizmosManager::EType gizmo_type = static_cast(gizmo); - - GLVolume *gl_volume = get_first_hovered_gl_volume(*canvas_ptr); + auto gizmo_type = static_cast(gizmo); + const GLVolume *gl_volume = get_first_hovered_gl_volume(*canvas_ptr); if (gl_volume == nullptr) // object is not under mouse position soo create object on plater return priv::start_create_object_job(plater, std::move(data), mouse_pos, gizmo_type); @@ -755,11 +755,11 @@ bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){ } template -TriangleMesh priv::try_create_mesh(DataBase &base, Fnc was_canceled) +TriangleMesh priv::try_create_mesh(DataBase &base, const Fnc& was_canceled) { const EmbossShape& shape = base.create_shape(); if (shape.shapes.empty()) return {}; - float depth = shape.depth / shape.scale; + double depth = shape.depth / shape.scale; auto projectZ = std::make_unique(depth); ProjectScale project(std::move(projectZ), shape.scale); if (was_canceled()) return {}; @@ -767,7 +767,7 @@ TriangleMesh priv::try_create_mesh(DataBase &base, Fnc was_canceled) } template -TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl) +TriangleMesh priv::create_mesh(DataBase &input, const Fnc& was_canceled, Job::Ctl& ctl) { // It is neccessary to create some shape // Emboss text window is opened by creation new emboss text object @@ -1061,7 +1061,7 @@ OrthoProject3d priv::create_emboss_projection( } // input can't be const - cache of font -TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, std::function was_canceled) +TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, const std::function& was_canceled) { EmbossShape& emboss_shape = base.create_shape(); ExPolygons& shapes = emboss_shape.shapes; @@ -1077,7 +1077,7 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, bb.translate(-projection_center); const SurfaceVolumeData::ModelSources &sources = input2.sources; - const SurfaceVolumeData::ModelSource *biggest = nullptr; + const SurfaceVolumeData::ModelSource *biggest = &sources.front(); size_t biggest_count = 0; // convert index from (s)ources to (i)ndexed (t)riangle (s)ets @@ -1119,8 +1119,8 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, bool fix_reflected = true; indexed_triangle_set &its = itss[itss_index]; its_transform(its, tr, fix_reflected); - BoundingBoxf3 bb = bounding_box(its); - mesh_bb.merge(bb); + BoundingBoxf3 its_bb = bounding_box(its); + mesh_bb.merge(its_bb); } // tr_inv = transformation of mesh inverted @@ -1253,9 +1253,9 @@ const GLVolume * priv::find_closest( double center_sq_distance = std::numeric_limits::max(); for (unsigned int id : indices) { - const GLVolume *gl_volume = selection.get_volume(id); - const ModelVolume *volume = get_model_volume(*gl_volume, objects); - if (!volume->is_model_part()) + const GLVolume *gl_volume = selection.get_volume(id); + const ModelVolume *volume = get_model_volume(*gl_volume, objects); + if (volume == nullptr || !volume->is_model_part()) continue; Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); Vec2d c = hull.centroid().cast(); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 00de9b69f9..6b55e0da1f 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -32,11 +32,11 @@ namespace Slic3r::GUI::Emboss { class DataBase { 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) + DataBase(const std::string& volume_name, std::shared_ptr> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {} + DataBase(const std::string& volume_name, std::shared_ptr> cancel, EmbossShape&& shape) : volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)) {} - virtual ~DataBase() {} + virtual ~DataBase() = default; /// /// Create shape @@ -86,7 +86,7 @@ class UpdateJob : public Job public: // move params to private variable - UpdateJob(DataUpdate &&input); + explicit UpdateJob(DataUpdate &&input); /// /// Create new embossed volume by m_input data and store to m_result @@ -148,7 +148,7 @@ class UpdateSurfaceVolumeJob : public Job public: // move params to private variable - UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input); + explicit UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input); void process(Ctl &ctl) override; void finalize(bool canceled, std::exception_ptr &eptr) override; }; From 6807615c75dc861dd1578df7ab948b48720bb3ee Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 15 Mar 2023 14:20:04 +0100 Subject: [PATCH 010/120] remove duplicit codes --- src/slic3r/GUI/GUI_Factories.cpp | 91 ++++++++++++-------------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index daab5fafc9..8801bd397f 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -499,76 +499,55 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty return sub_menu; } -void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item/* = true*/) -{ - auto add_text = [type](wxCommandEvent& evt) { - GLCanvas3D* canvas = plater()->canvas3D(); - GLGizmosManager& mng = canvas->get_gizmos_manager(); - GLGizmoBase* gizmo = mng.get_gizmo(GLGizmosManager::Emboss); - GLGizmoEmboss* emboss = dynamic_cast(gizmo); - assert(emboss != nullptr); - if (emboss == nullptr) return; - +static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType gizmo_type, wxMenu *menu, ModelVolumeType type, bool is_submenu_item) { + auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) { + const GLCanvas3D *canvas = plater()->canvas3D(); + const GLGizmosManager &mng = canvas->get_gizmos_manager(); + GLGizmoBase *gizmo_base = mng.get_gizmo(gizmo_type); + ModelVolumeType volume_type = type; // no selected object means create new object if (volume_type == ModelVolumeType::INVALID) volume_type = ModelVolumeType::MODEL_PART; auto screen_position = canvas->get_popup_menu_position(); - if (screen_position.has_value()) { - emboss->create_volume(volume_type, *screen_position); - } else { - emboss->create_volume(volume_type); - } + if (gizmo_type == GLGizmosManager::Emboss) { + auto emboss = dynamic_cast(gizmo_base); + assert(emboss != nullptr); + if (emboss == nullptr) return; + if (screen_position.has_value()) { + emboss->create_volume(volume_type, *screen_position); + } else { + emboss->create_volume(volume_type); + } + } else if (gizmo_type == GLGizmosManager::Svg) { + auto svg = dynamic_cast(gizmo_base); + assert(svg != nullptr); + if (svg == nullptr) return; + if (screen_position.has_value()) { + svg->create_volume(volume_type, *screen_position); + } else { + svg->create_volume(volume_type); + } + } }; - if ( type == ModelVolumeType::MODEL_PART - || type == ModelVolumeType::NEGATIVE_VOLUME - || type == ModelVolumeType::PARAMETER_MODIFIER - || type == ModelVolumeType::INVALID // cannot use gizmo without selected object - ) { - wxString item_name = is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": "; - item_name += _L("Text"); + if (type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER || + type == ModelVolumeType::INVALID // cannot use gizmo without selected object + ) { + wxString item_name = wxString(is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": ") + name; menu->AppendSeparator(); const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second; - append_menu_item(menu, wxID_ANY, item_name, "", add_text, icon_name, menu); + append_menu_item(menu, wxID_ANY, item_name, "", add_, icon_name, menu); } } -void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/) -{ - auto open_svg = [type](wxCommandEvent &evt) { - GLCanvas3D* canvas = plater()->canvas3D(); - GLGizmosManager& mng = canvas->get_gizmos_manager(); - GLGizmoBase* gizmo = mng.get_gizmo(GLGizmosManager::Svg); - GLGizmoSVG* svg = dynamic_cast(gizmo); - assert(svg != nullptr); - if (svg == nullptr) return; - - ModelVolumeType volume_type = type; - // no selected object means create new object - if (volume_type == ModelVolumeType::INVALID) - volume_type = ModelVolumeType::MODEL_PART; +void MenuFactory::append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item/* = true*/){ + append_menu_itemm_add_(_L("Text"), GLGizmosManager::Emboss, menu, type, is_submenu_item); +} - auto screen_position = canvas->get_popup_menu_position(); - if (screen_position.has_value()) { - svg->create_volume(volume_type, *screen_position); - } else { - svg->create_volume(volume_type); - } - }; - - if ( type == ModelVolumeType::MODEL_PART - || type == ModelVolumeType::NEGATIVE_VOLUME - || type == ModelVolumeType::PARAMETER_MODIFIER - || type == ModelVolumeType::INVALID // cannot use gizmo without selected object - ) { - wxString item_name = is_submenu_item ? "" : _(ADD_VOLUME_MENU_ITEMS[int(type)].first) + ": "; - item_name += _L("SVG"); - menu->AppendSeparator(); - const std::string icon_name = is_submenu_item ? "" : ADD_VOLUME_MENU_ITEMS[int(type)].second; - append_menu_item(menu, wxID_ANY, item_name, "", open_svg, icon_name, menu); - } +void MenuFactory::append_menu_item_add_svg(wxMenu *menu, ModelVolumeType type, bool is_submenu_item /* = true*/){ + append_menu_itemm_add_(_L("Svg"), GLGizmosManager::Svg, menu, type, is_submenu_item); } void MenuFactory::append_menu_items_add_volume(MenuType menu_type) From 61fa3e88441568906fa348da2dbbdae6cc306e3a Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 16 Mar 2023 15:23:29 +0100 Subject: [PATCH 011/120] Clean up EmbossJob --- src/slic3r/GUI/GLCanvas3D.cpp | 4 +- src/slic3r/GUI/GLCanvas3D.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 39 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 9 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 17 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 1157 +++++++++++------------ src/slic3r/GUI/Jobs/EmbossJob.hpp | 57 +- 7 files changed, 629 insertions(+), 656 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e8abdcaa0c..4aaf5613ad 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7235,10 +7235,10 @@ const ModelVolume *get_model_volume(const GLVolume &v, const Model &model) return ret; } -const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects) +ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects) { for (const ModelObject *obj : objects) - for (const ModelVolume *vol : obj->volumes) + for (ModelVolume *vol : obj->volumes) if (vol->id() == volume_id) return vol; return nullptr; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6ebe7bcc62..6d3b16eb04 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1069,7 +1069,7 @@ private: }; const ModelVolume *get_model_volume(const GLVolume &v, const Model &model); -const ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects); +ModelVolume *get_model_volume(const ObjectID &volume_id, const ModelObjectPtrs &objects); ModelVolume *get_model_volume(const GLVolume &v, const ModelObjectPtrs &objects); ModelVolume *get_model_volume(const GLVolume &v, const ModelObject &object); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 62be7ceaca..75fe6cbfc5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -15,9 +15,7 @@ #include "slic3r/Utils/WxFontUtils.hpp" #include "slic3r/Utils/UndoRedo.hpp" -// TODO: remove include -#include "libslic3r/SVG.hpp" // debug store -#include "libslic3r/Geometry.hpp" // covex hull 2d +#include "libslic3r/Geometry.hpp" // to range pi pi #include "libslic3r/Timer.hpp" #include "libslic3r/NSVGUtils.hpp" @@ -92,16 +90,12 @@ using namespace priv; GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -2) - , m_volume(nullptr) - , m_is_unknown_font(false) - , m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis) , m_style_manager(m_imgui->get_glyph_ranges(), create_default_styles) - , m_job_cancel(nullptr) + , m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis) { m_rotate_gizmo.set_group_id(0); m_rotate_gizmo.set_force_local_coordinate(true); - // TODO: add suggestion to use https://fontawesome.com/ - // (copy & paste) unicode symbols from web + // to use https://fontawesome.com/ (copy & paste) unicode symbols from web // paste HEX unicode into notepad move cursor after unicode press [alt] + [x] } @@ -160,14 +154,25 @@ const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType typ static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); } // namespace priv +CreateVolumeParams create_input(GLCanvas3D &canvas, StyleManager &styler, RaycastManager& raycaster, ModelVolumeType volume_type) +{ + auto gizmo = static_cast(GLGizmosManager::Emboss); + const GLVolume *gl_volume = get_first_hovered_gl_volume(canvas); + const FontProp &fp = styler.get_style().prop; + Plater *plater = wxGetApp().plater(); + return CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), + plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume, fp.distance, fp.angle}; +} + bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) { m_style_manager.discard_style_changes(); set_default_text(); + + // NOTE: change style manager - be carefull with order changes DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - Plater *plater_ptr = wxGetApp().plater(); - const FontProp &fp = m_style_manager.get_style().prop; - return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Emboss, mouse_pos, fp.distance, fp.angle); + CreateVolumeParams input = create_input(m_parent, m_style_manager, m_raycast_manager, volume_type); + return start_create_volume(input, std::move(base), mouse_pos); } // Designed for create volume without information of mouse in scene @@ -175,11 +180,11 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type) { m_style_manager.discard_style_changes(); set_default_text(); - std::unique_ptr emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - Plater *plater_ptr = wxGetApp().plater(); - const FontProp &fp = m_style_manager.get_style().prop; - return start_create_volume_without_position(plater_ptr, std::move(emboss_data), - volume_type, m_raycast_manager, GLGizmosManager::Emboss, fp.distance, fp.angle); + + // NOTE: change style manager - be carefull with order changes + DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); + CreateVolumeParams input = create_input(m_parent, m_style_manager, m_raycast_manager, volume_type); + return start_create_volume_without_position(input, std::move(base)); } bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 0f3a82a849..3422039fa2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -147,7 +147,7 @@ private: bool on_mouse_for_translate(const wxMouseEvent &mouse_event); // When open text loaded from .3mf it could be written with unknown font - bool m_is_unknown_font; + bool m_is_unknown_font = false; void create_notification_not_valid_font(const TextConfiguration& tc); void remove_notification_not_valid_font(); @@ -272,7 +272,8 @@ private: // filtration pattern std::string search = ""; std::vector hide; // result of filtration - } m_face_names; + }; + Facenames m_face_names; static bool store(const Facenames &facenames); static bool load(Facenames &facenames); @@ -287,7 +288,7 @@ private: // current selected volume // NOTE: Be carefull could be uninitialized (removed from Model) - ModelVolume *m_volume; + ModelVolume *m_volume = nullptr; // When work with undo redo stack there could be situation that // m_volume point to unexisting volume so One need also objectID @@ -297,7 +298,7 @@ private: bool m_text_contain_unknown_glyph = false; // cancel for previous update of volume to cancel finalize part - std::shared_ptr> m_job_cancel; + std::shared_ptr> m_job_cancel = nullptr; // Rotation gizmo GLGizmoRotate m_rotate_gizmo; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index c8eb662b55..8c56912b5f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -99,21 +99,30 @@ static std::string get_file_name(const std::string &file_path); /// Name for volume static std::string volume_name(const EmbossShape& shape); +static CreateVolumeParams create_input(GLCanvas3D &canvas, RaycastManager &raycaster, ModelVolumeType volume_type); } // namespace priv +CreateVolumeParams priv::create_input(GLCanvas3D &canvas, RaycastManager& raycaster, ModelVolumeType volume_type) +{ + auto gizmo = static_cast(GLGizmosManager::Svg); + const GLVolume *gl_volume = get_first_hovered_gl_volume(canvas); + Plater *plater = wxGetApp().plater(); + return CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), + plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume}; +} bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) { + CreateVolumeParams input = priv::create_input(m_parent, m_raycast_manager, volume_type); DataBasePtr base = priv::create_emboss_data_base(m_job_cancel); - Plater *plater_ptr = wxGetApp().plater(); - return start_create_volume(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg, mouse_pos); + return start_create_volume(input, std::move(base), mouse_pos); } bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) { + CreateVolumeParams input = priv::create_input(m_parent, m_raycast_manager, volume_type); DataBasePtr base = priv::create_emboss_data_base(m_job_cancel); - Plater *plater_ptr = wxGetApp().plater(); - return start_create_volume_without_position(plater_ptr, std::move(base), volume_type, m_raycast_manager, GLGizmosManager::Svg); + return start_create_volume_without_position(input, std::move(base)); } bool GLGizmoSVG::is_svg(const ModelVolume &volume) { diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index d855e84065..9753fec088 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -29,7 +29,7 @@ using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; // Private implementation for create volume and objects jobs -namespace Slic3r::GUI::Emboss { +namespace { /// /// Hold neccessary data to create ModelVolume in job /// Volume is created on the surface of existing volume in object. @@ -140,25 +140,22 @@ public: void process(Ctl &ctl) override; void finalize(bool canceled, std::exception_ptr &eptr) override; }; -} // namespace Slic3r::GUI::Emboss - -// private namespace -namespace priv{ -// create sure that emboss object is bigger than source object [in mm] -constexpr float safe_extension = 1.0f; /// /// Assert check of inputs data /// -/// -/// -static bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false); -static bool check(GLGizmosManager::EType gizmo); -static bool check(const DataCreateVolume &input, bool is_main_thread = false); -static bool check(const DataCreateObject &input); -static bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); -static bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); -static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); +bool check(const DataBase &input, bool check_fontfile = true, bool use_surface = false); +bool check(GLGizmosManager::EType gizmo); +bool check(const CreateVolumeParams& input); +bool check(const DataCreateVolume &input, bool is_main_thread = false); +bool check(const DataCreateObject &input); +bool check(const DataUpdate &input, bool is_main_thread = false, bool use_surface = false); +bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false); +bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false); + + +// create sure that emboss object is bigger than source object [in mm] +constexpr float safe_extension = 1.0f; // /// Try to create mesh from text @@ -169,14 +166,14 @@ static bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = fa /// NOTE: Cache glyphs is changed /// To check if process was canceled /// Triangle mesh model -template static TriangleMesh try_create_mesh(DataBase &input, const Fnc& was_canceled); -template static TriangleMesh create_mesh(DataBase &input, const Fnc& was_canceled, Job::Ctl &ctl); +template TriangleMesh try_create_mesh(DataBase &input, const Fnc& was_canceled); +template TriangleMesh create_mesh(DataBase &input, const Fnc& was_canceled, Job::Ctl &ctl); /// /// Create default mesh for embossed text /// /// Not empty model(index trinagle set - its) -static TriangleMesh create_default_mesh(); +TriangleMesh create_default_mesh(); /// /// Must be called on main thread @@ -184,14 +181,14 @@ static TriangleMesh create_default_mesh(); /// New mesh data /// Text configuration, ... /// Transformation of volume -static void update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d *tr = nullptr); +void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const 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); +void update_name_in_list(const ObjectList &object_list, const ModelVolume &volume); /// /// Add new volume to object @@ -202,17 +199,9 @@ static void update_name_in_list(const ObjectList &object_list, const ModelVolume /// Transformation of volume inside of object /// Text configuration and New VolumeName /// Gizmo to open -static void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, const ModelVolumeType type, +void create_volume(TriangleMesh &&mesh, const ObjectID& object_id, const ModelVolumeType type, const std::optional& trmat, const DataBase &data, GLGizmosManager::EType gizmo); -/// -/// Select Volume from objects -/// -/// All objects in scene -/// Identifier of volume in object -/// Pointer to volume when exist otherwise nullptr -static ModelVolume *get_volume(ModelObjectPtrs &objects, const ObjectID &volume_id); - /// /// Create projection for cut surface from mesh /// @@ -220,7 +209,7 @@ static ModelVolume *get_volume(ModelObjectPtrs &objects, const ObjectID &volume_ /// Convert shape to milimeters /// Bounding box 3d of model volume for projection ranges /// Orthogonal cut_projection -static OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const std::pair &z_range); +OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const std::pair &z_range); /// /// Create tranformation for emboss Cutted surface @@ -230,7 +219,7 @@ static OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale /// Text voliume transformation inside object /// Cutted surface from model /// Projection -static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut); +OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut); /// /// Cut surface into triangle mesh @@ -239,7 +228,8 @@ static OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Tr /// SurfaceVolume data /// Check to interupt execution /// Extruded object from cuted surace -static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, const std::function& was_canceled); +template +TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeData &input2, const Fnc& was_canceled); /// /// Copied triangles from object to be able create mesh for cut surface from @@ -247,23 +237,23 @@ static TriangleMesh cut_surface(/*const*/ DataBase &input1, const SurfaceVolumeD /// Source object volumes for cut surface from /// Source volume id /// Source data for cut surface from -static SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); +SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id = {}); -static void create_message(const std::string &message); // only in finalize -static bool process(std::exception_ptr &eptr); -static bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input); +void create_message(const std::string &message); // only in finalize +bool process(std::exception_ptr &eptr); +bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input); class JobException : public std::runtime_error { -public: JobException(const char* message):runtime_error(message){}}; -static auto was_canceled(Job::Ctl &ctl, DataBase &base){ - return [&ctl, &cancel = base.cancel]() -> bool { +public: using std::runtime_error::runtime_error;}; +auto was_canceled(const Job::Ctl &ctl, const DataBase &base){ + return [&ctl, &cancel = base.cancel]() { if (cancel->load()) return true; return ctl.was_canceled(); }; } -}// namespace priv +} // namespace void Slic3r::GUI::Emboss::DataBase::write(ModelVolume &volume) const{ volume.name = volume_name; @@ -272,34 +262,34 @@ void Slic3r::GUI::Emboss::DataBase::write(ModelVolume &volume) const{ ///////////////// /// 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(check(m_input, true)); } void CreateVolumeJob::process(Ctl &ctl) { - if (!priv::check(m_input)) - throw std::runtime_error("Bad input data for EmbossCreateVolumeJob."); + if (!check(m_input)) + throw JobException("Bad input data for EmbossCreateVolumeJob."); - m_result = priv::create_mesh(*m_input.base, priv::was_canceled(ctl, *m_input.base), ctl); + m_result = create_mesh(*m_input.base, 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.base)) + if (!::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.base, m_input.gizmo); + return create_message(_u8L("Can't create empty volume.")); + create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.trmat, *m_input.base, m_input.gizmo); } ///////////////// /// 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(check(m_input)); } void CreateObjectJob::process(Ctl &ctl) { - if (!priv::check(m_input)) - throw std::runtime_error("Bad input data for EmbossCreateObjectJob."); + if (!check(m_input)) + throw JobException("Bad input data for EmbossCreateObjectJob."); if (m_input.base->shape.distance.has_value()) m_input.base->shape.distance.reset(); @@ -308,8 +298,8 @@ void CreateObjectJob::process(Ctl &ctl) if (m_input.base->shape.use_surface) m_input.base->shape.use_surface = false; - auto was_canceled = priv::was_canceled(ctl, *m_input.base); - m_result = priv::create_mesh(*m_input.base, was_canceled, ctl); + auto was_canceled = ::was_canceled(ctl, *m_input.base); + m_result = create_mesh(*m_input.base, was_canceled, ctl); if (was_canceled()) return; // Create new object @@ -339,12 +329,12 @@ void CreateObjectJob::process(Ctl &ctl) void CreateObjectJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, *m_input.base)) + if (!::finalize(canceled, eptr, *m_input.base)) return; // only for sure if (m_result.empty()) - return priv::create_message(_u8L("Can't create empty object.")); + return create_message(_u8L("Can't create empty object.")); GUI_App &app = wxGetApp(); Plater *plater = app.plater(); @@ -392,18 +382,18 @@ 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(check(m_input, true)); } void UpdateJob::process(Ctl &ctl) { - if (!priv::check(m_input)) - throw std::runtime_error("Bad input data for EmbossUpdateJob."); + if (!check(m_input)) + throw JobException("Bad input data for EmbossUpdateJob."); - auto was_canceled = priv::was_canceled(ctl, *m_input.base); - m_result = priv::try_create_mesh(*m_input.base, was_canceled); + auto was_canceled = ::was_canceled(ctl, *m_input.base); + m_result = ::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()); + throw JobException(_u8L("Created text volume is empty. Change text or font.").c_str()); // center triangle mesh Vec3d shift = m_result.bounding_box().center(); @@ -412,403 +402,18 @@ void UpdateJob::process(Ctl &ctl) void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) { - if (!priv::finalize(canceled, eptr, *m_input.base)) + if (!::finalize(canceled, eptr, *m_input.base)) return; - priv::update_volume(std::move(m_result), m_input); -} - -///////////////// -/// Create Surface volume -CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input) - : m_input(std::move(input)) -{ - assert(priv::check(m_input, true)); -} - -void CreateSurfaceVolumeJob::process(Ctl &ctl) { - if (!priv::check(m_input)) - throw std::runtime_error("Bad input data for CreateSurfaceVolumeJob."); - 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.base)) - return; - priv::create_volume(std::move(m_result), m_input.object_id, - m_input.volume_type, m_input.transform, *m_input.base, m_input.gizmo); -} - -///////////////// -/// Cut Surface -UpdateSurfaceVolumeJob::UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input) - : m_input(std::move(input)) -{ - assert(priv::check(m_input, true)); -} - -void UpdateSurfaceVolumeJob::process(Ctl &ctl) -{ - if (!priv::check(m_input)) - throw std::runtime_error("Bad input data for UseSurfaceJob."); - 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.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.transform; - priv::update_volume(std::move(m_result), m_input, tr); -} - -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); - -/// -/// Start job for add new volume to object with given transformation -/// -/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() -/// Define where to add -/// Wanted volume transformation, when not set will be calculated after creation to be near the object -/// Define what to emboss - shape -/// Type of volume: Part, negative, modifier -/// Define which gizmo open on the success -/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way -static bool start_create_volume_job(Worker &worker, const ModelObject &object, const std::optional& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo); - -/// -/// Find volume in selected objects with closest convex hull to screen center. -/// -/// Define where to search for closest -/// Canvas center(dependent on camera settings) -/// Actual objects -/// OUT: coordinate of controid of closest volume -/// closest volume when exists otherwise nullptr -static const GLVolume *find_closest( - const Selection &selection, const Vec2d &screen_center, - const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); - -/// -/// Start job for add object with text into scene -/// -/// Contain worker and build shape -/// Define params of text -/// Screen coordinat, where to create new object laying on bed -/// Define which gizmo open on the success -/// True when can add job to worker otherwise FALSE -static bool start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo); - -/// -/// Start job to create volume on the surface of object -/// -/// scene RayCasters + Objects + Camera + worker -/// Describe what to emboss -/// Type of new volume -/// Where to add -/// Surface point is belonge to -/// For project on surface -/// Define which gizmo open on the success -/// Distance from surface -/// Angle around emboss direction -/// True on add job to worker otherwise false -/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way -static DataBasePtr start_create_volume_on_surface_job(Plater &plater, - DataBasePtr data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume &gl_volume, - RaycastManager &raycaster, - GLGizmosManager::EType gizmo, - const std::optional &distance, - const std::optional &angle, - bool &success); - -} - -namespace Slic3r::GUI::Emboss { - -SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_volume) -{ - const ModelVolumePtrs &volumes = text_volume.get_object()->volumes; - // no other volume in object - if (volumes.size() <= 1) - return {}; - return priv::create_sources(volumes, text_volume.id().id); -} - -bool start_create_volume(Plater *plater_ptr, - DataBasePtr data, - ModelVolumeType volume_type, - RaycastManager &raycaster, - unsigned char gizmo, - const Vec2d &mouse_pos, - const std::optional &distance, - const std::optional &angle) -{ - if (data == nullptr) - return false; - if (!priv::is_valid(volume_type)) - return false; - - assert(plater_ptr); - if (plater_ptr == nullptr) - return false; - Plater &plater = *plater_ptr; - - const GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); - assert(canvas_ptr); - if (canvas_ptr == nullptr) - return false; - - auto gizmo_type = static_cast(gizmo); - const GLVolume *gl_volume = get_first_hovered_gl_volume(*canvas_ptr); - if (gl_volume == nullptr) - // object is not under mouse position soo create object on plater - return priv::start_create_object_job(plater, std::move(data), mouse_pos, gizmo_type); - - bool success = true; - DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data), - volume_type, mouse_pos, *gl_volume, raycaster, gizmo_type, distance, angle, success); - - // Is successfull created job for add volume on surface? - if (success) - return true; - - // not success and consume data - if (data2 == nullptr) - return false; - - // Can't create on coordinate try to create somewhere - return start_create_volume_without_position(plater_ptr, std::move(data2), volume_type, raycaster, gizmo, distance, angle); -} - -bool start_create_volume_without_position(Plater *plater_ptr, - DataBasePtr data, - ModelVolumeType volume_type, - RaycastManager &raycaster, - unsigned char gizmo, - const std::optional &distance, - const std::optional &angle) -{ - if (!priv::is_valid(volume_type)) - return false; - - assert(plater_ptr); - if (plater_ptr == nullptr) - return false; - Plater &plater = *plater_ptr; - - GLCanvas3D *canvas_ptr = plater.get_current_canvas3D(); - assert(canvas_ptr); - if (canvas_ptr == nullptr) - return false; - const GLCanvas3D &canvas = *canvas_ptr; - - // select position by camera position and view direction - const Selection &selection = canvas.get_selection(); - int object_idx = selection.get_object_idx(); - - Size s = canvas.get_canvas_size(); - Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); - const ModelObjectPtrs &objects = selection.get_model()->objects; - - GLGizmosManager::EType gizmo_type = static_cast(gizmo); - - // 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 - return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type); - - // create volume inside of selected object - Vec2d coor; - const Camera &camera = wxGetApp().plater()->get_camera(); - const GLVolume *gl_volume = priv::find_closest(selection, screen_center, camera, objects, &coor); - - if (gl_volume == nullptr) - return priv::start_create_object_job(plater, std::move(data), screen_center, gizmo_type); - - bool success = true; - DataBasePtr data2 = priv::start_create_volume_on_surface_job(plater, std::move(data), - volume_type, coor, *gl_volume, raycaster, gizmo_type, distance, angle, success); - - // Is successfull created job for add volume on surface? - if (success) - return true; - - // not success and consume data - if (data2 == nullptr) - return false; - - // In centroid of convex hull is not hit with object. e.g. torid - // soo create transfomation on border of object - - // there is no point on surface so no use of surface will be applied - if (data2->shape.use_surface) - data2->shape.use_surface = false; - - Worker &worker = plater.get_ui_job_worker(); - const ModelObject *object = get_model_object(*gl_volume, objects); - if (object == nullptr) - return false; - - return priv::start_create_volume_job(worker, *object, {}, std::move(data2), volume_type, gizmo_type); -} - -} // namespace Slic3r::GUI::Emboss - -//////////////////////////// -/// private namespace implementation -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(); - assert(!input.volume_name.empty()); - res &= !input.volume_name.empty(); - //assert(input.text_configuration.style.prop.use_surface == use_surface); - //res &= input.text_configuration.style.prop.use_surface == use_surface; - return res; -} - -bool priv::check(GLGizmosManager::EType gizmo) -{ - assert(gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg); - return gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg; -} - -bool priv::check(const DataCreateVolume &input, bool is_main_thread) { - bool check_fontfile = false; - 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); - res &= input.object_id.id >= 0; - res &= check(input.gizmo); - return res; -} -bool priv::check(const DataCreateObject &input) { - bool check_fontfile = false; - 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.); - res &= input.screen_coor.y() >= 0.; - assert(input.bed_shape.size() >= 3); // at least triangle - res &= input.bed_shape.size() >= 3; - res &= check(input.gizmo); - return res; -} -bool priv::check(const DataUpdate &input, bool is_main_thread, bool use_surface){ - bool check_fontfile = true; - 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.base->cancel != nullptr); - res &= input.base->cancel != nullptr; - if (is_main_thread) - assert(!input.base->cancel->load()); - return res; -} -bool priv::check(const CreateSurfaceVolumeData &input, bool is_main_thread) -{ - bool use_surface = true; - 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(); - res &= check(input.gizmo); - return res; -} -bool priv::check(const UpdateSurfaceVolumeData &input, bool is_main_thread){ - bool use_surface = true; - 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 -TriangleMesh priv::try_create_mesh(DataBase &base, const Fnc& was_canceled) -{ - const EmbossShape& shape = base.create_shape(); - if (shape.shapes.empty()) return {}; - double depth = shape.depth / shape.scale; - auto projectZ = std::make_unique(depth); - ProjectScale project(std::move(projectZ), shape.scale); - if (was_canceled()) return {}; - return TriangleMesh(polygons2model(shape.shapes, project)); -} - -template -TriangleMesh priv::create_mesh(DataBase &input, const 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 = try_create_mesh(input, 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 to fix it.")); - }); - } - - assert(!result.its.empty()); - return result; -} - -TriangleMesh priv::create_default_mesh() -{ - // When cant load any font use default object loaded from file - std::string path = Slic3r::resources_dir() + "/data/embossed_text.obj"; - TriangleMesh triangle_mesh; - if (!load_obj(path.c_str(), &triangle_mesh)) { - // when can't load mesh use cube - return TriangleMesh(its_make_cube(36., 4., 2.5)); - } - return triangle_mesh; + ::update_volume(std::move(m_result), m_input); } void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base) { // check inputs - bool is_valid_input = - volume != nullptr && - !mesh.empty() && - !base.volume_name.empty(); + bool is_valid_input = volume != nullptr && !mesh.empty() && !base.volume_name.empty(); assert(is_valid_input); - if (!is_valid_input) return; + if (!is_valid_input) + return; // update volume volume->set_mesh(std::move(mesh)); @@ -818,18 +423,18 @@ void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const Da // write data from base into volume base.write(*volume); - + 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(); + + const ObjectList *obj_list = app.obj_list(); if (obj_list != nullptr) - priv::update_name_in_list(*obj_list, *volume); + update_name_in_list(*obj_list, *volume); } // When text is object. - // When text positive volume is lowest part of object than modification of text + // When text positive volume is lowest part of object than modification of text // have to move object on bed. if (volume->type() == ModelVolumeType::MODEL_PART) volume->get_object()->ensure_on_bed(); @@ -844,32 +449,342 @@ void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const Da canvas->post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -void priv::update_name_in_list(const ObjectList &object_list, const ModelVolume &volume) +///////////////// +/// Create Surface volume +CreateSurfaceVolumeJob::CreateSurfaceVolumeJob(CreateSurfaceVolumeData &&input) + : m_input(std::move(input)) +{ + assert(check(m_input, true)); +} + +void CreateSurfaceVolumeJob::process(Ctl &ctl) { + if (!check(m_input)) + throw JobException("Bad input data for CreateSurfaceVolumeJob."); + m_result = cut_surface(*m_input.base, m_input, was_canceled(ctl, *m_input.base)); +} + +void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { + if (!::finalize(canceled, eptr, *m_input.base)) + return; + create_volume(std::move(m_result), m_input.object_id, + m_input.volume_type, m_input.transform, *m_input.base, m_input.gizmo); +} + +///////////////// +/// Cut Surface +UpdateSurfaceVolumeJob::UpdateSurfaceVolumeJob(UpdateSurfaceVolumeData &&input) + : m_input(std::move(input)) +{ + assert(check(m_input, true)); +} + +void UpdateSurfaceVolumeJob::process(Ctl &ctl) +{ + if (!check(m_input)) + throw JobException("Bad input data for UseSurfaceJob."); + m_result = cut_surface(*m_input.base, m_input, was_canceled(ctl, *m_input.base)); +} + +void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) +{ + if (!::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 + ::update_volume(std::move(m_result), m_input, &m_input.transform); +} + +namespace { +/// +/// Check if volume type is possible use for new text volume +/// +/// Type +/// True when allowed otherwise false +bool is_valid(ModelVolumeType volume_type); + +/// +/// Start job for add new volume to object with given transformation +/// +/// Define where to queue the job. e.g. wxGetApp().plater()->get_ui_job_worker() +/// Define where to add +/// Wanted volume transformation, when not set will be calculated after creation to be near the object +/// Define what to emboss - shape +/// Type of volume: Part, negative, modifier +/// Define which gizmo open on the success +/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way +bool start_create_volume_job(Worker &worker, + const ModelObject &object, + const std::optional &volume_tr, + DataBasePtr data, + ModelVolumeType volume_type, + GLGizmosManager::EType gizmo); + +/// +/// Find volume in selected objects with closest convex hull to screen center. +/// +/// Define where to search for closest +/// Canvas center(dependent on camera settings) +/// Actual objects +/// OUT: coordinate of controid of closest volume +/// closest volume when exists otherwise nullptr +const GLVolume *find_closest( + const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center); + +/// +/// Start job for add object with text into scene +/// +/// Contain worker, build shape, gizmo +/// Define params for create volume +/// Screen coordinat, where to create new object laying on bed +/// True when can add job to worker otherwise FALSE +bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor); + +/// +/// Start job to create volume on the surface of object +/// +/// Variabless needed to create volume +/// Describe what to emboss - shape +/// Where to add +/// True .. try to create volume without screen_coor, +/// False .. +/// Nullptr when job is sucessfully add to worker otherwise return data to be processed different way +bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &screen_coor, bool try_no_coor); + +} // namespace + +namespace Slic3r::GUI::Emboss { + +SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_volume) +{ + const ModelVolumePtrs &volumes = text_volume.get_object()->volumes; + // no other volume in object + if (volumes.size() <= 1) + return {}; + return ::create_sources(volumes, text_volume.id().id); +} + +bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos) +{ + if (data == nullptr) + return false; + if (!check(input)) + return false; + + if (input.gl_volume == nullptr) + // object is not under mouse position soo create object on plater + return ::start_create_object_job(input, std::move(data), mouse_pos); + + bool try_no_coor = true; + return ::start_create_volume_on_surface_job(input, std::move(data), mouse_pos, try_no_coor); +} + +bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data) +{ + assert(data != nullptr); + if (data == nullptr) + return false; + if (!check(input)) + return false; + + // select position by camera position and view direction + const Selection &selection = input.canvas.get_selection(); + int object_idx = selection.get_object_idx(); + + Size s = input.canvas.get_canvas_size(); + Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); + 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 + return ::start_create_object_job(input, std::move(data), screen_center); + + // create volume inside of selected object + Vec2d coor; + const Camera &camera = wxGetApp().plater()->get_camera(); + input.gl_volume = ::find_closest(selection, screen_center, camera, objects, &coor); + if (input.gl_volume == nullptr) + return ::start_create_object_job(input, std::move(data), screen_center); + + bool try_no_coor = false; + return ::start_create_volume_on_surface_job(input, std::move(data), coor, try_no_coor); +} +} // namespace Slic3r::GUI::Emboss + +//////////////////////////// +/// private namespace implementation +namespace { +bool 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(); + assert(!input.volume_name.empty()); + res &= !input.volume_name.empty(); + // assert(input.text_configuration.style.prop.use_surface == use_surface); + // res &= input.text_configuration.style.prop.use_surface == use_surface; + return res; +} + +bool check(GLGizmosManager::EType gizmo) +{ + assert(gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg); + return gizmo == GLGizmosManager::Emboss || gizmo == GLGizmosManager::Svg; +} + +bool check(const CreateVolumeParams &input) +{ + bool res = is_valid(input.volume_type); + auto gizmo_type = static_cast(input.gizmo); + res &= ::check(gizmo_type); + return res; +} + +bool check(const DataCreateVolume &input, bool is_main_thread) +{ + bool check_fontfile = false; + 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; + res &= check(input.gizmo); + return res; +} +bool check(const DataCreateObject &input) +{ + bool check_fontfile = false; + 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.); + res &= input.screen_coor.y() >= 0.; + assert(input.bed_shape.size() >= 3); // at least triangle + res &= input.bed_shape.size() >= 3; + res &= check(input.gizmo); + return res; +} +bool check(const DataUpdate &input, bool is_main_thread, bool use_surface) +{ + bool check_fontfile = true; + assert(input.base != nullptr); + bool res = input.base != nullptr; + res &= check(*input.base, check_fontfile, use_surface); + if (is_main_thread) + assert(get_model_volume(input.volume_id, wxGetApp().model().objects) != nullptr); + assert(input.base->cancel != nullptr); + res &= input.base->cancel != nullptr; + if (is_main_thread) + assert(!input.base->cancel->load()); + return res; +} +bool check(const CreateSurfaceVolumeData &input, bool is_main_thread) +{ + bool use_surface = true; + 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(); + res &= check(input.gizmo); + return res; +} +bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread) +{ + bool use_surface = true; + 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 TriangleMesh try_create_mesh(DataBase &base, const Fnc &was_canceled) +{ + const EmbossShape &shape = base.create_shape(); + if (shape.shapes.empty()) + return {}; + double depth = shape.depth / shape.scale; + auto projectZ = std::make_unique(depth); + ProjectScale project(std::move(projectZ), shape.scale); + if (was_canceled()) + return {}; + return TriangleMesh(polygons2model(shape.shapes, project)); +} + +template TriangleMesh create_mesh(DataBase &input, const 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 = try_create_mesh(input, was_canceled); + if (was_canceled()) + return {}; + + if (result.its.empty()) { + result = 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 to fix it.")); + }); + } + + assert(!result.its.empty()); + return result; +} + +TriangleMesh create_default_mesh() +{ + // When cant load any font use default object loaded from file + std::string path = Slic3r::resources_dir() + "/data/embossed_text.obj"; + TriangleMesh triangle_mesh; + if (!load_obj(path.c_str(), &triangle_mesh)) { + // when can't load mesh use cube + return TriangleMesh(its_make_cube(36., 4., 2.5)); + } + return triangle_mesh; +} + +void 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(); + 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) + for (size_t i = 0; i < objects.size(); ++i) if (objects[i]->id() == object_id) { - object_index = i; + object_index = static_cast(i); break; } - const ModelVolumePtrs volumes = object->volumes; - const ObjectID& volume_id = volume.id(); + 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) + for (size_t i = 0; i < volumes.size(); ++i) if (volumes[i]->id() == volume_id) { - volume_index = i; + volume_index = static_cast(i); break; } @@ -879,20 +794,21 @@ void priv::update_name_in_list(const ObjectList &object_list, const ModelVolume object_list.update_name_in_list(object_index, volume_index); } -void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d* tr) +void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr) { // for sure that some object will be created - if (mesh.its.empty()) + if (mesh.its.empty()) return create_message("Empty mesh can't be created."); 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 || + assert(plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss || plater->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Svg); - std::string snap_name = GUI::format(_L("Change: %1%"), data.base->volume_name); + 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); + + ModelVolume *volume = get_model_volume(data.volume_id, plater->model().objects); // could appear when user delete edited volume if (volume == nullptr) @@ -911,12 +827,12 @@ void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3 UpdateJob::update_volume(volume, std::move(mesh), *data.base); } -void priv::create_volume(TriangleMesh &&mesh, - const ObjectID &object_id, - const ModelVolumeType type, - const std::optional &trmat, - const DataBase &data, - GLGizmosManager::EType gizmo) +void create_volume(TriangleMesh &&mesh, + const ObjectID &object_id, + const ModelVolumeType type, + const std::optional &trmat, + const DataBase &data, + GLGizmosManager::EType gizmo) { GUI_App &app = wxGetApp(); Plater *plater = app.plater(); @@ -924,23 +840,23 @@ void priv::create_volume(TriangleMesh &&mesh, GLCanvas3D *canvas = plater->canvas3D(); ModelObjectPtrs &objects = plater->model().objects; - ModelObject *obj = nullptr; - size_t object_idx = 0; + ModelObject *obj = nullptr; + size_t object_idx = 0; for (; object_idx < objects.size(); ++object_idx) { ModelObject *o = objects[object_idx]; - if (o->id() == object_id) { + if (o->id() == object_id) { obj = o; break; - } + } } // Parent object for text volume was propably removed. // Assumption: User know what he does, so text volume is no more needed. - if (obj == nullptr) - return priv::create_message(_u8L("Bad object to create volume.")); + if (obj == nullptr) + return create_message(_u8L("Bad object to create volume.")); - if (mesh.its.empty()) - return priv::create_message(_u8L("Can't create empty volume.")); + if (mesh.its.empty()) + return create_message(_u8L("Can't create empty volume.")); plater->take_snapshot(_L("Add Emboss text Volume")); @@ -948,7 +864,7 @@ void priv::create_volume(TriangleMesh &&mesh, if (!trmat.has_value()) { // used for align to instance size_t instance_index = 0; // must exist - instance_bb = obj->instance_bounding_box(instance_index); + instance_bb = obj->instance_bounding_box(instance_index); } // NOTE: be carefull add volume also center mesh !!! @@ -969,7 +885,7 @@ void priv::create_volume(TriangleMesh &&mesh, volume->name = data.volume_name; // copy if (trmat.has_value()) { - volume->set_transformation(*trmat); + volume->set_transformation(*trmat); } else { assert(!data.shape.use_surface); // Create transformation for volume near from object(defined by glVolume) @@ -978,9 +894,9 @@ void priv::create_volume(TriangleMesh &&mesh, // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created -instance_bb.size().y() / 2 - volume_size.y() / 2, // under - volume_size.z() / 2 - instance_bb.size().z() / 2); // lay on bed + volume_size.z() / 2 - instance_bb.size().z() / 2); // lay on bed // use same instance as for calculation of instance_bounding_box - Transform3d tr = obj->instances.front()->get_transformation().get_matrix_no_offset().inverse(); + Transform3d tr = obj->instances.front()->get_transformation().get_matrix_no_offset().inverse(); Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); volume->set_transformation(volume_trmat); } @@ -998,40 +914,29 @@ void priv::create_volume(TriangleMesh &&mesh, // change name of volume in right panel // select only actual volume // when new volume is created change selection to this volume - auto add_to_selection = [volume](const ModelVolume *vol) { return vol == volume; }; - wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection); - if (!sel.IsEmpty()) obj_list->select_item(sel.front()); + 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()); obj_list->selection_changed(); // Now is valid text volume selected open emboss gizmo GLGizmosManager &manager = canvas->get_gizmos_manager(); - if (manager.get_current_type() != GLGizmosManager::Emboss) - manager.open_gizmo(GLGizmosManager::Emboss); + if (manager.get_current_type() != gizmo) + manager.open_gizmo(gizmo); // redraw scene canvas->reload_scene(true); } -ModelVolume *priv::get_volume(ModelObjectPtrs &objects, - const ObjectID &volume_id) +OrthoProject create_projection_for_cut(Transform3d tr, double shape_scale, const std::pair &z_range) { - for (ModelObject *obj : objects) - for (ModelVolume *vol : obj->volumes) - if (vol->id() == volume_id) return vol; - return nullptr; -}; - -OrthoProject priv::create_projection_for_cut( - Transform3d tr, - double shape_scale, - const std::pair &z_range) -{ - double min_z = z_range.first - priv::safe_extension; - double max_z = z_range.second + priv::safe_extension; + double min_z = z_range.first - safe_extension; + double max_z = z_range.second + safe_extension; assert(min_z < max_z); // range between min and max value - double projection_size = max_z - min_z; + double projection_size = max_z - min_z; Matrix3d transformation_for_vector = tr.linear(); // Projection must be negative value. // System of text coordinate @@ -1047,33 +952,33 @@ OrthoProject priv::create_projection_for_cut( return OrthoProject(tr, project_direction); } -OrthoProject3d priv::create_emboss_projection( - bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut) +OrthoProject3d create_emboss_projection(bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut) { // Offset of clossed side to model const float surface_offset = 0.015f; // [in mm] - float - front_move = (is_outside) ? emboss : surface_offset, - back_move = -((is_outside) ? surface_offset : emboss); - its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move))); + float front_move = is_outside ? emboss : surface_offset, + back_move = -(is_outside ? surface_offset : emboss); + its_transform(cut, tr.pretranslate(Vec3d(0., 0., front_move))); Vec3d from_front_to_back(0., 0., back_move - front_move); return OrthoProject3d(from_front_to_back); } // input can't be const - cache of font -TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, const std::function& was_canceled) +template TriangleMesh cut_surface(DataBase &base, const SurfaceVolumeData &input2, const Fnc &was_canceled) { - EmbossShape& emboss_shape = base.create_shape(); - ExPolygons& shapes = emboss_shape.shapes; + 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 {}; + 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); + for (ExPolygon &shape : shapes) + shape.translate(-projection_center); bb.translate(-projection_center); const SurfaceVolumeData::ModelSources &sources = input2.sources; @@ -1081,42 +986,44 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, size_t biggest_count = 0; // convert index from (s)ources to (i)ndexed (t)riangle (s)ets - std::vector s_to_itss(sources.size(), std::numeric_limits::max()); - std::vector itss; + std::vector s_to_itss(sources.size(), std::numeric_limits::max()); + std::vector itss; itss.reserve(sources.size()); for (const SurfaceVolumeData::ModelSource &s : sources) { - Transform3d mesh_tr_inv = s.tr.inverse(); - Transform3d cut_projection_tr = mesh_tr_inv * input2.transform; + Transform3d mesh_tr_inv = s.tr.inverse(); + Transform3d cut_projection_tr = mesh_tr_inv * input2.transform; std::pair z_range{0., 1.}; - OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, emboss_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; + if (its.indices.empty()) + continue; if (biggest_count < its.vertices.size()) { biggest_count = its.vertices.size(); biggest = &s; } - size_t source_index = &s - &sources.front(); - size_t its_index = itss.size(); + size_t source_index = &s - &sources.front(); + size_t its_index = itss.size(); s_to_itss[source_index] = its_index; itss.emplace_back(std::move(its)); } if (itss.empty()) throw JobException(_u8L("There is no volume in projection direction.").c_str()); - Transform3d tr_inv = biggest->tr.inverse(); + Transform3d tr_inv = biggest->tr.inverse(); Transform3d cut_projection_tr = tr_inv * input2.transform; - size_t itss_index = s_to_itss[biggest - &sources.front()]; + size_t itss_index = s_to_itss[biggest - &sources.front()]; BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]); for (const SurfaceVolumeData::ModelSource &s : sources) { - size_t itss_index = s_to_itss[&s - &sources.front()]; - if (itss_index == std::numeric_limits::max()) continue; - if (&s == biggest) + itss_index = s_to_itss[&s - &sources.front()]; + if (itss_index == std::numeric_limits::max()) + continue; + if (&s == biggest) continue; - Transform3d tr = s.tr * tr_inv; - bool fix_reflected = true; + Transform3d tr = s.tr * tr_inv; + bool fix_reflected = true; indexed_triangle_set &its = itss[itss_index]; its_transform(its, tr, fix_reflected); BoundingBoxf3 its_bb = bounding_box(its); @@ -1124,11 +1031,11 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, } // tr_inv = transformation of mesh inverted - Transform3d emboss_tr = cut_projection_tr.inverse(); - BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); + 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, emboss_shape.scale, z_range); - float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension); + OrthoProject cut_projection = 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.transform); if (is_text_reflected) { @@ -1151,18 +1058,21 @@ TriangleMesh priv::cut_surface(DataBase& base, const SurfaceVolumeData& input2, std::swap(t[0], t[1]); } - if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str()); - if (was_canceled()) return {}; + if (cut.empty()) + 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, emboss_shape.depth, emboss_tr, cut); - indexed_triangle_set new_its = cut2model(cut, projection); + // !! Projection needs to transform cut + OrthoProject3d projection = create_emboss_projection(input2.is_outside, static_cast(emboss_shape.depth), emboss_tr, cut); + indexed_triangle_set new_its = cut2model(cut, projection); assert(!new_its.empty()); - if (was_canceled()) return {}; + if (was_canceled()) + return {}; return TriangleMesh(std::move(new_its)); } -SurfaceVolumeData::ModelSources priv::create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) +SurfaceVolumeData::ModelSources create_sources(const ModelVolumePtrs &volumes, std::optional text_volume_id) { SurfaceVolumeData::ModelSources result; result.reserve(volumes.size() - 1); @@ -1182,18 +1092,20 @@ SurfaceVolumeData::ModelSources priv::create_sources(const ModelVolumePtrs &volu return result; } -bool priv::process(std::exception_ptr &eptr) { - if (!eptr) return false; +bool process(std::exception_ptr &eptr) +{ + if (!eptr) + return false; try { std::rethrow_exception(eptr); - } catch (priv::JobException &e) { + } catch (JobException &e) { create_message(e.what()); eptr = nullptr; } return true; } -bool priv::finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input) +bool finalize(bool canceled, std::exception_ptr &eptr, const DataBase &input) { // doesn't care about exception when process was canceled by user if (canceled || input.cancel->load()) { @@ -1203,21 +1115,24 @@ bool priv::finalize(bool canceled, std::exception_ptr &eptr, const DataBase &inp return !process(eptr); } -bool priv::is_valid(ModelVolumeType volume_type) +bool is_valid(ModelVolumeType volume_type) { - if (volume_type == ModelVolumeType::MODEL_PART || - volume_type == ModelVolumeType::NEGATIVE_VOLUME || - volume_type == ModelVolumeType::PARAMETER_MODIFIER ) + 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; return false; } -bool priv::start_create_volume_job( - Worker &worker, const ModelObject &object, const std::optional& volume_tr, DataBasePtr data, ModelVolumeType volume_type, GLGizmosManager::EType gizmo) +bool start_create_volume_job(Worker &worker, + const ModelObject &object, + const std::optional &volume_tr, + DataBasePtr data, + ModelVolumeType volume_type, + GLGizmosManager::EType gizmo) { - bool &use_surface = data->shape.use_surface; + bool &use_surface = data->shape.use_surface; std::unique_ptr job; if (use_surface) { // Model to cut surface from. @@ -1241,7 +1156,7 @@ bool priv::start_create_volume_job( return queue_job(worker, std::move(job)); } -const GLVolume * priv::find_closest( +const GLVolume *find_closest( const Selection &selection, const Vec2d &screen_center, const Camera &camera, const ModelObjectPtrs &objects, Vec2d *closest_center) { assert(closest_center != nullptr); @@ -1253,15 +1168,16 @@ const GLVolume * priv::find_closest( double center_sq_distance = std::numeric_limits::max(); for (unsigned int id : indices) { - const GLVolume *gl_volume = selection.get_volume(id); - const ModelVolume *volume = get_model_volume(*gl_volume, objects); - if (volume == nullptr || !volume->is_model_part()) + const GLVolume *gl_volume = selection.get_volume(id); + if (const ModelVolume *volume = get_model_volume(*gl_volume, objects); + volume == nullptr || !volume->is_model_part()) continue; Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *gl_volume); Vec2d c = hull.centroid().cast(); Vec2d d = c - screen_center; bool is_bigger_x = std::fabs(d.x()) > std::fabs(d.y()); - if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || (!is_bigger_x && d.y() * d.y() > center_sq_distance)) + if ((is_bigger_x && d.x() * d.x() > center_sq_distance) || + (!is_bigger_x && d.y() * d.y() > center_sq_distance)) continue; double distance = d.squaredNorm(); @@ -1275,47 +1191,67 @@ const GLVolume * priv::find_closest( return closest; } -bool priv::start_create_object_job(Plater &plater, DataBasePtr emboss_data, const Vec2d &coor, GLGizmosManager::EType gizmo) +bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor) { - const Camera &camera = plater.get_camera(); - const Pointfs &bed_shape = plater.build_volume().bed_shape(); - - DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape, gizmo}; - auto job = std::make_unique(std::move(data)); - - Worker &worker = plater.get_ui_job_worker(); - return queue_job(worker, std::move(job)); + const Pointfs &bed_shape = input.build_volume.bed_shape(); + auto gizmo_type = static_cast(input.gizmo); + DataCreateObject data{std::move(emboss_data), coor, input.camera, bed_shape, gizmo_type}; + auto job = std::make_unique(std::move(data)); + return queue_job(input.worker, std::move(job)); } -DataBasePtr priv::start_create_volume_on_surface_job(Plater &plater, - DataBasePtr data, - ModelVolumeType volume_type, - const Vec2d &screen_coor, - const GLVolume &gl_volume, - RaycastManager &raycaster, - GLGizmosManager::EType gizmo, - const std::optional &distance, - const std::optional &angle, - bool &success) +bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &screen_coor, bool try_no_coor) { - success = false; - const ModelObjectPtrs &objects = plater.model().objects; - const ModelVolume* volume = get_model_volume(gl_volume, objects); - const ModelInstance *instance = get_model_instance(gl_volume, objects); - if (volume == nullptr || instance == nullptr || - volume->get_object() == nullptr) { - // weird situation - assert(false); - return data; - } - - const Camera &camera = plater.get_camera(); - GLCanvas3D &canvas = *plater.get_current_canvas3D(); + auto on_bad_state = [&input, try_no_coor](DataBasePtr data_, const ModelObject *object = nullptr) { + if (try_no_coor) { + // Can't create on coordinate try to create somewhere + return start_create_volume_without_position(input, std::move(data_)); + } else { + // In centroid of convex hull is not hit with object. e.g. torid + // soo create transfomation on border of object - auto cond = RaycastManager::AllowVolumes({volume->id().id}); - RaycastManager::Meshes meshes = create_meshes(canvas, cond); - raycaster.actualize(*instance, &cond, &meshes); - std::optional hit = ray_from_camera(raycaster, screen_coor, camera, &cond); + // there is no point on surface so no use of surface will be applied + if (data_->shape.use_surface) + data_->shape.use_surface = false; + + if (object == nullptr) + return false; + + auto gizmo_type = static_cast(input.gizmo); + return start_create_volume_job(input.worker, *object, {}, std::move(data_), input.volume_type, gizmo_type); + } + }; + + assert(input.gl_volume != nullptr); + if (input.gl_volume == nullptr) + return on_bad_state(std::move(data)); + + const Model *model = input.canvas.get_model(); + + assert(model != nullptr); + if (model == nullptr) + return on_bad_state(std::move(data)); + + const ModelObjectPtrs &objects = model->objects; + const ModelVolume *volume = get_model_volume(*input.gl_volume, objects); + assert(volume != nullptr); + if (volume == nullptr) + return on_bad_state(std::move(data)); + + const ModelInstance *instance = get_model_instance(*input.gl_volume, objects); + assert(instance != nullptr); + if (instance == nullptr) + return on_bad_state(std::move(data)); + + const ModelObject *object = volume->get_object(); + assert(object != nullptr); + if (object == nullptr) + return on_bad_state(std::move(data)); + + auto cond = RaycastManager::AllowVolumes({volume->id().id}); + RaycastManager::Meshes meshes = create_meshes(input.canvas, cond); + input.raycaster.actualize(*instance, &cond, &meshes); + std::optional hit = ray_from_camera(input.raycaster, screen_coor, input.camera, &cond); // context menu for add text could be open only by right click on an // object. After right click, object is selected and object_idx is set @@ -1323,25 +1259,22 @@ DataBasePtr priv::start_create_volume_on_surface_job(Plater if (!hit.has_value()) // When model is broken. It could appear that hit miss the object. // So add part near by in simmilar manner as right panel do - return data; + return on_bad_state(std::move(data), object); // Create result volume transformation Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, Slic3r::GUI::up_limit); - - apply_transformation(angle, distance, surface_trmat); - - Transform3d transform = instance->get_matrix().inverse() * surface_trmat; - + apply_transformation(input.angle, input.distance, surface_trmat); + Transform3d transform = instance->get_matrix().inverse() * surface_trmat; + auto gizmo_type = static_cast(input.gizmo); // Try to cast ray into scene and find object for add volume - Worker &worker = plater.get_ui_job_worker(); - - success = priv::start_create_volume_job(worker, *volume->get_object(), transform, std::move(data), volume_type, gizmo); - return nullptr; + return start_create_volume_job(input.worker, *object, transform, std::move(data), input.volume_type, gizmo_type); } +} // namespace #include - -void priv::create_message(const std::string &message) { +namespace{ +void create_message(const std::string &message) { wxMessageBox(wxString(message), _L("Issue during embossing the text."), wxOK | wxICON_WARNING); } +} // namespace diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 6b55e0da1f..ae281c12b4 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -19,9 +19,12 @@ namespace Slic3r { class TriangleMesh; class ModelVolume; enum class ModelVolumeType : int; +class BuildVolume; namespace GUI { class RaycastManager; class Plater; +class GLCanvas3D; +class Worker; }} namespace Slic3r::GUI::Emboss { @@ -160,6 +163,42 @@ public: /// Source data for cut surface from SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume); +/// +/// shorten params for start_crate_volume functions +/// +struct CreateVolumeParams +{ + + GLCanvas3D &canvas; + + // Direction of ray into scene + const Camera &camera; + + // To put new object on the build volume + const BuildVolume &build_volume; + + // used to emplace job for execution + Worker &worker; + + // New created volume type + ModelVolumeType volume_type; + + // Contain AABB trees from scene + RaycastManager &raycaster; + + // Define which gizmo open on the success + unsigned char gizmo; // GLGizmosManager::EType + + // Volume define object to add new volume + const GLVolume *gl_volume; + + // Wanted additionl move in Z(emboss) direction of new created volume + std::optional distance = {}; + + // Wanted additionl rotation around Z of new created volume + std::optional angle = {}; +}; + /// /// Create new volume on position of mouse cursor /// @@ -172,27 +211,13 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume) /// Wanted additionl move in Z(emboss) direction of new created volume /// Wanted additionl rotation around Z of new created volume /// True on success otherwise False -bool start_create_volume(Plater *plater_ptr, - DataBasePtr data, - ModelVolumeType volume_type, - RaycastManager &raycaster, - unsigned char gizmo, - const Vec2d &mouse_pos, - const std::optional &distance = {}, - const std::optional &angle = {}); - +bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos); /// /// Same as previous function but without mouse position /// Need to suggest position or put near the selection /// -bool start_create_volume_without_position(Plater *plater_ptr, - DataBasePtr data, - ModelVolumeType volume_type, - RaycastManager &raycaster, - unsigned char gizmo, - const std::optional &distance = {}, - const std::optional &angle = {}); +bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data); } // namespace Slic3r::GUI #endif // slic3r_EmbossJob_hpp_ From 574df48f85df2fe82b9da342d5dac19bdc190fe6 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 16 Mar 2023 18:21:48 +0100 Subject: [PATCH 012/120] Reorganize data structures --- src/libslic3r/EmbossShape.hpp | 54 ++-- src/libslic3r/TextConfiguration.hpp | 85 ++---- src/slic3r/CMakeLists.txt | 2 - src/slic3r/GUI/Jobs/EmbossJob.cpp | 31 ++- src/slic3r/GUI/Jobs/EmbossJob.hpp | 1 - src/slic3r/Utils/EmbossStyleManager.cpp | 253 +++++++++++++++++- src/slic3r/Utils/EmbossStyleManager.hpp | 27 +- src/slic3r/Utils/EmbossStylesSerializable.cpp | 201 -------------- src/slic3r/Utils/EmbossStylesSerializable.hpp | 58 ---- tests/libslic3r/test_emboss.cpp | 14 +- 10 files changed, 349 insertions(+), 377 deletions(-) delete mode 100644 src/slic3r/Utils/EmbossStylesSerializable.cpp delete mode 100644 src/slic3r/Utils/EmbossStylesSerializable.hpp diff --git a/src/libslic3r/EmbossShape.hpp b/src/libslic3r/EmbossShape.hpp index 0946235d87..1dccb58c65 100644 --- a/src/libslic3r/EmbossShape.hpp +++ b/src/libslic3r/EmbossShape.hpp @@ -2,6 +2,7 @@ #define slic3r_EmbossShape_hpp_ #include +#include #include #include #include @@ -12,6 +13,39 @@ namespace Slic3r { +struct EmbossProjection +{ + // Emboss depth, Size in local Z direction + double depth = 1.; // [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; + + // enum class Align { + // left, + // right, + // center, + // top_left, + // top_right, + // top_center, + // bottom_left, + // bottom_right, + // bottom_center + // }; + //// change pivot of volume + //// When not set, center is used and is not stored + // std::optional align; + + // compare TextStyle + bool operator==(const EmbossProjection &other) const { + return depth == other.depth && use_surface == other.use_surface; + } + + // undo / redo stack recovery + template void serialize(Archive &ar) { ar(depth, use_surface); } +}; + /// /// Contain plane shape information to be able emboss it and edit it /// @@ -22,19 +56,9 @@ struct EmbossShape // scale of shape, multiplier to get 3d point in mm from integer shape double scale = 1.; - - // Emboss depth, Size in local Z direction - double depth = 1.; // [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; - - // 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] + // Define how to emboss shape + EmbossProjection projection; // !!! Volume stored in .3mf has transformed vertices. // (baked transformation into vertices position) @@ -50,14 +74,12 @@ struct EmbossShape // undo / redo stack recovery template void save(Archive &ar) const { - ar(shapes, scale, depth, use_surface, svg_file_path); - cereal::save(ar, distance); + ar(shapes, scale, projection, svg_file_path); cereal::save(ar, fix_3mf_tr); } template void load(Archive &ar) { - ar(shapes, scale, depth, use_surface, svg_file_path); - cereal::load(ar, distance); + ar(shapes, scale, projection, svg_file_path); cereal::load(ar, fix_3mf_tr); } }; diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 1c1ce77567..ef1df301ba 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -26,15 +26,6 @@ struct FontProp // When not set value is zero and is not stored std::optional line_gap; // [in font point] - // Z depth of text - float emboss; // [in mm] - - // Flag that text should use surface cutted from object - // FontProp::distance should without value - // FontProp::emboss should be positive number - // Note: default value is false - bool use_surface; - // positive value mean wider character shape // negative value mean tiner character shape // When not set value is zero and is not stored @@ -45,35 +36,21 @@ struct FontProp // When not set value is zero and is not stored std::optional skew; // [ration x:y] - // distance from surface point - // used for move over model surface - // When not set value is zero and is not stored - std::optional distance; // [in mm] - - // Angle of rotation around emboss direction (Z axis) - // It is calculate on the fly from volume world transformation - // only StyleManager keep actual value for comparision with style - // When not set value is zero and is not stored - std::optional angle; // [in radians] form -Pi to Pi - // Parameter for True Type Font collections // Select index of font in collection std::optional collection_number; - //enum class Align { - // left, - // right, - // center, - // top_left, - // top_right, - // top_center, - // bottom_left, - // bottom_right, - // bottom_center - //}; - //// change pivot of text - //// When not set, center is used and is not stored - //std::optional align; + [[deprecated("Back compatibility only, now it is stored EmbossProjection like depth")]] + float emboss; + + [[deprecated("Back compatibility only, now it is stored EmbossProjection")]] + bool use_surface; + + [[deprecated("it is calculated on the fly")]] + std::optional distance; + + [[deprecated("it is calculated on the fly")]] + std::optional angle; ////// // Duplicit data to wxFontDescriptor @@ -104,45 +81,29 @@ struct FontProp return char_gap == other.char_gap && line_gap == other.line_gap && - use_surface == other.use_surface && - is_approx(emboss, other.emboss) && is_approx(size_in_mm, other.size_in_mm) && is_approx(boldness, other.boldness) && - is_approx(skew, other.skew) && - is_approx(distance, other.distance) && - is_approx(angle, other.angle); + is_approx(skew, other.skew); } // undo / redo stack recovery template void save(Archive &ar) const { - ar(emboss, use_surface, size_in_mm); + ar(size_in_mm); cereal::save(ar, char_gap); cereal::save(ar, line_gap); cereal::save(ar, boldness); cereal::save(ar, skew); - cereal::save(ar, distance); - cereal::save(ar, angle); cereal::save(ar, collection_number); - cereal::save(ar, family); - cereal::save(ar, face_name); - cereal::save(ar, style); - cereal::save(ar, weight); } template void load(Archive &ar) { - ar(emboss, use_surface, size_in_mm); + ar(size_in_mm); cereal::load(ar, char_gap); cereal::load(ar, line_gap); cereal::load(ar, boldness); cereal::load(ar, skew); - cereal::load(ar, distance); - cereal::load(ar, angle); cereal::load(ar, collection_number); - cereal::load(ar, family); - cereal::load(ar, face_name); - cereal::load(ar, style); - cereal::load(ar, weight); } }; @@ -196,9 +157,7 @@ struct EmbossStyle } // undo / redo stack recovery - template void serialize(Archive &ar){ - ar(name, path, type, prop); - } + template void serialize(Archive &ar){ ar(name, path, type, prop); } }; // Emboss style name inside vector is unique @@ -219,21 +178,11 @@ struct TextConfiguration // Embossed text value std::string text = "None"; - // !!! Volume stored in .3mf has transformed vertices. - // (baked transformation into vertices position) - // Only place for fill this is when load from .3mf - // This is correct volume transformation + [[deprecated("only for back compatibility, now it is stored in EmbossShape")]] std::optional fix_3mf_tr; // undo / redo stack recovery - template void save(Archive &ar) const{ - ar(text, style); - cereal::save(ar, fix_3mf_tr); - } - template void load(Archive &ar){ - ar(text, style); - cereal::load(ar, fix_3mf_tr); - } + template void serialize(Archive &ar) { ar(style, text); } }; } // namespace Slic3r diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 6db1456e3d..d252462a6c 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -258,8 +258,6 @@ set(SLIC3R_GUI_SOURCES Utils/Duet.hpp Utils/EmbossStyleManager.cpp Utils/EmbossStyleManager.hpp - Utils/EmbossStylesSerializable.cpp - Utils/EmbossStylesSerializable.hpp Utils/FlashAir.cpp Utils/FlashAir.hpp Utils/FontConfigHelp.cpp diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 9753fec088..c1671db2d7 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -291,12 +291,9 @@ void CreateObjectJob::process(Ctl &ctl) if (!check(m_input)) throw JobException("Bad input data for EmbossCreateObjectJob."); - if (m_input.base->shape.distance.has_value()) - m_input.base->shape.distance.reset(); - // can't create new object with using surface - if (m_input.base->shape.use_surface) - m_input.base->shape.use_surface = false; + if (m_input.base->shape.projection.use_surface) + m_input.base->shape.projection.use_surface = false; auto was_canceled = ::was_canceled(ctl, *m_input.base); m_result = create_mesh(*m_input.base, was_canceled, ctl); @@ -319,7 +316,7 @@ void CreateObjectJob::process(Ctl &ctl) bed_coor = bed.centroid().cast(); // TODO: need TextConfiguration refactor to work !!! - double z = m_input.base->shape.depth / 2; + double z = m_input.base->shape.projection.depth / 2; Vec3d offset(bed_coor.x(), bed_coor.y(), z); offset -= m_result.center(); @@ -658,6 +655,8 @@ bool check(const DataCreateVolume &input, bool is_main_thread) assert(input.volume_type != ModelVolumeType::INVALID); res &= input.volume_type != ModelVolumeType::INVALID; res &= check(input.gizmo); + assert(!input.base->shape.projection.use_surface); + res &= !input.base->shape.projection.use_surface; return res; } bool check(const DataCreateObject &input) @@ -673,6 +672,8 @@ bool check(const DataCreateObject &input) assert(input.bed_shape.size() >= 3); // at least triangle res &= input.bed_shape.size() >= 3; res &= check(input.gizmo); + assert(!input.base->shape.projection.use_surface); + res &= !input.base->shape.projection.use_surface; return res; } bool check(const DataUpdate &input, bool is_main_thread, bool use_surface) @@ -687,6 +688,8 @@ bool check(const DataUpdate &input, bool is_main_thread, bool use_surface) res &= input.base->cancel != nullptr; if (is_main_thread) assert(!input.base->cancel->load()); + assert(!input.base->shape.projection.use_surface); + res &= !input.base->shape.projection.use_surface; return res; } bool check(const CreateSurfaceVolumeData &input, bool is_main_thread) @@ -698,6 +701,8 @@ bool check(const CreateSurfaceVolumeData &input, bool is_main_thread) assert(!input.sources.empty()); res &= !input.sources.empty(); res &= check(input.gizmo); + assert(input.base->shape.projection.use_surface); + res &= input.base->shape.projection.use_surface; return res; } bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread) @@ -708,6 +713,8 @@ bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread) res &= check(*input.base, is_main_thread, use_surface); assert(!input.sources.empty()); res &= !input.sources.empty(); + assert(input.base->shape.projection.use_surface); + res &= input.base->shape.projection.use_surface; return res; } @@ -716,7 +723,7 @@ template TriangleMesh try_create_mesh(DataBase &base, const Fnc &w const EmbossShape &shape = base.create_shape(); if (shape.shapes.empty()) return {}; - double depth = shape.depth / shape.scale; + double depth = shape.projection.depth / shape.scale; auto projectZ = std::make_unique(depth); ProjectScale project(std::move(projectZ), shape.scale); if (was_canceled()) @@ -887,7 +894,7 @@ void create_volume(TriangleMesh &&mesh, if (trmat.has_value()) { volume->set_transformation(*trmat); } else { - assert(!data.shape.use_surface); + assert(!data.shape.projection.use_surface); // Create transformation for volume near from object(defined by glVolume) // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject Vec3d volume_size = volume->mesh().bounding_box().size(); @@ -1064,7 +1071,7 @@ template TriangleMesh cut_surface(DataBase &base, const SurfaceVol return {}; // !! Projection needs to transform cut - OrthoProject3d projection = create_emboss_projection(input2.is_outside, static_cast(emboss_shape.depth), emboss_tr, cut); + OrthoProject3d projection = create_emboss_projection(input2.is_outside, static_cast(emboss_shape.projection.depth), emboss_tr, cut); indexed_triangle_set new_its = cut2model(cut, projection); assert(!new_its.empty()); if (was_canceled()) @@ -1132,7 +1139,7 @@ bool start_create_volume_job(Worker &worker, ModelVolumeType volume_type, GLGizmosManager::EType gizmo) { - bool &use_surface = data->shape.use_surface; + bool &use_surface = data->shape.projection.use_surface; std::unique_ptr job; if (use_surface) { // Model to cut surface from. @@ -1211,8 +1218,8 @@ bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr d // soo create transfomation on border of object // there is no point on surface so no use of surface will be applied - if (data_->shape.use_surface) - data_->shape.use_surface = false; + if (data_->shape.projection.use_surface) + data_->shape.projection.use_surface = false; if (object == nullptr) return false; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index ae281c12b4..173eec399e 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -168,7 +168,6 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume) /// struct CreateVolumeParams { - GLCanvas3D &canvas; // Direction of ray into scene diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 5740ac13aa..32e003c6d7 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -1,15 +1,15 @@ #include "EmbossStyleManager.hpp" +#include #include // Imgui texture #include // ImTextCharFromUtf8 -#include "WxFontUtils.hpp" -#include "libslic3r/Utils.hpp" // ScopeGuard +#include +#include // ScopeGuard +#include "WxFontUtils.hpp" #include "slic3r/GUI/3DScene.hpp" // ::glsafe #include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" // check of font ranges -#include "slic3r/Utils/EmbossStylesSerializable.hpp" - using namespace Slic3r; using namespace Slic3r::Emboss; using namespace Slic3r::GUI::Emboss; @@ -28,11 +28,22 @@ StyleManager::~StyleManager() { free_style_images(); } +/// +/// For store/load emboss style to/from AppConfig +/// +namespace { +void store_style_index(AppConfig &cfg, unsigned index); +::std::optional load_style_index(const AppConfig &cfg); + +EmbossStyles load_styles(const AppConfig &cfg); +void store_styles(AppConfig &cfg, const EmbossStyles &styles); +} // namespace + void StyleManager::init(AppConfig *app_config) { m_app_config = app_config; EmbossStyles styles = (app_config != nullptr) ? - EmbossStylesSerializable::load_styles(*app_config) : + ::load_styles(*app_config) : EmbossStyles{}; if (styles.empty()) styles = m_create_default_styles(); @@ -42,7 +53,7 @@ void StyleManager::init(AppConfig *app_config) } std::optional active_index_opt = (app_config != nullptr) ? - EmbossStylesSerializable::load_style_index(*app_config) : + ::load_style_index(*app_config) : std::optional{}; size_t active_index = 0; @@ -83,13 +94,13 @@ bool StyleManager::store_styles_to_app_config(bool use_modification, bool store_ size_t style_index = exist_stored_style() ? m_style_cache.style_index : m_last_style_index; - EmbossStylesSerializable::store_style_index(*m_app_config, style_index); + store_style_index(*m_app_config, style_index); } EmbossStyles styles; styles.reserve(m_style_items.size()); for (const Item &item : m_style_items) styles.push_back(item.style); - EmbossStylesSerializable::store_styles(*m_app_config, styles); + store_styles(*m_app_config, styles); return true; } @@ -542,3 +553,229 @@ bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr clear_imgui_font(); return true; } + +#include +#include "WxFontUtils.hpp" +#include "fast_float/fast_float.h" + +// StylesSerializable +namespace { + +using namespace Slic3r; +using namespace Slic3r::GUI; +using Section = std::map; + +const std::string APP_CONFIG_FONT_NAME = "name"; +const std::string APP_CONFIG_FONT_DESCRIPTOR = "descriptor"; +const std::string APP_CONFIG_FONT_LINE_HEIGHT = "line_height"; +const std::string APP_CONFIG_FONT_DEPTH = "depth"; +const std::string APP_CONFIG_FONT_USE_SURFACE = "use_surface"; +const std::string APP_CONFIG_FONT_BOLDNESS = "boldness"; +const std::string APP_CONFIG_FONT_SKEW = "skew"; +const std::string APP_CONFIG_FONT_DISTANCE = "distance"; +const std::string APP_CONFIG_FONT_ANGLE = "angle"; +const std::string APP_CONFIG_FONT_COLLECTION = "collection"; +const std::string APP_CONFIG_FONT_CHAR_GAP = "char_gap"; +const std::string APP_CONFIG_FONT_LINE_GAP = "line_gap"; + +const std::string APP_CONFIG_ACTIVE_FONT = "active_font"; + +std::string create_section_name(unsigned index) +{ + return AppConfig::SECTION_EMBOSS_STYLE + ':' + std::to_string(index); +} + +// check only existence of flag +bool read(const Section §ion, const std::string &key, bool &value) +{ + auto item = section.find(key); + if (item == section.end()) + return false; + + value = true; + return true; +} + +bool read(const Section §ion, const std::string &key, float &value) +{ + auto item = section.find(key); + if (item == section.end()) + return false; + const std::string &data = item->second; + if (data.empty()) + return false; + float value_; + fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); + // read only non zero value + if (fabs(value_) <= std::numeric_limits::epsilon()) + return false; + + value = value_; + return true; +} + +bool read(const Section §ion, const std::string &key, std::optional &value) +{ + auto item = section.find(key); + if (item == section.end()) + return false; + const std::string &data = item->second; + if (data.empty()) + return false; + int value_ = std::atoi(data.c_str()); + if (value_ == 0) + return false; + + value = value_; + return true; +} + +bool read(const Section §ion, const std::string &key, std::optional &value) +{ + auto item = section.find(key); + if (item == section.end()) + return false; + const std::string &data = item->second; + if (data.empty()) + return false; + int value_ = std::atoi(data.c_str()); + if (value_ <= 0) + return false; + + value = static_cast(value_); + return true; +} + +bool read(const Section §ion, const std::string &key, std::optional &value) +{ + auto item = section.find(key); + if (item == section.end()) + return false; + const std::string &data = item->second; + if (data.empty()) + return false; + float value_; + fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); + // read only non zero value + if (fabs(value_) <= std::numeric_limits::epsilon()) + return false; + + value = value_; + return true; +} + +std::optional load_style(const Section &app_cfg_section) +{ + auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR); + if (path_it == app_cfg_section.end()) + return {}; + const std::string &path = path_it->second; + + auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME); + const std::string default_name = "font_name"; + const std::string &name = (name_it == app_cfg_section.end()) ? default_name : name_it->second; + + FontProp fp; + read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm); + read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss); + read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, fp.use_surface); + read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness); + read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew); + read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, fp.distance); + read(app_cfg_section, APP_CONFIG_FONT_ANGLE, fp.angle); + read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number); + read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap); + read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap); + + EmbossStyle::Type type = WxFontUtils::get_actual_type(); + return EmbossStyle{name, path, type, fp}; +} + +void store_style(AppConfig &cfg, const EmbossStyle &fi, unsigned index) +{ + Section data; + data[APP_CONFIG_FONT_NAME] = fi.name; + data[APP_CONFIG_FONT_DESCRIPTOR] = fi.path; + const FontProp &fp = fi.prop; + data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm); + data[APP_CONFIG_FONT_DEPTH] = std::to_string(fp.emboss); + if (fp.use_surface) + data[APP_CONFIG_FONT_USE_SURFACE] = "true"; + if (fp.boldness.has_value()) + data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness); + if (fp.skew.has_value()) + data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew); + if (fp.distance.has_value()) + data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*fp.distance); + if (fp.angle.has_value()) + data[APP_CONFIG_FONT_ANGLE] = std::to_string(*fp.angle); + if (fp.collection_number.has_value()) + data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number); + if (fp.char_gap.has_value()) + data[APP_CONFIG_FONT_CHAR_GAP] = std::to_string(*fp.char_gap); + if (fp.line_gap.has_value()) + data[APP_CONFIG_FONT_LINE_GAP] = std::to_string(*fp.line_gap); + cfg.set_section(create_section_name(index), std::move(data)); +} + +void store_style_index(AppConfig &cfg, unsigned index) +{ + // store actual font index + // active font first index is +1 to correspond with section name + Section data; + data[APP_CONFIG_ACTIVE_FONT] = std::to_string(index); + cfg.set_section(AppConfig::SECTION_EMBOSS_STYLE, std::move(data)); +} + +std::optional load_style_index(const AppConfig &cfg) +{ + if (!cfg.has_section(AppConfig::SECTION_EMBOSS_STYLE)) + return {}; + + auto section = cfg.get_section(AppConfig::SECTION_EMBOSS_STYLE); + auto it = section.find(APP_CONFIG_ACTIVE_FONT); + if (it == section.end()) + return {}; + + size_t active_font = static_cast(std::atoi(it->second.c_str())); + // order in config starts with number 1 + return active_font - 1; +} + +EmbossStyles load_styles(const AppConfig &cfg) +{ + EmbossStyles result; + // human readable index inside of config starts from 1 !! + unsigned index = 1; + std::string section_name = create_section_name(index); + while (cfg.has_section(section_name)) { + std::optional style_opt = load_style(cfg.get_section(section_name)); + if (style_opt.has_value()) + result.emplace_back(*style_opt); + section_name = create_section_name(++index); + } + return result; +} + +void store_styles(AppConfig &cfg, const EmbossStyles &styles) +{ + // store styles + unsigned index = 1; + for (const EmbossStyle &style : styles) { + // skip file paths + fonts from other OS(loaded from .3mf) + assert(style.type == WxFontUtils::get_actual_type()); + // if (style_opt.type != WxFontUtils::get_actual_type()) continue; + store_style(cfg, style, index); + ++index; + } + + // remove rest of font sections (after deletation) + std::string section_name = create_section_name(index); + while (cfg.has_section(section_name)) { + cfg.clear_section(section_name); + section_name = create_section_name(index); + ++index; + } +} + +} // namespace diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index dd6b9ca129..5bab7adce5 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include namespace Slic3r::GUI::Emboss { @@ -176,7 +178,9 @@ public: { void* texture_id = 0; // GLuint BoundingBox bounding_box; - ImVec2 tex_size, uv0, uv1; + ImVec2 tex_size; + ImVec2 uv0; + ImVec2 uv1; Point offset = Point(0, 0); StyleImage() = default; }; @@ -187,9 +191,28 @@ public: /// struct Item { - // define font, style and other property of text + // parent Text style EmbossStyle style; + // Define how to emboss shape + EmbossProjection projection; + + // distance from surface point + // used for move over model surface + // When not set value is zero and is not stored + std::optional distance; // [in mm] + + // Angle of rotation around emboss direction (Z axis) + // It is calculate on the fly from volume world transformation + // only StyleManager keep actual value for comparision with style + // When not set value is zero and is not stored + std::optional angle; // [in radians] form -Pi to Pi + + bool operator==(const Item &other) const + { + return style == other.style && projection == other.projection && distance == other.distance && angle == other.angle; + } + // cache for view font name with maximal width in imgui std::string truncated_name; diff --git a/src/slic3r/Utils/EmbossStylesSerializable.cpp b/src/slic3r/Utils/EmbossStylesSerializable.cpp deleted file mode 100644 index 111bb597b1..0000000000 --- a/src/slic3r/Utils/EmbossStylesSerializable.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include "EmbossStylesSerializable.hpp" - -#include -#include "WxFontUtils.hpp" - -using namespace Slic3r; -using namespace Slic3r::GUI; - -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_NAME = "name"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DESCRIPTOR = "descriptor"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_HEIGHT = "line_height"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DEPTH = "depth"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_USE_SURFACE = "use_surface"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_BOLDNESS = "boldness"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_SKEW = "skew"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_DISTANCE = "distance"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_ANGLE = "angle"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_COLLECTION = "collection"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_CHAR_GAP = "char_gap"; -const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_GAP = "line_gap"; - -const std::string EmbossStylesSerializable::APP_CONFIG_ACTIVE_FONT = "active_font"; - -std::string EmbossStylesSerializable::create_section_name(unsigned index) -{ - return AppConfig::SECTION_EMBOSS_STYLE + ':' + std::to_string(index); -} - -// check only existence of flag -bool EmbossStylesSerializable::read(const std::map& section, const std::string& key, bool& value){ - auto item = section.find(key); - if (item == section.end()) return false; - - value = true; - return true; -} - -#include "fast_float/fast_float.h" -bool EmbossStylesSerializable::read(const std::map& section, const std::string& key, float& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - float value_; - fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); - // read only non zero value - if (fabs(value_) <= std::numeric_limits::epsilon()) return false; - - value = value_; - return true; -} - -bool EmbossStylesSerializable::read(const std::map& section, const std::string& key, std::optional& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - int value_ = std::atoi(data.c_str()); - if (value_ == 0) return false; - - value = value_; - return true; -} - -bool EmbossStylesSerializable::read(const std::map& section, const std::string& key, std::optional& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - int value_ = std::atoi(data.c_str()); - if (value_ <= 0) return false; - - value = static_cast(value_); - return true; -} - -bool EmbossStylesSerializable::read(const std::map& section, const std::string& key, std::optional& value){ - auto item = section.find(key); - if (item == section.end()) return false; - const std::string &data = item->second; - if (data.empty()) return false; - float value_; - fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_); - // read only non zero value - if (fabs(value_) <= std::numeric_limits::epsilon()) return false; - - value = value_; - return true; -} - -std::optional EmbossStylesSerializable::load_style( - const std::map &app_cfg_section) -{ - auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR); - if (path_it == app_cfg_section.end()) return {}; - const std::string &path = path_it->second; - - auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME); - const std::string default_name = "font_name"; - const std::string &name = - (name_it == app_cfg_section.end()) ? - default_name : name_it->second; - - FontProp fp; - read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm); - read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss); - read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, fp.use_surface); - read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness); - read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew); - read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, fp.distance); - read(app_cfg_section, APP_CONFIG_FONT_ANGLE, fp.angle); - read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number); - read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap); - read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap); - - EmbossStyle::Type type = WxFontUtils::get_actual_type(); - return EmbossStyle{ name, path, type, fp }; -} - -void EmbossStylesSerializable::store_style(AppConfig & cfg, - const EmbossStyle &fi, - unsigned index) -{ - std::map data; - data[APP_CONFIG_FONT_NAME] = fi.name; - data[APP_CONFIG_FONT_DESCRIPTOR] = fi.path; - const FontProp &fp = fi.prop; - data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm); - data[APP_CONFIG_FONT_DEPTH] = std::to_string(fp.emboss); - if (fp.use_surface) - data[APP_CONFIG_FONT_USE_SURFACE] = "true"; - if (fp.boldness.has_value()) - data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness); - if (fp.skew.has_value()) - data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew); - if (fp.distance.has_value()) - data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*fp.distance); - if (fp.angle.has_value()) - data[APP_CONFIG_FONT_ANGLE] = std::to_string(*fp.angle); - if (fp.collection_number.has_value()) - data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number); - if (fp.char_gap.has_value()) - data[APP_CONFIG_FONT_CHAR_GAP] = std::to_string(*fp.char_gap); - if (fp.line_gap.has_value()) - data[APP_CONFIG_FONT_LINE_GAP] = std::to_string(*fp.line_gap); - cfg.set_section(create_section_name(index), std::move(data)); -} - -void EmbossStylesSerializable::store_style_index(AppConfig &cfg, unsigned index) { - // store actual font index - // active font first index is +1 to correspond with section name - std::map data; - data[APP_CONFIG_ACTIVE_FONT] = std::to_string(index); - cfg.set_section(AppConfig::SECTION_EMBOSS_STYLE, std::move(data)); -} - -std::optional EmbossStylesSerializable::load_style_index(const AppConfig &cfg) -{ - if (!cfg.has_section(AppConfig::SECTION_EMBOSS_STYLE)) return {}; - - auto section = cfg.get_section(AppConfig::SECTION_EMBOSS_STYLE); - auto it = section.find(APP_CONFIG_ACTIVE_FONT); - if (it == section.end()) return {}; - - size_t active_font = static_cast(std::atoi(it->second.c_str())); - // order in config starts with number 1 - return active_font - 1; -} - -EmbossStyles EmbossStylesSerializable::load_styles(const AppConfig &cfg) -{ - EmbossStyles result; - // human readable index inside of config starts from 1 !! - unsigned index = 1; - std::string section_name = create_section_name(index); - while (cfg.has_section(section_name)) { - std::optional style_opt = load_style(cfg.get_section(section_name)); - if (style_opt.has_value()) result.emplace_back(*style_opt); - section_name = create_section_name(++index); - } - return result; -} - -void EmbossStylesSerializable::store_styles(AppConfig &cfg, const EmbossStyles& styles) -{ - // store styles - unsigned index = 1; - for (const EmbossStyle &style : styles) { - // skip file paths + fonts from other OS(loaded from .3mf) - assert(style.type == WxFontUtils::get_actual_type()); - // if (style_opt.type != WxFontUtils::get_actual_type()) continue; - store_style(cfg, style, index++); - } - - // remove rest of font sections (after deletation) - std::string section_name = create_section_name(index); - while (cfg.has_section(section_name)) { - cfg.clear_section(section_name); - section_name = create_section_name(++index); - } -} \ No newline at end of file diff --git a/src/slic3r/Utils/EmbossStylesSerializable.hpp b/src/slic3r/Utils/EmbossStylesSerializable.hpp deleted file mode 100644 index da87af8203..0000000000 --- a/src/slic3r/Utils/EmbossStylesSerializable.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef slic3r_EmbossStylesSerializable_hpp_ -#define slic3r_EmbossStylesSerializable_hpp_ - -#include -#include -#include -#include // EmbossStyles+EmbossStyle - -namespace Slic3r { -class AppConfig; -} - -namespace Slic3r::GUI { - -/// -/// For store/load font list to/from AppConfig -/// -class EmbossStylesSerializable -{ - static const std::string APP_CONFIG_FONT_NAME; - static const std::string APP_CONFIG_FONT_DESCRIPTOR; - static const std::string APP_CONFIG_FONT_LINE_HEIGHT; - static const std::string APP_CONFIG_FONT_DEPTH; - static const std::string APP_CONFIG_FONT_USE_SURFACE; - static const std::string APP_CONFIG_FONT_BOLDNESS; - static const std::string APP_CONFIG_FONT_SKEW; - static const std::string APP_CONFIG_FONT_DISTANCE; - static const std::string APP_CONFIG_FONT_ANGLE; - static const std::string APP_CONFIG_FONT_COLLECTION; - static const std::string APP_CONFIG_FONT_CHAR_GAP; - static const std::string APP_CONFIG_FONT_LINE_GAP; - - static const std::string APP_CONFIG_ACTIVE_FONT; -public: - EmbossStylesSerializable() = delete; - - static void store_style_index(AppConfig &cfg, unsigned index); - static std::optional load_style_index(const AppConfig &cfg); - - static EmbossStyles load_styles(const AppConfig &cfg); - static void store_styles(AppConfig &cfg, const EmbossStyles& styles); - -private: - static std::string create_section_name(unsigned index); - static std::optional load_style(const std::map &app_cfg_section); - static void store_style(AppConfig &cfg, const EmbossStyle &style, unsigned index); - - // TODO: move to app config like read from section - static bool read(const std::map& section, const std::string& key, bool& value); - static bool read(const std::map& section, const std::string& key, float& value); - static bool read(const std::map& section, const std::string& key, std::optional& value); - static bool read(const std::map& section, const std::string& key, std::optional& value); - static bool read(const std::map& section, const std::string& key, std::optional& value); -}; -} // namespace Slic3r - -#endif // #define slic3r_EmbossStylesSerializable_hpp_ - diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index f087dbf3a2..4c22d588de 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -511,8 +511,7 @@ TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]") std::stringstream ss; // any stream can be used { - cereal::BinaryOutputArchive oarchive(ss); // Create an output archive - + cereal::BinaryOutputArchive oarchive(ss); // Create an output archive oarchive(tc); } // archive goes out of scope, ensuring all contents are flushed @@ -532,9 +531,8 @@ TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]") EmbossShape emboss; 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; + emboss.projection.depth = 5.; + emboss.projection.use_surface = true; emboss.fix_3mf_tr = Transform3d::Identity(); emboss.svg_file_path = "Everything starts somewhere, though many physicists disagree.\ But people have always been dimly aware of the problem with the start of things.\ @@ -543,7 +541,6 @@ TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]") std::stringstream ss; // any stream can be used { cereal::BinaryOutputArchive oarchive(ss); // Create an output archive - oarchive(emboss); } // archive goes out of scope, ensuring all contents are flushed @@ -554,9 +551,8 @@ TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]") } 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); + CHECK(emboss.projection.depth == emboss_loaded.projection.depth); + CHECK(emboss.projection.use_surface == emboss_loaded.projection.use_surface); } From e0a4ac03131feda71eb16e7d1fd330419a549f25 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 22 Mar 2023 09:11:12 +0100 Subject: [PATCH 013/120] Partialy calculated distance on the fly. --- src/libslic3r/Point.hpp | 15 + src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 371 +++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 11 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 7 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 8 +- src/slic3r/GUI/Jobs/EmbossJob.hpp | 4 +- src/slic3r/GUI/SurfaceDrag.cpp | 29 +- src/slic3r/Utils/EmbossStyleManager.cpp | 265 +++++++++-------- src/slic3r/Utils/EmbossStyleManager.hpp | 70 +++-- src/slic3r/Utils/WxFontUtils.cpp | 4 +- src/slic3r/Utils/WxFontUtils.hpp | 2 +- 11 files changed, 418 insertions(+), 368 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index d97f1b32d0..a46422022b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -144,6 +144,21 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t); /// Is positive determinant inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; } +/// +/// Getter on base of transformation matrix +/// +/// column index +/// source transformation +/// Base of transformation matrix +inline const Vec3d &get_base(unsigned index, const Transform3d &transform) { return transform.linear().col(index); } +inline const Vec3d &get_base(unsigned index, const Transform3d::LinearPart &transform) { return transform.col(index); } +inline const Vec3d& get_x_base(const Transform3d &transform) { return get_base(0, transform); } +inline const Vec3d& get_y_base(const Transform3d &transform) { return get_base(1, transform); } +inline const Vec3d& get_z_base(const Transform3d &transform) { return get_base(2, transform); } +inline const Vec3d &get_x_base(const Transform3d::LinearPart &transform) { return get_base(0, transform); } +inline const Vec3d &get_y_base(const Transform3d::LinearPart &transform) { return get_base(1, transform); } +inline const Vec3d &get_z_base(const Transform3d::LinearPart &transform) { return get_base(2, transform); } + template using Vec = Eigen::Matrix; class Point : public Vec2crd diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 75fe6cbfc5..f00ae83aed 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -154,14 +154,13 @@ const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType typ static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false); } // namespace priv -CreateVolumeParams create_input(GLCanvas3D &canvas, StyleManager &styler, RaycastManager& raycaster, ModelVolumeType volume_type) +CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager& raycaster, ModelVolumeType volume_type) { auto gizmo = static_cast(GLGizmosManager::Emboss); const GLVolume *gl_volume = get_first_hovered_gl_volume(canvas); - const FontProp &fp = styler.get_style().prop; Plater *plater = wxGetApp().plater(); return CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), - plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume, fp.distance, fp.angle}; + plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume, style.distance, style.angle}; } bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos) @@ -171,7 +170,7 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous // NOTE: change style manager - be carefull with order changes DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - CreateVolumeParams input = create_input(m_parent, m_style_manager, m_raycast_manager, volume_type); + CreateVolumeParams input = create_input(m_parent, m_style_manager.get_style(), m_raycast_manager, volume_type); return start_create_volume(input, std::move(base), mouse_pos); } @@ -183,7 +182,7 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type) // NOTE: change style manager - be carefull with order changes DataBasePtr base = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel); - CreateVolumeParams input = create_input(m_parent, m_style_manager, m_raycast_manager, volume_type); + CreateVolumeParams input = create_input(m_parent, m_style_manager.get_style(), m_raycast_manager, volume_type); return start_create_volume_without_position(input, std::move(base)); } @@ -197,9 +196,9 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) if (mouse_event.Dragging()) { if (!m_rotate_start_angle.has_value()) { // when m_rotate_start_angle is not set mean it is not Dragging - // when angle_opt is not set mean angle is Zero + // when angle_opt is not set than angle is Zero const std::optional &angle_opt = m_style_manager.get_font_prop().angle; - m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f; + m_rotate_start_angle = angle_opt.value_or(0.f); } double angle = m_rotate_gizmo.get_angle(); @@ -269,7 +268,7 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) if (gl_volume == nullptr || !m_style_manager.is_active_font()) return res; - m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + m_style_manager.get_style().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); } } return res; @@ -576,7 +575,7 @@ void GLGizmoEmboss::on_stop_dragging() assert(m_style_manager.is_active_font()); assert(gl_volume != nullptr); if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + m_style_manager.get_style().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); m_rotate_start_angle.reset(); @@ -754,15 +753,66 @@ EmbossStyles GLGizmoEmboss::create_default_styles() void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); } +namespace { + +/// +/// Throow ray by embossing params to object and find surface point +/// +/// Define embossed volume +/// Way to cast rays to object +/// Contain model +/// Calculated distance from surface +std::optional calc_distance(const GLVolume &gl_volume, RaycastManager &raycaster, GLCanvas3D& canvas) +{ + const ModelObject *object = get_model_object(gl_volume, canvas.get_model()->objects); + assert(object != nullptr); + if (object == nullptr) + return {}; + + const ModelInstance *instance = get_model_instance(gl_volume, *object); + const ModelVolume *volume = get_model_volume(gl_volume, *object); + assert(instance != nullptr && volume != nullptr); + if (object == nullptr || instance == nullptr || volume == nullptr) + return {}; + + if (volume->is_the_only_one_part()) + return {}; + + const ModelVolumePtrs &volumes = object->volumes; + std::vector allowed_volumes_id; + allowed_volumes_id.reserve(volumes.size() - 1); + for (const ModelVolume *v : volumes) { + // skip actual selected object + if (v->id() == volume->id()) + continue; + // collect hit only from object parts not modifiers neither negative + if (!v->is_model_part()) + continue; + allowed_volumes_id.emplace_back(v->id().id); + } + RaycastManager::AllowVolumes condition(std::move(allowed_volumes_id)); + RaycastManager::Meshes meshes = create_meshes(canvas, condition); + raycaster.actualize(*instance, &condition, &meshes); + + Transform3d w = gl_volume.world_matrix(); + Vec3d p = w.translation(); + const Vec3d& dir = get_z_base(w); + auto hit_opt = raycaster.first_hit(p, dir, &condition); + if (!hit_opt.has_value()) + return {}; +} + +} // namespace + void GLGizmoEmboss::set_volume_by_selection() { const Selection &selection = m_parent.get_selection(); - const GLVolume *gl_volume = get_selected_gl_volume(selection); + const GLVolume *gl_volume = get_selected_gl_volume(selection); if (gl_volume == nullptr) return reset_volume(); const ModelObjectPtrs &objects = selection.get_model()->objects; - ModelVolume *volume =get_model_volume(*gl_volume, objects); + ModelVolume *volume = get_model_volume(*gl_volume, objects); if (volume == nullptr) return reset_volume(); @@ -774,24 +824,28 @@ void GLGizmoEmboss::set_volume_by_selection() remove_notification_not_valid_font(); // Do not use focused input value when switch volume(it must swith value) - if (m_volume != nullptr && - m_volume != volume) // when update volume it changed id BUT not pointer + if (m_volume != nullptr && m_volume != volume) // when update volume it changed id BUT not pointer ImGuiWrapper::left_inputs(); // Is selected volume text volume? - const std::optional& tc_opt = volume->text_configuration; - if (!tc_opt.has_value()) + const std::optional &tc_opt = volume->text_configuration; + if (!tc_opt.has_value()) return reset_volume(); + // Emboss shape must be setted + assert(volume->emboss_shape.has_value()); + if (!volume->emboss_shape.has_value()) + return; + const TextConfiguration &tc = *tc_opt; const EmbossStyle &style = tc.style; // Could exist OS without getter on face_name, // but it is able to restore font from descriptor // Soo default value must be TRUE - bool is_font_installed = true; - wxString face_name; - std::optional face_name_opt = style.prop.face_name; + bool is_font_installed = true; + wxString face_name; + const std::optional &face_name_opt = style.prop.face_name; if (face_name_opt.has_value()) { face_name = wxString(face_name_opt->c_str()); @@ -800,21 +854,20 @@ void GLGizmoEmboss::set_volume_by_selection() init_face_names(m_face_names); m_face_names.is_init = false; - auto cmp = [](const FaceName &fn, const wxString& face_name)->bool { return fn.wx_name < face_name; }; + auto cmp = [](const FaceName &fn, const wxString &face_name) -> bool { return fn.wx_name < face_name; }; const std::vector &faces = m_face_names.faces; auto it = std::lower_bound(faces.begin(), faces.end(), face_name, cmp); is_font_installed = it != faces.end() && it->wx_name == face_name; if (!is_font_installed) { - const std::vector &bad = m_face_names.bad; - auto it_bad = std::lower_bound(bad.begin(), bad.end(), face_name); - if (it_bad == bad.end() || *it_bad != face_name){ + const std::vector &bad = m_face_names.bad; + auto it_bad = std::lower_bound(bad.begin(), bad.end(), face_name); + if (it_bad == bad.end() || *it_bad != face_name) { // check if wx allowed to set it up - another encoding of name wxFontEnumerator::InvalidateCache(); - wxFont wx_font_; // temporary structure - if (wx_font_.SetFaceName(face_name) && - WxFontUtils::create_font_file(wx_font_) != nullptr // can load TTF file? - ) { + wxFont wx_font_; // temporary structure + if (wx_font_.SetFaceName(face_name) && WxFontUtils::create_font_file(wx_font_) != nullptr // can load TTF file? + ) { is_font_installed = true; // QUESTION: add this name to allowed faces? // Could create twin of font face name @@ -826,8 +879,8 @@ void GLGizmoEmboss::set_volume_by_selection() wxFont wx_font; // load wxFont from same OS when font name is installed - if (style.type == WxFontUtils::get_actual_type() && is_font_installed) - wx_font = WxFontUtils::load_wxFont(style.path); + if (style.type == WxFontUtils::get_current_type() && is_font_installed) + wx_font = WxFontUtils::load_wxFont(style.path); // Flag that is selected same font bool is_exact_font = true; @@ -837,7 +890,7 @@ void GLGizmoEmboss::set_volume_by_selection() // Try create similar wx font by FontFamily wx_font = WxFontUtils::create_wxFont(style); if (is_font_installed) - is_exact_font = wx_font.SetFaceName(face_name); + is_exact_font = wx_font.SetFaceName(face_name); // Have to use some wxFont if (!wx_font.IsOk()) @@ -846,25 +899,28 @@ void GLGizmoEmboss::set_volume_by_selection() assert(wx_font.IsOk()); // Load style to style manager - const auto& styles = m_style_manager.get_styles(); - auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool { - const EmbossStyle &es = style_item.style; - return es.name == style.name; - }; + const auto &styles = m_style_manager.get_styles(); + auto has_same_name = [&name = style.name](const StyleManager::Style &style_item) { return style_item.name == name; }; + + StyleManager::Style style_{style}; + style_.projection = volume->emboss_shape->projection; + style_.angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + style_.distance = calc_distance(*gl_volume, m_raycast_manager, m_parent); + auto it = std::find_if(styles.begin(), styles.end(), has_same_name); if (it == styles.end()) { // style was not found - m_style_manager.load_style(style, wx_font); + m_style_manager.load_style(style_, wx_font); } else { // style name is in styles list size_t style_index = it - styles.begin(); if (!m_style_manager.load_style(style_index)) { // can`t load stored style m_style_manager.erase(style_index); - m_style_manager.load_style(style, wx_font); + m_style_manager.load_style(style_, wx_font); } else { // stored style is loaded, now set modification of style - m_style_manager.get_style() = style; + m_style_manager.get_style() = style_; m_style_manager.set_wx_font(wx_font); } } @@ -890,11 +946,6 @@ void GLGizmoEmboss::set_volume_by_selection() m_volume = volume; m_volume_id = volume->id(); - // Calculate current angle of up vector - assert(m_style_manager.is_active_font()); - if (m_style_manager.is_active_font()) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); - // calculate scale for height and depth inside of scaled object instance calculate_scale(); } @@ -1858,14 +1909,10 @@ void GLGizmoEmboss::draw_style_rename_popup() { const std::string &old_name = m_style_manager.get_stored_style()->name; std::string text_in_popup = GUI::format(_L("Rename style(%1%) for embossing text: "), old_name); ImGui::Text("%s", text_in_popup.c_str()); - - bool is_unique = true; - for (const auto &item : m_style_manager.get_styles()) { - const EmbossStyle &style = item.style; - if (&style == &m_style_manager.get_style()) - continue; // could be same as original name - if (style.name == new_name) is_unique = false; - } + + bool is_unique = (new_name == old_name) || // could be same as before rename + m_style_manager.is_unique_style_name(new_name); + bool allow_change = false; if (new_name.empty()) { m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_DARK, _u8L("Name can't be empty.")); @@ -1946,11 +1993,7 @@ void GLGizmoEmboss::draw_style_save_as_popup() { // use name inside of volume configuration as temporary new name std::string &new_name = m_volume->text_configuration->style.name; - - bool is_unique = true; - for (const auto &item : m_style_manager.get_styles()) - if (item.style.name == new_name) is_unique = false; - + bool is_unique = m_style_manager.is_unique_style_name(new_name); bool allow_change = false; if (new_name.empty()) { m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_DARK, _u8L("Name can't be empty.")); @@ -1988,7 +2031,7 @@ void GLGizmoEmboss::draw_style_add_button() bool only_add_style = !m_style_manager.exist_stored_style(); bool can_add = true; if (only_add_style && - m_volume->text_configuration->style.type != WxFontUtils::get_actual_type()) + m_volume->text_configuration->style.type != WxFontUtils::get_current_type()) can_add = false; std::string title = _u8L("Save as new style"); @@ -2080,46 +2123,46 @@ void GLGizmoEmboss::draw_delete_style_button() { } } -// FIX IT: it should not change volume position before successfull change -void GLGizmoEmboss::fix_transformation(const FontProp &from, - const FontProp &to) -{ +namespace { +// FIX IT: It should not change volume position before successfull change volume by process +void fix_transformation(const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas) { // fix Z rotation when exists difference in styles const std::optional &f_angle_opt = from.angle; const std::optional &t_angle_opt = to.angle; if (!is_approx(f_angle_opt, t_angle_opt)) { // fix rotation - float f_angle = f_angle_opt.has_value() ? *f_angle_opt : .0f; - float t_angle = t_angle_opt.has_value() ? *t_angle_opt : .0f; - do_local_z_rotate(m_parent, t_angle - f_angle); + float f_angle = f_angle_opt.value_or(.0f); + float t_angle = t_angle_opt.value_or(.0f); + do_local_z_rotate(canvas, t_angle - f_angle); } // fix distance (Z move) when exists difference in styles const std::optional &f_move_opt = from.distance; const std::optional &t_move_opt = to.distance; if (!is_approx(f_move_opt, t_move_opt)) { - float f_move = f_move_opt.has_value() ? *f_move_opt : .0f; - float t_move = t_move_opt.has_value() ? *t_move_opt : .0f; - do_local_z_move(m_parent, t_move - f_move); + float f_move = f_move_opt.value_or(.0f); + float t_move = t_move_opt.value_or(.0f); + do_local_z_move(canvas, t_move - f_move); } } +} // namesapce void GLGizmoEmboss::draw_style_list() { if (!m_style_manager.is_active_font()) return; - const EmbossStyle *stored_style = nullptr; + const StyleManager::Style *stored_style = nullptr; bool is_stored = m_style_manager.exist_stored_style(); if (is_stored) stored_style = m_style_manager.get_stored_style(); - const EmbossStyle &actual_style = m_style_manager.get_style(); - bool is_changed = (stored_style)? !(*stored_style == actual_style) : true; + const StyleManager::Style ¤t_style = m_style_manager.get_style(); + bool is_changed = (stored_style)? !(*stored_style == current_style) : true; bool is_modified = is_stored && is_changed; const float &max_style_name_width = m_gui_cfg->max_style_name_width; std::string &trunc_name = m_style_manager.get_truncated_name(); if (trunc_name.empty()) { // generate trunc name - std::string current_name = actual_style.name; + std::string current_name = current_style.name; ImGuiWrapper::escape_double_hash(current_name); trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width); } @@ -2140,19 +2183,19 @@ void GLGizmoEmboss::draw_style_list() { m_style_manager.init_style_images(m_gui_cfg->max_style_image_size, m_text); m_style_manager.init_trunc_names(max_style_name_width); std::optional> swap_indexes; - const std::vector &styles = m_style_manager.get_styles(); - for (const auto &item : styles) { - size_t index = &item - &styles.front(); - const EmbossStyle &style = item.style; + const StyleManager::Styles &styles = m_style_manager.get_styles(); + for (const StyleManager::Style &style : styles) { + size_t index = &style - &styles.front(); const std::string &actual_style_name = style.name; ImGui::PushID(actual_style_name.c_str()); bool is_selected = (index == m_style_manager.get_style_index()); - ImVec2 select_size(0,m_gui_cfg->max_style_image_size.y()); // 0,0 --> calculate in draw - const std::optional &img = item.image; + float select_height = static_cast(m_gui_cfg->max_style_image_size.y()); + ImVec2 select_size(0.f, select_height); // 0,0 --> calculate in draw + const std::optional &img = style.image; // allow click delete button ImGuiSelectableFlags_ flags = ImGuiSelectableFlags_AllowItemOverlap; - if (ImGui::Selectable(item.truncated_name.c_str(), is_selected, flags, select_size)) { + if (ImGui::Selectable(style.truncated_name.c_str(), is_selected, flags, select_size)) { selected_style_index = index; } else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", actual_style_name.c_str()); @@ -2184,10 +2227,10 @@ void GLGizmoEmboss::draw_style_list() { // do not keep in memory style images when no combo box open m_style_manager.free_style_images(); if (ImGui::IsItemHovered()) { - std::string style_name = add_text_modify(actual_style.name); + std::string style_name = add_text_modify(current_style.name); std::string tooltip = is_modified? - GUI::format(_L("Modified style \"%1%\""), actual_style.name): - GUI::format(_L("Current style is \"%1%\""), actual_style.name); + GUI::format(_L("Modified style \"%1%\""), current_style.name): + GUI::format(_L("Current style is \"%1%\""), current_style.name); ImGui::SetTooltip(" %s", tooltip.c_str()); } } @@ -2195,7 +2238,7 @@ void GLGizmoEmboss::draw_style_list() { // Check whether user wants lose actual style modification if (selected_style_index.has_value() && is_modified) { wxString title = _L("Style modification will be lost."); - const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index].style; + const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index]; wxString message = GUI::format_wxstr(_L("Changing style to '%1%' will discard current style modification.\n\n Would you like to continue anyway?"), style.name); MessageDialog not_loaded_style_message(nullptr, message, title, wxICON_WARNING | wxYES|wxNO); if (not_loaded_style_message.ShowModal() != wxID_YES) @@ -2204,12 +2247,12 @@ void GLGizmoEmboss::draw_style_list() { // selected style from combo box if (selected_style_index.has_value()) { - const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index].style; + const StyleManager::Style &style = m_style_manager.get_styles()[*selected_style_index]; // create copy to be able do fix transformation only when successfully load style - FontProp act_prop = actual_style.prop; // copy - FontProp new_prop = style.prop; // copy + StyleManager::Style cur_s = current_style; // copy + StyleManager::Style new_s = style; // copy if (m_style_manager.load_style(*selected_style_index)) { - fix_transformation(act_prop, new_prop); + ::fix_transformation(cur_s, new_s, m_parent); process(); } else { wxString title = _L("Not valid style."); @@ -2667,11 +2710,10 @@ void GLGizmoEmboss::draw_advanced() return; } - FontProp &font_prop = m_style_manager.get_style().prop; - const auto &cn = m_style_manager.get_font_prop().collection_number; - unsigned int font_index = (cn.has_value()) ? *cn : 0; - const auto &font_info = ff.font_file->infos[font_index]; - + StyleManager::Style ¤t_style = m_style_manager.get_style(); + FontProp ¤t_prop = current_style.prop; + + const FontFile::Info &font_info = ff.font_file->infos[current_prop.collection_number.value_or(0)]; #ifdef SHOW_FONT_FILE_PROPERTY ImGui::SameLine(); int cache_size = ff.has_value()? (int)ff.cache->size() : 0; @@ -2682,8 +2724,8 @@ void GLGizmoEmboss::draw_advanced() ", unitPerEm=" + std::to_string(font_info.unit_per_em) + ", cache(" + std::to_string(cache_size) + " glyphs)"; if (font_file->infos.size() > 1) { - unsigned int collection = font_prop.collection_number.has_value() ? - *font_prop.collection_number : 0; + unsigned int collection = current_prop.collection_number.has_value() ? + *current_prop.collection_number : 0; ff_property += ", collect=" + std::to_string(collection+1) + "/" + std::to_string(font_file->infos.size()); } m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property); @@ -2692,26 +2734,22 @@ void GLGizmoEmboss::draw_advanced() bool exist_change = false; auto &tr = m_gui_cfg->translations; - const EmbossStyle *stored_style = nullptr; + const StyleManager::Style *stored_style = nullptr; if (m_style_manager.exist_stored_style()) stored_style = m_style_manager.get_stored_style(); - bool can_use_surface = (m_volume==nullptr)? false : - (font_prop.use_surface)? true : // already used surface must have option to uncheck - (m_volume->get_object()->volumes.size() > 1); + bool can_use_surface = (m_volume == nullptr)? false : + (m_volume->emboss_shape->projection.use_surface)? true : // already used surface must have option to uncheck + !m_volume->is_the_only_one_part(); m_imgui->disabled_begin(!can_use_surface); const bool *def_use_surface = stored_style ? - &stored_style->prop.use_surface : nullptr; - if (rev_checkbox(tr.use_surface, font_prop.use_surface, def_use_surface, + &stored_style->projection.use_surface : nullptr; + bool &use_surface = current_style.projection.use_surface; + if (rev_checkbox(tr.use_surface, use_surface, def_use_surface, _u8L("Revert using of model surface."))) { - if (font_prop.use_surface) { + if (use_surface) // when using surface distance is not used - font_prop.distance.reset(); - - // there should be minimal embossing depth - if (font_prop.emboss < 0.1) - font_prop.emboss = 1; - } + current_style.distance.reset(); process(); } m_imgui->disabled_end(); // !can_use_surface @@ -2724,13 +2762,14 @@ void GLGizmoEmboss::draw_advanced() &stored_style->prop.char_gap : nullptr; int half_ascent = font_info.ascent / 2; - int min_char_gap = -half_ascent, max_char_gap = half_ascent; - if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), + int min_char_gap = -half_ascent; + int max_char_gap = half_ascent; + if (rev_slider(tr.char_gap, current_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!apply(font_prop.char_gap, priv::limits.char_gap) || - !m_volume->text_configuration->style.prop.char_gap.has_value() || - m_volume->text_configuration->style.prop.char_gap != font_prop.char_gap) { + const std::optional &volume_char_gap = m_volume->text_configuration->style.prop.char_gap; + if (!apply(current_prop.char_gap, priv::limits.char_gap) || + !volume_char_gap.has_value() || volume_char_gap != current_prop.char_gap) { // char gap is stored inside of imgui font atlas m_style_manager.clear_imgui_font(); exist_change = true; @@ -2740,13 +2779,14 @@ void GLGizmoEmboss::draw_advanced() // input gap between lines auto def_line_gap = stored_style ? &stored_style->prop.line_gap : nullptr; - int min_line_gap = -half_ascent, max_line_gap = half_ascent; - if (rev_slider(tr.line_gap, font_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), + int min_line_gap = -half_ascent; + int max_line_gap = half_ascent; + if (rev_slider(tr.line_gap, current_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!apply(font_prop.line_gap, priv::limits.line_gap) || - !m_volume->text_configuration->style.prop.line_gap.has_value() || - m_volume->text_configuration->style.prop.line_gap != font_prop.line_gap) { + const std::optional &volume_line_gap = m_volume->text_configuration->style.prop.line_gap; + if (!apply(current_prop.line_gap, priv::limits.line_gap) || + !volume_line_gap.has_value() || volume_line_gap != current_prop.line_gap) { // line gap is planed to be stored inside of imgui font atlas m_style_manager.clear_imgui_font(); exist_change = true; @@ -2756,37 +2796,34 @@ void GLGizmoEmboss::draw_advanced() // input boldness auto def_boldness = stored_style ? &stored_style->prop.boldness : nullptr; - if (rev_slider(tr.boldness, font_prop.boldness, def_boldness, _u8L("Undo boldness"), + if (rev_slider(tr.boldness, current_prop.boldness, def_boldness, _u8L("Undo boldness"), priv::limits.boldness.gui.min, priv::limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){ - if (!apply(font_prop.boldness, priv::limits.boldness.values) || - !m_volume->text_configuration->style.prop.boldness.has_value() || - m_volume->text_configuration->style.prop.boldness != font_prop.boldness) + const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; + if (!apply(current_prop.boldness, priv::limits.boldness.values) || + !volume_boldness.has_value() || volume_boldness != current_prop.boldness) exist_change = true; } // input italic auto def_skew = stored_style ? &stored_style->prop.skew : nullptr; - if (rev_slider(tr.skew_ration, font_prop.skew, def_skew, _u8L("Undo letter's skew"), + if (rev_slider(tr.skew_ration, current_prop.skew, def_skew, _u8L("Undo letter's skew"), priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ - if (!apply(font_prop.skew, priv::limits.skew.values) || - !m_volume->text_configuration->style.prop.skew.has_value() || - m_volume->text_configuration->style.prop.skew != font_prop.skew) + const std::optional &volume_skew = m_volume->text_configuration->style.prop.skew; + if (!apply(current_prop.skew, priv::limits.skew.values) || + !volume_skew.has_value() ||volume_skew != current_prop.skew) exist_change = true; } // input surface distance - bool allowe_surface_distance = - !m_volume->text_configuration->style.prop.use_surface && - !m_volume->is_the_only_one_part(); - std::optional &distance = font_prop.distance; - float prev_distance = distance.has_value() ? *distance : .0f, - min_distance = -2 * font_prop.emboss, - max_distance = 2 * font_prop.emboss; + bool allowe_surface_distance = !use_surface && !m_volume->is_the_only_one_part(); + std::optional &distance = current_style.distance; + float prev_distance = distance.value_or(.0f); + float min_distance = static_cast(-2 * current_style.projection.depth); + float max_distance = static_cast(2 * current_style.projection.depth); auto def_distance = stored_style ? - &stored_style->prop.distance : nullptr; - m_imgui->disabled_begin(!allowe_surface_distance); - + &stored_style->distance : nullptr; + m_imgui->disabled_begin(!allowe_surface_distance); bool use_inch = wxGetApp().app_config->get_bool("use_inches"); const std::string undo_move_tooltip = _u8L("Undo translation"); const wxString move_tooltip = _L("Distance of the center of text from model surface"); @@ -2803,9 +2840,9 @@ void GLGizmoEmboss::draw_advanced() max_distance *= ObjectManipulation::mm_to_in; if (rev_slider(tr.from_surface, distance_inch, def_distance, undo_move_tooltip, min_distance, max_distance, "%.3f in", move_tooltip)) { if (distance_inch.has_value()) { - font_prop.distance = *distance_inch * ObjectManipulation::in_to_mm; + distance = *distance_inch * ObjectManipulation::in_to_mm; } else { - font_prop.distance.reset(); + distance.reset(); } is_moved = true; } @@ -2814,23 +2851,19 @@ void GLGizmoEmboss::draw_advanced() min_distance, max_distance, "%.2f mm", move_tooltip)) is_moved = true; } - if (is_moved){ - m_volume->text_configuration->style.prop.distance = font_prop.distance; - float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f; - do_local_z_move(m_parent, act_distance - prev_distance); - } - m_imgui->disabled_end(); + if (is_moved) + do_local_z_move(m_parent, distance.value_or(.0f) - prev_distance); + m_imgui->disabled_end(); // allowe_surface_distance // slider for Clock-wise angle in degress // stored angle is optional CCW and in radians // Convert stored value to degress // minus create clock-wise roation from CCW - const std::optional &angle_opt = m_style_manager.get_font_prop().angle; - float angle = angle_opt.has_value() ? *angle_opt: 0.f; + float angle = current_style.angle.value_or(0.f); float angle_deg = static_cast(-angle * 180 / M_PI); float def_angle_deg_val = - (!stored_style || !stored_style->prop.angle.has_value()) ? - 0.f : (*stored_style->prop.angle * -180 / M_PI); + (!stored_style || !stored_style->angle.has_value()) ? + 0.f : (*stored_style->angle * -180 / M_PI); float* def_angle_deg = stored_style ? &def_angle_deg_val : nullptr; if (rev_slider(tr.rotation, angle_deg, def_angle_deg, _u8L("Undo rotation"), @@ -2848,21 +2881,16 @@ void GLGizmoEmboss::draw_advanced() assert(gl_volume != nullptr); assert(m_style_manager.is_active_font()); if (m_style_manager.is_active_font() && gl_volume != nullptr) - m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); + m_style_manager.get_style().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit); // recalculate for surface cut - if (font_prop.use_surface) + if (use_surface) process(); } ImGui::Text("%s", tr.keep_up.c_str()); ImGui::SameLine(m_gui_cfg->advanced_input_offset); - if (ImGui::Checkbox("##keep_up", &m_keep_up)) { - if (m_keep_up) { - // copy angle to volume - m_volume->text_configuration->style.prop.angle = font_prop.angle; - } - } + ImGui::Checkbox("##keep_up", &m_keep_up); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Keep text orientation during surface dragging.\nNot stable between horizontal and vertical alignment.").c_str()); @@ -2871,15 +2899,15 @@ void GLGizmoEmboss::draw_advanced() ImGui::Text("%s", tr.collection.c_str()); ImGui::SameLine(m_gui_cfg->advanced_input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); - unsigned int selected = font_prop.collection_number.has_value() ? - *font_prop.collection_number : 0; + unsigned int selected = current_prop.collection_number.has_value() ? + *current_prop.collection_number : 0; if (ImGui::BeginCombo("## Font collection", std::to_string(selected).c_str())) { for (unsigned int i = 0; i < ff.font_file->infos.size(); ++i) { ImGui::PushID(1 << (10 + i)); bool is_selected = (i == selected); if (ImGui::Selectable(std::to_string(i).c_str(), is_selected)) { - if (i == 0) font_prop.collection_number.reset(); - else font_prop.collection_number = i; + if (i == 0) current_prop.collection_number.reset(); + else current_prop.collection_number = i; exist_change = true; } ImGui::PopID(); @@ -2897,25 +2925,24 @@ void GLGizmoEmboss::draw_advanced() if (ImGui::Button(_u8L("Set text to face camera").c_str())) { assert(get_selected_volume(m_parent.get_selection()) == m_volume); - const Camera &cam = wxGetApp().plater()->get_camera(); - bool use_surface = m_style_manager.get_style().prop.use_surface; + const Camera &cam = wxGetApp().plater()->get_camera(); if (face_selected_volume_to_camera(cam, m_parent) && use_surface) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); } #ifdef ALLOW_DEBUG_MODE - ImGui::Text("family = %s", (font_prop.family.has_value() ? - font_prop.family->c_str() : + ImGui::Text("family = %s", (current_prop.family.has_value() ? + current_prop.family->c_str() : " --- ")); - ImGui::Text("face name = %s", (font_prop.face_name.has_value() ? - font_prop.face_name->c_str() : + ImGui::Text("face name = %s", (current_prop.face_name.has_value() ? + current_prop.face_name->c_str() : " --- ")); ImGui::Text("style = %s", - (font_prop.style.has_value() ? font_prop.style->c_str() : + (current_prop.style.has_value() ? current_prop.style->c_str() : " --- ")); - ImGui::Text("weight = %s", (font_prop.weight.has_value() ? - font_prop.weight->c_str() : + ImGui::Text("weight = %s", (current_prop.weight.has_value() ? + current_prop.weight->c_str() : " --- ")); std::string descriptor = style.path; @@ -2961,7 +2988,7 @@ bool GLGizmoEmboss::choose_font_by_wxdialog() data.RestrictSelection(wxFONTRESTRICT_SCALABLE); // set previous selected font EmbossStyle &selected_style = m_style_manager.get_style(); - if (selected_style.type == WxFontUtils::get_actual_type()) { + if (selected_style.type == WxFontUtils::get_current_type()) { std::optional selected_font = WxFontUtils::load_wxFont( selected_style.path); if (selected_font.has_value()) data.SetInitialFont(*selected_font); @@ -3179,9 +3206,9 @@ priv::TextDataBase::TextDataBase(DataBase &&parent, const FontFileWithCache &fon // 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; + EmbossProjection &p = shape.projection; + p.depth = fp.emboss; + p.use_surface = fp.use_surface; const FontFile &ff = *this->font_file.font_file; shape.scale = get_text_shape_scale(fp, ff); @@ -3219,8 +3246,8 @@ void priv::TextDataBase::write(ModelVolume &volume) const volume.text_configuration->style.prop.angle.reset(); // only temporary solution - volume.text_configuration->style.prop.use_surface = shape.use_surface; - volume.text_configuration->style.prop.distance = shape.distance; + volume.text_configuration->style.prop.use_surface = shape.projection.use_surface; // copy + volume.text_configuration->style.prop.emboss = static_cast(shape.projection.depth); // copy DataBase::write(volume); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 3422039fa2..8f48628fb6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -32,7 +32,7 @@ namespace Slic3r::GUI { class GLGizmoEmboss : public GLGizmoBase { public: - GLGizmoEmboss(GLCanvas3D& parent); + explicit GLGizmoEmboss(GLCanvas3D& parent); /// /// Create new embossed text volume by type on position of mouse @@ -51,8 +51,8 @@ protected: bool on_init() override; std::string on_get_name() const override; void on_render() override; - virtual void on_register_raycasters_for_picking() override; - virtual void on_unregister_raycasters_for_picking() override; + void on_register_raycasters_for_picking() override; + void on_unregister_raycasters_for_picking() override; void on_render_input_window(float x, float y, float bottom_limit) override; bool on_is_activable() const override { return true; } bool on_is_selectable() const override { return false; } @@ -90,7 +90,6 @@ private: void draw_window(); void draw_text_input(); void draw_model_type(); - void fix_transformation(const FontProp &from, const FontProp &to); void draw_style_list(); void draw_delete_style_button(); void draw_style_rename_popup(); @@ -308,8 +307,8 @@ private: // Keep data about dragging only during drag&drop std::optional m_surface_drag; - // TODO: it should be accessible by other gizmo too. - // May be move to plater? + // Keep old scene triangle data in AABB trees, + // all the time it need actualize before use. RaycastManager m_raycast_manager; // For text on scaled objects diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 8c56912b5f..54ff00cbdf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -398,7 +398,7 @@ void GLGizmoSVG::on_stop_dragging() // recalculate for surface cut if (m_volume != nullptr && m_volume->emboss_shape.has_value() && - m_volume->emboss_shape->use_surface) + m_volume->emboss_shape->projection.use_surface) process(); } void GLGizmoSVG::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); } @@ -710,9 +710,8 @@ ExPolygons priv::default_shape() { EmbossShape priv::select_shape() { EmbossShape shape; - shape.depth = 10.; - shape.distance = 0; - shape.use_surface = false; + shape.projection.depth = 10.; + shape.projection.use_surface = false; shape.svg_file_path = choose_svg_file(); if (shape.svg_file_path.empty()) diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index c1671db2d7..15f9296374 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -825,10 +825,10 @@ void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3 volume->set_transformation(*tr); } else { // apply fix matrix made by store to .3mf - const auto &tc = volume->text_configuration; - assert(tc.has_value()); - if (tc.has_value() && tc->fix_3mf_tr.has_value()) - volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse()); + const std::optional &emboss_shape = volume->emboss_shape; + assert(emboss_shape.has_value()); + if (emboss_shape.has_value() && emboss_shape->fix_3mf_tr.has_value()) + volume->set_transformation(volume->get_matrix() * emboss_shape->fix_3mf_tr->inverse()); } UpdateJob::update_volume(volume, std::move(mesh), *data.base); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 173eec399e..7a3a39b7b7 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -37,8 +37,8 @@ class DataBase public: DataBase(const std::string& volume_name, std::shared_ptr> cancel) : volume_name(volume_name), cancel(std::move(cancel)) {} DataBase(const std::string& volume_name, std::shared_ptr> cancel, EmbossShape&& shape) - : volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)) - {} + : volume_name(volume_name), cancel(std::move(cancel)), shape(std::move(shape)){} + DataBase(DataBase &&) = default; virtual ~DataBase() = default; /// diff --git a/src/slic3r/GUI/SurfaceDrag.cpp b/src/slic3r/GUI/SurfaceDrag.cpp index b80e1fb894..a65acd452a 100644 --- a/src/slic3r/GUI/SurfaceDrag.cpp +++ b/src/slic3r/GUI/SurfaceDrag.cpp @@ -93,25 +93,28 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, // detect start text dragging if (mouse_event.LeftDown()) { // selected volume - GLVolume *gl_volume = get_selected_gl_volume(canvas); - if (gl_volume == nullptr) + GLVolume *gl_volume_ptr = get_selected_gl_volume(canvas); + if (gl_volume_ptr == nullptr) return false; + const GLVolume &gl_volume = *gl_volume_ptr; // is selected volume closest hovered? - const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; - int hovered_idx = canvas.get_first_hover_volume_idx(); - if (hovered_idx < 0 || - hovered_idx >= gl_volumes.size() || - gl_volumes[hovered_idx] != gl_volume) + const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes; + if (int hovered_idx = canvas.get_first_hover_volume_idx(); + hovered_idx < 0) + return false; + else if (auto hovered_idx_ = static_cast(hovered_idx); + hovered_idx_ >= gl_volumes.size() || + gl_volumes[hovered_idx_] != gl_volume_ptr) return false; - const ModelObject *object = get_model_object(*gl_volume, canvas.get_model()->objects); + const ModelObject *object = get_model_object(gl_volume, canvas.get_model()->objects); assert(object != nullptr); if (object == nullptr) return false; - const ModelInstance *instance = get_model_instance(*gl_volume, *object); - const ModelVolume *volume = get_model_volume(*gl_volume, *object); + const ModelInstance *instance = get_model_instance(gl_volume, *object); + const ModelVolume *volume = get_model_volume(gl_volume, *object); assert(instance != nullptr && volume != nullptr); if (object == nullptr || instance == nullptr || volume == nullptr) return false; @@ -124,7 +127,7 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, std::vector allowed_volumes_id; if (volumes.size() > 1) { allowed_volumes_id.reserve(volumes.size() - 1); - for (auto &v : volumes) { + for (const ModelVolume *v : volumes) { // skip actual selected object if (v->id() == volume->id()) continue; @@ -146,7 +149,7 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, Vec2d mouse_pos = mouse_coord.cast(); Vec2d mouse_offset = calc_screen_offset_to_volume_center(mouse_pos, *volume, camera); - Transform3d volume_tr = gl_volume->get_volume_transformation().get_matrix(); + Transform3d volume_tr = gl_volume.get_volume_transformation().get_matrix(); if (volume->text_configuration.has_value()) { const TextConfiguration &tc = *volume->text_configuration; @@ -161,7 +164,7 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event, std::optional start_angle; if (up_limit.has_value()) start_angle = Emboss::calc_up(world_tr, *up_limit); - surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume, condition, start_angle}; + surface_drag = SurfaceDrag{mouse_offset, world_tr, instance_tr_inv, gl_volume_ptr, condition, start_angle}; // disable moving with object by mouse canvas.enable_moving(false); diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 32e003c6d7..af9013301b 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -14,13 +14,9 @@ using namespace Slic3r; using namespace Slic3r::Emboss; using namespace Slic3r::GUI::Emboss; -StyleManager::StyleManager(const ImWchar *language_glyph_range, std::function create_default_styles) - : m_imgui_init_glyph_range(language_glyph_range) - , m_create_default_styles(create_default_styles) - , m_exist_style_images(false) - , m_temp_style_images(nullptr) - , m_app_config(nullptr) - , m_last_style_index(std::numeric_limits::max()) +StyleManager::StyleManager(const ImWchar *language_glyph_range, const std::function& create_default_styles) + : m_create_default_styles(create_default_styles) + , m_imgui_init_glyph_range(language_glyph_range) {} StyleManager::~StyleManager() { @@ -32,24 +28,26 @@ StyleManager::~StyleManager() { /// For store/load emboss style to/from AppConfig /// namespace { -void store_style_index(AppConfig &cfg, unsigned index); +void store_style_index(AppConfig &cfg, size_t index); ::std::optional load_style_index(const AppConfig &cfg); -EmbossStyles load_styles(const AppConfig &cfg); -void store_styles(AppConfig &cfg, const EmbossStyles &styles); +StyleManager::Styles load_styles(const AppConfig &cfg); +void store_styles(AppConfig &cfg, const StyleManager::Styles &styles); +void make_unique_name(const StyleManager::Styles &styles, std::string &name); } // namespace void StyleManager::init(AppConfig *app_config) { m_app_config = app_config; - EmbossStyles styles = (app_config != nullptr) ? - ::load_styles(*app_config) : - EmbossStyles{}; - if (styles.empty()) - styles = m_create_default_styles(); - for (EmbossStyle &style : styles) { - make_unique_name(style.name); - m_style_items.push_back({style}); + m_styles = ::load_styles(*app_config); + + if (m_styles.empty()) { + // No styles loaded from ini file so use default + EmbossStyles styles = m_create_default_styles(); + for (EmbossStyle &style : styles) { + ::make_unique_name(m_styles, style.name); + m_styles.push_back({style}); + } } std::optional active_index_opt = (app_config != nullptr) ? @@ -58,14 +56,14 @@ void StyleManager::init(AppConfig *app_config) size_t active_index = 0; if (active_index_opt.has_value()) active_index = *active_index_opt; - if (active_index >= m_style_items.size()) active_index = 0; + if (active_index >= m_styles.size()) active_index = 0; // find valid font item if (load_style(active_index)) return; // style is loaded // Try to fix that style can't be loaded - m_style_items.erase(m_style_items.begin() + active_index); + m_styles.erase(m_styles.begin() + active_index); load_valid_style(); } @@ -77,14 +75,14 @@ bool StyleManager::store_styles_to_app_config(bool use_modification, bool store_ if (use_modification) { if (exist_stored_style()) { // update stored item - m_style_items[m_style_cache.style_index].style = m_style_cache.style; + m_styles[m_style_cache.style_index] = m_style_cache.style; } else { // add new into stored list EmbossStyle &style = m_style_cache.style; - make_unique_name(style.name); + ::make_unique_name(m_styles, style.name); m_style_cache.truncated_name.clear(); - m_style_cache.style_index = m_style_items.size(); - m_style_items.push_back({style}); + m_style_cache.style_index = m_styles.size(); + m_styles.push_back({style}); } m_style_cache.stored_wx_font = m_style_cache.wx_font; } @@ -97,27 +95,24 @@ bool StyleManager::store_styles_to_app_config(bool use_modification, bool store_ store_style_index(*m_app_config, style_index); } - EmbossStyles styles; - styles.reserve(m_style_items.size()); - for (const Item &item : m_style_items) styles.push_back(item.style); - store_styles(*m_app_config, styles); + store_styles(*m_app_config, m_styles); return true; } void StyleManager::add_style(const std::string &name) { EmbossStyle& style = m_style_cache.style; style.name = name; - make_unique_name(style.name); - m_style_cache.style_index = m_style_items.size(); + ::make_unique_name(m_styles, style.name); + m_style_cache.style_index = m_styles.size(); m_style_cache.stored_wx_font = m_style_cache.wx_font; m_style_cache.truncated_name.clear(); - m_style_items.push_back({style}); + m_styles.push_back({style}); } void StyleManager::swap(size_t i1, size_t i2) { - if (i1 >= m_style_items.size() || - i2 >= m_style_items.size()) return; - std::swap(m_style_items[i1], m_style_items[i2]); + if (i1 >= m_styles.size() || + i2 >= m_styles.size()) return; + std::swap(m_styles[i1], m_styles[i2]); // fix selected index if (!exist_stored_style()) return; if (m_style_cache.style_index == i1) { @@ -141,7 +136,7 @@ void StyleManager::discard_style_changes() { } void StyleManager::erase(size_t index) { - if (index >= m_style_items.size()) return; + if (index >= m_styles.size()) return; // fix selected index if (exist_stored_style()) { @@ -150,15 +145,15 @@ void StyleManager::erase(size_t index) { else if (index == i) i = std::numeric_limits::max(); } - m_style_items.erase(m_style_items.begin() + index); + m_styles.erase(m_styles.begin() + index); } void StyleManager::rename(const std::string& name) { m_style_cache.style.name = name; m_style_cache.truncated_name.clear(); if (exist_stored_style()) { - Item &it = m_style_items[m_style_cache.style_index]; - it.style.name = name; + Style &it = m_styles[m_style_cache.style_index]; + it.name = name; it.truncated_name.clear(); } } @@ -166,28 +161,28 @@ void StyleManager::rename(const std::string& name) { void StyleManager::load_valid_style() { // iterate over all known styles - while (!m_style_items.empty()) { + while (!m_styles.empty()) { if (load_style(0)) return; // can't load so erase it from list - m_style_items.erase(m_style_items.begin()); + m_styles.erase(m_styles.begin()); } // no one style is loadable // set up default font list EmbossStyles def_style = m_create_default_styles(); for (EmbossStyle &style : def_style) { - make_unique_name(style.name); - m_style_items.push_back({std::move(style)}); + ::make_unique_name(m_styles, style.name); + m_styles.push_back({std::move(style)}); } // iterate over default styles // There have to be option to use build in font - while (!m_style_items.empty()) { + while (!m_styles.empty()) { if (load_style(0)) return; // can't load so erase it from list - m_style_items.erase(m_style_items.begin()); + m_styles.erase(m_styles.begin()); } // This OS doesn't have TTF as default font, @@ -197,15 +192,15 @@ void StyleManager::load_valid_style() bool StyleManager::load_style(size_t style_index) { - if (style_index >= m_style_items.size()) return false; - if (!load_style(m_style_items[style_index].style)) return false; + if (style_index >= m_styles.size()) return false; + if (!load_style(m_styles[style_index])) return false; m_style_cache.style_index = style_index; m_style_cache.stored_wx_font = m_style_cache.wx_font; // copy m_last_style_index = style_index; return true; } -bool StyleManager::load_style(const EmbossStyle &style) { +bool StyleManager::load_style(const Style &style) { if (style.type == EmbossStyle::Type::file_path) { std::unique_ptr font_ptr = create_font_file(style.path.c_str()); @@ -218,13 +213,13 @@ bool StyleManager::load_style(const EmbossStyle &style) { m_style_cache.stored_wx_font = {}; return true; } - if (style.type != WxFontUtils::get_actual_type()) return false; + if (style.type != WxFontUtils::get_current_type()) return false; std::optional wx_font_opt = WxFontUtils::load_wxFont(style.path); if (!wx_font_opt.has_value()) return false; return load_style(style, *wx_font_opt); } -bool StyleManager::load_style(const EmbossStyle &style, const wxFont &font) +bool StyleManager::load_style(const Style &style, const wxFont &font) { m_style_cache.style = style; // copy @@ -275,12 +270,19 @@ bool StyleManager::is_font_changed() const return is_bold != is_stored_bold; } +bool StyleManager::is_unique_style_name(const std::string &name) const { + for (const StyleManager::Style &style : m_styles) + if (style.name == name) + return false; + return true; +} + bool StyleManager::is_active_font() { return m_style_cache.font_file.has_value(); } -const EmbossStyle* StyleManager::get_stored_style() const +const StyleManager::Style *StyleManager::get_stored_style() const { - if (m_style_cache.style_index >= m_style_items.size()) return nullptr; - return &m_style_items[m_style_cache.style_index].style; + if (m_style_cache.style_index >= m_styles.size()) return nullptr; + return &m_styles[m_style_cache.style_index]; } void StyleManager::clear_glyphs_cache() @@ -308,44 +310,11 @@ ImFont *StyleManager::get_imgui_font() return font; } -const std::vector &StyleManager::get_styles() const{ return m_style_items; } - -void StyleManager::make_unique_name(std::string &name) -{ - auto is_unique = [&](const std::string &name) -> bool { - for (const Item &it : m_style_items) - if (it.style.name == name) return false; - return true; - }; - - // Style name can't be empty so default name is set - if (name.empty()) name = "Text style"; - - // When name is already unique, nothing need to be changed - if (is_unique(name)) return; - - // when there is previous version of style name only find number - const char *prefix = " ("; - const char suffix = ')'; - auto pos = name.find_last_of(prefix); - if (name.c_str()[name.size() - 1] == suffix && - pos != std::string::npos) { - // short name by ord number - name = name.substr(0, pos); - } - - int order = 1; // start with value 2 to represents same font name - std::string new_name; - do { - new_name = name + prefix + std::to_string(++order) + suffix; - } while (!is_unique(new_name)); - name = new_name; -} - +const StyleManager::Styles &StyleManager::get_styles() const{ return m_styles; } void StyleManager::init_trunc_names(float max_width) { - for (auto &s : m_style_items) + for (auto &s : m_styles) if (s.truncated_name.empty()) { - std::string name = s.style.name; + std::string name = s.name; ImGuiWrapper::escape_double_hash(name); s.truncated_name = ImGuiWrapper::trunc(name, max_width); } @@ -378,9 +347,9 @@ void StyleManager::init_style_images(const Vec2i &max_size, StyleImagesData::Item &style = m_temp_style_images->styles[index]; // find style in font list and copy to it - for (auto &it : m_style_items) { - if (it.style.name != style.text || - !(it.style.prop == style.prop)) + for (auto &it : m_styles) { + if (it.name != style.text || + !(it.prop == style.prop)) continue; it.image = image; break; @@ -397,9 +366,8 @@ void StyleManager::init_style_images(const Vec2i &max_size, // create job for init images m_temp_style_images = std::make_shared(); StyleImagesData::Items styles; - styles.reserve(m_style_items.size()); - for (const Item &item : m_style_items) { - const EmbossStyle &style = item.style; + styles.reserve(m_styles.size()); + for (const Style &style : m_styles) { std::optional wx_font_opt = WxFontUtils::load_wxFont(style.path); if (!wx_font_opt.has_value()) continue; std::unique_ptr font_file = @@ -426,7 +394,7 @@ void StyleManager::init_style_images(const Vec2i &max_size, void StyleManager::free_style_images() { if (!m_exist_style_images) return; GLuint tex_id = 0; - for (Item &it : m_style_items) { + for (Style &it : m_styles) { if (tex_id == 0 && it.image.has_value()) tex_id = (GLuint)(intptr_t) it.image->texture_id; it.image.reset(); @@ -546,7 +514,7 @@ bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr FontFileWithCache(std::move(font_file)); EmbossStyle &style = m_style_cache.style; - style.type = WxFontUtils::get_actual_type(); + style.type = WxFontUtils::get_current_type(); // update string path style.path = WxFontUtils::store_wxFont(wx_font); WxFontUtils::update_property(style.prop, wx_font); @@ -664,51 +632,55 @@ bool read(const Section §ion, const std::string &key, std::optional & return true; } -std::optional load_style(const Section &app_cfg_section) +std::optional load_style(const Section &app_cfg_section) { auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR); if (path_it == app_cfg_section.end()) return {}; - const std::string &path = path_it->second; - - auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME); + + StyleManager::Style s; + EmbossProjection& ep = s.projection; + FontProp& fp = s.prop; + + s.path = path_it->second; + s.type = WxFontUtils::get_current_type(); + auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME); const std::string default_name = "font_name"; - const std::string &name = (name_it == app_cfg_section.end()) ? default_name : name_it->second; + s.name = (name_it == app_cfg_section.end()) ? default_name : name_it->second; - FontProp fp; + float depth; read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm); - read(app_cfg_section, APP_CONFIG_FONT_DEPTH, fp.emboss); - read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, fp.use_surface); + read(app_cfg_section, APP_CONFIG_FONT_DEPTH, depth);ep.depth = depth; + read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, ep.use_surface); read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness); read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew); - read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, fp.distance); - read(app_cfg_section, APP_CONFIG_FONT_ANGLE, fp.angle); + read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, s.distance); + read(app_cfg_section, APP_CONFIG_FONT_ANGLE, s.angle); read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number); read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap); read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap); - - EmbossStyle::Type type = WxFontUtils::get_actual_type(); - return EmbossStyle{name, path, type, fp}; + return s; } -void store_style(AppConfig &cfg, const EmbossStyle &fi, unsigned index) +void store_style(AppConfig &cfg, const StyleManager::Style &s, unsigned index) { + const EmbossProjection &ep = s.projection; Section data; - data[APP_CONFIG_FONT_NAME] = fi.name; - data[APP_CONFIG_FONT_DESCRIPTOR] = fi.path; - const FontProp &fp = fi.prop; + data[APP_CONFIG_FONT_NAME] = s.name; + data[APP_CONFIG_FONT_DESCRIPTOR] = s.path; + const FontProp &fp = s.prop; data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm); - data[APP_CONFIG_FONT_DEPTH] = std::to_string(fp.emboss); - if (fp.use_surface) + data[APP_CONFIG_FONT_DEPTH] = std::to_string(ep.depth); + if (ep.use_surface) data[APP_CONFIG_FONT_USE_SURFACE] = "true"; if (fp.boldness.has_value()) data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness); if (fp.skew.has_value()) data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew); - if (fp.distance.has_value()) - data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*fp.distance); - if (fp.angle.has_value()) - data[APP_CONFIG_FONT_ANGLE] = std::to_string(*fp.angle); + if (s.distance.has_value()) + data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*s.distance); + if (s.angle.has_value()) + data[APP_CONFIG_FONT_ANGLE] = std::to_string(*s.angle); if (fp.collection_number.has_value()) data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number); if (fp.char_gap.has_value()) @@ -718,7 +690,7 @@ void store_style(AppConfig &cfg, const EmbossStyle &fi, unsigned index) cfg.set_section(create_section_name(index), std::move(data)); } -void store_style_index(AppConfig &cfg, unsigned index) +void store_style_index(AppConfig &cfg, size_t index) { // store actual font index // active font first index is +1 to correspond with section name @@ -742,29 +714,34 @@ std::optional load_style_index(const AppConfig &cfg) return active_font - 1; } -EmbossStyles load_styles(const AppConfig &cfg) +::StyleManager::Styles load_styles(const AppConfig &cfg) { - EmbossStyles result; + StyleManager::Styles result; // human readable index inside of config starts from 1 !! unsigned index = 1; std::string section_name = create_section_name(index); while (cfg.has_section(section_name)) { - std::optional style_opt = load_style(cfg.get_section(section_name)); - if (style_opt.has_value()) + std::optional style_opt = load_style(cfg.get_section(section_name)); + if (style_opt.has_value()) { + make_unique_name(result, style_opt->name); result.emplace_back(*style_opt); + } + section_name = create_section_name(++index); } return result; } -void store_styles(AppConfig &cfg, const EmbossStyles &styles) +void store_styles(AppConfig &cfg, const StyleManager::Styles &styles) { + EmbossStyle::Type current_type = WxFontUtils::get_current_type(); // store styles unsigned index = 1; - for (const EmbossStyle &style : styles) { + for (const StyleManager::Style &style : styles) { // skip file paths + fonts from other OS(loaded from .3mf) - assert(style.type == WxFontUtils::get_actual_type()); - // if (style_opt.type != WxFontUtils::get_actual_type()) continue; + assert(style.type == current_type); + if (style.type != current_type) + continue; store_style(cfg, style, index); ++index; } @@ -778,4 +755,36 @@ void store_styles(AppConfig &cfg, const EmbossStyles &styles) } } +void make_unique_name(const StyleManager::Styles& styles, std::string &name) +{ + auto is_unique = [&styles](const std::string &name){ + for (const StyleManager::Style &it : styles) + if (it.name == name) return false; + return true; + }; + + // Style name can't be empty so default name is set + if (name.empty()) name = "Text style"; + + // When name is already unique, nothing need to be changed + if (is_unique(name)) return; + + // when there is previous version of style name only find number + const char *prefix = " ("; + const char suffix = ')'; + auto pos = name.find_last_of(prefix); + if (name.c_str()[name.size() - 1] == suffix && + pos != std::string::npos) { + // short name by ord number + name = name.substr(0, pos); + } + + int order = 1; // start with value 2 to represents same font name + std::string new_name; + do { + new_name = name + prefix + std::to_string(++order) + suffix; + } while (!is_unique(new_name)); + name = new_name; +} + } // namespace diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index 5bab7adce5..492d3f51c4 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -24,11 +24,10 @@ namespace Slic3r::GUI::Emboss { class StyleManager { friend class CreateFontStyleImagesJob; // access to StyleImagesData - public: /// Character to load for imgui when initialize imgui font /// Function to create default styles - StyleManager(const ImWchar *language_glyph_range, std::function create_default_styles); + StyleManager(const ImWchar *language_glyph_range, const std::function& create_default_styles); /// /// Release imgui font and style images from GPU @@ -59,11 +58,11 @@ public: void add_style(const std::string& name); /// - /// Change order of style item in m_style_items. + /// Change order of style item in m_styles. /// Fix selected font index when (i1 || i2) == m_font_selected /// - /// First index to m_style_items - /// Second index to m_style_items + /// First index to m_styles + /// Second index to m_styles void swap(size_t i1, size_t i2); /// @@ -73,7 +72,7 @@ public: void discard_style_changes(); /// - /// Remove style from m_style_items. + /// Remove style from m_styles. /// Fix selected font index when index is under m_font_selected /// /// Index of style to be removed @@ -94,13 +93,14 @@ public: /// Change active font /// When font not loaded roll back activ font /// - /// New font index(from m_style_items range) + /// New font index(from m_styles range) /// True on succes. False on fail load font bool load_style(size_t font_index); // load font style not stored in list - bool load_style(const EmbossStyle &style); + struct Style; + bool load_style(const Style &style); // fastering load font on index by wxFont, ignore type and descriptor - bool load_style(const EmbossStyle &style, const wxFont &font); + bool load_style(const Style &style, const wxFont &font); // clear actual selected glyphs cache void clear_glyphs_cache(); @@ -109,10 +109,10 @@ public: void clear_imgui_font(); // getters for private data - const EmbossStyle *get_stored_style() const; + const Style *get_stored_style() const; - const EmbossStyle &get_style() const { return m_style_cache.style; } - EmbossStyle &get_style() { return m_style_cache.style; } + const Style &get_style() const { return m_style_cache.style; } + Style &get_style() { return m_style_cache.style; } size_t get_style_index() const { return m_style_cache.style_index; } std::string &get_truncated_name() { return m_style_cache.truncated_name; } const ImFontAtlas &get_atlas() const { return m_style_cache.atlas; } @@ -133,6 +133,8 @@ public: /// bool is_font_changed() const; + bool is_unique_style_name(const std::string &name) const; + /// /// Setter on wx_font when changed /// @@ -167,33 +169,28 @@ public: void init_style_images(const Vec2i& max_size, const std::string &text); void free_style_images(); - struct Item; // access to all managed font styles - const std::vector &get_styles() const; + const std::vector