diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 4f6fb9d963..90407d4886 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)); } @@ -1200,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 @@ -1322,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; @@ -1340,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); @@ -1887,12 +1887,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..ab100f2ad4 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 @@ -1316,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; @@ -1330,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)); @@ -2166,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; @@ -2175,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 @@ -2186,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 @@ -2297,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); @@ -2600,34 +2659,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 +2673,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 +2684,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 +2693,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 +2728,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 +2765,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 +2796,471 @@ 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)) { + 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; + 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)) { + 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; + 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)){ + 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 = 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; + 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); + } else if (is_last_change){ + m_parent.do_rotate(rotation_snapshot_name); + } + } + + // 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 +3547,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 +3589,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 @@ -3368,7 +3607,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(); @@ -3376,15 +3616,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..1671b28a65 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 /// @@ -114,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(); @@ -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..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 { @@ -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]; @@ -1020,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()) @@ -1044,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 @@ -1053,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); } @@ -1299,10 +1298,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 +1305,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 +1488,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 +1503,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 +1579,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..5923450cb3 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 = {}; @@ -116,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; }; /// @@ -203,6 +215,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 +252,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