From 108e201ec7c07a810952ae4a09da1d7ba44c1864 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 23 Jan 2024 11:11:19 +0100 Subject: [PATCH 1/7] SPE-2117 Fix embossing text PerGlyph stored in style (initialize per glyph on backend) Separate draw of advanced input. Fix generate of preview rotation(with "per glyph" or "use surface") Fix per glyph orientation is calculated from glyph width. (not fixed 5mm - better result on edges) Add Cancel job before check that value is changed. Remove "FontPoint" units from GUI (use mm or in) Change range for boldness values --- src/libslic3r/Emboss.cpp | 37 +- src/libslic3r/Emboss.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 884 ++++++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 25 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp | 55 +- src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp | 5 + src/slic3r/GUI/Jobs/EmbossJob.cpp | 94 +-- src/slic3r/GUI/Jobs/EmbossJob.hpp | 25 +- src/slic3r/GUI/TextLines.cpp | 145 ++-- src/slic3r/GUI/TextLines.hpp | 27 +- 10 files changed, 788 insertions(+), 517 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 4f6fb9d963..708e768000 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -775,14 +775,13 @@ const Glyph* get_glyph( unsigned int font_index = font_prop.collection_number.value_or(0); if (!is_valid(font, font_index)) return nullptr; - if (!font_info_opt.has_value()) { - - font_info_opt = load_font_info(font.data->data(), font_index); + if (!font_info_opt.has_value()) { + font_info_opt = load_font_info(font.data->data(), font_index); // can load font info? if (!font_info_opt.has_value()) return nullptr; } - float flatness = font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm; + float flatness = font.infos[font_index].unit_per_em / font_prop.size_in_mm * RESOLUTION; // Fix for very small flatness because it create huge amount of points from curve if (flatness < RESOLUTION) flatness = RESOLUTION; @@ -1066,11 +1065,10 @@ std::unique_ptr Emboss::create_font_file( int ascent, descent, linegap; stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap); - float pixels = 1000.; // value is irelevant - float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); - int units_per_em = static_cast(std::round(pixels / em_pixels)); - - infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em}); + float pixels = 1000.; // value is irelevant + float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); + int unit_per_em = static_cast(std::round(pixels / em_pixels)); + infos.emplace_back(FontFile::Info{ascent, descent, linegap, unit_per_em}); } return std::make_unique(std::move(data), std::move(infos)); } @@ -1887,12 +1885,27 @@ double Emboss::calculate_angle(int32_t distance, PolygonPoint polygon_point, con return std::atan2(norm_d.y(), norm_d.x()); } -std::vector Emboss::calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon) +std::vector Emboss::calculate_angles(const BoundingBoxes &glyph_sizes, const PolygonPoints& polygon_points, const Polygon &polygon) { + const int32_t default_distance = static_cast(std::round(scale_(5.))); + const int32_t min_distance = static_cast(std::round(scale_(.1))); + std::vector result; result.reserve(polygon_points.size()); - for(const PolygonPoint& pp: polygon_points) - result.emplace_back(calculate_angle(distance, pp, polygon)); + assert(glyph_sizes.size() == polygon_points.size()); + if (glyph_sizes.size() != polygon_points.size()) { + // only backup solution should not be used + for (const PolygonPoint &pp : polygon_points) + result.emplace_back(calculate_angle(default_distance, pp, polygon)); + return result; + } + + for (size_t i = 0; i < polygon_points.size(); i++) { + int32_t distance = glyph_sizes[i].size().x() / 2; + if (distance < min_distance) // too small could lead to false angle + distance = default_distance; + result.emplace_back(calculate_angle(distance, polygon_points[i], polygon)); + } return result; } diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index ded2196a7a..d629b3a4ef 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -282,8 +282,6 @@ namespace Emboss class IProjection : public IProject3d { public: - virtual ~IProjection() = default; - /// /// convert 2d point to 3d points /// @@ -461,7 +459,11 @@ namespace Emboss /// Polygon know neighbor of point /// angle(atan2) of normal in polygon point double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); - std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); + std::vector calculate_angles( + const BoundingBoxes &glyph_sizes, + const PolygonPoints &polygon_points, + const Polygon &polygon + ); } // namespace Emboss diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 612a25f364..66d3570fe2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -96,7 +96,7 @@ static const struct Limits { MinMax emboss{0.01, 1e4}; // in mm MinMax size_in_mm{0.1f, 1000.f}; // in mm - Limit boldness{{-.5f, .5f}, {-5e5f, 5e5f}}; // in font points + Limit boldness{{-.1f, .1f}, {-5e5f, 5e5f}}; // in font points Limit skew{{-1.f, 1.f}, {-100.f, 100.f}}; // ration without unit MinMax char_gap{-20000, 20000}; // in font points MinMax line_gap{-20000, 20000}; // in font points @@ -121,7 +121,6 @@ std::unique_ptr create_emboss_data_base( const Selection& selection, ModelVolumeType type, std::shared_ptr>& cancel); -CreateVolumeParams create_input(GLCanvas3D &canvas, const StyleManager::Style &style, RaycastManager &raycaster, ModelVolumeType volume_type); /// /// Move window for edit emboss text near to embossed object @@ -135,7 +134,22 @@ struct TextDataBase : public DataBase TextConfiguration &&text_configuration, const EmbossProjection& projection); // Create shape from text + font configuration EmbossShape &create_shape() override; + + /// + /// Write text property into volume + /// + /// Place to write Text emboss data void write(ModelVolume &volume) const override; + + /// + /// Used only with text for embossing per glyph. + /// Create text lines only for new added volume to object + /// otherwise textline is already setted before + /// + /// Embossed volume final transformation in object + /// Volumes to be sliced to text lines + /// True on succes otherwise False(Per glyph shoud be disabled) + bool create_text_lines(const Transform3d &tr, const ModelVolumePtrs &vols) override; private: // Keep pointer on Data of font (glyph shapes) @@ -326,10 +340,8 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous if (!init_create(volume_type)) return false; - // NOTE: change style manager - be carefull with order changes - DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), volume_type, m_job_cancel); - 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); + CreateVolumeParams input = create_input(volume_type); + return start_create_volume(input, mouse_pos); } // Designed for create volume without information of mouse in scene @@ -338,12 +350,23 @@ bool GLGizmoEmboss::create_volume(ModelVolumeType volume_type) if (!init_create(volume_type)) return false; - // NOTE: change style manager - be carefull with order changes - DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, m_parent.get_selection(), volume_type, m_job_cancel); - 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)); + CreateVolumeParams input = create_input(volume_type); + return start_create_volume_without_position(input); } +CreateVolumeParams GLGizmoEmboss::create_input(ModelVolumeType volume_type) +{ + // NOTE: change style manager - be carefull with order changes + DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, + m_parent.get_selection(), volume_type, m_job_cancel); + + const StyleManager::Style &style = m_style_manager.get_style(); + auto gizmo = static_cast(GLGizmosManager::Emboss); + const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); + Plater *plater = wxGetApp().plater(); + return CreateVolumeParams{std::move(base), m_parent, plater->get_camera(), plater->build_volume(), + plater->get_ui_job_worker(), volume_type, m_raycast_manager, gizmo, gl_volume, style.distance, style.angle}; +} void GLGizmoEmboss::on_shortcut_key() { set_volume_by_selection(); @@ -387,6 +410,7 @@ bool GLGizmoEmboss::re_emboss(const ModelVolume &text_volume, std::shared_ptrvolumes; @@ -495,6 +519,10 @@ bool GLGizmoEmboss::init_create(ModelVolumeType volume_type) m_style_manager.discard_style_changes(); + // It should be already free when create new volume + assert(!m_text_lines.is_init()); + m_text_lines.reset(); // remove not current text lines + // set default text m_text = _u8L("Embossed text"); return true; @@ -1109,7 +1137,11 @@ std::optional get_installed_face_name(const std::optional } void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* const*/ StyleManager &style_manager, unsigned count_lines) -{ +{ + text_lines.reset(); + if (selection.is_empty()) + return; + const GLVolume *gl_volume_ptr = selection.get_first_volume(); if (gl_volume_ptr == nullptr) return; @@ -1119,6 +1151,7 @@ void init_text_lines(TextLinesModel &text_lines, const Selection& selection, /* if (mv_ptr == nullptr) return; const ModelVolume &mv = *mv_ptr; + if (mv.is_the_only_one_part()) return; @@ -1282,6 +1315,7 @@ void GLGizmoEmboss::reset_volume() m_volume = nullptr; m_volume_id.id = 0; + m_text_lines.reset(); // No more need of current notification remove_notification_not_valid_font(); @@ -1290,7 +1324,7 @@ void GLGizmoEmboss::reset_volume() void GLGizmoEmboss::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 { + auto calc = [&to_world_linear](const Vec3d &axe, std::optional& scale) { Vec3d axe_world = to_world_linear * axe; double norm_sq = axe_world.squaredNorm(); if (is_approx(norm_sq, 1.)) { @@ -1305,6 +1339,7 @@ void GLGizmoEmboss::calculate_scale() { }; bool exist_change = calc(Vec3d::UnitY(), m_scale_height); + exist_change |= calc(Vec3d::UnitX(), m_scale_width); exist_change |= calc(Vec3d::UnitZ(), m_scale_depth); // Change of scale has to change font imgui font size @@ -2600,34 +2635,11 @@ void GLGizmoEmboss::draw_depth(bool use_inch) } } -bool GLGizmoEmboss::rev_slider(const std::string &name, - std::optional& value, - const std::optional *default_value, - const std::string &undo_tooltip, - int v_min, - int v_max, - const std::string& format, - const wxString &tooltip) const -{ - auto draw_slider_optional_int = [&]() -> bool { - float slider_offset = m_gui_cfg->advanced_input_offset; - float slider_width = m_gui_cfg->input_width; - ImGui::SameLine(slider_offset); - ImGui::SetNextItemWidth(slider_width); - return m_imgui->slider_optional_int( ("##" + name).c_str(), value, - v_min, v_max, format.c_str(), 1.f, false, tooltip); - }; - float undo_offset = ImGui::GetStyle().FramePadding.x; - return revertible(name, value, default_value, - undo_tooltip, undo_offset, draw_slider_optional_int); -} - bool GLGizmoEmboss::rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, const std::string &undo_tooltip, - float v_min, - float v_max, + const MinMax &min_max, const std::string& format, const wxString &tooltip) const { @@ -2637,7 +2649,7 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, ImGui::SameLine(slider_offset); ImGui::SetNextItemWidth(slider_width); return m_imgui->slider_optional_float(("##" + name).c_str(), value, - v_min, v_max, format.c_str(), 1.f, false, tooltip); + min_max.min, min_max.max, format.c_str(), 1.f, false, tooltip); }; float undo_offset = ImGui::GetStyle().FramePadding.x; return revertible(name, value, default_value, @@ -2648,8 +2660,7 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, float &value, const float *default_value, const std::string &undo_tooltip, - float v_min, - float v_max, + const MinMax &min_max, const std::string &format, const wxString &tooltip) const { @@ -2658,7 +2669,7 @@ bool GLGizmoEmboss::rev_slider(const std::string &name, float slider_width = m_gui_cfg->input_width; ImGui::SameLine(slider_offset); ImGui::SetNextItemWidth(slider_width); - return m_imgui->slider_float("##" + name, &value, v_min, v_max, + return m_imgui->slider_float("##" + name, &value, min_max.min, min_max.max, format.c_str(), 1.f, false, tooltip); }; float undo_offset = ImGui::GetStyle().FramePadding.x; @@ -2693,299 +2704,34 @@ void GLGizmoEmboss::draw_advanced() m_imgui->text_colored(ImGuiPureWrap::COL_GREY_DARK, ff_property); #endif // SHOW_FONT_FILE_PROPERTY - auto &tr = m_gui_cfg->translations; - - const StyleManager::Style *stored_style = nullptr; - if (m_style_manager.exist_stored_style()) - stored_style = m_style_manager.get_stored_style(); - - bool is_the_only_one_part = m_volume->is_the_only_one_part(); - bool can_use_surface = (m_volume->emboss_shape->projection.use_surface)? true : // already used surface must have option to uncheck - !is_the_only_one_part; - m_imgui->disabled_begin(!can_use_surface); - const bool *def_use_surface = stored_style ? - &stored_style->projection.use_surface : nullptr; - StyleManager::Style ¤t_style = m_style_manager.get_style(); - 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 (use_surface) - // when using surface distance is not used - current_style.distance.reset(); - process(); - } - m_imgui->disabled_end(); // !can_use_surface - - bool &per_glyph = font_prop.per_glyph; - bool can_use_per_glyph = (per_glyph) ? true : // already used surface must have option to uncheck - !is_the_only_one_part; - m_imgui->disabled_begin(!can_use_per_glyph); - const bool *def_per_glyph = stored_style ? &stored_style->prop.per_glyph : nullptr; - if (rev_checkbox(tr.per_glyph, per_glyph, def_per_glyph, - _u8L("Revert Transformation per glyph."))) { - if (per_glyph && !m_text_lines.is_init()) - reinit_text_lines(); - process(); - } else if (ImGui::IsItemHovered()) { - if (per_glyph) { - ImGui::SetTooltip("%s", _u8L("Set global orientation for whole text.").c_str()); - } else { - ImGui::SetTooltip("%s", _u8L("Set position and orientation per glyph.").c_str()); - if (!m_text_lines.is_init()) - reinit_text_lines(); - } - } else if (!per_glyph && m_text_lines.is_init()) - m_text_lines.reset(); - m_imgui->disabled_end(); // !can_use_per_glyph - - auto draw_align = [&align = font_prop.align, input_offset = m_gui_cfg->advanced_input_offset, &icons = m_icons]() { - bool is_change = false; - ImGui::SameLine(input_offset); - if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Center", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered)); - else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment").c_str()); - - ImGui::SameLine(); - if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment").c_str()); - ImGui::SameLine(); - if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered)); - else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; } - else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment").c_str()); - return is_change; - }; - const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr; - float undo_offset = ImGui::GetStyle().FramePadding.x; - if (revertible(tr.alignment, font_prop.align, def_align, _u8L("Revert alignment."), undo_offset, draw_align)) { - if (font_prop.per_glyph) - reinit_text_lines(m_text_lines.get_lines().size()); - // TODO: move with text in finalize to not change position - process(); - } - - // TRN EmbossGizmo: font units - std::string units = _u8L("points"); - std::string units_fmt = "%.0f " + units; - - // input gap between characters - auto def_char_gap = stored_style ? - &stored_style->prop.char_gap : nullptr; - - bool exist_change = false; - int half_ascent = font_info.ascent / 2; - int min_char_gap = -half_ascent; - int max_char_gap = half_ascent; - FontProp ¤t_prop = current_style.prop; - if (rev_slider(tr.char_gap, current_prop.char_gap, def_char_gap, _u8L("Revert gap between characters"), - min_char_gap, max_char_gap, units_fmt, _L("Distance between characters"))){ - // Condition prevent recalculation when insertint out of limits value by imgui input - const std::optional &volume_char_gap = m_volume->text_configuration->style.prop.char_gap; - if (!apply(current_prop.char_gap, 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; - } - } - bool last_change = false; - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - - // input gap between lines - bool is_multiline = get_count_lines(m_volume->text_configuration->text) > 1; // TODO: cache count lines - m_imgui->disabled_begin(!is_multiline); - auto def_line_gap = stored_style ? - &stored_style->prop.line_gap : nullptr; - 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 - const std::optional &volume_line_gap = m_volume->text_configuration->style.prop.line_gap; - if (!apply(current_prop.line_gap, 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(); - if (font_prop.per_glyph) - reinit_text_lines(m_text_lines.get_lines().size()); - exist_change = true; - } - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - m_imgui->disabled_end(); // !is_multiline - - // input boldness - auto def_boldness = stored_style ? - &stored_style->prop.boldness : nullptr; - int min_boldness = static_cast(font_info.ascent * limits.boldness.gui.min); - int max_boldness = static_cast(font_info.ascent * limits.boldness.gui.max); - if (rev_slider(tr.boldness, current_prop.boldness, def_boldness, _u8L("Undo boldness"), - min_boldness, max_boldness, units_fmt, _L("Tiny / Wide glyphs"))){ - const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; - if (!apply(current_prop.boldness, limits.boldness.values) || - !volume_boldness.has_value() || volume_boldness != current_prop.boldness) - exist_change = true; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - - // input italic - auto def_skew = stored_style ? - &stored_style->prop.skew : nullptr; - if (rev_slider(tr.skew_ration, current_prop.skew, def_skew, _u8L("Undo letter's skew"), - limits.skew.gui.min, limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ - const std::optional &volume_skew = m_volume->text_configuration->style.prop.skew; - if (!apply(current_prop.skew, limits.skew.values) || - !volume_skew.has_value() ||volume_skew != current_prop.skew) - exist_change = true; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) - last_change = true; - - // input surface distance - 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->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 the text to the model surface."); - bool is_moved = false; - if (use_inch) { - std::optional distance_inch; - if (distance.has_value()) distance_inch = (*distance * ObjectManipulation::mm_to_in); - std::optional def_distance_inch; - if (def_distance != nullptr) { - if (def_distance->has_value()) def_distance_inch = ObjectManipulation::mm_to_in * (*(*def_distance)); - def_distance = &def_distance_inch; - } - min_distance *= ObjectManipulation::mm_to_in; - 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()) { - distance = *distance_inch * ObjectManipulation::in_to_mm; - } else { - distance.reset(); - } - is_moved = true; - } - } else { - if (rev_slider(tr.from_surface, distance, def_distance, undo_move_tooltip, - min_distance, max_distance, "%.2f mm", move_tooltip)) is_moved = true; - } - - if (is_moved){ - if (font_prop.per_glyph){ - process(false); - } else { - do_local_z_move(m_parent.get_selection(), distance.value_or(.0f) - prev_distance); - } - } - - // Apply move to model(backend) - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - m_parent.do_move(move_snapshot_name); - if (font_prop.per_glyph) - process(); - } - - 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 - 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->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"), - limits.angle.min, limits.angle.max, u8"%.2f °", - _L("Rotate text Clock-wise."))) { - // convert back to radians and CCW - double angle_rad = -angle_deg * M_PI / 180.0; - Geometry::to_range_pi_pi(angle_rad); - - double diff_angle = angle_rad - angle; - do_local_z_rotate(m_parent.get_selection(), diff_angle); - - // calc angle after rotation - const Selection &selection = m_parent.get_selection(); - const GLVolume *gl_volume = get_selected_gl_volume(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_style().angle = calc_angle(selection); - - if (font_prop.per_glyph) - reinit_text_lines(m_text_lines.get_lines().size()); - - // recalculate for surface cut - if (use_surface || font_prop.per_glyph) - process(false); - } - - // Apply rotation on model (backend) - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - m_parent.do_rotate(rotation_snapshot_name); - - // recalculate for surface cut - if (use_surface || font_prop.per_glyph) - process(); - } - - // Keep up - lock button icon - if (!m_volume->is_the_only_one_part()) { - ImGui::SameLine(m_gui_cfg->lock_offset); - const IconManager::Icon &icon = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::activable); - const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_up ? IconType::lock_bold : IconType::unlock_bold, IconState::activable); - const IconManager::Icon &icon_disable = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::disabled); - if (button(icon, icon_hover, icon_disable)) - m_keep_up = !m_keep_up; - - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", (m_keep_up? - _u8L("Unlock the text's rotation when moving text along the object's surface."): - _u8L("Lock the text's rotation when moving text along the object's surface.") - ).c_str()); - } + draw_use_surface(); + draw_per_glyph(); + draw_align(); + draw_char_gap(); + draw_line_gap(); + draw_boldness(); + draw_skew(); + draw_surface_distance(); + draw_rotation(); // when more collection add selector if (ff.font_file->infos.size() > 1) { - ImGui::Text("%s", tr.collection.c_str()); + ImGui::Text("%s", m_gui_cfg->translations.collection.c_str()); ImGui::SameLine(m_gui_cfg->advanced_input_offset); ImGui::SetNextItemWidth(m_gui_cfg->input_width); - unsigned int selected = current_prop.collection_number.value_or(0); + std::optional &collection_number = m_style_manager.get_font_prop().collection_number; + + unsigned int selected = collection_number.value_or(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) current_prop.collection_number.reset(); - else current_prop.collection_number = i; - exist_change = true; - last_change = true; + if (i == 0) collection_number.reset(); + else collection_number = i; + + m_style_manager.clear_glyphs_cache(); + process(); } ImGui::PopID(); } @@ -2995,15 +2741,6 @@ void GLGizmoEmboss::draw_advanced() } } - if (exist_change || last_change) { - m_style_manager.clear_glyphs_cache(); - if (font_prop.per_glyph) - reinit_text_lines(); - else - m_text_lines.reset(); - process(last_change); - } - 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(); @@ -3035,6 +2772,453 @@ void GLGizmoEmboss::draw_advanced() #endif // ALLOW_DEBUG_MODE } +void GLGizmoEmboss::draw_use_surface(){ + bool can_use_surface = (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 std::string &tr_use_surface = m_gui_cfg->translations.use_surface; + bool &use_surface = m_style_manager.get_style().projection.use_surface; + const bool *def_use_surface = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->projection.use_surface : nullptr; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert using of model surface."); + if (rev_checkbox(tr_use_surface, use_surface, def_use_surface, undo_tooltip)) { + if (use_surface) + // when using surface distance is not used + m_style_manager.get_style().distance.reset(); + process(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("If checked,\n\ +model surface under the text's shape is shift in the embossing direction,\n\ +otherwise text is flat and you have to deal with distance from surface.").c_str()); + } + m_imgui->disabled_end(); // !can_use_surface +} + +void GLGizmoEmboss::draw_per_glyph() { + bool can_use_per_glyph = (m_volume->text_configuration->style.prop.per_glyph) ? + true : // already used surface must have option to uncheck + !m_volume->is_the_only_one_part(); + + m_imgui->disabled_begin(!can_use_per_glyph); + const std::string &tr_per_glyph = m_gui_cfg->translations.per_glyph; + bool &per_glyph = m_style_manager.get_font_prop().per_glyph; + const bool *def_per_glyph = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.per_glyph : nullptr; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert Transformation per glyph."); + if (rev_checkbox(tr_per_glyph, per_glyph, def_per_glyph, undo_tooltip)) { + if (per_glyph && !m_text_lines.is_init()) + reinit_text_lines(); // text line has to be initialized before process + process(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("If checked,\n\ +each letter(glyph) has an independent orthogonal projection,\n\ +otherwise, the whole text has the same orthogonal projection.").c_str()); + if (!per_glyph && !m_text_lines.is_init()) + reinit_text_lines(); // create text line preview on hover + } else if (!per_glyph && m_text_lines.is_init()) + m_text_lines.reset(); // remove text line preview when not hovered + m_imgui->disabled_end(); // !can_use_per_glyph +} + +void GLGizmoEmboss::draw_align() +{ + FontProp::Align &align = m_style_manager.get_font_prop().align; + auto draw_fnc = [&align, input_offset = m_gui_cfg->advanced_input_offset, &icons = m_icons]() { + struct HAlignItem{FontProp::HorizontalAlign align; IconType icon; std::string tooltip;}; + static const std::array h_aligns{{ + {FontProp::HorizontalAlign::left, IconType::align_horizontal_left, _CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment")}, + {FontProp::HorizontalAlign::center,IconType::align_horizontal_center,_CTX_utf8(L_CONTEXT("Center","Alignment"), "Alignment")}, + {FontProp::HorizontalAlign::right, IconType::align_horizontal_right, _CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment")} + }}; + struct VAlignItem{FontProp::VerticalAlign align; IconType icon; std::string tooltip;}; + static const std::array v_aligns{{ + {FontProp::VerticalAlign::top, IconType::align_vertical_top, _CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment")}, + {FontProp::VerticalAlign::center, IconType::align_vertical_center, _CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment")}, + {FontProp::VerticalAlign::bottom, IconType::align_vertical_bottom, _CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment")} + }}; + bool is_change = false; + float offset = input_offset; + for (const HAlignItem& h_align : h_aligns){ + ImGui::SameLine(offset); offset = .0f; + if (align.first==h_align.align) draw(get_icon(icons, h_align.icon, IconState::hovered)); + else if (draw_button(icons, h_align.icon)){ align.first=h_align.align; is_change=true; } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", h_align.tooltip.c_str()); + } + + for (const VAlignItem& v_align : v_aligns){ + ImGui::SameLine(); + if (align.second==v_align.align) draw(get_icon(icons, v_align.icon, IconState::hovered)); + else if (draw_button(icons, v_align.icon)){ align.second=v_align.align; is_change=true; } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", v_align.tooltip.c_str()); + } + return is_change; + }; + + const FontProp::Align * def_align = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.align : nullptr; + float undo_offset = ImGui::GetStyle().FramePadding.x; + const std::string &tr_alignment = m_gui_cfg->translations.alignment; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert alignment."); + if (revertible(tr_alignment, align, def_align, undo_tooltip, undo_offset, draw_fnc)) { + // align change origin for per glyph slice + if (m_style_manager.get_font_prop().per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + + // TODO: move with text in finalize to not change position + + process(); + } +} + +void GLGizmoEmboss::draw_char_gap() +{ + // input gap between characters + auto def_char_gap_font_point = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.char_gap : nullptr; + + const FontFile &ff = *m_style_manager.get_font_file_with_cache().font_file; + const FontProp &fp = m_style_manager.get_font_prop(); + const FontFile::Info &font_info = get_font_info(ff, fp); + + const std::string &tr_char_gap = m_gui_cfg->translations.char_gap; + std::optional &char_gap_font_point = m_style_manager.get_font_prop().char_gap; + + double font_point_to_volume_mm = fp.size_in_mm / (double)font_info.unit_per_em; + double font_point_to_world_mm = font_point_to_volume_mm * m_scale_width.value_or(1.f); + double scale = font_point_to_world_mm; + std::string units_fmt = "%.2f mm"; + + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + if (use_inch){ + scale *= ObjectManipulation::mm_to_in; + units_fmt = "%.2f in"; + } + + float char_gap = static_cast(char_gap_font_point.value_or(0) * scale); + float def_char_gap_value = def_char_gap_font_point ? + static_cast(def_char_gap_font_point->value_or(0) * scale) : 0.f; + float *def_char_gap = def_char_gap_font_point ? &def_char_gap_value : nullptr; + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert gap between characters"); + float max_char_gap = static_cast(font_info.ascent / 2. * scale); + MinMax min_max_char_gap{-max_char_gap, max_char_gap}; + // TRN - Tooltip above char gap slider + const wxString tooltip = _L("Additional distance between characters"); + bool exist_change = false; + if (rev_slider(tr_char_gap, char_gap, def_char_gap, undo_tooltip, + min_max_char_gap, units_fmt, tooltip)) { + char_gap_font_point = static_cast(std::round(char_gap / scale)); + if (*char_gap_font_point == 0) + char_gap_font_point.reset(); + + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_char_gap = m_volume->text_configuration->style.prop.char_gap; + apply(char_gap_font_point, limits.char_gap); + + // Condition prevent recalculation when value are same + if (volume_char_gap.value_or(0) != char_gap_font_point.value_or(0)) { + // char gap is stored inside of imgui font atlas + m_style_manager.clear_imgui_font(); + m_style_manager.clear_glyphs_cache(); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_line_gap() { + const FontFile &ff = *m_style_manager.get_font_file_with_cache().font_file; + const FontProp &fp = m_style_manager.get_font_prop(); + const FontFile::Info &font_info = get_font_info(ff, fp); + + double font_point_to_volume_mm = fp.size_in_mm / (double) font_info.unit_per_em; + double font_point_to_world_mm = font_point_to_volume_mm * m_scale_width.value_or(1.f); + double scale = font_point_to_world_mm; + std::string units_fmt = "%.2f mm"; + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + if (use_inch) { + scale *= ObjectManipulation::mm_to_in; + units_fmt = "%.2f in"; + } + + std::optional &line_gap_font_point = m_style_manager.get_font_prop().line_gap; + auto def_line_gap_font_point = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.line_gap : nullptr; + float def_line_gap_value = def_line_gap_font_point ? + static_cast(def_line_gap_font_point->value_or(0) * scale) : 0.f; + float *def_line_gap = def_line_gap_font_point ? &def_line_gap_value : nullptr; + float max_line_gap = static_cast(font_info.ascent * scale); + MinMax min_max_line_gap{-max_line_gap, max_line_gap}; + + // input gap between lines + bool is_multiline = get_count_lines(m_volume->text_configuration->text) > 1; + // TODO: cache count lines + + m_imgui->disabled_begin(!is_multiline); + + const std::string& tr_line_gap = m_gui_cfg->translations.line_gap; + float line_gap = static_cast(fp.line_gap.value_or(0) * scale); + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Revert gap between lines"); + // TRN - Tooltip above line gap slider + const wxString tooltip = _L("Additional distance between lines"); + + bool exist_change = false; + if (rev_slider(tr_line_gap, line_gap, def_line_gap, undo_tooltip, + min_max_line_gap, units_fmt, tooltip)) { + line_gap_font_point = static_cast(std::round(line_gap/scale)); + if (line_gap_font_point == 0) + line_gap_font_point.reset(); + + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_line_gap = m_volume->text_configuration->style.prop.line_gap; + apply(line_gap_font_point, limits.line_gap); + // Condition prevent recalculation when insert same value. e.g. over limits + if (volume_line_gap.value_or(0) != line_gap_font_point.value_or(0)) { + // line gap is planed to be stored inside of imgui font atlas + m_style_manager.clear_imgui_font(); + + // different line gap need to reinitialize text lines + if (fp.per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + m_imgui->disabled_end(); // !is_multiline + + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_boldness(){ + const std::string& tr_boldness = m_gui_cfg->translations.boldness; + + const FontFile &ff = *m_style_manager.get_font_file_with_cache().font_file; + const FontProp &fp = m_style_manager.get_font_prop(); + const FontFile::Info &font_info = get_font_info(ff, fp); + + double font_point_to_volume_mm = fp.size_in_mm / (double) font_info.unit_per_em; + double font_point_to_world_mm = font_point_to_volume_mm * m_scale_width.value_or(1.f); + double scale = font_point_to_world_mm; + std::string units_fmt = "%.2f mm"; + bool use_inch = wxGetApp().app_config->get_bool("use_inches"); + if (use_inch) { + scale *= ObjectManipulation::mm_to_in; + units_fmt = "%.2f in"; + } + + std::optional &boldness_font_point = m_style_manager.get_font_prop().boldness; + float boldness = boldness_font_point.value_or(0.f) * scale; + + auto def_boldness_font_point = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.boldness : nullptr; + float def_boldness_value = def_boldness_font_point ? + static_cast(def_boldness_font_point->value_or(0) * scale) : 0.f; + float *def_boldness = def_boldness_font_point ? &def_boldness_value : nullptr; + + float min_boldness = static_cast((double)font_info.ascent * limits.boldness.gui.min * scale); + float max_boldness = static_cast((double)font_info.ascent * limits.boldness.gui.max * scale); + MinMax min_max_boldness{min_boldness, max_boldness}; + + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Undo boldness"); + // TRN - Tooltip above line gap slider + const wxString tooltip = _L("Tiny / Wide glyphs"); + + bool exist_change = false; + if (rev_slider(tr_boldness, boldness, def_boldness, undo_tooltip, + min_max_boldness, units_fmt, tooltip)){ + + if (is_approx(boldness, 0.f)) + boldness_font_point.reset(); + else + boldness_font_point = static_cast(std::round(boldness / scale)); + + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; + apply(boldness_font_point, limits.boldness.values); + + if (!is_approx(boldness_font_point.value_or(0.f), volume_boldness.value_or(0.f))) { + // glyph shape is modified by boldness + m_style_manager.clear_glyphs_cache(); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_skew() +{ + const std::string tr_skew = m_gui_cfg->translations.skew_ration; + std::optional &skew = m_style_manager.get_font_prop().skew; + auto def_skew = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->prop.skew : nullptr; + + // TRN - Tooltip after mouse hover above reverting button (undo icon) + const std::string undo_tooltip = _u8L("Undo letter's skew"); + // TRN - Tooltip above skew ration slider + const wxString tooltip = _L("Italic strength ratio"); + bool exist_change = false; + if (rev_slider(tr_skew, skew, def_skew, undo_tooltip, + limits.skew.gui, "%.2f", tooltip)){ + apply(skew, limits.skew.values); + if (m_job_cancel != nullptr) m_job_cancel->store(true); + const std::optional &volume_skew = m_volume->text_configuration->style.prop.skew; + if (is_approx(skew.value_or(0.f), volume_skew.value_or(0.f))) { + // glyph shape is modified by skew + m_style_manager.clear_glyphs_cache(); + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (exist_change || is_last_change) + process(is_last_change); +} + +void GLGizmoEmboss::draw_surface_distance() +{ + const StyleManager::Style ¤t_style = m_style_manager.get_style(); + const EmbossProjection &projection = current_style.projection; + // input surface distance + bool allowe_surface_distance = + !projection.use_surface && + !m_volume->is_the_only_one_part(); + + std::optional &distance = m_style_manager.get_style().distance; + float prev_distance = distance.value_or(.0f); + float min_distance = static_cast(-2 * projection.depth); + float max_distance = static_cast(2 * projection.depth); + MinMax min_max_distance{min_distance, max_distance}; + auto def_distance = m_style_manager.exist_stored_style() ? + &m_style_manager.get_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 the text to the model surface."); + const std::string &tr_from_surface = m_gui_cfg->translations.from_surface; + bool is_moved = false; + if (use_inch) { + std::optional distance_inch; + if (distance.has_value()) distance_inch = (*distance * ObjectManipulation::mm_to_in); + std::optional def_distance_inch; + if (def_distance != nullptr) { + if (def_distance->has_value()) def_distance_inch = ObjectManipulation::mm_to_in * (*(*def_distance)); + def_distance = &def_distance_inch; + } + min_max_distance.min *= ObjectManipulation::mm_to_in; + min_max_distance.max *= ObjectManipulation::mm_to_in; + if (rev_slider(tr_from_surface, distance_inch, def_distance, undo_move_tooltip, min_max_distance, "%.3f in", move_tooltip)) { + if (distance_inch.has_value()) { + distance = *distance_inch * ObjectManipulation::in_to_mm; + } else { + distance.reset(); + } + is_moved = true; + } + } else { + if (rev_slider(tr_from_surface, distance, def_distance, undo_move_tooltip, + min_max_distance, "%.2f mm", move_tooltip)) is_moved = true; + } + + if (is_moved){ + if (current_style.prop.per_glyph) { + process(false); + } else { + do_local_z_move(m_parent.get_selection(), distance.value_or(.0f) - prev_distance); + } + } + + // Apply move to model(backend) + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + m_parent.do_move(move_snapshot_name); + if (current_style.prop.per_glyph) + process(); + } + + m_imgui->disabled_end(); // allowe_surface_distance +} + +void GLGizmoEmboss::draw_rotation() { + const std::string &tr_rotation = m_gui_cfg->translations.rotation; + + // 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 + float angle = m_style_manager.get_style().angle.value_or(0.f); + float angle_deg = static_cast(-angle * 180 / M_PI); + const auto def_angle_rad = m_style_manager.exist_stored_style() ? + &m_style_manager.get_stored_style()->angle : nullptr; + float def_angle_deg_val = def_angle_rad ? + static_cast(def_angle_rad->value_or(0.f) * -180 / M_PI) : 0.f; + float *def_angle_deg = def_angle_rad ? &def_angle_deg_val : nullptr; + const std::string undo_tooltip = _u8L("Undo rotation"); + // TRN - Tooltip above rotation slider + const wxString tooltip = _L("Rotate text Clock-wise."); + bool exist_change = false; + if (rev_slider(tr_rotation, angle_deg, def_angle_deg, undo_tooltip, + limits.angle, u8"%.2f °", tooltip)) { + // convert back to radians and CCW + double angle_rad = -angle_deg * M_PI / 180.0; + Geometry::to_range_pi_pi(angle_rad); + + double diff_angle = angle_rad - angle; + if (!is_approx(diff_angle, 0.)) { + do_local_z_rotate(m_parent.get_selection(), diff_angle); + + // calc angle after rotation + const Selection &selection = m_parent.get_selection(); + const GLVolume *gl_volume = get_selected_gl_volume(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_style().angle = calc_angle(selection); + + exist_change = true; + } + } + bool is_last_change = m_imgui->get_last_slider_status().deactivated_after_edit; + if (is_last_change || exist_change) { + bool per_glyph = m_style_manager.get_font_prop().per_glyph; + if (per_glyph) + reinit_text_lines(m_text_lines.get_lines().size()); + + // recalculate for surface cut + if (m_style_manager.get_style().projection.use_surface || per_glyph) { + std::string no_snapshot; + m_parent.do_rotate(is_last_change? rotation_snapshot_name : no_snapshot); + process(is_last_change); + } + } + + // Keep up - lock button icon + if (!m_volume->is_the_only_one_part()) { + ImGui::SameLine(m_gui_cfg->lock_offset); + const IconManager::Icon &icon = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::activable); + const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_up ? IconType::lock_bold : IconType::unlock_bold, IconState::activable); + const IconManager::Icon &icon_disable = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::disabled); + if (button(icon, icon_hover, icon_disable)) + m_keep_up = !m_keep_up; + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", (m_keep_up? + _u8L("Unlock the text's rotation when moving text along the object's surface."): + _u8L("Lock the text's rotation when moving text along the object's surface.") + ).c_str()); + } +} + void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style) { ImVec2 window_size = ImGui::GetWindowSize(); @@ -3321,6 +3505,25 @@ void TextDataBase::write(ModelVolume &volume) const DataBase::write(volume); volume.text_configuration = m_text_configuration; // copy assert(volume.emboss_shape.has_value()); + + // Fix for object: stored attribute that volume is embossed per glyph when it is object + if (m_text_configuration.style.prop.per_glyph && volume.is_the_only_one_part()) + volume.text_configuration->style.prop.per_glyph = false; +} + +bool TextDataBase::create_text_lines(const Transform3d &tr, const ModelVolumePtrs &vols) { + // only when per glyph is on + if (!m_text_configuration.style.prop.per_glyph) + return false; + + unsigned count_lines = get_count_lines(m_text_configuration.text); + text_lines = Slic3r::Emboss::create_text_lines( + tr, vols, + *m_font_file.font_file, m_text_configuration.style.prop, count_lines); + if (text_lines.empty()) + m_text_configuration.style.prop.per_glyph = false; + + return !text_lines.empty(); } std::unique_ptr create_emboss_data_base(const std::string &text, @@ -3344,18 +3547,12 @@ std::unique_ptr create_emboss_data_base(const std::string return {}; // no active font in style, should never happend !!! } - const StyleManager::Style &style = style_manager.get_style(); + StyleManager::Style style = style_manager.get_style(); // copy // actualize font path - during changes in gui it could be corrupted // volume must store valid path assert(style_manager.get_wx_font().IsOk()); assert(style.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0); - if (style.prop.per_glyph) { - if (!text_lines.is_init()) - init_text_lines(text_lines, selection, style_manager); - } else - text_lines.reset(); - bool is_outside = (type == ModelVolumeType::MODEL_PART); // Cancel previous Job, when it is in process @@ -3376,15 +3573,6 @@ std::unique_ptr create_emboss_data_base(const std::string return std::make_unique(std::move(base), font, std::move(tc), style.projection); } -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); - Plater *plater = wxGetApp().plater(); - return CreateVolumeParams{canvas, plater->get_camera(), plater->build_volume(), - plater->get_ui_job_worker(), volume_type, raycaster, gizmo, gl_volume, style.distance, style.angle}; -} - ImVec2 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 f2187e46b1..94150de6d7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -30,6 +30,9 @@ namespace Slic3r{ class AppConfig; class GLVolume; enum class ModelVolumeType : int; + namespace GUI::Emboss { + struct CreateVolumeParams; + } } namespace Slic3r::GUI { @@ -64,7 +67,6 @@ public: /// True on success start job otherwise False bool do_mirror(size_t axis); - /// /// Call on change inside of object conatining projected volume /// @@ -139,6 +141,17 @@ private: bool draw_bold_button(); void draw_advanced(); + void draw_use_surface(); + void draw_per_glyph(); + void draw_align(); + void draw_char_gap(); + void draw_line_gap(); + void draw_boldness(); + void draw_skew(); + void draw_rotation(); + + void draw_surface_distance(); + bool select_facename(const wxString& facename); template bool rev_input_mm(const std::string &name, T &value, const T *default_value, @@ -152,12 +165,10 @@ private: template bool rev_input(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, T step, T step_fast, const char *format, ImGuiInputTextFlags flags = 0) const; bool rev_checkbox(const std::string &name, bool &value, const bool* default_value, const std::string &undo_tooltip) const; - bool rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, - const std::string &undo_tooltip, int v_min, int v_max, const std::string &format, const wxString &tooltip) const; bool rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, - const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const; + const std::string &undo_tooltip, const MinMax& min_max, const std::string &format, const wxString &tooltip) const; bool rev_slider(const std::string &name, float &value, const float *default_value, - const std::string &undo_tooltip, float v_min, float v_max, const std::string &format, const wxString &tooltip) const; + const std::string &undo_tooltip, const MinMax& min_max, const std::string &format, const wxString &tooltip) const; template bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw) const; @@ -176,6 +187,9 @@ private: void create_notification_not_valid_font(const std::string& text); void remove_notification_not_valid_font(); + // initialize data for create volume in job + Emboss::CreateVolumeParams create_input(ModelVolumeType volume_type); + struct GuiCfg; std::unique_ptr m_gui_cfg; @@ -234,6 +248,7 @@ private: // For text on scaled objects std::optional m_scale_height; std::optional m_scale_depth; + std::optional m_scale_width; void calculate_scale(); // drawing icons diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp index 7b55a99041..3c4fb8bff9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.cpp @@ -118,15 +118,6 @@ std::string get_file_name(const std::string &file_path); /// Name for volume std::string volume_name(const EmbossShape& shape); -/// -/// Create input for volume creation -/// -/// parent of gizmo -/// Keep scene -/// Type of volume to be created -/// Params -CreateVolumeParams create_input(GLCanvas3D &canvas, RaycastManager &raycaster, ModelVolumeType volume_type); - enum class IconType : unsigned { reset_value, reset_value_hover, @@ -198,33 +189,38 @@ struct GLGizmoSVG::GuiCfg: public ::GuiCfg{}; bool GLGizmoSVG::create_volume(ModelVolumeType volume_type, const Vec2d &mouse_pos) { - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type); - if (!base) return false; // Uninterpretable svg - return start_create_volume(input, std::move(base), mouse_pos); + CreateVolumeParams input = create_input(volume_type); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume(input, mouse_pos); } bool GLGizmoSVG::create_volume(ModelVolumeType volume_type) { - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel,volume_type); - if (!base) return false; // Uninterpretable svg - return start_create_volume_without_position(input, std::move(base)); + CreateVolumeParams input = create_input(volume_type); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume_without_position(input); } bool GLGizmoSVG::create_volume(std::string_view svg_file, ModelVolumeType volume_type){ - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_file); - if (!base) return false; // Uninterpretable svg - return start_create_volume_without_position(input, std::move(base)); + CreateVolumeParams input = create_input(volume_type, svg_file); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume_without_position(input); } bool GLGizmoSVG::create_volume(std::string_view svg_file, const Vec2d &mouse_pos, ModelVolumeType volume_type) { - CreateVolumeParams input = create_input(m_parent, m_raycast_manager, volume_type); - DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_file); - if (!base) return false; // Uninterpretable svg - return start_create_volume(input, std::move(base), mouse_pos); + CreateVolumeParams input = create_input(volume_type, svg_file); + if (!input.data) return false; // Uninterpretable svg + return start_create_volume(input, mouse_pos); +} + +CreateVolumeParams GLGizmoSVG::create_input(ModelVolumeType volume_type, std::string_view svg_filepath) { + DataBasePtr base = create_emboss_data_base(m_job_cancel, volume_type, svg_filepath); + auto gizmo = static_cast(GLGizmosManager::Svg); + const GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent); + Plater *plater = wxGetApp().plater(); + return CreateVolumeParams{std::move(base), m_parent, plater->get_camera(), plater->build_volume(), + plater->get_ui_job_worker(), volume_type, m_raycast_manager, gizmo, gl_volume}; } bool GLGizmoSVG::is_svg(const ModelVolume &volume) { @@ -2117,15 +2113,6 @@ std::string volume_name(const EmbossShape &shape) return "SVG shape"; } -CreateVolumeParams 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}; -} - GuiCfg create_gui_configuration() { GuiCfg cfg; // initialize by default values; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp index 42cd3fc007..24d8c7350b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSVG.hpp @@ -24,6 +24,9 @@ namespace Slic3r{ class ModelVolume; enum class ModelVolumeType : int; +namespace GUI::Emboss { +struct CreateVolumeParams; +} } namespace Slic3r::GUI { @@ -133,6 +136,8 @@ private: void volume_transformation_changed(); + Emboss::CreateVolumeParams create_input(ModelVolumeType volume_type, std::string_view svg_filepath = ""); + struct GuiCfg; std::unique_ptr m_gui_cfg; diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 1a72cd7dfc..c37e5f6a8c 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -538,11 +538,11 @@ const GLVolume *find_closest( /// /// Start job for add object with text into scene /// -/// Contain worker, build shape, gizmo -/// Define params for create volume +/// Contain worker, build shape, gizmo, +/// emboss_data is moved out soo it can't be const /// 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); +bool start_create_object_job(CreateVolumeParams &input, const Vec2d &coor); /// /// Start job to create volume on the surface of object @@ -553,7 +553,7 @@ bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss /// 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); +bool start_create_volume_on_surface_job(CreateVolumeParams &input, const Vec2d &screen_coor, bool try_no_coor); } // namespace @@ -568,25 +568,25 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &text_vo return ::create_sources(volumes, text_volume.id().id); } -bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos) +bool start_create_volume(CreateVolumeParams &input, const Vec2d &mouse_pos) { - if (data == nullptr) + if (input.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); + return ::start_create_object_job(input, mouse_pos); bool try_no_coor = true; - return ::start_create_volume_on_surface_job(input, std::move(data), mouse_pos, try_no_coor); + return ::start_create_volume_on_surface_job(input, mouse_pos, try_no_coor); } -bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr data) +bool start_create_volume_without_position(CreateVolumeParams &input) { - assert(data != nullptr); - if (data == nullptr) + assert(input.data != nullptr); + if (input.data == nullptr) return false; if (!check(input)) return false; @@ -604,17 +604,17 @@ bool start_create_volume_without_position(CreateVolumeParams &input, DataBasePtr 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); + return ::start_create_object_job(input, 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); + return ::start_create_object_job(input, screen_center); bool try_no_coor = false; - return ::start_create_volume_on_surface_job(input, std::move(data), coor, try_no_coor); + return ::start_create_volume_on_surface_job(input, coor, try_no_coor); } #ifdef EXECUTE_UPDATE_ON_MAIN_THREAD @@ -850,18 +850,13 @@ template TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w double depth = shape.projection.depth / shape.scale; auto scale_tr = Eigen::Scaling(shape.scale); - // half of font em size for direction of letter emboss - // double em_2_mm = prop.size_in_mm / 2.; // TODO: fix it - double em_2_mm = 5.; - int32_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); - size_t s_i_offset = 0; // shape index offset(for next lines) indexed_triangle_set result; for (size_t text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) { const BoundingBoxes &line_bbs = bbs[text_line_index]; const TextLine &line = input.text_lines[text_line_index]; PolygonPoints samples = sample_slice(line, line_bbs, shape.scale); - std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + std::vector angles = calculate_angles(line_bbs, samples, line.polygon); for (size_t i = 0; i < line_bbs.size(); ++i) { const BoundingBox &letter_bb = line_bbs[i]; @@ -1299,10 +1294,6 @@ TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &in assert(get_count_lines(es.shapes_with_ids) == input1.text_lines.size()); size_t count_lines = input1.text_lines.size(); std::vector bbs = create_line_bounds(es.shapes_with_ids, count_lines); - - // half of font em size for direction of letter emboss - double em_2_mm = 5.; // TODO: fix it - int32_t em_2_polygon = static_cast(std::round(scale_(em_2_mm))); size_t s_i_offset = 0; // shape index offset(for next lines) indexed_triangle_set result; @@ -1310,7 +1301,7 @@ TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &in const BoundingBoxes &line_bbs = bbs[text_line_index]; const TextLine &line = input1.text_lines[text_line_index]; PolygonPoints samples = sample_slice(line, line_bbs, es.scale); - std::vector angles = calculate_angles(em_2_polygon, samples, line.polygon); + std::vector angles = calculate_angles(line_bbs, samples, line.polygon); for (size_t i = 0; i < line_bbs.size(); ++i) { const BoundingBox &glyph_bb = line_bbs[i]; @@ -1493,11 +1484,11 @@ const GLVolume *find_closest( return closest; } -bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss_data, const Vec2d &coor) +bool start_create_object_job(CreateVolumeParams &input, const Vec2d &coor) { 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, input.angle}; + DataCreateObject data{std::move(input.data), coor, input.camera, bed_shape, gizmo_type, input.angle}; // Fix: adding text on print bed with style containing use_surface if (data.base->shape.projection.use_surface) @@ -1508,53 +1499,70 @@ bool start_create_object_job(const CreateVolumeParams &input, DataBasePtr emboss return queue_job(input.worker, std::move(job)); } -bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr data, const Vec2d &screen_coor, bool try_no_coor) +namespace { +// for creation volume +ModelVolumePtrs prepare_volumes_to_slice(const ModelObject &mo) { + const ModelVolumePtrs &volumes = mo.volumes; + ModelVolumePtrs result; + result.reserve(volumes.size()); + for (ModelVolume *volume : volumes) { + // only part could be surface for volumes + if (!volume->is_model_part()) + continue; + + result.push_back(volume); + } + return result; +} +} // namespace + +bool start_create_volume_on_surface_job(CreateVolumeParams &input, const Vec2d &screen_coor, bool try_no_coor) { - auto on_bad_state = [&input, try_no_coor](DataBasePtr data_, const ModelObject *object = nullptr) { + auto on_bad_state = [&input, try_no_coor](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_)); + return start_create_volume_without_position(input); } else { // 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 (data_->shape.projection.use_surface) - data_->shape.projection.use_surface = false; + if (input.data->shape.projection.use_surface) + input.data->shape.projection.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); + return start_create_volume_job(input.worker, *object, {}, std::move(input.data), input.volume_type, gizmo_type); } }; assert(input.gl_volume != nullptr); if (input.gl_volume == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); const Model *model = input.canvas.get_model(); assert(model != nullptr); if (model == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); 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)); + return on_bad_state(); const ModelInstance *instance = get_model_instance(*input.gl_volume, objects); assert(instance != nullptr); if (instance == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); const ModelObject *object = volume->get_object(); assert(object != nullptr); if (object == nullptr) - return on_bad_state(std::move(data)); + return on_bad_state(); auto cond = RaycastManager::AllowVolumes({volume->id().id}); RaycastManager::Meshes meshes = create_meshes(input.canvas, cond); @@ -1567,15 +1575,19 @@ bool start_create_volume_on_surface_job(CreateVolumeParams &input, DataBasePtr d 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 on_bad_state(std::move(data), object); + return on_bad_state(object); // Create result volume transformation Transform3d surface_trmat = create_transformation_onto_surface(hit->position, hit->normal, UP_LIMIT); apply_transformation(input.angle, input.distance, surface_trmat); Transform3d transform = instance->get_matrix().inverse() * surface_trmat; - auto gizmo_type = static_cast(input.gizmo); + auto gizmo_type = static_cast(input.gizmo); + + // Create text lines for Per Glyph projection when needed + input.data->create_text_lines(transform, prepare_volumes_to_slice(*object)); + // Try to cast ray into scene and find object for add volume - return start_create_volume_job(input.worker, *object, transform, std::move(data), input.volume_type, gizmo_type); + return start_create_volume_job(input.worker, *object, transform, std::move(input.data), input.volume_type, gizmo_type); } void create_message(const std::string &message) { diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index 46061f3bce..62c6f9a22b 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -65,6 +65,14 @@ public: // False (engraved).. move into object (NEGATIVE_VOLUME) bool is_outside = true; + /// + /// Used only with text for embossing per glyph + /// + /// Embossed volume final transformation in world + /// Volumes to be sliced to text lines + /// True on succes otherwise False(Per glyph shoud be disabled) + virtual bool create_text_lines(const Transform3d& tr, const ModelVolumePtrs &vols) { return false; } + // Define per letter projection on one text line // [optional] It is not used when empty Slic3r::Emboss::TextLines text_lines = {}; @@ -203,6 +211,10 @@ SurfaceVolumeData::ModelSources create_volume_sources(const ModelVolume &volume) /// struct CreateVolumeParams { + // base input data for job + // When nullptr there is some issue with creation params ... + DataBasePtr data; + GLCanvas3D &canvas; // Direction of ray into scene @@ -236,22 +248,15 @@ struct CreateVolumeParams /// /// Create new volume on position of mouse cursor /// -/// 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 +/// Cantain all needed data for start creation job /// True on success otherwise False -bool start_create_volume(CreateVolumeParams &input, DataBasePtr data, const Vec2d &mouse_pos); +bool start_create_volume(CreateVolumeParams &input, 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(CreateVolumeParams &input, DataBasePtr data); +bool start_create_volume_without_position(CreateVolumeParams &input); /// /// Start job for update embossed volume diff --git a/src/slic3r/GUI/TextLines.cpp b/src/slic3r/GUI/TextLines.cpp index 258d4916e4..56a84b6300 100644 --- a/src/slic3r/GUI/TextLines.cpp +++ b/src/slic3r/GUI/TextLines.cpp @@ -25,6 +25,14 @@ using namespace Slic3r::Emboss; using namespace Slic3r::GUI; namespace { + +// Used to move slice (text line) on place where is approx vertical center of text +// When copy value const double ASCENT_CENTER from Emboss.cpp and Vertical align is center than +// text line will cross object center +const double ascent_ratio_offset = 1 / 3.; + +double calc_line_height_in_mm(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); // return lineheight in mm + // Be careful it is not water tide and contain self intersections // It is only for visualization purposes indexed_triangle_set its_create_torus(const Slic3r::Polygon &polygon, float radius, size_t steps = 20) @@ -238,63 +246,13 @@ void TextLinesModel::init(const Transform3d &text_tr, const FontFile &ff = *ff_ptr; const FontProp &fp = style_manager.get_font_prop(); - FontProp::VerticalAlign align = fp.align.second; - - double line_height_mm = calc_line_height_in_mm(ff, fp); - assert(line_height_mm > 0); - if (line_height_mm <= 0) - return; - m_model.reset(); - m_lines.clear(); - // size_in_mm .. contain volume scale and should be ascent value in mm - double line_offset = fp.size_in_mm * ascent_ratio_offset; - double first_line_center = line_offset + get_align_y_offset_in_mm(align, count_lines, ff, fp); - std::vector line_centers(count_lines); - for (size_t i = 0; i < count_lines; ++i) - line_centers[i] = static_cast(first_line_center - i * line_height_mm); - - // contour transformation - Transform3d c_trafo = text_tr * get_rotation(); - Transform3d c_trafo_inv = c_trafo.inverse(); - - std::vector line_contours(count_lines); - for (const ModelVolume *volume : volumes_to_slice) { - MeshSlicingParams slicing_params; - slicing_params.trafo = c_trafo_inv * volume->get_matrix(); - for (size_t i = 0; i < count_lines; ++i) { - const Polygons polys = Slic3r::slice_mesh(volume->mesh().its, line_centers[i], slicing_params); - if (polys.empty()) - continue; - Polygons &contours = line_contours[i]; - contours.insert(contours.end(), polys.begin(), polys.end()); - } - } - - // fix for text line out of object - // When move text close to edge - line center could be out of object - for (Polygons &contours: line_contours) { - if (!contours.empty()) - continue; - - // use line center at zero, there should be some contour. - float line_center = 0.f; - for (const ModelVolume *volume : volumes_to_slice) { - MeshSlicingParams slicing_params; - slicing_params.trafo = c_trafo_inv * volume->get_matrix(); - const Polygons polys = Slic3r::slice_mesh(volume->mesh().its, line_center, slicing_params); - if (polys.empty()) - continue; - contours.insert(contours.end(), polys.begin(), polys.end()); - } - } - - m_lines = select_closest_contour(line_contours); - assert(m_lines.size() == count_lines); - assert(line_centers.size() == count_lines); - for (size_t i = 0; i < count_lines; ++i) - m_lines[i].y = line_centers[i]; + double line_height_mm; + m_lines = Slic3r::Emboss::create_text_lines( + text_tr, volumes_to_slice, ff, fp, count_lines, &line_height_mm); + if (m_lines.empty()) + return; bool is_mirrored = has_reflection(text_tr); float radius = static_cast(line_height_mm / 20.); @@ -346,9 +304,82 @@ void TextLinesModel::render(const Transform3d &text_world) shader->stop_using(); } -double TextLinesModel::calc_line_height_in_mm(const Slic3r::Emboss::FontFile &ff, const FontProp &fp) -{ +namespace { +double calc_line_height_in_mm(const Slic3r::Emboss::FontFile &ff, const FontProp &fp) { int line_height = Slic3r::Emboss::get_line_height(ff, fp); // In shape size double scale = Slic3r::Emboss::get_text_shape_scale(fp, ff); return line_height * scale; } +} // namespace + +Slic3r::Emboss::TextLines Slic3r::Emboss::create_text_lines( + const Transform3d &text_tr, + const ModelVolumePtrs &volumes_to_slice, + const FontFile &ff, + const FontProp &fp, + unsigned count_lines, + double *line_height_mm_ptr +) { + FontProp::VerticalAlign align = fp.align.second; + + double line_height_mm = calc_line_height_in_mm(ff, fp); + assert(line_height_mm > 0); + if (line_height_mm <= 0) + return {}; + + // size_in_mm .. contain volume scale and should be ascent value in mm + double line_offset = fp.size_in_mm * ascent_ratio_offset; + double first_line_center = line_offset + get_align_y_offset_in_mm(align, count_lines, ff, fp); + std::vector line_centers(count_lines); + for (size_t i = 0; i < count_lines; ++i) + line_centers[i] = static_cast(first_line_center - i * line_height_mm); + + // contour transformation + Transform3d c_trafo = text_tr * get_rotation(); + Transform3d c_trafo_inv = c_trafo.inverse(); + + std::vector line_contours(count_lines); + for (const ModelVolume *volume : volumes_to_slice) { + MeshSlicingParams slicing_params; + slicing_params.trafo = c_trafo_inv * volume->get_matrix(); + for (size_t i = 0; i < count_lines; ++i) { + const Polygons polys = + Slic3r::slice_mesh(volume->mesh().its, line_centers[i], slicing_params); + if (polys.empty()) + continue; + Polygons &contours = line_contours[i]; + contours.insert(contours.end(), polys.begin(), polys.end()); + } + } + + // fix for text line out of object + // When move text close to edge - line center could be out of object + for (Polygons &contours : line_contours) { + if (!contours.empty()) + continue; + + // use line center at zero, there should be some contour. + float line_center = 0.f; + for (const ModelVolume *volume : volumes_to_slice) { + MeshSlicingParams slicing_params; + slicing_params.trafo = c_trafo_inv * volume->get_matrix(); + const Polygons polys = + Slic3r::slice_mesh(volume->mesh().its, line_center, slicing_params); + if (polys.empty()) + continue; + contours.insert(contours.end(), polys.begin(), polys.end()); + } + } + + TextLines result = select_closest_contour(line_contours); + assert(result.size() == count_lines); + assert(line_centers.size() == count_lines); + // Fill centers + for (size_t i = 0; i < count_lines; ++i) + result[i].y = line_centers[i]; + + if (line_height_mm_ptr != nullptr) + *line_height_mm_ptr = line_height_mm; + + return result; +} \ No newline at end of file diff --git a/src/slic3r/GUI/TextLines.hpp b/src/slic3r/GUI/TextLines.hpp index f6e297cc22..2764e04693 100644 --- a/src/slic3r/GUI/TextLines.hpp +++ b/src/slic3r/GUI/TextLines.hpp @@ -11,6 +11,7 @@ namespace Slic3r { class ModelVolume; typedef std::vector ModelVolumePtrs; +struct FontProp; } namespace Slic3r::GUI { @@ -32,18 +33,30 @@ public: void reset() { m_model.reset(); m_lines.clear(); } const Slic3r::Emboss::TextLines &get_lines() const { return m_lines; } - static double calc_line_height_in_mm(const Slic3r::Emboss::FontFile& ff, const FontProp& fp); // return lineheight in mm private: Slic3r::Emboss::TextLines m_lines; // Keep model for visualization text lines GLModel m_model; - - // Used to move slice (text line) on place where is approx vertical center of text - // When copy value const double ASCENT_CENTER from Emboss.cpp and Vertical align is center than - // text line will cross object center - const double ascent_ratio_offset = 1/3.; }; - } // namespace Slic3r::GUI + +namespace Slic3r::Emboss{ +/// +/// creation line without model for backend only +/// +/// Transformation of text volume inside object (aka inside of +/// instance) Vector of volumes to be sliced Count lines of embossed +/// text(for veritcal alignment) [output] line height in +/// mm +TextLines create_text_lines( + const Transform3d &text_tr, + const ModelVolumePtrs &volumes_to_slice, + const FontFile &ff, + const FontProp &fp, + unsigned count_lines = 1, + double *line_height_mm_ptr = nullptr +); +} #endif // slic3r_TextLines_hpp_ \ No newline at end of file From 0767d56fb136740f52aeda31da5212564b59afd7 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 17 May 2024 09:27:11 +0200 Subject: [PATCH 2/7] Add volume transformation into Update volume data - which is used only for style change at the moment It will be fine to unify filnal volume transformation when it is used together with use surface --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 46 +++++++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 2 +- src/slic3r/GUI/Jobs/EmbossJob.cpp | 16 +++++---- src/slic3r/GUI/Jobs/EmbossJob.hpp | 4 +++ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 66d3570fe2..c62341225c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1351,8 +1351,7 @@ namespace { bool is_text_empty(std::string_view text) { return text.empty() || text.find_first_not_of(" \n\t\r") == std::string::npos; } } // namespace -bool GLGizmoEmboss::process(bool make_snapshot) -{ +bool GLGizmoEmboss::process(bool make_snapshot, std::optional volume_transformation) { // no volume is selected -> selection from right panel assert(m_volume != nullptr); if (m_volume == nullptr) return false; @@ -1365,7 +1364,7 @@ bool GLGizmoEmboss::process(bool make_snapshot) const Selection& selection = m_parent.get_selection(); DataBasePtr base = create_emboss_data_base(m_text, m_style_manager, m_text_lines, selection, m_volume->type(), m_job_cancel); - DataUpdate data{std::move(base), m_volume->id(), make_snapshot}; + DataUpdate data{std::move(base), m_volume->id(), make_snapshot, volume_transformation}; // check valid count of text lines assert(data.base->text_lines.empty() || data.base->text_lines.size() == get_count_lines(m_text)); @@ -2201,8 +2200,11 @@ void GLGizmoEmboss::draw_delete_style_button() { } 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) { +// When during change style is need to change transformation it calculate it +std::optional fix_transformation( + const StyleManager::Style &from, const StyleManager::Style &to, GLCanvas3D &canvas +) { + bool exist_transformation = false; // fix Z rotation when exists difference in styles const std::optional &f_angle_opt = from.angle; const std::optional &t_angle_opt = to.angle; @@ -2210,9 +2212,9 @@ void fix_transformation(const StyleManager::Style &from, const StyleManager::Sty // fix rotation float f_angle = f_angle_opt.value_or(.0f); float t_angle = t_angle_opt.value_or(.0f); + // Rotate only UI do_local_z_rotate(canvas.get_selection(), t_angle - f_angle); - std::string no_snapshot; - canvas.do_rotate(no_snapshot); + exist_transformation = true; } // fix distance (Z move) when exists difference in styles @@ -2221,10 +2223,33 @@ void fix_transformation(const StyleManager::Style &from, const StyleManager::Sty if (!is_approx(f_move_opt, t_move_opt)) { float f_move = f_move_opt.value_or(.0f); float t_move = t_move_opt.value_or(.0f); + // Move only UI do_local_z_move(canvas.get_selection(), t_move - f_move); - std::string no_snapshot; - canvas.do_move(no_snapshot); + exist_transformation = true; } + + if (!exist_transformation) + return std::nullopt; + + const GLVolume* gl_volume = canvas.get_selection().get_first_volume(); + assert(gl_volume != nullptr); + if (gl_volume == nullptr) + return std::nullopt; + + const Transform3d &tr = gl_volume->get_volume_transformation().get_matrix(); + + // exist fix matrix made by store to .3mf + const ModelVolume* volume = get_model_volume(*gl_volume, canvas.get_model()->objects); + assert(volume != nullptr); + if (volume == nullptr) + return tr; + 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()) + return tr * emboss_shape->fix_3mf_tr->inverse(); + + // no fix matrix just return transformation + return tr; } } // namesapce @@ -2332,8 +2357,7 @@ void GLGizmoEmboss::draw_style_list() { StyleManager::Style cur_s = current_style; // copy StyleManager::Style new_s = style; // copy if (m_style_manager.load_style(*selected_style_index)) { - ::fix_transformation(cur_s, new_s, m_parent); - process(); + process(true, ::fix_transformation(cur_s, new_s, m_parent)); } else { wxString title = _L("Not valid style."); wxString message = GUI::format_wxstr(_L("Style \"%1%\" can't be used and will be removed from a list."), style.name); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 94150de6d7..1671b28a65 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -116,7 +116,7 @@ private: void reset_volume(); // create volume from text - main functionality - bool process(bool make_snapshot = true); + bool process(bool make_snapshot = true, std::optional volume_transformation = std::nullopt); void close(); void draw_window(); void draw_text_input(); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index c37e5f6a8c..f86e436353 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -197,7 +197,7 @@ TriangleMesh create_default_mesh(); /// New mesh data /// Text configuration, ... /// Transformation of volume -void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr); +void final_update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr = nullptr); /// /// Update name in right panel @@ -414,7 +414,7 @@ void UpdateJob::finalize(bool canceled, std::exception_ptr &eptr) { if (!::finalize(canceled, eptr, *m_input.base)) return; - ::update_volume(std::move(m_result), m_input); + ::final_update_volume(std::move(m_result), m_input); } void UpdateJob::update_volume(ModelVolume *volume, TriangleMesh &&mesh, const DataBase &base) @@ -496,7 +496,7 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) // 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); + ::final_update_volume(std::move(m_result), m_input, &m_input.transform); } namespace { @@ -1015,7 +1015,7 @@ void update_name_in_list(const ObjectList& object_list, const ModelVolume& volum object_list.update_name_in_list(object_index, volume_index); } -void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr) +void final_update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3d *tr) { // for sure that some object will be created if (mesh.its.empty()) @@ -1039,7 +1039,12 @@ void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3 if (volume == nullptr) return; - if (tr) { + if (data.trmat.has_value()) { + assert(tr == nullptr); + tr = &(*data.trmat); + } + + if (tr != nullptr) { volume->set_transformation(*tr); } else { // apply fix matrix made by store to .3mf @@ -1048,7 +1053,6 @@ void update_volume(TriangleMesh &&mesh, const DataUpdate &data, const Transform3 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 62c6f9a22b..5923450cb3 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -124,6 +124,10 @@ struct DataUpdate // Used for prevent flooding Undo/Redo stack on slider. bool make_snapshot; + + // Transformation of volume after update volume shape + // NOTE: Add for style change, because it change rotation and distance from surface + std::optional trmat; }; /// From 0e1664be4092566aa0e85774d9bc8ae93ca600d5 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 17 May 2024 09:48:20 +0200 Subject: [PATCH 3/7] Fix disabling per_glyph --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index c62341225c..322f881fd2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -3589,7 +3589,8 @@ std::unique_ptr create_emboss_data_base(const std::string DataBase base(volume_name, cancel); base.is_outside = is_outside; - base.text_lines = text_lines.get_lines(); + if (style.prop.per_glyph) // lines are already created when hover checkbox per_glyph + base.text_lines = text_lines.get_lines(); base.from_surface = style.distance; FontFileWithCache &font = style_manager.get_font_file_with_cache(); From 117df630616cdce068ba220fab3279258eb60023 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 17 May 2024 10:55:09 +0200 Subject: [PATCH 4/7] Fix skew real time preview --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 322f881fd2..4424af6ff1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -3098,9 +3098,9 @@ void GLGizmoEmboss::draw_skew() apply(skew, limits.skew.values); if (m_job_cancel != nullptr) m_job_cancel->store(true); const std::optional &volume_skew = m_volume->text_configuration->style.prop.skew; - if (is_approx(skew.value_or(0.f), volume_skew.value_or(0.f))) { + if (!is_approx(skew.value_or(0.f), volume_skew.value_or(0.f))) { // glyph shape is modified by skew - m_style_manager.clear_glyphs_cache(); + m_style_manager.clear_glyphs_cache(); exist_change = true; } } From a37581242704d1db422ce119962c3f0d2ab4d905 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 17 May 2024 11:16:44 +0200 Subject: [PATCH 5/7] Fix multiple initialization of same cache from different threads --- src/libslic3r/Emboss.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 708e768000..90407d4886 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1198,15 +1198,13 @@ int Emboss::get_line_height(const FontFile &font, const FontProp &prop) { namespace { ExPolygons letter2shapes( - wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt& font_info_cache) -{ - assert(font_with_cache.has_value()); - if (!font_with_cache.has_value()) - return {}; - - Glyphs &cache = *font_with_cache.cache; - const FontFile &font = *font_with_cache.font_file; - + wchar_t letter, + Point &cursor, + const FontFile &font, + Glyphs &cache, + const FontProp &font_prop, + fontinfo_opt &font_info_cache +) { if (letter == '\n') { cursor.x() = 0; // 2d shape has opposit direction of y @@ -1320,12 +1318,16 @@ void align_shape(ExPolygonsWithIds &shapes, const std::wstring &text, const Font ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled){ assert(font_with_cache.has_value()); + if (!font_with_cache.has_value()) + return {}; + const FontFile &font = *font_with_cache.font_file; unsigned int font_index = font_prop.collection_number.value_or(0); if (!is_valid(font, font_index)) return {}; - unsigned counter = 0; + std::shared_ptr cache = font_with_cache.cache; // copy pointer + unsigned counter = CANCEL_CHECK-1; // it is needed to validate using of cache Point cursor(0, 0); fontinfo_opt font_info_cache; @@ -1338,7 +1340,7 @@ ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const return {}; } unsigned id = static_cast(letter); - result.push_back({id, letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)}); + result.push_back({id, letter2shapes(letter, cursor, font, *cache, font_prop, font_info_cache)}); } align_shape(result, text, font_prop, font); From 1a0a83f07a9b678291fac3104810fccef0f39ce0 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 17 May 2024 13:38:50 +0200 Subject: [PATCH 6/7] Fix infinit loop shape creation (char gap, line gap, boldness) appear when emboss job throw error - e.g. no volume (no symbol in font for current text: english text and Japanese font without english symbols) --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 30 +++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 4424af6ff1..ada950018a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -2936,9 +2936,15 @@ void GLGizmoEmboss::draw_char_gap() bool exist_change = false; if (rev_slider(tr_char_gap, char_gap, def_char_gap, undo_tooltip, min_max_char_gap, units_fmt, tooltip)) { - char_gap_font_point = static_cast(std::round(char_gap / scale)); - if (*char_gap_font_point == 0) + int char_gap_font_point_new = static_cast(std::round(char_gap / scale)); + if (char_gap_font_point_new == char_gap_font_point.value_or(0)) + // appear when emboss job throw error - e.g. no volume (no symbol font) + // -> cause infinit loop call of process() + return; // no change + else if (char_gap_font_point_new == 0) char_gap_font_point.reset(); + else + char_gap_font_point = char_gap_font_point_new; if (m_job_cancel != nullptr) m_job_cancel->store(true); const std::optional &volume_char_gap = m_volume->text_configuration->style.prop.char_gap; @@ -2997,9 +3003,15 @@ void GLGizmoEmboss::draw_line_gap() { bool exist_change = false; if (rev_slider(tr_line_gap, line_gap, def_line_gap, undo_tooltip, min_max_line_gap, units_fmt, tooltip)) { - line_gap_font_point = static_cast(std::round(line_gap/scale)); - if (line_gap_font_point == 0) + int line_gap_font_point_new = static_cast(std::round(line_gap / scale)); + if (line_gap_font_point_new == line_gap_font_point.value_or(0)) + // appear when emboss job throw error - e.g. no volume (no symbol font) + // -> cause infinit loop call of process() + return; // no change + else if (line_gap_font_point_new == 0) line_gap_font_point.reset(); + else + line_gap_font_point = line_gap_font_point_new; if (m_job_cancel != nullptr) m_job_cancel->store(true); const std::optional &volume_line_gap = m_volume->text_configuration->style.prop.line_gap; @@ -3060,11 +3072,15 @@ void GLGizmoEmboss::draw_boldness(){ bool exist_change = false; if (rev_slider(tr_boldness, boldness, def_boldness, undo_tooltip, min_max_boldness, units_fmt, tooltip)){ - - if (is_approx(boldness, 0.f)) + float boldness_font_point_new = static_cast(std::round(boldness / scale)); + if (is_approx(boldness_font_point_new, boldness_font_point.value_or(0.f))) + // appear when emboss job throw error - e.g. no volume (no symbol font) + // -> cause infinit loop call of process() + return; // no change + else if (is_approx(boldness_font_point_new, 0.f)) boldness_font_point.reset(); else - boldness_font_point = static_cast(std::round(boldness / scale)); + boldness_font_point = boldness_font_point_new; if (m_job_cancel != nullptr) m_job_cancel->store(true); const std::optional &volume_boldness = m_volume->text_configuration->style.prop.boldness; From ab7dbe364eb5accd42c560545375c355ae710c48 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 27 May 2024 18:53:19 +0200 Subject: [PATCH 7/7] Fix: rotation was not applied on model, when use rotation drag and do not use function per_glyph or use_surface --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index ada950018a..ab100f2ad4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -3239,6 +3239,8 @@ void GLGizmoEmboss::draw_rotation() { std::string no_snapshot; m_parent.do_rotate(is_last_change? rotation_snapshot_name : no_snapshot); process(is_last_change); + } else if (is_last_change){ + m_parent.do_rotate(rotation_snapshot_name); } }