PrusaSlicer/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
2023-03-13 20:03:03 +01:00

3419 lines
136 KiB
C++

#include "GLGizmoEmboss.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/MainFrame.hpp" // to update title when add text
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/CameraUtils.hpp"
#include "slic3r/GUI/Jobs/EmbossJob.hpp"
#include "slic3r/GUI/Jobs/CreateFontNameImageJob.hpp"
#include "slic3r/GUI/Jobs/NotificationProgressIndicator.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/Utils/EmbossGui.hpp"
// TODO: remove include
#include "libslic3r/SVG.hpp" // debug store
#include "libslic3r/Geometry.hpp" // covex hull 2d
#include "libslic3r/Timer.hpp"
#include "libslic3r/NSVGUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ClipperUtils.hpp" // union_ex
#include "libslic3r/AppConfig.hpp" // store/load font list
#include "libslic3r/Format/OBJ.hpp" // load obj file for default object
#include "libslic3r/BuildVolume.hpp"
#include "imgui/imgui_stdlib.h" // using std::string for inputs
#include "nanosvg/nanosvg.h" // load SVG file
#include <wx/font.h>
#include <wx/fontutil.h>
#include <wx/fontdlg.h>
#include <wx/fontenum.h>
#include <wx/display.h> // detection of change DPI
#include <boost/log/trivial.hpp>
#include <GL/glew.h>
#include <chrono> // measure enumeration of fonts
// uncomment for easier debug
//#define ALLOW_DEBUG_MODE
#ifdef ALLOW_DEBUG_MODE
#define ALLOW_ADD_FONT_BY_FILE
#define ALLOW_ADD_FONT_BY_OS_SELECTOR
#define SHOW_WX_FONT_DESCRIPTOR // OS specific descriptor | file path --> in edit style <tree header>
#define SHOW_FONT_FILE_PROPERTY // ascent, descent, line gap, cache --> in advanced <tree header>
#define SHOW_CONTAIN_3MF_FIX // when contain fix matrix --> show gray '3mf' next to close button
#define SHOW_OFFSET_DURING_DRAGGING // when drag with text over surface visualize used center
#define SHOW_IMGUI_ATLAS
#define SHOW_ICONS_TEXTURE
#define SHOW_FINE_POSITION // draw convex hull around volume
#define SHOW_WX_WEIGHT_INPUT
#define DRAW_PLACE_TO_ADD_TEXT // Interactive draw of window position
#define ALLOW_OPEN_NEAR_VOLUME
#define EXECUTE_PROCESS_ON_MAIN_THREAD // debug execution on main thread
#endif // ALLOW_DEBUG_MODE
//#define USE_PIXEL_SIZE_IN_WX_FONT
using namespace Slic3r;
using namespace Slic3r::Emboss;
using namespace Slic3r::GUI;
using namespace Slic3r::GUI::Emboss;
namespace priv {
template<typename T> struct Limit {
// Limitation for view slider range in GUI
MinMax<T> gui;
// Real limits for setting exacts values
MinMax<T> values;
};
// Variable keep limits for variables
static const struct Limits
{
MinMax<float> emboss{0.01f, 1e4f}; // in mm
MinMax<float> size_in_mm{0.1f, 1000.f}; // in mm
Limit<float> boldness{{-200.f, 200.f}, {-2e4f, 2e4f}}; // in font points
Limit<float> skew{{-1.f, 1.f}, {-100.f, 100.f}}; // ration without unit
MinMax<int> char_gap{-20000, 20000}; // in font points
MinMax<int> line_gap{-20000, 20000}; // in font points
// distance text object from surface
MinMax<float> angle{-180.f, 180.f}; // in degrees
} limits;
} // namespace priv
using namespace priv;
GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
: GLGizmoBase(parent, M_ICON_FILENAME, -2)
, m_volume(nullptr)
, m_is_unknown_font(false)
, m_rotate_gizmo(parent, GLGizmoRotate::Axis::Z) // grab id = 2 (Z axis)
, m_style_manager(m_imgui->get_glyph_ranges(), create_default_styles)
, m_job_cancel(nullptr)
{
m_rotate_gizmo.set_group_id(0);
m_rotate_gizmo.set_force_local_coordinate(true);
// TODO: add suggestion to use https://fontawesome.com/
// (copy & paste) unicode symbols from web
// paste HEX unicode into notepad move cursor after unicode press [alt] + [x]
}
// Private namespace with helper function for create volume
namespace priv {
/// <summary>
/// Check if volume type is possible use for new text volume
/// </summary>
/// <param name="volume_type">Type</param>
/// <returns>True when allowed otherwise false</returns>
static bool is_valid(ModelVolumeType volume_type);
/// <summary>
/// Prepare data for emboss
/// </summary>
/// <param name="text">Text to emboss</param>
/// <param name="style_manager">Keep actual selected style</param>
/// <param name="cancel">Cancel for previous job</param>
/// <returns>Base data for emboss text</returns>
static std::unique_ptr<DataBase> create_emboss_data_base(
const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>> &cancel);
/// <summary>
/// Data for emboss job to create shape
/// </summary>
struct TextDataBase : public DataBase
{
TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration);
// Create shape from text + font configuration
EmbossShape &create_shape() override;
void write(ModelVolume &volume) const override;
// private:
// Keep pointer on Data of font (glyph shapes)
FontFileWithCache font_file;
// font item is not used for create object
TextConfiguration text_configuration;
};
/// <summary>
/// Start job for add object with text into scene
/// </summary>
/// <param name="emboss_data">Define params of text</param>
/// <param name="coor">Screen coordinat, where to create new object laying on bed</param>
static void start_create_object_job(std::unique_ptr<DataBase> emboss_data, const Vec2d &coor);
/// <summary>
/// Start job for add new volume on surface of object defined by screen coor
/// </summary>
/// <param name="emboss_data">Define params of text</param>
/// <param name="volume_type">Emboss / engrave</param>
/// <param name="screen_coor">Mouse position which define position</param>
/// <param name="gl_volume">Volume to find surface for create</param>
/// <param name="raycaster">Ability to ray cast to model</param>
/// <param name="canvas">Contain already used scene RayCasters</param>
/// <returns>True when start creation, False when there is no hit surface by screen coor</returns>
static bool start_create_volume_on_surface_job(const FontProp &fp,
std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLCanvas3D &canvas);
// Loaded icons enum
// Have to match order of files in function GLGizmoEmboss::init_icons()
enum class IconType : unsigned {
rename = 0,
erase,
add,
save,
undo,
italic,
unitalic,
bold,
unbold,
system_selector,
open_file,
exclamation,
// automatic calc of icon's count
_count
};
// Define rendered version of icon
enum class IconState : unsigned { activable = 0, hovered /*1*/, disabled /*2*/ };
// selector for icon by enum
const IconManager::Icon &get_icon(const IconManager::VIcons& icons, IconType type, IconState state);
// short call of Slic3r::GUI::button
static bool draw_button(const IconManager::VIcons& icons, IconType type, bool disable = false);
} // namespace priv
void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mouse_pos)
{
if (!priv::is_valid(volume_type)) return;
m_style_manager.discard_style_changes();
set_default_text();
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
GLVolume *gl_volume = get_first_hovered_gl_volume(m_parent);
if (gl_volume == nullptr)
// object is not under mouse position soo create object on plater
return priv::start_create_object_job(std::move(emboss_data), mouse_pos);
const FontProp &fp = m_style_manager.get_style().prop;
if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, mouse_pos, *gl_volume, m_raycast_manager, m_parent))
return create_volume(volume_type);
}
// Designed for create volume without information of mouse in scene
void GLGizmoEmboss::create_volume(ModelVolumeType volume_type)
{
if (!priv::is_valid(volume_type)) return;
m_style_manager.discard_style_changes();
set_default_text();
// select position by camera position and view direction
const Selection &selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
Size s = m_parent.get_canvas_size();
Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.);
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
const ModelObjectPtrs &objects = selection.get_model()->objects;
// No selected object so create new object
if (selection.is_empty() || object_idx < 0 || static_cast<size_t>(object_idx) >= objects.size()) {
// create Object on center of screen
// when ray throw center of screen not hit bed it create object on center of bed
priv::start_create_object_job(std::move(emboss_data), screen_center);
return;
}
// create volume inside of selected object
Vec2d coor;
const Camera &camera = wxGetApp().plater()->get_camera();
const GLVolume *gl_volume = find_closest(selection, screen_center, camera, objects, &coor);
FontProp &fp = m_style_manager.get_style().prop;
if (gl_volume == nullptr) {
return priv::start_create_object_job(std::move(emboss_data), screen_center);
} else if (!priv::start_create_volume_on_surface_job(fp, std::move(emboss_data), volume_type, coor, *gl_volume, m_raycast_manager, m_parent)){
// In centroid of convex hull is not hit with object. e.g. torid
// soo create transfomation on border of object
// unique pointer is already destoyed need to create again
std::unique_ptr<DataBase> emboss_data = priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel);
// there is no point on surface so no use of surface will be applied
if (emboss_data->shape.use_surface)
emboss_data->shape.use_surface = false;
const ModelObject *object = get_model_object(*gl_volume, objects);
Worker &worker = wxGetApp().plater()->get_ui_job_worker();
Transform3d volume_trmat = create_volume_transformation(*gl_volume, objects, fp.size_in_mm, fp.emboss);
start_create_volume_job(worker, *object, volume_trmat, std::move(emboss_data), volume_type);
}
}
bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event)
{
if (mouse_event.Moving()) return false;
bool used = use_grabbers(mouse_event);
if (!m_dragging) return used;
if (mouse_event.Dragging()) {
if (!m_rotate_start_angle.has_value()) {
// when m_rotate_start_angle is not set mean it is not Dragging
// when angle_opt is not set mean angle is Zero
const std::optional<float> &angle_opt = m_style_manager.get_font_prop().angle;
m_rotate_start_angle = angle_opt.has_value() ? *angle_opt : 0.f;
}
double angle = m_rotate_gizmo.get_angle();
angle -= PI / 2; // Grabber is upward
// temporary rotation
const TransformationType transformation_type = m_parent.get_selection().is_single_text() ?
TransformationType::Local_Relative_Joint : TransformationType::World_Relative_Joint;
m_parent.get_selection().rotate(Vec3d(0., 0., angle), transformation_type);
angle += *m_rotate_start_angle;
// move to range <-M_PI, M_PI>
Geometry::to_range_pi_pi(angle);
// set into activ style
assert(m_style_manager.is_active_font());
if (m_style_manager.is_active_font()) {
std::optional<float> angle_opt;
if (!is_approx(angle, 0.))
angle_opt = angle;
m_style_manager.get_font_prop().angle = angle_opt;
}
}
return used;
}
bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
{
// exist selected volume?
if (m_volume == nullptr)
return false;
std::optional<double> up_limit;
if (m_keep_up) up_limit = Slic3r::GUI::up_limit;
const Camera &camera = wxGetApp().plater()->get_camera();
bool was_dragging = m_surface_drag.has_value();
bool res = on_mouse_surface_drag(mouse_event, camera, m_surface_drag, m_parent, m_raycast_manager, up_limit);
bool is_dragging = m_surface_drag.has_value();
// End with surface dragging?
if (was_dragging && !is_dragging) {
// Update surface by new position
if (m_volume->text_configuration->style.prop.use_surface)
process();
// Show correct value of height & depth inside of inputs
calculate_scale();
}
// Start with dragging
else if (!was_dragging && is_dragging) {
// Cancel job to prevent interuption of dragging (duplicit result)
if (m_job_cancel != nullptr)
m_job_cancel->store(true);
}
// during drag
else if (was_dragging && is_dragging) {
// update scale of selected volume --> should be approx the same
calculate_scale();
// Recalculate angle for GUI
if (!m_keep_up) {
const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection());
assert(gl_volume != nullptr);
assert(m_style_manager.is_active_font());
if (gl_volume == nullptr || !m_style_manager.is_active_font())
return res;
m_style_manager.get_style().prop.angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
}
}
return res;
}
bool GLGizmoEmboss::on_mouse(const wxMouseEvent &mouse_event)
{
// not selected volume
if (m_volume == nullptr ||
get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ||
!m_volume->text_configuration.has_value()) return false;
if (on_mouse_for_rotation(mouse_event)) return true;
if (on_mouse_for_translate(mouse_event)) return true;
return false;
}
bool GLGizmoEmboss::on_init()
{
m_rotate_gizmo.init();
ColorRGBA gray_color(.6f, .6f, .6f, .3f);
m_rotate_gizmo.set_highlight_color(gray_color);
m_shortcut_key = WXK_CONTROL_T;
// initialize text styles
m_style_manager.init(wxGetApp().app_config);
// Set rotation gizmo upwardrotate
m_rotate_gizmo.set_angle(PI / 2);
return true;
}
std::string GLGizmoEmboss::on_get_name() const { return _u8L("Emboss"); }
void GLGizmoEmboss::on_render() {
// no volume selected
if (m_volume == nullptr ||
get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr)
return;
Selection &selection = m_parent.get_selection();
if (selection.is_empty()) return;
// prevent get local coordinate system on multi volumes
if (!selection.is_single_volume_or_modifier() &&
!selection.is_single_volume_instance()) return;
bool is_surface_dragging = m_surface_drag.has_value();
bool is_parent_dragging = m_parent.is_mouse_dragging();
// Do NOT render rotation grabbers when dragging object
bool is_rotate_by_grabbers = m_dragging;
if (is_rotate_by_grabbers ||
(!is_surface_dragging && !is_parent_dragging)) {
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
m_rotate_gizmo.render();
}
}
void GLGizmoEmboss::on_register_raycasters_for_picking(){
m_rotate_gizmo.register_raycasters_for_picking();
}
void GLGizmoEmboss::on_unregister_raycasters_for_picking(){
m_rotate_gizmo.unregister_raycasters_for_picking();
}
#ifdef SHOW_FINE_POSITION
// draw suggested position of window
static void draw_fine_position(const Selection &selection,
const Size &canvas,
const ImVec2 &windows_size)
{
const Selection::IndicesList& indices = selection.get_volume_idxs();
// no selected volume
if (indices.empty()) return;
const GLVolume *volume = selection.get_volume(*indices.begin());
// bad volume selected (e.g. deleted one)
if (volume == nullptr) return;
const Camera &camera = wxGetApp().plater()->get_camera();
Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *volume);
ImVec2 canvas_size(canvas.get_width(), canvas.get_height());
ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull,
canvas_size);
Slic3r::Polygon rect(
{Point(offset.x, offset.y), Point(offset.x + windows_size.x, offset.y),
Point(offset.x + windows_size.x, offset.y + windows_size.y),
Point(offset.x, offset.y + windows_size.y)});
ImGuiWrapper::draw(hull);
ImGuiWrapper::draw(rect);
}
#endif // SHOW_FINE_POSITION
#ifdef DRAW_PLACE_TO_ADD_TEXT
static void draw_place_to_add_text()
{
ImVec2 mp = ImGui::GetMousePos();
Vec2d mouse_pos(mp.x, mp.y);
const Camera &camera = wxGetApp().plater()->get_camera();
Vec3d p1 = CameraUtils::get_z0_position(camera, mouse_pos);
std::vector<Vec3d> rect3d{p1 + Vec3d(5, 5, 0), p1 + Vec3d(-5, 5, 0),
p1 + Vec3d(-5, -5, 0), p1 + Vec3d(5, -5, 0)};
Points rect2d = CameraUtils::project(camera, rect3d);
ImGuiWrapper::draw(Slic3r::Polygon(rect2d));
}
#endif // DRAW_PLACE_TO_ADD_TEXT
#ifdef SHOW_OFFSET_DURING_DRAGGING
static void draw_mouse_offset(const std::optional<Vec2d> &offset)
{
if (!offset.has_value()) return;
// debug draw
auto draw_list = ImGui::GetOverlayDrawList();
ImVec2 p1 = ImGui::GetMousePos();
ImVec2 p2(p1.x + offset->x(), p1.y + offset->y());
ImU32 color = ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT);
float thickness = 3.f;
draw_list->AddLine(p1, p2, color, thickness);
}
#endif // SHOW_OFFSET_DURING_DRAGGING
void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
{
set_volume_by_selection();
// Do not render window for not selected text volume
if (m_volume == nullptr ||
get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ||
!m_volume->text_configuration.has_value()) {
close();
return;
}
// Configuration creation
double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor();
float main_toolbar_height = m_parent.get_main_toolbar_height();
if (!m_gui_cfg.has_value() || // Exist configuration - first run
m_gui_cfg->screen_scale != screen_scale || // change of DPI
m_gui_cfg->main_toolbar_height != main_toolbar_height // change size of view port
) {
// Create cache for gui offsets
GuiCfg cfg = create_gui_configuration();
cfg.screen_scale = screen_scale;
cfg.main_toolbar_height = main_toolbar_height;
m_gui_cfg.emplace(std::move(cfg));
// set position near toolbar
m_set_window_offset = ImVec2(-1.f, -1.f);
// change resolution regenerate icons
init_icons();
m_style_manager.clear_imgui_font();
}
const ImVec2 &min_window_size = get_minimal_window_size();
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size);
// Draw origin position of text during dragging
if (m_surface_drag.has_value()) {
ImVec2 mouse_pos = ImGui::GetMousePos();
ImVec2 center(
mouse_pos.x + m_surface_drag->mouse_offset.x(),
mouse_pos.y + m_surface_drag->mouse_offset.y());
ImU32 color = ImGui::GetColorU32(
m_surface_drag->exist_hit ?
ImVec4(1.f, 1.f, 1.f, .75f) : // transparent white
ImVec4(1.f, .3f, .3f, .75f)
); // Warning color
const float radius = 16.f;
ImGuiWrapper::draw_cross_hair(center, radius, color);
}
#ifdef SHOW_FINE_POSITION
draw_fine_position(m_parent.get_selection(), m_parent.get_canvas_size(), min_window_size);
#endif // SHOW_FINE_POSITION
#ifdef DRAW_PLACE_TO_ADD_TEXT
draw_place_to_add_text();
#endif // DRAW_PLACE_TO_ADD_TEXT
#ifdef SHOW_OFFSET_DURING_DRAGGING
draw_mouse_offset(m_dragging_mouse_offset);
#endif // SHOW_OFFSET_DURING_DRAGGING
// check if is set window offset
if (m_set_window_offset.has_value()) {
if (m_set_window_offset->y < 0)
// position near toolbar
m_set_window_offset = ImVec2(x, std::min(y, bottom_limit - min_window_size.y));
ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always);
m_set_window_offset.reset();
} else if (!m_allow_open_near_volume) {
y = std::min(y, bottom_limit - min_window_size.y);
// position near toolbar
ImVec2 pos(x, y);
ImGui::SetNextWindowPos(pos, ImGuiCond_Once);
}
bool is_opened = true;
ImGuiWindowFlags flag = ImGuiWindowFlags_NoCollapse;
if (ImGui::Begin(on_get_name().c_str(), &is_opened, flag)) {
// Need to pop var before draw window
ImGui::PopStyleVar(); // WindowMinSize
draw_window();
} else {
ImGui::PopStyleVar(); // WindowMinSize
}
// after change volume from object to volume it is necessary to recalculate
// minimal windows size because of set type
if (m_should_set_minimal_windows_size) {
m_should_set_minimal_windows_size = false;
ImGui::SetWindowSize(ImVec2(0.f, min_window_size.y), ImGuiCond_Always);
}
ImGui::End();
if (!is_opened)
close();
}
namespace priv {
/// <summary>
/// Move window for edit emboss text near to embossed object
/// NOTE: embossed object must be selected
/// </summary>
static ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size);
} // namespace priv
void GLGizmoEmboss::on_set_state()
{
// enable / disable bed from picking
// Rotation gizmo must work through bed
m_parent.set_raycaster_gizmos_on_top(GLGizmoBase::m_state == GLGizmoBase::On);
m_rotate_gizmo.set_state(GLGizmoBase::m_state);
// Closing gizmo. e.g. selecting another one
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
// refuse outgoing during text preview
if (false) {
GLGizmoBase::m_state = GLGizmoBase::On;
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
_u8L("ERROR: Wait until ends or Cancel process."));
return;
}
reset_volume();
// Store order and last activ index into app.ini
// TODO: what to do when can't store into file?
m_style_manager.store_styles_to_app_config(false);
remove_notification_not_valid_font();
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
// to reload fonts from system, when install new one
wxFontEnumerator::InvalidateCache();
// Try(when exist) set text configuration by volume
set_volume_by_selection();
// when open window by "T" and no valid volume is selected, so Create new one
if (m_volume == nullptr ||
get_model_volume(m_volume_id, m_parent.get_selection().get_model()->objects) == nullptr ) {
// reopen gizmo when new object is created
GLGizmoBase::m_state = GLGizmoBase::Off;
if (wxGetApp().get_mode() == comSimple || wxGetApp().obj_list()->has_selected_cut_object())
// It's impossible to add a part in simple mode
return;
// start creating new object
create_volume(ModelVolumeType::MODEL_PART);
}
// change position of just opened emboss window
if (m_allow_open_near_volume) {
m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size());
} else {
if (m_gui_cfg.has_value())
m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), false);
else
m_set_window_offset = ImVec2(-1, -1);
}
// when open by hyperlink it needs to show up
// or after key 'T' windows doesn't appear
m_parent.set_as_dirty();
}
}
void GLGizmoEmboss::data_changed() {
set_volume_by_selection();
}
void GLGizmoEmboss::on_start_dragging() { m_rotate_gizmo.start_dragging(); }
void GLGizmoEmboss::on_stop_dragging()
{
m_rotate_gizmo.stop_dragging();
// TODO: when start second rotatiton previous rotation rotate draggers
// This is fast fix for second try to rotate
// When fixing, move grabber above text (not on side)
m_rotate_gizmo.set_angle(PI/2);
// apply rotation
m_parent.do_rotate(L("Text-Rotate"));
// Re-Calculate current angle of up vector
const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection());
assert(m_style_manager.is_active_font());
assert(gl_volume != nullptr);
if (m_style_manager.is_active_font() && gl_volume != nullptr)
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
m_rotate_start_angle.reset();
// recalculate for surface cut
const FontProp &font_prop = m_style_manager.get_style().prop;
if (font_prop.use_surface) process();
}
void GLGizmoEmboss::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(data); }
GLGizmoEmboss::GuiCfg GLGizmoEmboss::create_gui_configuration()
{
GuiCfg cfg; // initialize by default values;
float line_height = ImGui::GetTextLineHeight();
float line_height_with_spacing = ImGui::GetTextLineHeightWithSpacing();
float space = line_height_with_spacing - line_height;
const ImGuiStyle &style = ImGui::GetStyle();
cfg.max_style_name_width = ImGui::CalcTextSize("Maximal font name, extended").x;
cfg.icon_width = static_cast<unsigned int>(std::ceil(line_height));
// make size pair number
if (cfg.icon_width % 2 != 0) ++cfg.icon_width;
cfg.delete_pos_x = cfg.max_style_name_width + space;
int count_line_of_text = 3;
cfg.text_size = ImVec2(-FLT_MIN, line_height_with_spacing * count_line_of_text);
ImVec2 letter_m_size = ImGui::CalcTextSize("M");
int count_letter_M_in_input = 12;
cfg.input_width = letter_m_size.x * count_letter_M_in_input;
GuiCfg::Translations &tr = cfg.translations;
tr.font = _u8L("Font");
tr.height = _u8L("Height");
tr.depth = _u8L("Depth");
float max_text_width = std::max({
ImGui::CalcTextSize(tr.font.c_str()).x,
ImGui::CalcTextSize(tr.height.c_str()).x,
ImGui::CalcTextSize(tr.depth.c_str()).x});
cfg.indent = static_cast<float>(cfg.icon_width);
cfg.input_offset = style.WindowPadding.x + cfg.indent + max_text_width + space;
tr.use_surface = _u8L("Use surface");
tr.char_gap = _u8L("Char gap");
tr.line_gap = _u8L("Line gap");
tr.boldness = _u8L("Boldness");
tr.skew_ration = _u8L("Skew ratio");
tr.from_surface = _u8L("From surface");
tr.rotation = _u8L("Rotation");
tr.keep_up = _u8L("Keep Up");
tr.collection = _u8L("Collection");
float max_advanced_text_width = std::max({
ImGui::CalcTextSize(tr.use_surface.c_str()).x,
ImGui::CalcTextSize(tr.char_gap.c_str()).x,
ImGui::CalcTextSize(tr.line_gap.c_str()).x,
ImGui::CalcTextSize(tr.boldness.c_str()).x,
ImGui::CalcTextSize(tr.skew_ration.c_str()).x,
ImGui::CalcTextSize(tr.from_surface.c_str()).x,
ImGui::CalcTextSize(tr.rotation.c_str()).x,
ImGui::CalcTextSize(tr.keep_up.c_str()).x,
ImGui::CalcTextSize(tr.collection.c_str()).x });
cfg.advanced_input_offset = max_advanced_text_width
+ 3 * space + cfg.indent;
// calculate window size
float window_title = line_height + 2*style.FramePadding.y + 2 * style.WindowTitleAlign.y;
float input_height = line_height_with_spacing + 2*style.FramePadding.y;
float tree_header = line_height_with_spacing;
float separator_height = 1 + style.FramePadding.y;
// "Text is to object" + radio buttons
cfg.height_of_volume_type_selector = separator_height + line_height_with_spacing + input_height;
float window_height =
window_title + // window title
cfg.text_size.y + // text field
input_height * 4 + // font name + height + depth + style selector
tree_header + // advance tree
separator_height + // presets separator line
line_height_with_spacing + // "Presets"
2 * style.WindowPadding.y;
float window_width = cfg.input_offset + cfg.input_width + 2*style.WindowPadding.x
+ 2 * (cfg.icon_width + space);
cfg.minimal_window_size = ImVec2(window_width, window_height);
// 9 = useSurface, charGap, lineGap, bold, italic, surfDist, rotation, keepUp, textFaceToCamera
// 4 = 1px for fix each edit image of drag float
float advance_height = input_height * 9 + 8;
cfg.minimal_window_size_with_advance =
ImVec2(cfg.minimal_window_size.x,
cfg.minimal_window_size.y + advance_height);
cfg.minimal_window_size_with_collections =
ImVec2(cfg.minimal_window_size_with_advance.x,
cfg.minimal_window_size_with_advance.y + input_height);
int max_style_image_width = cfg.max_style_name_width /2 -
2 * style.FramePadding.x;
int max_style_image_height = 1.5 * input_height;
cfg.max_style_image_size = Vec2i(max_style_image_width, max_style_image_height);
cfg.face_name_size.y() = line_height_with_spacing;
return cfg;
}
EmbossStyles GLGizmoEmboss::create_default_styles()
{
wxFontEnumerator::InvalidateCache();
wxArrayString facenames = wxFontEnumerator::GetFacenames(Facenames::encoding);
wxFont wx_font_normal = *wxNORMAL_FONT;
#ifdef __APPLE__
// Set normal font to helvetica when possible
for (const wxString &facename : facenames) {
if (facename.IsSameAs("Helvetica")) {
wx_font_normal = wxFont(wxFontInfo().FaceName(facename).Encoding(Facenames::encoding));
break;
}
}
#endif // __APPLE__
// https://docs.wxwidgets.org/3.0/classwx_font.html
// Predefined objects/pointers: wxNullFont, wxNORMAL_FONT, wxSMALL_FONT, wxITALIC_FONT, wxSWISS_FONT
EmbossStyles styles = {
WxFontUtils::create_emboss_style(wx_font_normal, _u8L("NORMAL")), // wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)
WxFontUtils::create_emboss_style(*wxSMALL_FONT, _u8L("SMALL")), // A font using the wxFONTFAMILY_SWISS family and 2 points smaller than wxNORMAL_FONT.
WxFontUtils::create_emboss_style(*wxITALIC_FONT, _u8L("ITALIC")), // A font using the wxFONTFAMILY_ROMAN family and wxFONTSTYLE_ITALIC style and of the same size of wxNORMAL_FONT.
WxFontUtils::create_emboss_style(*wxSWISS_FONT, _u8L("SWISS")), // A font identic to wxNORMAL_FONT except for the family used which is wxFONTFAMILY_SWISS.
WxFontUtils::create_emboss_style(wxFont(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD), _u8L("MODERN")),
};
// Not all predefined font for wx must be valid TTF, but at least one style must be loadable
styles.erase(std::remove_if(styles.begin(), styles.end(), [](const EmbossStyle& style) {
wxFont wx_font = WxFontUtils::create_wxFont(style);
// check that face name is setabled
if (style.prop.face_name.has_value()) {
wxString face_name(style.prop.face_name->c_str());
wxFont wx_font_temp;
if (!wx_font_temp.SetFaceName(face_name))
return true;
}
// Check that exsit valid TrueType Font for wx font
return WxFontUtils::create_font_file(wx_font) == nullptr;
}),styles.end()
);
// exist some valid style?
if (!styles.empty())
return styles;
// No valid style in defult list
// at least one style must contain loadable font
wxFont wx_font;
for (const wxString &face : facenames) {
wx_font = wxFont(face);
if (WxFontUtils::create_font_file(wx_font) != nullptr)
break;
wx_font = wxFont(); // NotOk
}
if (wx_font.IsOk()) {
// use first alphabetic sorted installed font
styles.push_back(WxFontUtils::create_emboss_style(wx_font, _u8L("First font")));
} else {
// On current OS is not installed any correct TTF font
// use font packed with Slic3r
std::string font_path = Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf";
styles.push_back(EmbossStyle{_u8L("Default font"), font_path, EmbossStyle::Type::file_path});
}
return styles;
}
void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); }
void GLGizmoEmboss::set_volume_by_selection()
{
const Selection &selection = m_parent.get_selection();
const GLVolume *gl_volume = get_selected_gl_volume(selection);
if (gl_volume == nullptr)
return reset_volume();
const ModelObjectPtrs &objects = selection.get_model()->objects;
ModelVolume *volume =get_model_volume(*gl_volume, objects);
if (volume == nullptr)
return reset_volume();
// is same volume as actual selected?
if (volume->id() == m_volume_id)
return;
// for changed volume notification is NOT valid
remove_notification_not_valid_font();
// Do not use focused input value when switch volume(it must swith value)
if (m_volume != nullptr &&
m_volume != volume) // when update volume it changed id BUT not pointer
ImGuiWrapper::left_inputs();
// Is selected volume text volume?
const std::optional<TextConfiguration>& tc_opt = volume->text_configuration;
if (!tc_opt.has_value())
return reset_volume();
const TextConfiguration &tc = *tc_opt;
const EmbossStyle &style = tc.style;
// Could exist OS without getter on face_name,
// but it is able to restore font from descriptor
// Soo default value must be TRUE
bool is_font_installed = true;
wxString face_name;
std::optional<std::string> face_name_opt = style.prop.face_name;
if (face_name_opt.has_value()) {
face_name = wxString(face_name_opt->c_str());
// search in enumerated fonts
// refresh list of installed font in the OS.
init_face_names(m_face_names);
m_face_names.is_init = false;
auto cmp = [](const FaceName &fn, const wxString& face_name)->bool { return fn.wx_name < face_name; };
const std::vector<FaceName> &faces = m_face_names.faces;
auto it = std::lower_bound(faces.begin(), faces.end(), face_name, cmp);
is_font_installed = it != faces.end() && it->wx_name == face_name;
if (!is_font_installed) {
const std::vector<wxString> &bad = m_face_names.bad;
auto it_bad = std::lower_bound(bad.begin(), bad.end(), face_name);
if (it_bad == bad.end() || *it_bad != face_name){
// check if wx allowed to set it up - another encoding of name
wxFontEnumerator::InvalidateCache();
wxFont wx_font_; // temporary structure
if (wx_font_.SetFaceName(face_name) &&
WxFontUtils::create_font_file(wx_font_) != nullptr // can load TTF file?
) {
is_font_installed = true;
// QUESTION: add this name to allowed faces?
// Could create twin of font face name
// When not add it will be hard to select it again when change font
}
}
}
}
wxFont wx_font;
// load wxFont from same OS when font name is installed
if (style.type == WxFontUtils::get_actual_type() && is_font_installed)
wx_font = WxFontUtils::load_wxFont(style.path);
// Flag that is selected same font
bool is_exact_font = true;
// Different OS or try found on same OS
if (!wx_font.IsOk()) {
is_exact_font = false;
// Try create similar wx font by FontFamily
wx_font = WxFontUtils::create_wxFont(style);
if (is_font_installed)
is_exact_font = wx_font.SetFaceName(face_name);
// Have to use some wxFont
if (!wx_font.IsOk())
wx_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
}
assert(wx_font.IsOk());
// Load style to style manager
const auto& styles = m_style_manager.get_styles();
auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool {
const EmbossStyle &es = style_item.style;
return es.name == style.name;
};
auto it = std::find_if(styles.begin(), styles.end(), has_same_name);
if (it == styles.end()) {
// style was not found
m_style_manager.load_style(style, wx_font);
} else {
// style name is in styles list
size_t style_index = it - styles.begin();
if (!m_style_manager.load_style(style_index)) {
// can`t load stored style
m_style_manager.erase(style_index);
m_style_manager.load_style(style, wx_font);
} else {
// stored style is loaded, now set modification of style
m_style_manager.get_style() = style;
m_style_manager.set_wx_font(wx_font);
}
}
if (!is_exact_font)
create_notification_not_valid_font(tc);
// The change of volume could show or hide part with setter on volume type
if (m_volume == nullptr ||
get_model_volume(m_volume_id, objects) == nullptr ||
(m_volume->get_object()->volumes.size() == 1) !=
(volume->get_object()->volumes.size() == 1)){
m_should_set_minimal_windows_size = true;
}
// cancel previous job
if (m_job_cancel != nullptr) {
m_job_cancel->store(true);
m_job_cancel = nullptr;
}
m_text = tc.text;
m_volume = volume;
m_volume_id = volume->id();
// Calculate current angle of up vector
assert(m_style_manager.is_active_font());
if (m_style_manager.is_active_font())
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
// calculate scale for height and depth inside of scaled object instance
calculate_scale();
}
void GLGizmoEmboss::reset_volume()
{
if (m_volume == nullptr)
return; // already reseted
m_volume = nullptr;
m_volume_id.id = 0;
// No more need of current notification
remove_notification_not_valid_font();
}
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<float>& scale)->bool {
Vec3d axe_world = to_world_linear * axe;
double norm_sq = axe_world.squaredNorm();
if (is_approx(norm_sq, 1.)) {
if (scale.has_value())
scale.reset();
else
return false;
} else {
scale = sqrt(norm_sq);
}
return true;
};
bool exist_change = calc(Vec3d::UnitY(), m_scale_height);
exist_change |= calc(Vec3d::UnitZ(), m_scale_depth);
// Change of scale has to change font imgui font size
if (exist_change)
m_style_manager.clear_imgui_font();
}
#ifdef EXECUTE_PROCESS_ON_MAIN_THREAD
namespace priv {
// Run Job on main thread (blocking) - ONLY DEBUG
static inline void execute_job(std::shared_ptr<Job> j)
{
struct MyCtl : public Job::Ctl
{
void update_status(int st, const std::string &msg = "") override{};
bool was_canceled() const override { return false; }
std::future<void> call_on_main_thread(std::function<void()> fn) override
{
return std::future<void>{};
}
} ctl;
j->process(ctl);
wxGetApp().plater()->CallAfter([j]() {
std::exception_ptr e_ptr = nullptr;
j->finalize(false, e_ptr);
});
}
} // namespace priv
#endif
bool GLGizmoEmboss::process()
{
// no volume is selected -> selection from right panel
assert(m_volume != nullptr);
if (m_volume == nullptr) return false;
// without text there is nothing to emboss
if (m_text.empty()) return false;
// exist loaded font file?
if (!m_style_manager.is_active_font()) return false;
DataUpdate data{priv::create_emboss_data_base(m_text, m_style_manager, m_job_cancel), m_volume->id()};
std::unique_ptr<Job> job = nullptr;
// check cutting from source mesh
// TODO: do it better way
FontProp &fp = static_cast<TextDataBase *>(data.base.get())->text_configuration.style.prop;
bool &use_surface = fp.use_surface;
bool is_object = m_volume->get_object()->volumes.size() == 1;
if (use_surface && is_object)
use_surface = false;
if (use_surface) {
// Model to cut surface from.
SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume);
if (sources.empty()) return false;
Transform3d text_tr = m_volume->get_matrix();
auto& fix_3mf = m_volume->text_configuration->fix_3mf_tr;
if (fix_3mf.has_value())
text_tr = text_tr * fix_3mf->inverse();
// when it is new applying of use surface than move origin onto surfaca
if (!m_volume->text_configuration->style.prop.use_surface) {
auto offset = calc_surface_offset(m_parent.get_selection(), m_raycast_manager);
if (offset.has_value())
text_tr *= Eigen::Translation<double, 3>(*offset);
}
bool is_outside = m_volume->is_model_part();
// check that there is not unexpected volume type
assert(is_outside || m_volume->is_negative_volume() ||
m_volume->is_modifier());
UpdateSurfaceVolumeData surface_data{std::move(data), {text_tr, is_outside, std::move(sources)}};
job = std::make_unique<UpdateSurfaceVolumeJob>(std::move(surface_data));
} else {
job = std::make_unique<UpdateJob>(std::move(data));
}
#ifndef EXECUTE_PROCESS_ON_MAIN_THREAD
auto &worker = wxGetApp().plater()->get_ui_job_worker();
queue_job(worker, std::move(job));
#else
// Run Job on main thread (blocking) - ONLY DEBUG
priv::execute_job(std::move(job));
#endif // EXECUTE_PROCESS_ON_MAIN_THREAD
// notification is removed befor object is changed by job
remove_notification_not_valid_font();
return true;
}
namespace priv {
static bool is_text_empty(const std::string &text) { return text.empty() || text.find_first_not_of(" \n\t\r") == std::string::npos; }
}
void GLGizmoEmboss::close()
{
// remove volume when text is empty
if (m_volume != nullptr &&
m_volume->text_configuration.has_value() &&
priv::is_text_empty(m_text)) {
Plater &p = *wxGetApp().plater();
if (is_text_object(m_volume)) {
// delete whole object
p.remove(m_parent.get_selection().get_object_idx());
} else {
// delete text volume
p.remove_selected();
}
}
// close gizmo == open it again
auto& mng = m_parent.get_gizmos_manager();
if (mng.get_current_type() == GLGizmosManager::Emboss)
mng.open_gizmo(GLGizmosManager::Emboss);
}
void GLGizmoEmboss::draw_window()
{
#ifdef ALLOW_DEBUG_MODE
if (ImGui::Button("re-process")) process();
if (ImGui::Button("add svg")) choose_svg_file();
#endif // ALLOW_DEBUG_MODE
bool is_active_font = m_style_manager.is_active_font();
if (!is_active_font)
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Warning: No font is selected. Select correct one."));
// Disable all except selection of font, when open text from 3mf with unknown font
m_imgui->disabled_begin(m_is_unknown_font);
ScopeGuard unknown_font_sc([&]() {
m_imgui->disabled_end();
});
draw_text_input();
m_imgui->disabled_begin(!is_active_font);
ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, m_gui_cfg->indent);
ImGui::Indent();
draw_style_edit();
ImGui::Unindent();
// close advanced style property when unknown font is selected
if (m_is_unknown_font && m_is_advanced_edit_style)
ImGui::SetNextTreeNodeOpen(false);
if (ImGui::TreeNode(_u8L("Advanced").c_str())) {
if (!m_is_advanced_edit_style) {
set_minimal_window_size(true);
} else {
draw_advanced();
}
ImGui::TreePop();
} else if (m_is_advanced_edit_style)
set_minimal_window_size(false);
ImGui::PopStyleVar(); // ImGuiStyleVar_IndentSpacing
ImGui::Separator();
draw_style_list();
// Do not select volume type, when it is text object
if (m_volume->get_object()->volumes.size() != 1) {
ImGui::Separator();
draw_model_type();
}
m_imgui->disabled_end(); // !is_active_font
#ifdef SHOW_WX_FONT_DESCRIPTOR
if (is_selected_style)
m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, m_style_manager.get_style().path);
#endif // SHOW_WX_FONT_DESCRIPTOR
#ifdef SHOW_CONTAIN_3MF_FIX
if (m_volume!=nullptr &&
m_volume->text_configuration.has_value() &&
m_volume->text_configuration->fix_3mf_tr.has_value()) {
ImGui::SameLine();
m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ".3mf");
if (ImGui::IsItemHovered()) {
Transform3d &fix = *m_volume->text_configuration->fix_3mf_tr;
std::stringstream ss;
ss << fix.matrix();
std::string filename = (m_volume->source.input_file.empty())? "unknown.3mf" :
m_volume->source.input_file + ".3mf";
ImGui::SetTooltip("Text configuation contain \n"
"Fix Transformation Matrix \n"
"%s\n"
"loaded from \"%s\" file.",
ss.str().c_str(), filename.c_str()
);
}
}
#endif // SHOW_CONTAIN_3MF_FIX
#ifdef SHOW_ICONS_TEXTURE
auto &t = m_icons_texture;
ImGui::Image((void *) t.get_id(), ImVec2(t.get_width(), t.get_height()));
#endif //SHOW_ICONS_TEXTURE
#ifdef SHOW_IMGUI_ATLAS
const auto &atlas = m_style_manager.get_atlas();
ImGui::Image(atlas.TexID, ImVec2(atlas.TexWidth, atlas.TexHeight));
#endif // SHOW_IMGUI_ATLAS
#ifdef ALLOW_OPEN_NEAR_VOLUME
ImGui::SameLine();
if (ImGui::Checkbox("##ALLOW_OPEN_NEAR_VOLUME", &m_allow_open_near_volume)) {
if (m_allow_open_near_volume)
m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size());
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", ((m_allow_open_near_volume) ?
"Fix settings position":
"Allow floating window near text").c_str());
}
#endif // ALLOW_FLOAT_WINDOW
}
#include "imgui/imgui_internal.h" // scroll bar existence
void GLGizmoEmboss::draw_text_input()
{
auto create_range_text_prep = [&mng = m_style_manager, &text = m_text, &exist_unknown = m_text_contain_unknown_glyph]() {
auto& ff = mng.get_font_file_with_cache();
assert(ff.has_value());
const auto &cn = mng.get_font_prop().collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
return create_range_text(text, *ff.font_file, font_index, &exist_unknown);
};
double scale = m_scale_height.has_value() ? *m_scale_height : 1.;
ImFont *imgui_font = m_style_manager.get_imgui_font();
if (imgui_font == nullptr) {
// try create new imgui font
double screen_scale = wxDisplay(wxGetApp().plater()).GetScaleFactor();
double imgui_scale = scale * screen_scale;
m_style_manager.create_imgui_font(create_range_text_prep(), imgui_scale);
imgui_font = m_style_manager.get_imgui_font();
}
bool exist_font =
imgui_font != nullptr &&
imgui_font->IsLoaded() &&
imgui_font->Scale > 0.f &&
imgui_font->ContainerAtlas != nullptr;
// NOTE: Symbol fonts doesn't have atlas
// when their glyph range is out of language character range
if (exist_font) ImGui::PushFont(imgui_font);
// show warning about incorrectness view of font
std::string warning_tool_tip;
if (!exist_font) {
warning_tool_tip = _u8L("Can't write text by selected font.Try to choose another font.");
} else {
auto append_warning = [&warning_tool_tip](std::string t) {
if (!warning_tool_tip.empty())
warning_tool_tip += "\n";
warning_tool_tip += t;
};
if (priv::is_text_empty(m_text))
append_warning(_u8L("Embossed text can NOT contain only white spaces."));
if (m_text_contain_unknown_glyph)
append_warning(_u8L("Text contain character glyph (represented by '?') unknown by font."));
const FontProp &prop = m_style_manager.get_font_prop();
if (prop.skew.has_value()) append_warning(_u8L("Text input do not show font skew."));
if (prop.boldness.has_value()) append_warning(_u8L("Text input do not show font boldness."));
if (prop.line_gap.has_value())
append_warning(_u8L("Text input do not show gap between lines."));
auto &ff = m_style_manager.get_font_file_with_cache();
float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale);
if (imgui_size > StyleManager::max_imgui_font_size)
append_warning(_u8L("Too tall, diminished font height inside text input."));
if (imgui_size < StyleManager::min_imgui_font_size)
append_warning(_u8L("Too small, enlarged font height inside text input."));
}
// flag for extend font ranges if neccessary
// ranges can't be extend during font is activ(pushed)
std::string range_text;
float window_height = ImGui::GetWindowHeight();
float minimal_height = get_minimal_window_size().y;
float extra_height = window_height - minimal_height;
ImVec2 input_size(m_gui_cfg->text_size.x, m_gui_cfg->text_size.y + extra_height);
const ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_AutoSelectAll;
if (ImGui::InputTextMultiline("##Text", &m_text, input_size, flags)) {
process();
range_text = create_range_text_prep();
}
if (exist_font) ImGui::PopFont();
// warning tooltip has to be with default font
if (!warning_tool_tip.empty()) {
// Multiline input has hidden window for scrolling
ImGuiWindow *input = ImGui::GetCurrentWindow()->DC.ChildWindows.front();
const ImGuiStyle &style = ImGui::GetStyle();
float scrollbar_width = (input->ScrollbarY) ? style.ScrollbarSize : 0.f;
float scrollbar_height = (input->ScrollbarX) ? style.ScrollbarSize : 0.f;
bool hovered = ImGui::IsItemHovered();
if (hovered)
ImGui::SetTooltip("%s", warning_tool_tip.c_str());
ImVec2 cursor = ImGui::GetCursorPos();
float width = ImGui::GetContentRegionAvailWidth();
const ImVec2& padding = style.FramePadding;
ImVec2 icon_pos(width - m_gui_cfg->icon_width - scrollbar_width + padding.x,
cursor.y - m_gui_cfg->icon_width - scrollbar_height - 2*padding.y);
ImGui::SetCursorPos(icon_pos);
draw(get_icon(m_icons, IconType::exclamation, IconState::hovered));
ImGui::SetCursorPos(cursor);
}
// NOTE: must be after ImGui::font_pop()
// -> imgui_font has to be unused
// IMPROVE: only extend not clear
// Extend font ranges
if (!range_text.empty() &&
!m_imgui->contain_all_glyphs(imgui_font, range_text) )
m_style_manager.clear_imgui_font();
}
#include <boost/functional/hash.hpp>
#include "wx/hashmap.h"
std::size_t hash_value(wxString const &s)
{
boost::hash<std::string> hasher;
return hasher(s.ToStdString());
}
static std::string concat(std::vector<wxString> data) {
std::stringstream ss;
for (const auto &d : data)
ss << d.c_str() << ", ";
return ss.str();
}
#include <boost/filesystem.hpp>
static boost::filesystem::path get_fontlist_cache_path()
{
return boost::filesystem::path(data_dir()) / "cache" / "fonts.cereal";
}
// cache font list by cereal
#include <cereal/cereal.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/string.hpp>
#include <cereal/archives/binary.hpp>
// increase number when change struct FacenamesSerializer
#define FACENAMES_VERSION 1
struct FacenamesSerializer
{
// hash number for unsorted vector of installed font into system
size_t hash = 0;
// assumption that is loadable
std::vector<wxString> good;
// Can't load for some reason
std::vector<wxString> bad;
};
template<class Archive> void save(Archive &archive, wxString const &d)
{ std::string s(d.ToUTF8().data()); archive(s);}
template<class Archive> void load(Archive &archive, wxString &d)
{ std::string s; archive(s); d = s;}
template<class Archive> void serialize(Archive &ar, FacenamesSerializer &t, const std::uint32_t version)
{
// When performing a load, the version associated with the class
// is whatever it was when that data was originally serialized
// When we save, we'll use the version that is defined in the macro
if (version != FACENAMES_VERSION) return;
ar(t.hash, t.good, t.bad);
}
CEREAL_CLASS_VERSION(FacenamesSerializer, FACENAMES_VERSION); // register class version
#include <boost/nowide/fstream.hpp>
bool GLGizmoEmboss::store(const Facenames &facenames) {
std::string cache_path = get_fontlist_cache_path().string();
boost::nowide::ofstream file(cache_path, std::ios::binary);
cereal::BinaryOutputArchive archive(file);
std::vector<wxString> good;
good.reserve(facenames.faces.size());
for (const FaceName &face : facenames.faces) good.push_back(face.wx_name);
FacenamesSerializer data = {facenames.hash, good, facenames.bad};
assert(std::is_sorted(data.bad.begin(), data.bad.end()));
assert(std::is_sorted(data.good.begin(), data.good.end()));
try {
archive(data);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Failed to write fontlist cache - " << cache_path << ex.what();
return false;
}
return true;
}
bool GLGizmoEmboss::load(Facenames &facenames) {
boost::filesystem::path path = get_fontlist_cache_path();
std::string path_str = path.string();
if (!boost::filesystem::exists(path)) {
BOOST_LOG_TRIVIAL(warning) << "Fontlist cache - '" << path_str << "' does not exists.";
return false;
}
boost::nowide::ifstream file(path_str, std::ios::binary);
cereal::BinaryInputArchive archive(file);
FacenamesSerializer data;
try {
archive(data);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Failed to load fontlist cache - '" << path_str << "'. Exception: " << ex.what();
return false;
}
assert(std::is_sorted(data.bad.begin(), data.bad.end()));
assert(std::is_sorted(data.good.begin(), data.good.end()));
facenames.hash = data.hash;
facenames.faces.reserve(data.good.size());
for (const wxString &face : data.good)
facenames.faces.push_back({face});
facenames.bad = data.bad;
return true;
}
void GLGizmoEmboss::init_truncated_names(Facenames &face_names, float max_width)
{
for (FaceName &face : face_names.faces) {
std::string name_str(face.wx_name.ToUTF8().data());
face.name_truncated = ImGuiWrapper::trunc(name_str, max_width);
}
face_names.has_truncated_names = true;
}
void GLGizmoEmboss::init_face_names(Facenames &face_names)
{
Timer t("enumerate_fonts");
if (face_names.is_init) return;
face_names.is_init = true;
// to reload fonts from system, when install new one
wxFontEnumerator::InvalidateCache();
// try load cache
// Only not OS enumerated face has hash value 0
if (face_names.hash == 0) {
load(face_names);
face_names.has_truncated_names = false;
}
using namespace std::chrono;
steady_clock::time_point enumerate_start = steady_clock::now();
ScopeGuard sg([&enumerate_start, &face_names = face_names]() {
steady_clock::time_point enumerate_end = steady_clock::now();
long long enumerate_duration = duration_cast<milliseconds>(enumerate_end - enumerate_start).count();
BOOST_LOG_TRIVIAL(info) << "OS enumerate " << face_names.faces.size() << " fonts "
<< "(+ " << face_names.bad.size() << " can't load "
<< "= " << face_names.faces.size() + face_names.bad.size() << " fonts) "
<< "in " << enumerate_duration << " ms\n" << concat(face_names.bad);
});
wxArrayString facenames = wxFontEnumerator::GetFacenames(face_names.encoding);
size_t hash = boost::hash_range(facenames.begin(), facenames.end());
// Zero value is used as uninitialized hash
if (hash == 0) hash = 1;
// check if it is same as last time
if (face_names.hash == hash) {
// no new installed font
BOOST_LOG_TRIVIAL(info) << "Same FontNames hash, cache is used. "
<< "For clear cache delete file: " << get_fontlist_cache_path().string();
return;
}
BOOST_LOG_TRIVIAL(info) << ((face_names.hash == 0) ?
"FontName list is generate from scratch." :
"Hash are different. Only previous bad fonts are used and set again as bad");
face_names.hash = hash;
// validation lambda
auto is_valid_font = [encoding = face_names.encoding, bad = face_names.bad /*copy*/](const wxString &name) {
if (name.empty()) return false;
// vertical font start with @, we will filter it out
// Not sure if it is only in Windows so filtering is on all platforms
if (name[0] == '@') return false;
// previously detected bad font
auto it = std::lower_bound(bad.begin(), bad.end(), name);
if (it != bad.end() && *it == name) return false;
wxFont wx_font(wxFontInfo().FaceName(name).Encoding(encoding));
//*
// Faster chech if wx_font is loadable but not 100%
// names could contain not loadable font
if (!WxFontUtils::can_load(wx_font)) return false;
/*/
// Slow copy of font files to try load font
// After this all files are loadable
auto font_file = WxFontUtils::create_font_file(wx_font);
if (font_file == nullptr)
return false; // can't create font file
// */
return true;
};
face_names.faces.clear();
face_names.bad.clear();
face_names.faces.reserve(facenames.size());
std::sort(facenames.begin(), facenames.end());
for (const wxString &name : facenames) {
if (is_valid_font(name)) {
face_names.faces.push_back({name});
}else{
face_names.bad.push_back(name);
}
}
assert(std::is_sorted(face_names.bad.begin(), face_names.bad.end()));
face_names.has_truncated_names = false;
store(face_names);
}
// create texture for visualization font face
void GLGizmoEmboss::init_font_name_texture() {
Timer t("init_font_name_texture");
// check if already exists
GLuint &id = m_face_names.texture_id;
if (id != 0) return;
// create texture for font
GLenum target = GL_TEXTURE_2D;
glsafe(::glGenTextures(1, &id));
glsafe(::glBindTexture(target, id));
glsafe(::glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(::glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
const Vec2i &size = m_gui_cfg->face_name_size;
GLint w = size.x(), h = m_face_names.count_cached_textures * size.y();
std::vector<unsigned char> data(4*w * h, {0});
const GLenum format = GL_RGBA, type = GL_UNSIGNED_BYTE;
const GLint level = 0, internal_format = GL_RGBA, border = 0;
glsafe(::glTexImage2D(target, level, internal_format, w, h, border, format, type, data.data()));
// bind default texture
GLuint no_texture_id = 0;
glsafe(::glBindTexture(target, no_texture_id));
// clear info about creation of texture - no one is initialized yet
for (FaceName &face : m_face_names.faces) {
face.cancel = nullptr;
face.is_created = nullptr;
}
// Prepare filtration cache
m_face_names.hide = std::vector<bool>(m_face_names.faces.size(), {false});
}
void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible)
{
// Limit for opened font files at one moment
unsigned int &count_opened_fonts = m_face_names.count_opened_font_files;
// Size of texture
ImVec2 size(m_gui_cfg->face_name_size.x(), m_gui_cfg->face_name_size.y());
float count_cached_textures_f = static_cast<float>(m_face_names.count_cached_textures);
std::string state_text;
// uv0 and uv1 set to pixel 0,0 in texture
ImVec2 uv0(0.f, 0.f), uv1(1.f / size.x, 1.f / size.y / count_cached_textures_f);
if (face.is_created != nullptr) {
// not created preview
if (*face.is_created) {
// Already created preview
size_t texture_index = face.texture_index;
uv0 = ImVec2(0.f, texture_index / count_cached_textures_f);
uv1 = ImVec2(1.f, (texture_index + 1) / count_cached_textures_f);
} else {
// Not finished preview
if (is_visible) {
// when not canceled still loading
state_text = (face.cancel->load())?
_u8L(" No symbol"):
_u8L(" ... Loading");
} else {
// not finished and not visible cancel job
face.is_created = nullptr;
face.cancel->store(true);
}
}
} else if (is_visible && count_opened_fonts < m_gui_cfg->max_count_opened_font_files) {
++count_opened_fonts;
face.cancel = std::make_shared<std::atomic_bool>(false);
face.is_created = std::make_shared<bool>(false);
const unsigned char gray_level = 5;
// format type and level must match to texture data
const GLenum format = GL_RGBA, type = GL_UNSIGNED_BYTE;
const GLint level = 0;
// select next texture index
size_t texture_index = (m_face_names.texture_index + 1) % m_face_names.count_cached_textures;
// set previous cach as deleted
for (FaceName &f : m_face_names.faces)
if (f.texture_index == texture_index) {
if (f.cancel != nullptr) f.cancel->store(true);
f.is_created = nullptr;
}
m_face_names.texture_index = texture_index;
face.texture_index = texture_index;
// render text to texture
FontImageData data{
m_text,
face.wx_name,
m_face_names.encoding,
m_face_names.texture_id,
m_face_names.texture_index,
m_gui_cfg->face_name_size,
gray_level,
format,
type,
level,
&count_opened_fonts,
face.cancel, // copy
face.is_created // copy
};
auto job = std::make_unique<CreateFontImageJob>(std::move(data));
auto &worker = wxGetApp().plater()->get_ui_job_worker();
queue_job(worker, std::move(job));
} else {
// cant start new thread at this moment so wait in queue
state_text = _u8L(" ... In queue");
}
if (!state_text.empty()) {
ImGui::SameLine(m_gui_cfg->face_name_texture_offset_x);
m_imgui->text(state_text);
}
ImGui::SameLine(m_gui_cfg->face_name_texture_offset_x);
ImTextureID tex_id = (void *) (intptr_t) m_face_names.texture_id;
ImGui::Image(tex_id, size, uv0, uv1);
}
bool GLGizmoEmboss::select_facename(const wxString &facename)
{
if (!wxFontEnumerator::IsValidFacename(facename)) return false;
// Select font
const wxFontEncoding &encoding = m_face_names.encoding;
wxFont wx_font(wxFontInfo().FaceName(facename).Encoding(encoding));
if (!wx_font.IsOk()) return false;
#ifdef USE_PIXEL_SIZE_IN_WX_FONT
// wx font could change source file by size of font
int point_size = static_cast<int>(m_style_manager.get_font_prop().size_in_mm);
wx_font.SetPointSize(point_size);
#endif // USE_PIXEL_SIZE_IN_WX_FONT
if (!m_style_manager.set_wx_font(wx_font)) return false;
process();
return true;
}
void GLGizmoEmboss::draw_font_list()
{
// Set partial
wxString actual_face_name;
if (m_style_manager.is_active_font()) {
const wxFont &wx_font = m_style_manager.get_wx_font();
if (wx_font.IsOk())
actual_face_name = wx_font.GetFaceName();
}
// name of actual selected font
const char * selected = (!actual_face_name.empty()) ?
actual_face_name.ToUTF8().data() : " --- ";
// Do not remove font face during enumeration
// When deletation of font appear this variable is set
std::optional<size_t> del_index;
// When is unknown font is inside .3mf only font selection is allowed
// Stop Imgui disable + Guard again start disabling
ScopeGuard unknown_font_sc;
if (m_is_unknown_font) {
m_imgui->disabled_end();
unknown_font_sc.closure = [&]() {
m_imgui->disabled_begin(true);
};
}
// Code
const char *popup_id = "##font_list_popup";
const char *input_id = "##font_list_input";
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
// change color of hint to normal text
bool is_popup_open = ImGui::IsPopupOpen(popup_id);
if (!is_popup_open) {
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyleColorVec4(ImGuiCol_Text));
// Fix clearance of search input,
// Sometime happens that search text not disapear after font select
m_face_names.search.clear();
}
if (ImGui::InputTextWithHint(input_id, selected, &m_face_names.search)) {
// update filtration result
m_face_names.hide = std::vector<bool>(m_face_names.faces.size(), {false});
// search to uppercase
std::string search = m_face_names.search; // copy
std::transform(search.begin(), search.end(), search.begin(), ::toupper);
for (FaceName &face : m_face_names.faces) {
size_t index = &face - &m_face_names.faces.front();
// font name to uppercase
std::string name(face.wx_name.ToUTF8().data());
std::transform(name.begin(), name.end(), name.begin(), ::toupper);
// It should use C++ 20 feature https://en.cppreference.com/w/cpp/string/basic_string/starts_with
bool start_with = boost::starts_with(name, search);
m_face_names.hide[index] = !start_with;
}
}
if (!is_popup_open)
ImGui::PopStyleColor(); // revert changes for hint color
const bool is_input_text_active = ImGui::IsItemActive();
// is_input_text_activated
if (ImGui::IsItemActivated())
ImGui::OpenPopup(popup_id);
ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y));
ImGui::SetNextWindowSize({2*m_gui_cfg->input_width, ImGui::GetTextLineHeight()*10});
ImGuiWindowFlags popup_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_ChildWindow;
if (ImGui::BeginPopup(popup_id, popup_flags))
{
bool set_selection_focus = false;
if (!m_face_names.is_init) {
init_face_names(m_face_names);
set_selection_focus = true;
}
if (!m_face_names.has_truncated_names)
init_truncated_names(m_face_names, m_gui_cfg->face_name_max_width);
if (m_face_names.texture_id == 0)
init_font_name_texture();
for (FaceName &face : m_face_names.faces) {
const wxString &wx_face_name = face.wx_name;
size_t index = &face - &m_face_names.faces.front();
// Filter for face names
if (m_face_names.hide[index])
continue;
ImGui::PushID(index);
ScopeGuard sg([]() { ImGui::PopID(); });
bool is_selected = (actual_face_name == wx_face_name);
ImVec2 selectable_size(0, m_gui_cfg->face_name_size.y());
ImGuiSelectableFlags flags = 0;
if (ImGui::Selectable(face.name_truncated.c_str(), is_selected, flags, selectable_size)) {
if (!select_facename(wx_face_name)) {
del_index = index;
wxMessageBox(GUI::format(_L("Font face \"%1%\" can't be selected."), wx_face_name));
}
}
// tooltip as full name of font face
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", wx_face_name.ToUTF8().data());
// on first draw set focus on selected font
if (set_selection_focus && is_selected)
ImGui::SetScrollHereY();
draw_font_preview(face, ImGui::IsItemVisible());
}
if (!ImGui::IsWindowFocused() ||
(!is_input_text_active && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape)))) {
// closing of popup
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} else if (m_face_names.is_init) {
// Just one after close combo box
// free texture and set id to zero
m_face_names.is_init = false;
m_face_names.hide.clear();
// cancel all process for generation of texture
for (FaceName &face : m_face_names.faces)
if (face.cancel != nullptr)
face.cancel->store(true);
glsafe(::glDeleteTextures(1, &m_face_names.texture_id));
m_face_names.texture_id = 0;
// Remove value from search input
ImGuiWrapper::left_inputs();
m_face_names.search.clear();
}
// delete unloadable face name when try to use
if (del_index.has_value()) {
auto face = m_face_names.faces.begin() + (*del_index);
std::vector<wxString>& bad = m_face_names.bad;
// sorted insert into bad fonts
auto it = std::upper_bound(bad.begin(), bad.end(), face->wx_name);
bad.insert(it, face->wx_name);
m_face_names.faces.erase(face);
// update cached file
store(m_face_names);
}
if (m_is_unknown_font) {
ImGui::SameLine();
// Apply for actual selected font
if (ImGui::Button(_u8L("Apply").c_str()))
process();
}
#ifdef ALLOW_ADD_FONT_BY_FILE
ImGui::SameLine();
// select font file by file browser
if (draw_button(IconType::open_file)) {
if (choose_true_type_file()) {
process();
}
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("add file with font(.ttf, .ttc)").c_str());
#endif // ALLOW_ADD_FONT_BY_FILE
#ifdef ALLOW_ADD_FONT_BY_OS_SELECTOR
ImGui::SameLine();
if (draw_button(IconType::system_selector)) {
if (choose_font_by_wxdialog()) {
process();
}
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Open dialog for choose from fonts.").c_str());
#endif // ALLOW_ADD_FONT_BY_OS_SELECTOR
}
void GLGizmoEmboss::draw_model_type()
{
bool is_last_solid_part = is_text_object(m_volume);
std::string title = _u8L("Text is to object");
if (is_last_solid_part) {
ImVec4 color{.5f, .5f, .5f, 1.f};
m_imgui->text_colored(color, title.c_str());
} else {
ImGui::Text("%s", title.c_str());
}
std::optional<ModelVolumeType> new_type;
ModelVolumeType modifier = ModelVolumeType::PARAMETER_MODIFIER;
ModelVolumeType negative = ModelVolumeType::NEGATIVE_VOLUME;
ModelVolumeType part = ModelVolumeType::MODEL_PART;
ModelVolumeType type = m_volume->type();
if (ImGui::RadioButton(_u8L("Added").c_str(), type == part))
new_type = part;
else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str());
ImGui::SameLine();
std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object.");
if (ImGui::RadioButton(_u8L("Subtracted").c_str(), type == negative))
new_type = negative;
else if (ImGui::IsItemHovered()) {
if (is_last_solid_part)
ImGui::SetTooltip("%s", last_solid_part_hint.c_str());
else if (type != negative)
ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str());
}
// In simple mode are not modifiers
if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) {
ImGui::SameLine();
if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier))
new_type = modifier;
else if (ImGui::IsItemHovered()) {
if (is_last_solid_part)
ImGui::SetTooltip("%s", last_solid_part_hint.c_str());
else if (type != modifier)
ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str());
}
}
if (m_volume != nullptr && new_type.has_value() && !is_last_solid_part) {
GUI_App &app = wxGetApp();
Plater * plater = app.plater();
Plater::TakeSnapshot snapshot(plater, _L("Change Text Type"), UndoRedo::SnapshotType::GizmoAction);
m_volume->set_type(*new_type);
// Update volume position when switch from part or into part
if (m_volume->text_configuration->style.prop.use_surface) {
// move inside
bool is_volume_move_inside = (type == part);
bool is_volume_move_outside = (*new_type == part);
if (is_volume_move_inside || is_volume_move_outside) process();
}
// inspiration in ObjectList::change_part_type()
// how to view correct side panel with objects
ObjectList *obj_list = app.obj_list();
wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(
obj_list->get_selected_obj_idx(),
[volume = m_volume](const ModelVolume *vol) { return vol == volume; });
if (!sel.IsEmpty()) obj_list->select_item(sel.front());
// NOTE: on linux, function reorder_volumes_and_get_selection call GLCanvas3D::reload_scene(refresh_immediately = false)
// which discard m_volume pointer and set it to nullptr also selection is cleared so gizmo is automaticaly closed
auto &mng = m_parent.get_gizmos_manager();
if (mng.get_current_type() != GLGizmosManager::Emboss)
mng.open_gizmo(GLGizmosManager::Emboss);
// TODO: select volume back - Ask @Sasa
}
}
void GLGizmoEmboss::draw_style_rename_popup() {
std::string& new_name = m_style_manager.get_style().name;
const std::string &old_name = m_style_manager.get_stored_style()->name;
std::string text_in_popup = GUI::format(_L("Rename style(%1%) for embossing text: "), old_name);
ImGui::Text("%s", text_in_popup.c_str());
bool is_unique = true;
for (const auto &item : m_style_manager.get_styles()) {
const EmbossStyle &style = item.style;
if (&style == &m_style_manager.get_style())
continue; // could be same as original name
if (style.name == new_name) is_unique = false;
}
bool allow_change = false;
if (new_name.empty()) {
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_DARK, _u8L("Name can't be empty."));
}else if (!is_unique) {
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_DARK, _u8L("Name has to be unique."));
} else {
ImGui::NewLine();
allow_change = true;
}
bool store = false;
ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue;
if (ImGui::InputText("##rename style", &new_name, flags) && allow_change) store = true;
if (m_imgui->button(_L("ok"), ImVec2(0.f, 0.f), allow_change)) store = true;
ImGui::SameLine();
if (ImGui::Button(_u8L("cancel").c_str())) {
new_name = old_name;
ImGui::CloseCurrentPopup();
}
if (store) {
// rename style in all objects and volumes
for (ModelObject *mo :wxGetApp().plater()->model().objects) {
for (ModelVolume *mv : mo->volumes) {
if (!mv->text_configuration.has_value()) continue;
std::string& name = mv->text_configuration->style.name;
if (name != old_name) continue;
name = new_name;
}
}
m_style_manager.rename(new_name);
m_style_manager.store_styles_to_app_config();
ImGui::CloseCurrentPopup();
}
}
void GLGizmoEmboss::draw_style_rename_button()
{
bool can_rename = m_style_manager.exist_stored_style();
std::string title = _u8L("Rename style");
const char * popup_id = title.c_str();
if (priv::draw_button(m_icons, IconType::rename, !can_rename)) {
assert(m_style_manager.get_stored_style());
ImGui::OpenPopup(popup_id);
}
else if (ImGui::IsItemHovered()) {
if (can_rename) ImGui::SetTooltip("%s", _u8L("Rename current style.").c_str());
else ImGui::SetTooltip("%s", _u8L("Can't rename temporary style.").c_str());
}
if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) {
m_imgui->disable_background_fadeout_animation();
draw_style_rename_popup();
ImGui::EndPopup();
}
}
void GLGizmoEmboss::draw_style_save_button(bool is_modified)
{
if (draw_button(m_icons, IconType::save, !is_modified)) {
// save styles to app config
m_style_manager.store_styles_to_app_config();
}else if (ImGui::IsItemHovered()) {
std::string tooltip;
if (!m_style_manager.exist_stored_style()) {
tooltip = _u8L("First Add style to list.");
} else if (is_modified) {
tooltip = GUI::format(_L("Save %1% style"), m_style_manager.get_style().name);
} else {
tooltip = _u8L("No changes to save.");
}
ImGui::SetTooltip("%s", tooltip.c_str());
}
}
void GLGizmoEmboss::draw_style_save_as_popup() {
ImGui::Text("%s", _u8L("New name of style: ").c_str());
// use name inside of volume configuration as temporary new name
std::string &new_name = m_volume->text_configuration->style.name;
bool is_unique = true;
for (const auto &item : m_style_manager.get_styles())
if (item.style.name == new_name) is_unique = false;
bool allow_change = false;
if (new_name.empty()) {
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_DARK, _u8L("Name can't be empty."));
}else if (!is_unique) {
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_DARK, _u8L("Name has to be unique."));
} else {
ImGui::NewLine();
allow_change = true;
}
bool save_style = false;
ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue;
if (ImGui::InputText("##save as style", &new_name, flags))
save_style = true;
if (m_imgui->button(_L("ok"), ImVec2(0.f, 0.f), allow_change))
save_style = true;
ImGui::SameLine();
if (ImGui::Button(_u8L("cancel").c_str())){
// write original name to volume TextConfiguration
new_name = m_style_manager.get_style().name;
ImGui::CloseCurrentPopup();
}
if (save_style && allow_change) {
m_style_manager.add_style(new_name);
m_style_manager.store_styles_to_app_config();
ImGui::CloseCurrentPopup();
}
}
void GLGizmoEmboss::draw_style_add_button()
{
bool only_add_style = !m_style_manager.exist_stored_style();
bool can_add = true;
if (only_add_style &&
m_volume->text_configuration->style.type != WxFontUtils::get_actual_type())
can_add = false;
std::string title = _u8L("Save as new style");
const char *popup_id = title.c_str();
// save as new style
ImGui::SameLine();
if (draw_button(m_icons, IconType::add, !can_add)) {
if (!m_style_manager.exist_stored_style()) {
m_style_manager.store_styles_to_app_config(wxGetApp().app_config);
} else {
ImGui::OpenPopup(popup_id);
}
} else if (ImGui::IsItemHovered()) {
if (!can_add) {
ImGui::SetTooltip("%s", _u8L("Only valid font can be added to style.").c_str());
}else if (only_add_style) {
ImGui::SetTooltip("%s", _u8L("Add style to my list.").c_str());
} else {
ImGui::SetTooltip("%s", _u8L("Add as new named style.").c_str());
}
}
if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) {
m_imgui->disable_background_fadeout_animation();
draw_style_save_as_popup();
ImGui::EndPopup();
}
}
void GLGizmoEmboss::draw_delete_style_button() {
bool is_stored = m_style_manager.exist_stored_style();
bool is_last = m_style_manager.get_styles().size() == 1;
bool can_delete = is_stored && !is_last;
std::string title = _u8L("Remove style");
const char * popup_id = title.c_str();
static size_t next_style_index = std::numeric_limits<size_t>::max();
if (draw_button(m_icons, IconType::erase, !can_delete)) {
while (true) {
// NOTE: can't use previous loaded activ index -> erase could change index
size_t active_index = m_style_manager.get_style_index();
next_style_index = (active_index > 0) ? active_index - 1 :
active_index + 1;
if (next_style_index >= m_style_manager.get_styles().size()) {
// can't remove last font style
// TODO: inform user
break;
}
// IMPROVE: add function can_load?
// clean unactivable styles
if (!m_style_manager.load_style(next_style_index)) {
m_style_manager.erase(next_style_index);
continue;
}
// load back
m_style_manager.load_style(active_index);
ImGui::OpenPopup(popup_id);
break;
}
}
if (ImGui::IsItemHovered()) {
const std::string &style_name = m_style_manager.get_style().name;
std::string tooltip;
if (can_delete) tooltip = GUI::format(_L("Delete \"%1%\" style."), style_name);
else if (is_last) tooltip = GUI::format(_L("Can't delete \"%1%\". It is last style."), style_name);
else/*if(!is_stored)*/ tooltip = GUI::format(_L("Can't delete temporary style \"%1%\"."), style_name);
ImGui::SetTooltip("%s", tooltip.c_str());
}
if (ImGui::BeginPopupModal(popup_id)) {
m_imgui->disable_background_fadeout_animation();
const std::string &style_name = m_style_manager.get_style().name;
std::string text_in_popup = GUI::format(_L("Are you sure,\nthat you want permanently and unrecoverable \nremove style \"%1%\"?"), style_name);
ImGui::Text("%s", text_in_popup.c_str());
if (ImGui::Button(_u8L("Yes").c_str())) {
size_t active_index = m_style_manager.get_style_index();
m_style_manager.load_style(next_style_index);
m_style_manager.erase(active_index);
m_style_manager.store_styles_to_app_config(wxGetApp().app_config);
ImGui::CloseCurrentPopup();
process();
}
ImGui::SameLine();
if (ImGui::Button(_u8L("No").c_str()))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
// FIX IT: it should not change volume position before successfull change
void GLGizmoEmboss::fix_transformation(const FontProp &from,
const FontProp &to)
{
// fix Z rotation when exists difference in styles
const std::optional<float> &f_angle_opt = from.angle;
const std::optional<float> &t_angle_opt = to.angle;
if (!is_approx(f_angle_opt, t_angle_opt)) {
// fix rotation
float f_angle = f_angle_opt.has_value() ? *f_angle_opt : .0f;
float t_angle = t_angle_opt.has_value() ? *t_angle_opt : .0f;
do_local_z_rotate(m_parent, t_angle - f_angle);
}
// fix distance (Z move) when exists difference in styles
const std::optional<float> &f_move_opt = from.distance;
const std::optional<float> &t_move_opt = to.distance;
if (!is_approx(f_move_opt, t_move_opt)) {
float f_move = f_move_opt.has_value() ? *f_move_opt : .0f;
float t_move = t_move_opt.has_value() ? *t_move_opt : .0f;
do_local_z_move(m_parent, t_move - f_move);
}
}
void GLGizmoEmboss::draw_style_list() {
if (!m_style_manager.is_active_font()) return;
const EmbossStyle *stored_style = nullptr;
bool is_stored = m_style_manager.exist_stored_style();
if (is_stored)
stored_style = m_style_manager.get_stored_style();
const EmbossStyle &actual_style = m_style_manager.get_style();
bool is_changed = (stored_style)? !(*stored_style == actual_style) : true;
bool is_modified = is_stored && is_changed;
const float &max_style_name_width = m_gui_cfg->max_style_name_width;
std::string &trunc_name = m_style_manager.get_truncated_name();
if (trunc_name.empty()) {
// generate trunc name
std::string current_name = actual_style.name;
ImGuiWrapper::escape_double_hash(current_name);
trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width);
}
std::string title = _u8L("Presets");
if (m_style_manager.exist_stored_style())
ImGui::Text("%s", title.c_str());
else
ImGui::TextColored(ImGuiWrapper::COL_ORANGE_LIGHT, "%s", title.c_str());
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
auto add_text_modify = [&is_modified](const std::string& name) {
if (!is_modified) return name;
return name + " (" + _u8L("modified") + ")";
};
std::optional<size_t> selected_style_index;
if (ImGui::BeginCombo("##style_selector", add_text_modify(trunc_name).c_str())) {
m_style_manager.init_style_images(m_gui_cfg->max_style_image_size, m_text);
m_style_manager.init_trunc_names(max_style_name_width);
std::optional<std::pair<size_t,size_t>> swap_indexes;
const std::vector<StyleManager::Item> &styles = m_style_manager.get_styles();
for (const auto &item : styles) {
size_t index = &item - &styles.front();
const EmbossStyle &style = item.style;
const std::string &actual_style_name = style.name;
ImGui::PushID(actual_style_name.c_str());
bool is_selected = (index == m_style_manager.get_style_index());
ImVec2 select_size(0,m_gui_cfg->max_style_image_size.y()); // 0,0 --> calculate in draw
const std::optional<StyleManager::StyleImage> &img = item.image;
// allow click delete button
ImGuiSelectableFlags_ flags = ImGuiSelectableFlags_AllowItemOverlap;
if (ImGui::Selectable(item.truncated_name.c_str(), is_selected, flags, select_size)) {
selected_style_index = index;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", actual_style_name.c_str());
// reorder items
if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) {
if (ImGui::GetMouseDragDelta(0).y < 0.f) {
if (index > 0)
swap_indexes = {index, index - 1};
} else if ((index + 1) < styles.size())
swap_indexes = {index, index + 1};
if (swap_indexes.has_value())
ImGui::ResetMouseDragDelta();
}
// draw style name
if (img.has_value()) {
ImGui::SameLine(max_style_name_width);
ImGui::Image(img->texture_id, img->tex_size, img->uv0, img->uv1);
}
ImGui::PopID();
}
if (swap_indexes.has_value())
m_style_manager.swap(swap_indexes->first,
swap_indexes->second);
ImGui::EndCombo();
} else {
// do not keep in memory style images when no combo box open
m_style_manager.free_style_images();
if (ImGui::IsItemHovered()) {
std::string style_name = add_text_modify(actual_style.name);
std::string tooltip = is_modified?
GUI::format(_L("Modified style \"%1%\""), actual_style.name):
GUI::format(_L("Current style is \"%1%\""), actual_style.name);
ImGui::SetTooltip(" %s", tooltip.c_str());
}
}
// Check whether user wants lose actual style modification
if (selected_style_index.has_value() && is_modified) {
wxString title = _L("Style modification will be lost.");
const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index].style;
wxString message = GUI::format_wxstr(_L("Changing style to '%1%' will discard current style modification.\n\n Would you like to continue anyway?"), style.name);
MessageDialog not_loaded_style_message(nullptr, message, title, wxICON_WARNING | wxYES|wxNO);
if (not_loaded_style_message.ShowModal() != wxID_YES)
selected_style_index.reset();
}
// selected style from combo box
if (selected_style_index.has_value()) {
const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index].style;
// create copy to be able do fix transformation only when successfully load style
FontProp act_prop = actual_style.prop; // copy
FontProp new_prop = style.prop; // copy
if (m_style_manager.load_style(*selected_style_index)) {
fix_transformation(act_prop, new_prop);
process();
} else {
wxString title = _L("Not valid style.");
wxString message = GUI::format_wxstr(_L("Style '%1%' can't be used and will be removed from list."), style.name);
MessageDialog not_loaded_style_message(nullptr, message, title, wxOK);
not_loaded_style_message.ShowModal();
m_style_manager.erase(*selected_style_index);
}
}
ImGui::SameLine();
draw_style_rename_button();
ImGui::SameLine();
draw_style_save_button(is_modified);
ImGui::SameLine();
draw_style_add_button();
// delete button
ImGui::SameLine();
draw_delete_style_button();
}
bool GLGizmoEmboss::draw_italic_button()
{
const wxFont &wx_font = m_style_manager.get_wx_font();
const auto& ff = m_style_manager.get_font_file_with_cache();
if (!wx_font.IsOk() || !ff.has_value()) {
draw(get_icon(m_icons, IconType::italic, IconState::disabled));
return false;
}
std::optional<float> &skew = m_style_manager.get_font_prop().skew;
bool is_font_italic = skew.has_value() || WxFontUtils::is_italic(wx_font);
if (is_font_italic) {
// unset italic
if (clickable(get_icon(m_icons, IconType::italic, IconState::hovered),
get_icon(m_icons, IconType::unitalic, IconState::hovered))) {
skew.reset();
if (wx_font.GetStyle() != wxFontStyle::wxFONTSTYLE_NORMAL) {
wxFont new_wx_font = wx_font; // copy
new_wx_font.SetStyle(wxFontStyle::wxFONTSTYLE_NORMAL);
if(!m_style_manager.set_wx_font(new_wx_font))
return false;
}
return true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Unset italic").c_str());
} else {
// set italic
if (draw_button(m_icons, IconType::italic)) {
wxFont new_wx_font = wx_font; // copy
auto new_ff = WxFontUtils::set_italic(new_wx_font, *ff.font_file);
if (new_ff != nullptr) {
if(!m_style_manager.set_wx_font(new_wx_font, std::move(new_ff)))
return false;
} else {
// italic font doesn't exist
// add skew when wxFont can't set it
skew = 0.2f;
}
return true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Set italic").c_str());
}
return false;
}
bool GLGizmoEmboss::draw_bold_button() {
const wxFont &wx_font = m_style_manager.get_wx_font();
const auto& ff = m_style_manager.get_font_file_with_cache();
if (!wx_font.IsOk() || !ff.has_value()) {
draw(get_icon(m_icons, IconType::bold, IconState::disabled));
return false;
}
std::optional<float> &boldness = m_style_manager.get_font_prop().boldness;
bool is_font_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font);
if (is_font_bold) {
// unset bold
if (clickable(get_icon(m_icons, IconType::bold, IconState::hovered),
get_icon(m_icons, IconType::unbold, IconState::hovered))) {
boldness.reset();
if (wx_font.GetWeight() != wxFontWeight::wxFONTWEIGHT_NORMAL) {
wxFont new_wx_font = wx_font; // copy
new_wx_font.SetWeight(wxFontWeight::wxFONTWEIGHT_NORMAL);
if(!m_style_manager.set_wx_font(new_wx_font))
return false;
}
return true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Unset bold").c_str());
} else {
// set bold
if (draw_button(m_icons, IconType::bold)) {
wxFont new_wx_font = wx_font; // copy
auto new_ff = WxFontUtils::set_bold(new_wx_font, *ff.font_file);
if (new_ff != nullptr) {
if(!m_style_manager.set_wx_font(new_wx_font, std::move(new_ff)))
return false;
} else {
// bold font can't be loaded
// set up boldness
boldness = 20.f;
//font_file->cache.empty();
}
return true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Set bold").c_str());
}
return false;
}
template<typename T> bool exist_change(const T &value, const T *default_value){
if (default_value == nullptr) return false;
return (value != *default_value);
}
template<> bool exist_change(const std::optional<float> &value, const std::optional<float> *default_value){
if (default_value == nullptr) return false;
return !is_approx(value, *default_value);
}
template<> bool exist_change(const float &value, const float *default_value){
if (default_value == nullptr) return false;
return !is_approx(value, *default_value);
}
template<typename T, typename Draw>
bool GLGizmoEmboss::revertible(const std::string &name,
T &value,
const T *default_value,
const std::string &undo_tooltip,
float undo_offset,
Draw draw)
{
bool changed = exist_change(value, default_value);
if (changed || default_value == nullptr)
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, name);
else
ImGuiWrapper::text(name);
bool result = draw();
// render revert changes button
if (changed) {
ImGui::SameLine(undo_offset);
if (draw_button(m_icons, IconType::undo)) {
value = *default_value;
return true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", undo_tooltip.c_str());
}
return result;
}
bool GLGizmoEmboss::rev_input(const std::string &name,
float &value,
const float *default_value,
const std::string &undo_tooltip,
float step,
float step_fast,
const char *format,
ImGuiInputTextFlags flags)
{
// draw offseted input
auto draw_offseted_input = [&]()->bool{
float input_offset = m_gui_cfg->input_offset;
float input_width = m_gui_cfg->input_width;
ImGui::SameLine(input_offset);
ImGui::SetNextItemWidth(input_width);
return ImGui::InputFloat(("##" + name).c_str(),
&value, step, step_fast, format, flags);
};
float undo_offset = ImGui::GetStyle().FramePadding.x;
return revertible(name, value, default_value, undo_tooltip, undo_offset, draw_offseted_input);
}
bool GLGizmoEmboss::rev_input_mm(const std::string &name,
float &value,
const float *default_value_ptr,
const std::string &undo_tooltip,
float step,
float step_fast,
const char *format,
bool use_inch,
const std::optional<float>& scale)
{
// _variable which temporary keep value
float value_ = value;
float default_value_;
if (use_inch) {
// calc value in inch
value_ *= ObjectManipulation::mm_to_in;
if (default_value_ptr) {
default_value_ = ObjectManipulation::mm_to_in * (*default_value_ptr);
default_value_ptr = &default_value_;
}
}
if (scale.has_value())
value_ *= *scale;
bool use_correction = use_inch || scale.has_value();
if (rev_input(name, use_correction ? value_ : value, default_value_ptr, undo_tooltip, step, step_fast, format)) {
if (use_correction) {
value = value_;
if (use_inch)
value *= ObjectManipulation::in_to_mm;
if (scale.has_value())
value /= *scale;
}
return true;
}
return false;
}
bool GLGizmoEmboss::rev_checkbox(const std::string &name,
bool &value,
const bool *default_value,
const std::string &undo_tooltip)
{
// draw offseted input
auto draw_offseted_input = [&]() -> bool {
ImGui::SameLine(m_gui_cfg->advanced_input_offset);
return ImGui::Checkbox(("##" + name).c_str(), &value);
};
float undo_offset = ImGui::GetStyle().FramePadding.x;
return revertible(name, value, default_value, undo_tooltip,
undo_offset, draw_offseted_input);
}
void GLGizmoEmboss::draw_style_edit()
{
{
// Check correct WxFont
const wxFont &wx_font = m_style_manager.get_wx_font();
assert(wx_font.IsOk());
if (!wx_font.IsOk()) {
ImGui::TextColored(ImGuiWrapper::COL_ORANGE_DARK, "%s", _u8L("WxFont is not loaded properly.").c_str());
return;
}
}
bool exist_stored_style = m_style_manager.exist_stored_style();
bool exist_change_in_font = m_style_manager.is_font_changed();
const GuiCfg::Translations &tr = m_gui_cfg->translations;
if (exist_change_in_font || !exist_stored_style)
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr.font);
else
ImGuiWrapper::text(tr.font);
ImGui::SameLine(m_gui_cfg->input_offset);
draw_font_list();
bool exist_change = false;
if (!m_is_unknown_font) {
ImGui::SameLine();
if (draw_italic_button())
exist_change = true;
ImGui::SameLine();
if (draw_bold_button())
exist_change = true;
}
EmbossStyle &style = m_style_manager.get_style();
if (exist_change_in_font) {
ImGui::SameLine(ImGui::GetStyle().FramePadding.x);
if (draw_button(m_icons, IconType::undo)) {
const EmbossStyle *stored_style = m_style_manager.get_stored_style();
style.path = stored_style->path;
style.prop.boldness = stored_style->prop.boldness;
style.prop.skew = stored_style->prop.skew;
wxFont new_wx_font = WxFontUtils::load_wxFont(style.path);
if (new_wx_font.IsOk() &&
m_style_manager.set_wx_font(new_wx_font))
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Revert font changes.").c_str());
}
if (exist_change) {
m_style_manager.clear_glyphs_cache();
process();
}
bool use_inch = wxGetApp().app_config->get_bool("use_inches");
draw_height(use_inch);
draw_depth(use_inch);
#ifdef SHOW_WX_WEIGHT_INPUT
if (wx_font.has_value()) {
ImGui::Text("%s", "weight");
ImGui::SameLine(m_gui_cfg->input_offset);
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
int weight = wx_font->GetNumericWeight();
int min_weight = 1, max_weight = 1000;
if (ImGui::SliderInt("##weight", &weight, min_weight, max_weight)) {
wx_font->SetNumericWeight(weight);
m_style_manager.wx_font_changed();
process();
}
wxFont f = wx_font->Bold();
bool disable = f == *wx_font;
ImGui::SameLine();
if (draw_button(IconType::bold, disable)) {
*wx_font = f;
m_style_manager.wx_font_changed();
process();
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("wx Make bold").c_str());
}
#endif // SHOW_WX_WEIGHT_INPUT
}
bool GLGizmoEmboss::set_height() {
float &value = m_style_manager.get_style().prop.size_in_mm;
// size can't be zero or negative
apply(value, priv::limits.size_in_mm);
if (m_volume == nullptr || !m_volume->text_configuration.has_value()) {
assert(false);
return false;
}
// only different value need process
if (is_approx(value, m_volume->text_configuration->style.prop.size_in_mm))
return false;
#ifdef USE_PIXEL_SIZE_IN_WX_FONT
// store font size into path serialization
const wxFont &wx_font = m_style_manager.get_wx_font();
if (wx_font.IsOk()) {
wxFont wx_font_new = wx_font; // copy
wx_font_new.SetPointSize(static_cast<int>(value));
m_style_manager.set_wx_font(wx_font_new);
}
#endif
return true;
}
void GLGizmoEmboss::draw_height(bool use_inch)
{
float &value = m_style_manager.get_style().prop.size_in_mm;
const EmbossStyle* stored_style = m_style_manager.get_stored_style();
const float *stored = ((stored_style)? &stored_style->prop.size_in_mm : nullptr);
const char *size_format = ((use_inch) ? "%.2f in" : "%.1f mm");
const std::string revert_text_size = _u8L("Revert text size.");
const std::string& name = m_gui_cfg->translations.height;
if (rev_input_mm(name, value, stored, revert_text_size, 0.1f, 1.f, size_format, use_inch, m_scale_height))
if (set_height())
process();
}
bool GLGizmoEmboss::set_depth()
{
float &value = m_style_manager.get_style().prop.emboss;
// size can't be zero or negative
apply(value, priv::limits.emboss);
// only different value need process
return !is_approx(value, m_volume->text_configuration->style.prop.emboss);
}
void GLGizmoEmboss::draw_depth(bool use_inch)
{
float &value = m_style_manager.get_style().prop.emboss;
const EmbossStyle* stored_style = m_style_manager.get_stored_style();
const float *stored = ((stored_style)? &stored_style->prop.emboss : nullptr);
const std::string revert_emboss_depth = _u8L("Revert embossed depth.");
const char *size_format = ((use_inch) ? "%.3f in" : "%.2f mm");
const std::string name = m_gui_cfg->translations.depth;
if (rev_input_mm(name, value, stored, revert_emboss_depth, 0.1f, 1.f, size_format, use_inch, m_scale_depth))
if (set_depth())
process();
}
bool GLGizmoEmboss::rev_slider(const std::string &name,
std::optional<int>& value,
const std::optional<int> *default_value,
const std::string &undo_tooltip,
int v_min,
int v_max,
const std::string& format,
const wxString &tooltip)
{
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<float>& value,
const std::optional<float> *default_value,
const std::string &undo_tooltip,
float v_min,
float v_max,
const std::string& format,
const wxString &tooltip)
{
auto draw_slider_optional_float = [&]() -> 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_float(("##" + 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_float);
}
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 std::string &format,
const wxString &tooltip)
{
auto draw_slider_float = [&]() -> 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_float("##" + name, &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_float);
}
void GLGizmoEmboss::draw_advanced()
{
const auto &ff = m_style_manager.get_font_file_with_cache();
if (!ff.has_value()) {
ImGui::Text("%s", _u8L("Advanced font options could be changed only for correct font.\n"
"Start with select correct font.").c_str());
return;
}
FontProp &font_prop = m_style_manager.get_style().prop;
const auto &cn = m_style_manager.get_font_prop().collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
const auto &font_info = ff.font_file->infos[font_index];
#ifdef SHOW_FONT_FILE_PROPERTY
ImGui::SameLine();
int cache_size = ff.has_value()? (int)ff.cache->size() : 0;
std::string ff_property =
"ascent=" + std::to_string(font_info.ascent) +
", descent=" + std::to_string(font_info.descent) +
", lineGap=" + std::to_string(font_info.linegap) +
", unitPerEm=" + std::to_string(font_info.unit_per_em) +
", cache(" + std::to_string(cache_size) + " glyphs)";
if (font_file->infos.size() > 1) {
unsigned int collection = font_prop.collection_number.has_value() ?
*font_prop.collection_number : 0;
ff_property += ", collect=" + std::to_string(collection+1) + "/" + std::to_string(font_file->infos.size());
}
m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, ff_property);
#endif // SHOW_FONT_FILE_PROPERTY
bool exist_change = false;
auto &tr = m_gui_cfg->translations;
const EmbossStyle *stored_style = nullptr;
if (m_style_manager.exist_stored_style())
stored_style = m_style_manager.get_stored_style();
bool can_use_surface = (m_volume==nullptr)? false :
(font_prop.use_surface)? true : // already used surface must have option to uncheck
(m_volume->get_object()->volumes.size() > 1);
m_imgui->disabled_begin(!can_use_surface);
const bool *def_use_surface = stored_style ?
&stored_style->prop.use_surface : nullptr;
if (rev_checkbox(tr.use_surface, font_prop.use_surface, def_use_surface,
_u8L("Revert using of model surface."))) {
if (font_prop.use_surface) {
// when using surface distance is not used
font_prop.distance.reset();
// there should be minimal embossing depth
if (font_prop.emboss < 0.1)
font_prop.emboss = 1;
}
process();
}
m_imgui->disabled_end(); // !can_use_surface
std::string units = _u8L("font points");
std::string units_fmt = "%.0f " + units;
// input gap between letters
auto def_char_gap = stored_style ?
&stored_style->prop.char_gap : nullptr;
int half_ascent = font_info.ascent / 2;
int min_char_gap = -half_ascent, max_char_gap = half_ascent;
if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"),
min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){
// Condition prevent recalculation when insertint out of limits value by imgui input
if (!apply(font_prop.char_gap, priv::limits.char_gap) ||
!m_volume->text_configuration->style.prop.char_gap.has_value() ||
m_volume->text_configuration->style.prop.char_gap != font_prop.char_gap) {
// char gap is stored inside of imgui font atlas
m_style_manager.clear_imgui_font();
exist_change = true;
}
}
// input gap between lines
auto def_line_gap = stored_style ?
&stored_style->prop.line_gap : nullptr;
int min_line_gap = -half_ascent, max_line_gap = half_ascent;
if (rev_slider(tr.line_gap, font_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"),
min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){
// Condition prevent recalculation when insertint out of limits value by imgui input
if (!apply(font_prop.line_gap, priv::limits.line_gap) ||
!m_volume->text_configuration->style.prop.line_gap.has_value() ||
m_volume->text_configuration->style.prop.line_gap != font_prop.line_gap) {
// line gap is planed to be stored inside of imgui font atlas
m_style_manager.clear_imgui_font();
exist_change = true;
}
}
// input boldness
auto def_boldness = stored_style ?
&stored_style->prop.boldness : nullptr;
if (rev_slider(tr.boldness, font_prop.boldness, def_boldness, _u8L("Undo boldness"),
priv::limits.boldness.gui.min, priv::limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){
if (!apply(font_prop.boldness, priv::limits.boldness.values) ||
!m_volume->text_configuration->style.prop.boldness.has_value() ||
m_volume->text_configuration->style.prop.boldness != font_prop.boldness)
exist_change = true;
}
// input italic
auto def_skew = stored_style ?
&stored_style->prop.skew : nullptr;
if (rev_slider(tr.skew_ration, font_prop.skew, def_skew, _u8L("Undo letter's skew"),
priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){
if (!apply(font_prop.skew, priv::limits.skew.values) ||
!m_volume->text_configuration->style.prop.skew.has_value() ||
m_volume->text_configuration->style.prop.skew != font_prop.skew)
exist_change = true;
}
// input surface distance
bool allowe_surface_distance =
!m_volume->text_configuration->style.prop.use_surface &&
!is_text_object(m_volume);
std::optional<float> &distance = font_prop.distance;
float prev_distance = distance.has_value() ? *distance : .0f,
min_distance = -2 * font_prop.emboss,
max_distance = 2 * font_prop.emboss;
auto def_distance = stored_style ?
&stored_style->prop.distance : nullptr;
m_imgui->disabled_begin(!allowe_surface_distance);
bool use_inch = wxGetApp().app_config->get_bool("use_inches");
const std::string undo_move_tooltip = _u8L("Undo translation");
const wxString move_tooltip = _L("Distance of the center of text from model surface");
bool is_moved = false;
if (use_inch) {
std::optional<float> distance_inch;
if (distance.has_value()) distance_inch = (*distance * ObjectManipulation::mm_to_in);
std::optional<float> 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()) {
font_prop.distance = *distance_inch * ObjectManipulation::in_to_mm;
} else {
font_prop.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){
m_volume->text_configuration->style.prop.distance = font_prop.distance;
float act_distance = font_prop.distance.has_value() ? *font_prop.distance : .0f;
do_local_z_move(m_parent, act_distance - prev_distance);
}
m_imgui->disabled_end();
// slider for Clock-wise angle in degress
// stored angle is optional CCW and in radians
// Convert stored value to degress
// minus create clock-wise roation from CCW
const std::optional<float> &angle_opt = m_style_manager.get_font_prop().angle;
float angle = angle_opt.has_value() ? *angle_opt: 0.f;
float angle_deg = static_cast<float>(-angle * 180 / M_PI);
float def_angle_deg_val =
(!stored_style || !stored_style->prop.angle.has_value()) ?
0.f : (*stored_style->prop.angle * -180 / M_PI);
float* def_angle_deg = stored_style ?
&def_angle_deg_val : nullptr;
if (rev_slider(tr.rotation, angle_deg, def_angle_deg, _u8L("Undo rotation"),
priv::limits.angle.min, priv::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, diff_angle);
// calc angle after rotation
const GLVolume *gl_volume = get_selected_gl_volume(m_parent.get_selection());
assert(gl_volume != nullptr);
assert(m_style_manager.is_active_font());
if (m_style_manager.is_active_font() && gl_volume != nullptr)
m_style_manager.get_font_prop().angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
// recalculate for surface cut
if (font_prop.use_surface)
process();
}
ImGui::Text("%s", tr.keep_up.c_str());
ImGui::SameLine(m_gui_cfg->advanced_input_offset);
if (ImGui::Checkbox("##keep_up", &m_keep_up)) {
if (m_keep_up) {
// copy angle to volume
m_volume->text_configuration->style.prop.angle = font_prop.angle;
}
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Keep text orientation during surface dragging.\nNot stable between horizontal and vertical alignment.").c_str());
// when more collection add selector
if (ff.font_file->infos.size() > 1) {
ImGui::Text("%s", tr.collection.c_str());
ImGui::SameLine(m_gui_cfg->advanced_input_offset);
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
unsigned int selected = font_prop.collection_number.has_value() ?
*font_prop.collection_number : 0;
if (ImGui::BeginCombo("## Font collection", std::to_string(selected).c_str())) {
for (unsigned int i = 0; i < ff.font_file->infos.size(); ++i) {
ImGui::PushID(1 << (10 + i));
bool is_selected = (i == selected);
if (ImGui::Selectable(std::to_string(i).c_str(), is_selected)) {
if (i == 0) font_prop.collection_number.reset();
else font_prop.collection_number = i;
exist_change = true;
}
ImGui::PopID();
}
ImGui::EndCombo();
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", _u8L("Select from True Type Collection.").c_str());
}
}
if (exist_change) {
m_style_manager.clear_glyphs_cache();
process();
}
if (ImGui::Button(_u8L("Set text to face camera").c_str())) {
assert(get_selected_volume(m_parent.get_selection()) == m_volume);
const Camera &cam = wxGetApp().plater()->get_camera();
bool use_surface = m_style_manager.get_style().prop.use_surface;
if (face_selected_volume_to_camera(cam, m_parent) && use_surface)
process();
} else if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str());
}
#ifdef ALLOW_DEBUG_MODE
ImGui::Text("family = %s", (font_prop.family.has_value() ?
font_prop.family->c_str() :
" --- "));
ImGui::Text("face name = %s", (font_prop.face_name.has_value() ?
font_prop.face_name->c_str() :
" --- "));
ImGui::Text("style = %s",
(font_prop.style.has_value() ? font_prop.style->c_str() :
" --- "));
ImGui::Text("weight = %s", (font_prop.weight.has_value() ?
font_prop.weight->c_str() :
" --- "));
std::string descriptor = style.path;
ImGui::Text("descriptor = %s", descriptor.c_str());
#endif // ALLOW_DEBUG_MODE
}
void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style)
{
ImVec2 window_size = ImGui::GetWindowSize();
const ImVec2& min_win_size_prev = get_minimal_window_size();
//ImVec2 diff(window_size.x - min_win_size_prev.x,
// window_size.y - min_win_size_prev.y);
float diff_y = window_size.y - min_win_size_prev.y;
m_is_advanced_edit_style = is_advance_edit_style;
const ImVec2 &min_win_size = get_minimal_window_size();
ImVec2 new_window_size(0.f, min_win_size.y + diff_y);
ImGui::SetWindowSize(new_window_size, ImGuiCond_Always);
m_set_window_offset = ImGuiWrapper::change_window_position(on_get_name().c_str(), true);
}
ImVec2 GLGizmoEmboss::get_minimal_window_size() const
{
ImVec2 res;
if (!m_is_advanced_edit_style)
res = m_gui_cfg->minimal_window_size;
else if (!m_style_manager.has_collections())
res = m_gui_cfg->minimal_window_size_with_advance;
else
res = m_gui_cfg->minimal_window_size_with_collections;
bool is_object = m_volume->get_object()->volumes.size() == 1;
if (!is_object)
res.y += m_gui_cfg->height_of_volume_type_selector;
return res;
}
#ifdef ALLOW_ADD_FONT_BY_OS_SELECTOR
bool GLGizmoEmboss::choose_font_by_wxdialog()
{
wxFontData data;
data.EnableEffects(false);
data.RestrictSelection(wxFONTRESTRICT_SCALABLE);
// set previous selected font
EmbossStyle &selected_style = m_style_manager.get_style();
if (selected_style.type == WxFontUtils::get_actual_type()) {
std::optional<wxFont> selected_font = WxFontUtils::load_wxFont(
selected_style.path);
if (selected_font.has_value()) data.SetInitialFont(*selected_font);
}
wxFontDialog font_dialog(wxGetApp().mainframe, data);
if (font_dialog.ShowModal() != wxID_OK) return false;
data = font_dialog.GetFontData();
wxFont wx_font = data.GetChosenFont();
size_t font_index = m_style_manager.get_fonts().size();
EmbossStyle emboss_style = WxFontUtils::create_emboss_style(wx_font);
// Check that deserialization NOT influence font
// false - use direct selected wxFont in dialog
// true - use font item (serialize and deserialize wxFont)
bool use_deserialized_font = false;
// Try load and use new added font
if ((use_deserialized_font && !m_style_manager.load_style(font_index)) ||
(!use_deserialized_font && !m_style_manager.load_style(emboss_style, wx_font))) {
m_style_manager.erase(font_index);
wxString message = GUI::format_wxstr(
_L("Font '%1%' can't be used. Please select another."),
emboss_style.name);
wxString title = _L("Selected font is NOT True-type.");
MessageDialog not_loaded_font_message(nullptr, message, title, wxOK);
not_loaded_font_message.ShowModal();
return choose_font_by_wxdialog();
}
// fix dynamic creation of italic font
const auto& cn = m_style_manager.get_font_prop().collection_number;
unsigned int font_collection = cn.has_value() ? *cn : 0;
const auto&ff = m_style_manager.get_font_file_with_cache();
if (WxFontUtils::is_italic(wx_font) &&
!Emboss::is_italic(*ff.font_file, font_collection)) {
m_style_manager.get_style().prop.skew = 0.2;
}
return true;
}
#endif // ALLOW_ADD_FONT_BY_OS_SELECTOR
#if defined ALLOW_ADD_FONT_BY_FILE or defined ALLOW_DEBUG_MODE
namespace priv {
static std::string get_file_name(const std::string &file_path)
{
size_t pos_last_delimiter = file_path.find_last_of("/\\");
size_t pos_point = file_path.find_last_of('.');
size_t offset = pos_last_delimiter + 1;
size_t count = pos_point - pos_last_delimiter - 1;
return file_path.substr(offset, count);
}
} // namespace priv
#endif // ALLOW_ADD_FONT_BY_FILE || ALLOW_DEBUG_MODE
#ifdef ALLOW_ADD_FONT_BY_FILE
bool GLGizmoEmboss::choose_true_type_file()
{
wxArrayString input_files;
wxString fontDir = wxEmptyString;
wxString selectedFile = wxEmptyString;
wxFileDialog dialog(nullptr, _L("Choose one or more files (TTF, TTC):"),
fontDir, selectedFile, file_wildcards(FT_FONTS),
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files);
if (input_files.IsEmpty()) return false;
size_t index = m_style_manager.get_fonts().size();
// use first valid font
for (auto &input_file : input_files) {
std::string path = std::string(input_file.c_str());
std::string name = priv::get_file_name(path);
//make_unique_name(name, m_font_list);
const FontProp& prop = m_style_manager.get_font_prop();
EmbossStyle style{ name, path, EmbossStyle::Type::file_path, prop };
m_style_manager.add_font(style);
// set first valid added font as active
if (m_style_manager.load_style(index)) return true;
m_style_manager.erase(index);
}
return false;
}
#endif // ALLOW_ADD_FONT_BY_FILE
#ifdef ALLOW_DEBUG_MODE
bool GLGizmoEmboss::choose_svg_file()
{
wxArrayString input_files;
wxString fontDir = wxEmptyString;
wxString selectedFile = wxEmptyString;
wxFileDialog dialog(nullptr, _L("Choose SVG file:"), fontDir,
selectedFile, file_wildcards(FT_SVG),
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files);
if (input_files.IsEmpty()) return false;
if (input_files.size() != 1) return false;
auto & input_file = input_files.front();
std::string path = std::string(input_file.c_str());
std::string name = priv::get_file_name(path);
NSVGimage *image = nsvgParseFromFile(path.c_str(), "mm", 96.0f);
ExPolygons polys = NSVGUtils::to_ExPolygons(image);
nsvgDelete(image);
BoundingBox bb;
for (const auto &p : polys) bb.merge(p.contour.points);
const FontProp &fp = m_style_manager.get_style().prop;
float scale = fp.size_in_mm / std::max(bb.max.x(), bb.max.y());
auto project = std::make_unique<ProjectScale>(
std::make_unique<ProjectZ>(fp.emboss / scale), scale);
indexed_triangle_set its = polygons2model(polys, *project);
return false;
// test store:
// for (auto &poly : polys) poly.scale(1e5);
// SVG svg("converted.svg", BoundingBox(polys.front().contour.points));
// svg.draw(polys);
//return add_volume(name, its);
}
#endif // ALLOW_DEBUG_MODE
void GLGizmoEmboss::create_notification_not_valid_font(
const TextConfiguration &tc)
{
// not neccessary, but for sure that old notification doesnt exist
if (m_is_unknown_font) remove_notification_not_valid_font();
m_is_unknown_font = true;
auto type = NotificationType::UnknownFont;
auto level =
NotificationManager::NotificationLevel::WarningNotificationLevel;
const EmbossStyle &es = m_style_manager.get_style();
const auto &face_name_opt = es.prop.face_name;
const auto &face_name_3mf_opt = tc.style.prop.face_name;
const std::string &face_name_3mf = face_name_3mf_opt.has_value() ?
*face_name_3mf_opt :
tc.style.path;
std::string face_name_by_wx;
if (!face_name_opt.has_value()) {
const wxFont& wx_font = m_style_manager.get_wx_font();
if (wx_font.IsOk()) {
wxString wx_face_name = wx_font.GetFaceName();
face_name_by_wx = std::string((const char *) wx_face_name.ToUTF8());
}
}
const std::string &face_name = face_name_opt.has_value() ? *face_name_opt :
(!face_name_by_wx.empty() ? face_name_by_wx : es.path);
std::string text =
GUI::format(_L("Can't load exactly same font(\"%1%\"), "
"Aplication selected a similar one(\"%2%\"). "
"You have to specify font for enable edit text."),
face_name_3mf, face_name);
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->push_notification(type, level, text);
}
void GLGizmoEmboss::remove_notification_not_valid_font()
{
if (!m_is_unknown_font) return;
m_is_unknown_font = false;
auto type = NotificationType::UnknownFont;
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->close_notification_of_type(type);
}
void GLGizmoEmboss::init_icons()
{
// icon order has to match the enum IconType
std::vector<std::string> filenames{
"edit_button.svg",
"delete.svg",
"add_copies.svg",
"save.svg",
"undo.svg",
"make_italic.svg",
"make_unitalic.svg",
"make_bold.svg",
"make_unbold.svg",
"search.svg",
"open.svg",
"exclamation.svg"
};
assert(filenames.size() == static_cast<size_t>(IconType::_count));
std::string path = resources_dir() + "/icons/";
for (std::string &filename : filenames) filename = path + filename;
ImVec2 size(m_gui_cfg->icon_width, m_gui_cfg->icon_width);
auto type = IconManager::RasterType::color_wite_gray;
m_icons = m_icon_manager.init(filenames, size, type);
}
const IconManager::Icon &priv::get_icon(const IconManager::VIcons& icons, IconType type, IconState state) { return *icons[(unsigned) type][(unsigned) state]; }
bool priv::draw_button(const IconManager::VIcons &icons, IconType type, bool disable)
{
return Slic3r::GUI::button(
get_icon(icons, type, IconState::activable),
get_icon(icons, type, IconState::hovered),
get_icon(icons, type, IconState::disabled),
disable
);
}
bool GLGizmoEmboss::is_text_object(const ModelVolume *text) {
if (text == nullptr) return false;
if (!text->text_configuration.has_value()) return false;
if (text->type() != ModelVolumeType::MODEL_PART) return false;
for (const ModelVolume *v : text->get_object()->volumes) {
if (v == text) continue;
if (v->type() == ModelVolumeType::MODEL_PART) return false;
}
return true;
}
/////////////
// priv namespace implementation
///////////////
bool priv::is_valid(ModelVolumeType volume_type)
{
if (volume_type == ModelVolumeType::MODEL_PART ||
volume_type == ModelVolumeType::NEGATIVE_VOLUME ||
volume_type == ModelVolumeType::PARAMETER_MODIFIER)
return true;
BOOST_LOG_TRIVIAL(error) << "Can't create embossed text with this type: " << (int) volume_type;
return false;
}
priv::TextDataBase::TextDataBase(DataBase &&parent, const FontFileWithCache &font_file, TextConfiguration &&text_configuration)
: DataBase(std::move(parent)), font_file(font_file) /* copy */, text_configuration(std::move(text_configuration))
{
assert(this->font_file.has_value());
// partialy fill shape from text configuration
const FontProp &fp = this->text_configuration.style.prop;
shape.depth = fp.emboss;
shape.use_surface = fp.use_surface;
shape.distance = fp.distance;
const FontFile &ff = *this->font_file.font_file;
shape.scale = get_text_shape_scale(fp, ff);
}
EmbossShape &priv::TextDataBase::create_shape()
{
if (!shape.shapes.empty())
return shape;
// create shape by configuration
const char *text = text_configuration.text.c_str();
const FontProp &fp = text_configuration.style.prop;
auto was_canceled = [&c = cancel]() -> bool { return c->load(); };
shape.shapes = text2shapes(font_file, text, fp, was_canceled);
// TEST
const FontProp &prop = text_configuration.style.prop;
const std::optional<unsigned int> &cn = prop.collection_number;
unsigned int font_index = (cn.has_value()) ? *cn : 0;
const FontFileWithCache &font = font_file;
assert(font_index < font.font_file->infos.size());
int unit_per_em = font.font_file->infos[font_index].unit_per_em;
float scale = prop.size_in_mm / unit_per_em;
float depth = prop.emboss / scale;
return shape;
}
void priv::TextDataBase::write(ModelVolume &volume) const
{
volume.text_configuration = text_configuration; // copy
// discard information about rotation, should not be stored in volume
volume.text_configuration->style.prop.angle.reset();
// only temporary solution
volume.text_configuration->style.prop.use_surface = shape.use_surface;
volume.text_configuration->style.prop.distance = shape.distance;
DataBase::write(volume);
}
std::unique_ptr<DataBase> priv::create_emboss_data_base(const std::string &text, StyleManager &style_manager, std::shared_ptr<std::atomic<bool>>& cancel)
{
// create volume_name
std::string volume_name = text; // copy
// contain_enter?
if (volume_name.find('\n') != std::string::npos)
// change enters to space
std::replace(volume_name.begin(), volume_name.end(), '\n', ' ');
if (!style_manager.is_active_font())
style_manager.load_valid_style();
assert(style_manager.is_active_font());
const EmbossStyle &es = style_manager.get_style();
// actualize font path - during changes in gui it could be corrupted
// volume must store valid path
assert(style_manager.get_wx_font().IsOk());
assert(es.path.compare(WxFontUtils::store_wxFont(style_manager.get_wx_font())) == 0);
// Cancel previous Job, when it is in process
// worker.cancel(); --> Use less in this case I want cancel only previous EmbossJob no other jobs
// Cancel only EmbossUpdateJob no others
if (cancel != nullptr)
cancel->store(true);
// create new shared ptr to cancel new job
cancel = std::make_shared<std::atomic<bool>>(false);
DataBase base(volume_name, cancel);
FontFileWithCache &font = style_manager.get_font_file_with_cache();
TextConfiguration tc{es, text};
return std::make_unique<TextDataBase>(std::move(base), font, std::move(tc));
}
void priv::start_create_object_job(std::unique_ptr<DataBase> emboss_data, const Vec2d &coor) {
Plater *plater = wxGetApp().plater();
const Camera &camera = plater->get_camera();
const Pointfs &bed_shape = plater->build_volume().bed_shape();
Worker &worker = plater->get_ui_job_worker();
DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape};
auto job = std::make_unique<CreateObjectJob>(std::move(data));
queue_job(worker, std::move(job));
}
bool priv::start_create_volume_on_surface_job(const FontProp &fp,
std::unique_ptr<DataBase> emboss_data,
ModelVolumeType volume_type,
const Vec2d &screen_coor,
const GLVolume &gl_volume,
RaycastManager &raycaster,
GLCanvas3D &canvas)
{
const ModelObjectPtrs &objects = canvas.get_model()->objects;
const ModelVolume* volume = get_model_volume(gl_volume, objects);
const ModelInstance *instance = get_model_instance(gl_volume, objects);
if (volume == nullptr || instance == nullptr ||
volume->get_object() == nullptr) {
// weird situation
assert(false);
return false;
}
Plater* plater = wxGetApp().plater();
const Camera &camera = plater->get_camera();
std::optional<Transform3d> transform = create_volume_transformation_on_surface(
screen_coor, camera, *volume, *instance, raycaster, canvas, fp.distance, fp.angle);
if (!transform.has_value()) {
// When model is broken. It could appear that hit miss the object.
// So add part near by in simmilar manner as right panel do
return false;
}
// Try to cast ray into scene and find object for add volume
Worker &worker = plater->get_ui_job_worker();
return start_create_volume_job(worker, *volume->get_object(), *transform, std::move(emboss_data), volume_type);
}
ImVec2 priv::calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size)
{
const Selection::IndicesList indices = selection.get_volume_idxs();
// no selected volume
if (indices.empty())
return {};
const GLVolume *volume = selection.get_volume(*indices.begin());
// bad volume selected (e.g. deleted one)
if (volume == nullptr)
return {};
const Camera &camera = wxGetApp().plater()->get_camera();
Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *volume);
ImVec2 c_size(canvas_size.get_width(), canvas_size.get_height());
ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, c_size);
return offset;
}
// any existing icon filename to not influence GUI
const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg";