mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-21 15:51:10 -06:00
Port Emboss & SVG gizmo from PrusaSlicer (#2819)
* Rework UI jobs to make them more understandable and flexible. * Update Orca specific jobs * Fix progress issue * Fix dark mode and window radius * Update cereal version from 1.2.2 to 1.3.0 (cherry picked from commit prusa3d/PrusaSlicer@057232a275) * Initial port of Emboss gizmo * Bump up CGAL version to 5.4 (cherry picked from commit prusa3d/PrusaSlicer@1bf9dee3e7) * Fix text rotation * Fix test dragging * Add text gizmo to right click menu * Initial port of SVG gizmo * Fix text rotation * Fix Linux build * Fix "from surface" * Fix -90 rotation * Fix icon path * Fix loading font with non-ascii name * Fix storing non-utf8 font descriptor in 3mf file * Fix filtering with non-utf8 characters * Emboss: Use Orca style input dialog * Fix build on macOS * Fix tooltip color in light mode * InputText: fixed incorrect padding when FrameBorder > 0. (ocornut/imgui#4794, ocornut/imgui#3781) InputTextMultiline: fixed vertical tracking with large values of FramePadding.y. (ocornut/imgui#3781, ocornut/imgui#4794) (cherry picked from commit ocornut/imgui@072caa4a90) (cherry picked from commit ocornut/imgui@bdd2a94315) * SVG: Use Orca style input dialog * Fix job progress update * Fix crash when select editing text in preview screen * Use Orca checkbox style * Fix issue that toolbar icons are kept regenerated * Emboss: Fix text & icon alignment * SVG: Fix text & icon alignment * Emboss: fix toolbar icon mouse hover state * Add a simple subtle outline effect by drawing back faces using wireframe mode * Disable selection outlines * Show outline in white if the model color is too dark * Make the outline algorithm more reliable * Enable cull face, which fix render on Linux * Fix `disable_cullface` * Post merge fix * Optimize selection rendering * Fix scale gizmo * Emboss: Fix text rotation if base object is scaled * Fix volume synchronize * Fix emboss rotation * Emboss: Fix advance toggle * Fix text position after reopened the project * Make font style preview darker * Make font style preview selector height shorter --------- Co-authored-by: tamasmeszaros <meszaros.q@gmail.com> Co-authored-by: ocornut <omarcornut@gmail.com> Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
parent
7a8e1929ee
commit
933aa3050b
197 changed files with 27190 additions and 2454 deletions
|
@ -7,11 +7,13 @@
|
|||
#include "libslic3r/CutUtils.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
|
||||
#include "slic3r/GUI/Jobs/PlaterWorker.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
std::shared_ptr<PrintJob> CalibUtils::print_job;
|
||||
std::unique_ptr<Worker> CalibUtils::print_worker;
|
||||
wxString wxstr_temp_dir = fs::path(fs::temp_directory_path() / "calib").wstring();
|
||||
static const std::string temp_dir = wxstr_temp_dir.utf8_string();
|
||||
static const std::string temp_gcode_path = temp_dir + "/temp.gcode";
|
||||
|
@ -1134,7 +1136,9 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess
|
|||
}
|
||||
}
|
||||
|
||||
print_job = std::make_shared<PrintJob>(std::move(process_bar), wxGetApp().plater(), dev_id);
|
||||
print_worker = std::make_unique<PlaterWorker<BoostThreadWorker>>(wxGetApp().plater(), std::move(process_bar), "calib_worker");
|
||||
|
||||
auto print_job = std::make_unique<PrintJob>(dev_id);
|
||||
print_job->m_dev_ip = obj_->dev_ip;
|
||||
print_job->m_ftp_folder = obj_->get_ftp_folder();
|
||||
print_job->m_access_code = obj_->get_access_code();
|
||||
|
@ -1187,7 +1191,7 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess
|
|||
BOOST_LOG_TRIVIAL(info) << "send_cali_job - after send: " << j.dump();
|
||||
}
|
||||
|
||||
print_job->start();
|
||||
replace_job(*print_worker, std::move(print_job));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "libslic3r/calib.hpp"
|
||||
#include "../GUI/DeviceManager.hpp"
|
||||
#include "../GUI/Jobs/PrintJob.hpp"
|
||||
#include "slic3r/GUI/Jobs/Worker.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -26,7 +27,7 @@ class CalibUtils
|
|||
{
|
||||
public:
|
||||
CalibUtils(){};
|
||||
static std::shared_ptr<PrintJob> print_job;
|
||||
static std::unique_ptr<Worker> print_worker;
|
||||
|
||||
static CalibMode get_calib_mode_by_name(const std::string name, int &cali_stage);
|
||||
|
||||
|
@ -71,4 +72,4 @@ private:
|
|||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
788
src/slic3r/Utils/EmbossStyleManager.cpp
Normal file
788
src/slic3r/Utils/EmbossStyleManager.cpp
Normal file
|
@ -0,0 +1,788 @@
|
|||
///|/ Copyright (c) Prusa Research 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "EmbossStyleManager.hpp"
|
||||
#include <optional>
|
||||
#include <GL/glew.h> // Imgui texture
|
||||
#include <imgui/imgui_internal.h> // ImTextCharFromUtf8
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include <libslic3r/Utils.hpp> // ScopeGuard
|
||||
|
||||
#include "WxFontUtils.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp" // ::glsafe
|
||||
#include "slic3r/GUI/Jobs/CreateFontStyleImagesJob.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp" // check of font ranges
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Emboss;
|
||||
using namespace Slic3r::GUI::Emboss;
|
||||
|
||||
StyleManager::StyleManager(const ImWchar *language_glyph_range, const std::function<EmbossStyles()>& create_default_styles)
|
||||
: m_create_default_styles(create_default_styles)
|
||||
, m_imgui_init_glyph_range(language_glyph_range)
|
||||
{}
|
||||
|
||||
StyleManager::~StyleManager() {
|
||||
clear_imgui_font();
|
||||
free_style_images();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For store/load emboss style to/from AppConfig
|
||||
/// </summary>
|
||||
namespace {
|
||||
void store_style_index(AppConfig &cfg, size_t index);
|
||||
::std::optional<size_t> load_style_index(const AppConfig &cfg);
|
||||
|
||||
StyleManager::Styles load_styles(const AppConfig &cfg);
|
||||
void store_styles(AppConfig &cfg, const StyleManager::Styles &styles);
|
||||
void make_unique_name(const StyleManager::Styles &styles, std::string &name);
|
||||
} // namespace
|
||||
|
||||
void StyleManager::init(AppConfig *app_config)
|
||||
{
|
||||
assert(app_config != nullptr);
|
||||
m_app_config = app_config;
|
||||
m_styles = ::load_styles(*app_config);
|
||||
|
||||
if (m_styles.empty()) {
|
||||
// No styles loaded from ini file so use default
|
||||
EmbossStyles styles = m_create_default_styles();
|
||||
for (EmbossStyle &style : styles) {
|
||||
::make_unique_name(m_styles, style.name);
|
||||
m_styles.push_back({style});
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<size_t> active_index_opt = (app_config != nullptr) ?
|
||||
::load_style_index(*app_config) :
|
||||
std::optional<size_t>{};
|
||||
|
||||
size_t active_index = 0;
|
||||
if (active_index_opt.has_value()) active_index = *active_index_opt;
|
||||
if (active_index >= m_styles.size()) active_index = 0;
|
||||
|
||||
// find valid font item
|
||||
if (load_style(active_index))
|
||||
return; // style is loaded
|
||||
|
||||
// Try to fix that style can't be loaded
|
||||
m_styles.erase(m_styles.begin() + active_index);
|
||||
|
||||
load_valid_style();
|
||||
}
|
||||
|
||||
bool StyleManager::store_styles_to_app_config(bool use_modification, bool store_active_index)
|
||||
{
|
||||
assert(m_app_config != nullptr);
|
||||
if (m_app_config == nullptr) return false;
|
||||
if (use_modification) {
|
||||
if (exist_stored_style()) {
|
||||
// update stored item
|
||||
m_styles[m_style_cache.style_index] = m_style_cache.style;
|
||||
} else {
|
||||
// add new into stored list
|
||||
EmbossStyle &style = m_style_cache.style;
|
||||
::make_unique_name(m_styles, style.name);
|
||||
m_style_cache.truncated_name.clear();
|
||||
m_style_cache.style_index = m_styles.size();
|
||||
m_styles.push_back({style});
|
||||
}
|
||||
m_style_cache.stored_wx_font = m_style_cache.wx_font;
|
||||
}
|
||||
|
||||
if (store_active_index)
|
||||
{
|
||||
size_t style_index = exist_stored_style() ?
|
||||
m_style_cache.style_index :
|
||||
m_last_style_index;
|
||||
store_style_index(*m_app_config, style_index);
|
||||
}
|
||||
|
||||
store_styles(*m_app_config, m_styles);
|
||||
return true;
|
||||
}
|
||||
|
||||
void StyleManager::add_style(const std::string &name) {
|
||||
EmbossStyle& style = m_style_cache.style;
|
||||
style.name = name;
|
||||
::make_unique_name(m_styles, style.name);
|
||||
m_style_cache.style_index = m_styles.size();
|
||||
m_style_cache.stored_wx_font = m_style_cache.wx_font;
|
||||
m_style_cache.truncated_name.clear();
|
||||
m_styles.push_back({style});
|
||||
}
|
||||
|
||||
void StyleManager::swap(size_t i1, size_t i2) {
|
||||
if (i1 >= m_styles.size() ||
|
||||
i2 >= m_styles.size()) return;
|
||||
std::swap(m_styles[i1], m_styles[i2]);
|
||||
// fix selected index
|
||||
if (!exist_stored_style()) return;
|
||||
if (m_style_cache.style_index == i1) {
|
||||
m_style_cache.style_index = i2;
|
||||
} else if (m_style_cache.style_index == i2) {
|
||||
m_style_cache.style_index = i1;
|
||||
}
|
||||
}
|
||||
|
||||
void StyleManager::discard_style_changes() {
|
||||
if (exist_stored_style()) {
|
||||
if (load_style(m_style_cache.style_index))
|
||||
return; // correct reload style
|
||||
} else {
|
||||
if(load_style(m_last_style_index))
|
||||
return; // correct load last used style
|
||||
}
|
||||
|
||||
// try to save situation by load some font
|
||||
load_valid_style();
|
||||
}
|
||||
|
||||
void StyleManager::erase(size_t index) {
|
||||
if (index >= m_styles.size()) return;
|
||||
|
||||
// fix selected index
|
||||
if (exist_stored_style()) {
|
||||
size_t &i = m_style_cache.style_index;
|
||||
if (index < i) --i;
|
||||
else if (index == i) i = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
m_styles.erase(m_styles.begin() + index);
|
||||
}
|
||||
|
||||
void StyleManager::rename(const std::string& name) {
|
||||
m_style_cache.style.name = name;
|
||||
m_style_cache.truncated_name.clear();
|
||||
if (exist_stored_style()) {
|
||||
Style &it = m_styles[m_style_cache.style_index];
|
||||
it.name = name;
|
||||
it.truncated_name.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void StyleManager::load_valid_style()
|
||||
{
|
||||
// iterate over all known styles
|
||||
while (!m_styles.empty()) {
|
||||
if (load_style(0))
|
||||
return;
|
||||
// can't load so erase it from list
|
||||
m_styles.erase(m_styles.begin());
|
||||
}
|
||||
|
||||
// no one style is loadable
|
||||
// set up default font list
|
||||
EmbossStyles def_style = m_create_default_styles();
|
||||
for (EmbossStyle &style : def_style) {
|
||||
::make_unique_name(m_styles, style.name);
|
||||
m_styles.push_back({std::move(style)});
|
||||
}
|
||||
|
||||
// iterate over default styles
|
||||
// There have to be option to use build in font
|
||||
while (!m_styles.empty()) {
|
||||
if (load_style(0))
|
||||
return;
|
||||
// can't load so erase it from list
|
||||
m_styles.erase(m_styles.begin());
|
||||
}
|
||||
|
||||
// This OS doesn't have TTF as default font,
|
||||
// find some loadable font out of default list
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool StyleManager::load_style(size_t style_index)
|
||||
{
|
||||
if (style_index >= m_styles.size()) return false;
|
||||
if (!load_style(m_styles[style_index])) return false;
|
||||
m_style_cache.style_index = style_index;
|
||||
m_style_cache.stored_wx_font = m_style_cache.wx_font; // copy
|
||||
m_last_style_index = style_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StyleManager::load_style(const Style &style) {
|
||||
if (style.type == EmbossStyle::Type::file_path) {
|
||||
std::unique_ptr<FontFile> font_ptr =
|
||||
create_font_file(style.path.c_str());
|
||||
if (font_ptr == nullptr) return false;
|
||||
m_style_cache.wx_font = {};
|
||||
m_style_cache.font_file =
|
||||
FontFileWithCache(std::move(font_ptr));
|
||||
m_style_cache.style = style; // copy
|
||||
m_style_cache.style_index = std::numeric_limits<size_t>::max();
|
||||
m_style_cache.stored_wx_font = {};
|
||||
return true;
|
||||
}
|
||||
if (style.type != WxFontUtils::get_current_type()) return false;
|
||||
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
|
||||
if (!wx_font_opt.has_value()) return false;
|
||||
return load_style(style, *wx_font_opt);
|
||||
}
|
||||
|
||||
bool StyleManager::load_style(const Style &style, const wxFont &font)
|
||||
{
|
||||
m_style_cache.style = style; // copy
|
||||
|
||||
// wx font property has bigger priority to set
|
||||
// it must be after copy of the style
|
||||
if (!set_wx_font(font)) return false;
|
||||
|
||||
m_style_cache.style_index = std::numeric_limits<size_t>::max();
|
||||
m_style_cache.stored_wx_font = {};
|
||||
m_style_cache.truncated_name.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StyleManager::is_font_changed() const
|
||||
{
|
||||
const wxFont &wx_font = get_wx_font();
|
||||
if (!wx_font.IsOk())
|
||||
return false;
|
||||
if (!exist_stored_style())
|
||||
return false;
|
||||
const EmbossStyle *stored_style = get_stored_style();
|
||||
if (stored_style == nullptr)
|
||||
return false;
|
||||
|
||||
const wxFont &wx_font_stored = get_stored_wx_font();
|
||||
if (!wx_font_stored.IsOk())
|
||||
return false;
|
||||
|
||||
const FontProp &prop = get_style().prop;
|
||||
const FontProp &prop_stored = stored_style->prop;
|
||||
|
||||
// Exist change in face name?
|
||||
if(wx_font_stored.GetFaceName() != wx_font.GetFaceName()) return true;
|
||||
|
||||
const std::optional<float> &skew = prop.skew;
|
||||
bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font);
|
||||
const std::optional<float> &skew_stored = prop_stored.skew;
|
||||
bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(wx_font_stored);
|
||||
// is italic changed
|
||||
if (is_italic != is_stored_italic)
|
||||
return true;
|
||||
|
||||
const std::optional<float> &boldness = prop.boldness;
|
||||
bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font);
|
||||
const std::optional<float> &boldness_stored = prop_stored.boldness;
|
||||
bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(wx_font_stored);
|
||||
// is bold changed
|
||||
return is_bold != is_stored_bold;
|
||||
}
|
||||
|
||||
bool StyleManager::is_unique_style_name(const std::string &name) const {
|
||||
for (const StyleManager::Style &style : m_styles)
|
||||
if (style.name == name)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StyleManager::is_active_font() { return m_style_cache.font_file.has_value(); }
|
||||
|
||||
const StyleManager::Style *StyleManager::get_stored_style() const
|
||||
{
|
||||
if (m_style_cache.style_index >= m_styles.size()) return nullptr;
|
||||
return &m_styles[m_style_cache.style_index];
|
||||
}
|
||||
|
||||
void StyleManager::clear_glyphs_cache()
|
||||
{
|
||||
FontFileWithCache &ff = m_style_cache.font_file;
|
||||
if (!ff.has_value()) return;
|
||||
ff.cache = std::make_shared<Glyphs>();
|
||||
}
|
||||
|
||||
void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); }
|
||||
|
||||
ImFont *StyleManager::get_imgui_font()
|
||||
{
|
||||
if (!is_active_font()) return nullptr;
|
||||
|
||||
ImVector<ImFont *> &fonts = m_style_cache.atlas.Fonts;
|
||||
if (fonts.empty()) return nullptr;
|
||||
|
||||
// check correct index
|
||||
int f_size = fonts.size();
|
||||
assert(f_size == 1);
|
||||
if (f_size != 1) return nullptr;
|
||||
ImFont *font = fonts.front();
|
||||
if (font == nullptr) return nullptr;
|
||||
return font;
|
||||
}
|
||||
|
||||
const StyleManager::Styles &StyleManager::get_styles() const{ return m_styles; }
|
||||
void StyleManager::init_trunc_names(float max_width) {
|
||||
for (auto &s : m_styles)
|
||||
if (s.truncated_name.empty()) {
|
||||
std::string name = s.name;
|
||||
ImGuiWrapper::escape_double_hash(name);
|
||||
s.truncated_name = ImGuiWrapper::trunc(name, max_width);
|
||||
}
|
||||
}
|
||||
|
||||
// for access to worker
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
// for get DPI
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
|
||||
|
||||
void StyleManager::init_style_images(const Vec2i &max_size,
|
||||
const std::string &text)
|
||||
{
|
||||
// check already initialized
|
||||
if (m_exist_style_images) return;
|
||||
|
||||
// check is initializing
|
||||
if (m_temp_style_images != nullptr) {
|
||||
// is initialization finished
|
||||
if (!m_temp_style_images->styles.empty()) {
|
||||
assert(m_temp_style_images->images.size() ==
|
||||
m_temp_style_images->styles.size());
|
||||
// copy images into styles
|
||||
for (StyleManager::StyleImage &image : m_temp_style_images->images){
|
||||
size_t index = &image - &m_temp_style_images->images.front();
|
||||
StyleImagesData::Item &style = m_temp_style_images->styles[index];
|
||||
|
||||
// find style in font list and copy to it
|
||||
for (auto &it : m_styles) {
|
||||
if (it.name != style.text ||
|
||||
!(it.prop == style.prop))
|
||||
continue;
|
||||
it.image = image;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_temp_style_images = nullptr;
|
||||
m_exist_style_images = true;
|
||||
return;
|
||||
}
|
||||
// in process of initialization inside of job
|
||||
return;
|
||||
}
|
||||
|
||||
// create job for init images
|
||||
m_temp_style_images = std::make_shared<StyleImagesData::StyleImages>();
|
||||
StyleImagesData::Items styles;
|
||||
styles.reserve(m_styles.size());
|
||||
for (const Style &style : m_styles) {
|
||||
std::optional<wxFont> wx_font_opt = WxFontUtils::load_wxFont(style.path);
|
||||
if (!wx_font_opt.has_value()) continue;
|
||||
std::unique_ptr<FontFile> font_file =
|
||||
WxFontUtils::create_font_file(*wx_font_opt);
|
||||
if (font_file == nullptr) continue;
|
||||
styles.push_back({
|
||||
FontFileWithCache(std::move(font_file)),
|
||||
style.name,
|
||||
style.prop
|
||||
});
|
||||
}
|
||||
|
||||
auto mf = wxGetApp().mainframe;
|
||||
// dot per inch for monitor
|
||||
int dpi = get_dpi_for_window(mf);
|
||||
// pixel per milimeter
|
||||
double ppm = dpi / GizmoObjectManipulation::in_to_mm;
|
||||
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
StyleImagesData data{std::move(styles), max_size, text, m_temp_style_images, ppm};
|
||||
queue_job(worker, std::make_unique<CreateFontStyleImagesJob>(std::move(data)));
|
||||
}
|
||||
|
||||
void StyleManager::free_style_images() {
|
||||
if (!m_exist_style_images) return;
|
||||
GLuint tex_id = 0;
|
||||
for (Style &it : m_styles) {
|
||||
if (tex_id == 0 && it.image.has_value())
|
||||
tex_id = (GLuint)(intptr_t) it.image->texture_id;
|
||||
it.image.reset();
|
||||
}
|
||||
if (tex_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &tex_id));
|
||||
m_exist_style_images = false;
|
||||
}
|
||||
|
||||
float StyleManager::min_imgui_font_size = 18.f;
|
||||
float StyleManager::max_imgui_font_size = 60.f;
|
||||
float StyleManager::get_imgui_font_size(const FontProp &prop, const FontFile &file, double scale)
|
||||
{
|
||||
const FontFile::Info& info = get_font_info(file, prop);
|
||||
// coeficient for convert line height to font size
|
||||
float c1 = (info.ascent - info.descent + info.linegap) /
|
||||
(float) info.unit_per_em;
|
||||
|
||||
// The point size is defined as 1/72 of the Anglo-Saxon inch (25.4 mm):
|
||||
// It is approximately 0.0139 inch or 352.8 um.
|
||||
return c1 * std::abs(prop.size_in_mm) / 0.3528f * scale;
|
||||
}
|
||||
|
||||
ImFont *StyleManager::create_imgui_font(const std::string &text, double scale)
|
||||
{
|
||||
// inspiration inside of ImGuiWrapper::init_font
|
||||
auto& ff = m_style_cache.font_file;
|
||||
if (!ff.has_value()) return nullptr;
|
||||
const FontFile &font_file = *ff.font_file;
|
||||
|
||||
ImFontGlyphRangesBuilder builder;
|
||||
builder.AddRanges(m_imgui_init_glyph_range);
|
||||
if (!text.empty())
|
||||
builder.AddText(text.c_str());
|
||||
|
||||
ImVector<ImWchar> &ranges = m_style_cache.ranges;
|
||||
ranges.clear();
|
||||
builder.BuildRanges(&ranges);
|
||||
|
||||
m_style_cache.atlas.Flags |= ImFontAtlasFlags_NoMouseCursors |
|
||||
ImFontAtlasFlags_NoPowerOfTwoHeight;
|
||||
|
||||
const FontProp &font_prop = m_style_cache.style.prop;
|
||||
float font_size = get_imgui_font_size(font_prop, font_file, scale);
|
||||
if (font_size < min_imgui_font_size)
|
||||
font_size = min_imgui_font_size;
|
||||
if (font_size > max_imgui_font_size)
|
||||
font_size = max_imgui_font_size;
|
||||
|
||||
ImFontConfig font_config;
|
||||
// TODO: start using merge mode
|
||||
//font_config.MergeMode = true;
|
||||
int unit_per_em = get_font_info(font_file, font_prop).unit_per_em;
|
||||
float coef = font_size / (double) unit_per_em;
|
||||
if (font_prop.char_gap.has_value())
|
||||
font_config.GlyphExtraSpacing.x = coef * (*font_prop.char_gap);
|
||||
if (font_prop.line_gap.has_value())
|
||||
font_config.GlyphExtraSpacing.y = coef * (*font_prop.line_gap);
|
||||
|
||||
font_config.FontDataOwnedByAtlas = false;
|
||||
|
||||
const std::vector<unsigned char> &buffer = *font_file.data;
|
||||
ImFont * font = m_style_cache.atlas.AddFontFromMemoryTTF(
|
||||
(void *) buffer.data(), buffer.size(), font_size, &font_config, m_style_cache.ranges.Data);
|
||||
|
||||
unsigned char *pixels;
|
||||
int width, height;
|
||||
m_style_cache.atlas.GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
// Upload texture to graphics system
|
||||
GLint last_texture;
|
||||
glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
|
||||
ScopeGuard sg([last_texture]() {
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, last_texture));
|
||||
});
|
||||
|
||||
GLuint font_texture;
|
||||
glsafe(::glGenTextures(1, &font_texture));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, font_texture));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
|
||||
if (OpenGLManager::are_compressed_textures_supported())
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
|
||||
|
||||
// Store our identifier
|
||||
m_style_cache.atlas.TexID = (ImTextureID) (intptr_t) font_texture;
|
||||
assert(!m_style_cache.atlas.Fonts.empty());
|
||||
if (m_style_cache.atlas.Fonts.empty()) return nullptr;
|
||||
assert(font == m_style_cache.atlas.Fonts.back());
|
||||
if (!font->IsLoaded()) return nullptr;
|
||||
assert(font->IsLoaded());
|
||||
return font;
|
||||
}
|
||||
|
||||
bool StyleManager::set_wx_font(const wxFont &wx_font) {
|
||||
std::unique_ptr<FontFile> font_file =
|
||||
WxFontUtils::create_font_file(wx_font);
|
||||
return set_wx_font(wx_font, std::move(font_file));
|
||||
}
|
||||
|
||||
bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr<FontFile> font_file)
|
||||
{
|
||||
if (font_file == nullptr) return false;
|
||||
m_style_cache.wx_font = wx_font; // copy
|
||||
m_style_cache.font_file =
|
||||
FontFileWithCache(std::move(font_file));
|
||||
|
||||
EmbossStyle &style = m_style_cache.style;
|
||||
style.type = WxFontUtils::get_current_type();
|
||||
// update string path
|
||||
style.path = WxFontUtils::store_wxFont(wx_font);
|
||||
WxFontUtils::update_property(style.prop, wx_font);
|
||||
clear_imgui_font();
|
||||
return true;
|
||||
}
|
||||
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include "WxFontUtils.hpp"
|
||||
#include "fast_float/fast_float.h"
|
||||
|
||||
// StylesSerializable
|
||||
namespace {
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
using Section = std::map<std::string,std::string>;
|
||||
|
||||
const std::string APP_CONFIG_FONT_NAME = "name";
|
||||
const std::string APP_CONFIG_FONT_DESCRIPTOR = "descriptor";
|
||||
const std::string APP_CONFIG_FONT_LINE_HEIGHT = "line_height";
|
||||
const std::string APP_CONFIG_FONT_DEPTH = "depth";
|
||||
const std::string APP_CONFIG_FONT_USE_SURFACE = "use_surface";
|
||||
const std::string APP_CONFIG_FONT_BOLDNESS = "boldness";
|
||||
const std::string APP_CONFIG_FONT_SKEW = "skew";
|
||||
const std::string APP_CONFIG_FONT_DISTANCE = "distance";
|
||||
const std::string APP_CONFIG_FONT_ANGLE = "angle";
|
||||
const std::string APP_CONFIG_FONT_COLLECTION = "collection";
|
||||
const std::string APP_CONFIG_FONT_CHAR_GAP = "char_gap";
|
||||
const std::string APP_CONFIG_FONT_LINE_GAP = "line_gap";
|
||||
|
||||
const std::string APP_CONFIG_ACTIVE_FONT = "active_font";
|
||||
|
||||
std::string create_section_name(unsigned index)
|
||||
{
|
||||
return AppConfig::SECTION_EMBOSS_STYLE + ':' + std::to_string(index);
|
||||
}
|
||||
|
||||
// check only existence of flag
|
||||
bool read(const Section §ion, const std::string &key, bool &value)
|
||||
{
|
||||
auto item = section.find(key);
|
||||
if (item == section.end())
|
||||
return false;
|
||||
|
||||
value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read(const Section §ion, const std::string &key, float &value)
|
||||
{
|
||||
auto item = section.find(key);
|
||||
if (item == section.end())
|
||||
return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty())
|
||||
return false;
|
||||
float value_;
|
||||
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
|
||||
// read only non zero value
|
||||
if (fabs(value_) <= std::numeric_limits<float>::epsilon())
|
||||
return false;
|
||||
|
||||
value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read(const Section §ion, const std::string &key, std::optional<int> &value)
|
||||
{
|
||||
auto item = section.find(key);
|
||||
if (item == section.end())
|
||||
return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty())
|
||||
return false;
|
||||
int value_ = std::atoi(data.c_str());
|
||||
if (value_ == 0)
|
||||
return false;
|
||||
|
||||
value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read(const Section §ion, const std::string &key, std::optional<unsigned int> &value)
|
||||
{
|
||||
auto item = section.find(key);
|
||||
if (item == section.end())
|
||||
return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty())
|
||||
return false;
|
||||
int value_ = std::atoi(data.c_str());
|
||||
if (value_ <= 0)
|
||||
return false;
|
||||
|
||||
value = static_cast<unsigned int>(value_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read(const Section §ion, const std::string &key, std::optional<float> &value)
|
||||
{
|
||||
auto item = section.find(key);
|
||||
if (item == section.end())
|
||||
return false;
|
||||
const std::string &data = item->second;
|
||||
if (data.empty())
|
||||
return false;
|
||||
float value_;
|
||||
fast_float::from_chars(data.c_str(), data.c_str() + data.length(), value_);
|
||||
// read only non zero value
|
||||
if (fabs(value_) <= std::numeric_limits<float>::epsilon())
|
||||
return false;
|
||||
|
||||
value = value_;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<StyleManager::Style> load_style(const Section &app_cfg_section)
|
||||
{
|
||||
auto path_it = app_cfg_section.find(APP_CONFIG_FONT_DESCRIPTOR);
|
||||
if (path_it == app_cfg_section.end())
|
||||
return {};
|
||||
|
||||
StyleManager::Style s;
|
||||
EmbossProjection& ep = s.projection;
|
||||
FontProp& fp = s.prop;
|
||||
|
||||
s.path = path_it->second;
|
||||
s.type = WxFontUtils::get_current_type();
|
||||
auto name_it = app_cfg_section.find(APP_CONFIG_FONT_NAME);
|
||||
const std::string default_name = "font_name";
|
||||
s.name = (name_it == app_cfg_section.end()) ? default_name : name_it->second;
|
||||
|
||||
read(app_cfg_section, APP_CONFIG_FONT_LINE_HEIGHT, fp.size_in_mm);
|
||||
float depth = 1.;
|
||||
read(app_cfg_section, APP_CONFIG_FONT_DEPTH, depth);
|
||||
ep.depth = depth;
|
||||
read(app_cfg_section, APP_CONFIG_FONT_USE_SURFACE, ep.use_surface);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_BOLDNESS, fp.boldness);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_SKEW, fp.skew);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_DISTANCE, s.distance);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_ANGLE, s.angle);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_COLLECTION, fp.collection_number);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_CHAR_GAP, fp.char_gap);
|
||||
read(app_cfg_section, APP_CONFIG_FONT_LINE_GAP, fp.line_gap);
|
||||
return s;
|
||||
}
|
||||
|
||||
void store_style(AppConfig &cfg, const StyleManager::Style &s, unsigned index)
|
||||
{
|
||||
const EmbossProjection &ep = s.projection;
|
||||
Section data;
|
||||
data[APP_CONFIG_FONT_NAME] = s.name;
|
||||
data[APP_CONFIG_FONT_DESCRIPTOR] = s.path;
|
||||
const FontProp &fp = s.prop;
|
||||
data[APP_CONFIG_FONT_LINE_HEIGHT] = std::to_string(fp.size_in_mm);
|
||||
data[APP_CONFIG_FONT_DEPTH] = std::to_string(ep.depth);
|
||||
if (ep.use_surface)
|
||||
data[APP_CONFIG_FONT_USE_SURFACE] = "true";
|
||||
if (fp.boldness.has_value())
|
||||
data[APP_CONFIG_FONT_BOLDNESS] = std::to_string(*fp.boldness);
|
||||
if (fp.skew.has_value())
|
||||
data[APP_CONFIG_FONT_SKEW] = std::to_string(*fp.skew);
|
||||
if (s.distance.has_value())
|
||||
data[APP_CONFIG_FONT_DISTANCE] = std::to_string(*s.distance);
|
||||
if (s.angle.has_value())
|
||||
data[APP_CONFIG_FONT_ANGLE] = std::to_string(*s.angle);
|
||||
if (fp.collection_number.has_value())
|
||||
data[APP_CONFIG_FONT_COLLECTION] = std::to_string(*fp.collection_number);
|
||||
if (fp.char_gap.has_value())
|
||||
data[APP_CONFIG_FONT_CHAR_GAP] = std::to_string(*fp.char_gap);
|
||||
if (fp.line_gap.has_value())
|
||||
data[APP_CONFIG_FONT_LINE_GAP] = std::to_string(*fp.line_gap);
|
||||
cfg.set_section(create_section_name(index), std::move(data));
|
||||
}
|
||||
|
||||
void store_style_index(AppConfig &cfg, size_t index)
|
||||
{
|
||||
// store actual font index
|
||||
// active font first index is +1 to correspond with section name
|
||||
Section data;
|
||||
data[APP_CONFIG_ACTIVE_FONT] = std::to_string(index);
|
||||
cfg.set_section(AppConfig::SECTION_EMBOSS_STYLE, std::move(data));
|
||||
}
|
||||
|
||||
std::optional<size_t> load_style_index(const AppConfig &cfg)
|
||||
{
|
||||
if (!cfg.has_section(AppConfig::SECTION_EMBOSS_STYLE))
|
||||
return {};
|
||||
|
||||
auto section = cfg.get_section(AppConfig::SECTION_EMBOSS_STYLE);
|
||||
auto it = section.find(APP_CONFIG_ACTIVE_FONT);
|
||||
if (it == section.end())
|
||||
return {};
|
||||
|
||||
size_t active_font = static_cast<size_t>(std::atoi(it->second.c_str()));
|
||||
// order in config starts with number 1
|
||||
return active_font - 1;
|
||||
}
|
||||
|
||||
::StyleManager::Styles load_styles(const AppConfig &cfg)
|
||||
{
|
||||
StyleManager::Styles result;
|
||||
// human readable index inside of config starts from 1 !!
|
||||
unsigned index = 1;
|
||||
std::string section_name = create_section_name(index);
|
||||
while (cfg.has_section(section_name)) {
|
||||
std::optional<StyleManager::Style> style_opt = load_style(cfg.get_section(section_name));
|
||||
if (style_opt.has_value()) {
|
||||
make_unique_name(result, style_opt->name);
|
||||
result.emplace_back(*style_opt);
|
||||
}
|
||||
|
||||
section_name = create_section_name(++index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void store_styles(AppConfig &cfg, const StyleManager::Styles &styles)
|
||||
{
|
||||
EmbossStyle::Type current_type = WxFontUtils::get_current_type();
|
||||
// store styles
|
||||
unsigned index = 1;
|
||||
for (const StyleManager::Style &style : styles) {
|
||||
// skip file paths + fonts from other OS(loaded from .3mf)
|
||||
assert(style.type == current_type);
|
||||
if (style.type != current_type)
|
||||
continue;
|
||||
store_style(cfg, style, index);
|
||||
++index;
|
||||
}
|
||||
|
||||
// remove rest of font sections (after deletation)
|
||||
std::string section_name = create_section_name(index);
|
||||
while (cfg.has_section(section_name)) {
|
||||
cfg.clear_section(section_name);
|
||||
section_name = create_section_name(index);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
void make_unique_name(const StyleManager::Styles& styles, std::string &name)
|
||||
{
|
||||
auto is_unique = [&styles](const std::string &name){
|
||||
for (const StyleManager::Style &it : styles)
|
||||
if (it.name == name) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Style name can't be empty so default name is set
|
||||
if (name.empty()) name = "Text style";
|
||||
|
||||
// When name is already unique, nothing need to be changed
|
||||
if (is_unique(name)) return;
|
||||
|
||||
// when there is previous version of style name only find number
|
||||
const char *prefix = " (";
|
||||
const char suffix = ')';
|
||||
auto pos = name.find_last_of(prefix);
|
||||
if (name.c_str()[name.size() - 1] == suffix &&
|
||||
pos != std::string::npos) {
|
||||
// short name by ord number
|
||||
name = name.substr(0, pos);
|
||||
}
|
||||
|
||||
int order = 1; // start with value 2 to represents same font name
|
||||
std::string new_name;
|
||||
do {
|
||||
new_name = name + prefix + std::to_string(++order) + suffix;
|
||||
} while (!is_unique(new_name));
|
||||
name = new_name;
|
||||
}
|
||||
|
||||
} // namespace
|
325
src/slic3r/Utils/EmbossStyleManager.hpp
Normal file
325
src/slic3r/Utils/EmbossStyleManager.hpp
Normal file
|
@ -0,0 +1,325 @@
|
|||
///|/ Copyright (c) Prusa Research 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_EmbossStyleManager_hpp_
|
||||
#define slic3r_EmbossStyleManager_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <imgui/imgui.h>
|
||||
#include <wx/font.h>
|
||||
#include <GL/glew.h>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/Emboss.hpp>
|
||||
#include <libslic3r/TextConfiguration.hpp>
|
||||
#include <libslic3r/EmbossShape.hpp>
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
|
||||
namespace Slic3r::GUI::Emboss {
|
||||
/// <summary>
|
||||
/// Manage Emboss text styles
|
||||
/// Cache actual state of style
|
||||
/// + imgui font
|
||||
/// + wx font
|
||||
/// </summary>
|
||||
class StyleManager
|
||||
{
|
||||
friend class CreateFontStyleImagesJob; // access to StyleImagesData
|
||||
public:
|
||||
/// <param name="language_glyph_range">Character to load for imgui when initialize imgui font</param>
|
||||
/// <param name="create_default_styles">Function to create default styles</param>
|
||||
StyleManager(const ImWchar *language_glyph_range, const std::function<EmbossStyles()>& create_default_styles);
|
||||
|
||||
/// <summary>
|
||||
/// Release imgui font and style images from GPU
|
||||
/// </summary>
|
||||
~StyleManager();
|
||||
|
||||
/// <summary>
|
||||
/// Load font style list from config
|
||||
/// Also select actual activ font
|
||||
/// </summary>
|
||||
/// <param name="app_config">Application configuration loaded from file "PrusaSlicer.ini"
|
||||
/// + cfg is stored to privat variable</param>
|
||||
void init(AppConfig *app_config);
|
||||
|
||||
/// <summary>
|
||||
/// Write font list into AppConfig
|
||||
/// </summary>
|
||||
/// <param name="item_to_store">Configuration</param>
|
||||
/// <param name="use_modification">When true cache state will be used for store</param>
|
||||
/// <param name="use_modification">When true store activ index into configuration</param>
|
||||
/// <returns>True on succes otherwise False.</returns>
|
||||
bool store_styles_to_app_config(bool use_modification = true, bool store_active_index = true);
|
||||
|
||||
/// <summary>
|
||||
/// Append actual style to style list
|
||||
/// </summary>
|
||||
/// <param name="name">New name for style</param>
|
||||
void add_style(const std::string& name);
|
||||
|
||||
/// <summary>
|
||||
/// Change order of style item in m_styles.
|
||||
/// Fix selected font index when (i1 || i2) == m_font_selected
|
||||
/// </summary>
|
||||
/// <param name="i1">First index to m_styles</param>
|
||||
/// <param name="i2">Second index to m_styles</param>
|
||||
void swap(size_t i1, size_t i2);
|
||||
|
||||
/// <summary>
|
||||
/// Discard changes in activ style
|
||||
/// When no activ style use last used OR first loadable
|
||||
/// </summary>
|
||||
void discard_style_changes();
|
||||
|
||||
/// <summary>
|
||||
/// Remove style from m_styles.
|
||||
/// Fix selected font index when index is under m_font_selected
|
||||
/// </summary>
|
||||
/// <param name="index">Index of style to be removed</param>
|
||||
void erase(size_t index);
|
||||
|
||||
/// <summary>
|
||||
/// Rename actual selected font item
|
||||
/// </summary>
|
||||
/// <param name="name">New name</param>
|
||||
void rename(const std::string &name);
|
||||
|
||||
/// <summary>
|
||||
/// load some valid style
|
||||
/// </summary>
|
||||
void load_valid_style();
|
||||
|
||||
/// <summary>
|
||||
/// Change active font
|
||||
/// When font not loaded roll back activ font
|
||||
/// </summary>
|
||||
/// <param name="font_index">New font index(from m_styles range)</param>
|
||||
/// <returns>True on succes. False on fail load font</returns>
|
||||
bool load_style(size_t font_index);
|
||||
// load font style not stored in list
|
||||
struct Style;
|
||||
bool load_style(const Style &style);
|
||||
// fastering load font on index by wxFont, ignore type and descriptor
|
||||
bool load_style(const Style &style, const wxFont &font);
|
||||
|
||||
// clear actual selected glyphs cache
|
||||
void clear_glyphs_cache();
|
||||
|
||||
// remove cached imgui font for actual selected font
|
||||
void clear_imgui_font();
|
||||
|
||||
// getters for private data
|
||||
const Style *get_stored_style() const;
|
||||
|
||||
const Style &get_style() const { return m_style_cache.style; }
|
||||
Style &get_style() { return m_style_cache.style; }
|
||||
size_t get_style_index() const { return m_style_cache.style_index; }
|
||||
std::string &get_truncated_name() { return m_style_cache.truncated_name; }
|
||||
const ImFontAtlas &get_atlas() const { return m_style_cache.atlas; }
|
||||
const FontProp &get_font_prop() const { return get_style().prop; }
|
||||
FontProp &get_font_prop() { return get_style().prop; }
|
||||
const wxFont &get_wx_font() const { return m_style_cache.wx_font; }
|
||||
const wxFont &get_stored_wx_font() const { return m_style_cache.stored_wx_font; }
|
||||
Slic3r::Emboss::FontFileWithCache &get_font_file_with_cache() { return m_style_cache.font_file; }
|
||||
bool has_collections() const { return m_style_cache.font_file.font_file != nullptr &&
|
||||
m_style_cache.font_file.font_file->infos.size() > 1; }
|
||||
|
||||
// True when activ style has same name as some of stored style
|
||||
bool exist_stored_style() const { return m_style_cache.style_index != std::numeric_limits<size_t>::max(); }
|
||||
|
||||
/// <summary>
|
||||
/// check whether current style differ to selected
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool is_font_changed() const;
|
||||
|
||||
bool is_unique_style_name(const std::string &name) const;
|
||||
|
||||
/// <summary>
|
||||
/// Setter on wx_font when changed
|
||||
/// </summary>
|
||||
/// <param name="wx_font">new wx font</param>
|
||||
/// <returns>True on success set otherwise FALSE</returns>
|
||||
bool set_wx_font(const wxFont &wx_font);
|
||||
|
||||
/// <summary>
|
||||
/// Faster way of set wx_font when font file is known(do not load font file twice)
|
||||
/// When you not sure that wx_font is made by font_file use only set_wx_font(wx_font)
|
||||
/// </summary>
|
||||
/// <param name="wx_font">Must be source of font file</param>
|
||||
/// <param name="font_file">font file created by WxFontUtils::create_font_file(wx_font)</param>
|
||||
/// <returns>True on success otherwise false</returns>
|
||||
bool set_wx_font(const wxFont &wx_font, std::unique_ptr<Slic3r::Emboss::FontFile> font_file);
|
||||
|
||||
// Getter on acitve font pointer for imgui
|
||||
// Initialize imgui font(generate texture) when doesn't exist yet.
|
||||
// Extend font atlas when not in glyph range
|
||||
ImFont *get_imgui_font();
|
||||
// initialize font range by unique symbols in text
|
||||
ImFont *create_imgui_font(const std::string& text, double scale);
|
||||
|
||||
// init truncated names of styles
|
||||
void init_trunc_names(float max_width);
|
||||
|
||||
/// <summary>
|
||||
/// Initialization texture with rendered font style
|
||||
/// </summary>
|
||||
/// <param name="max_size">Maximal width and height of one style texture</param>
|
||||
/// <param name="text">Text to render by style</param>
|
||||
void init_style_images(const Vec2i& max_size, const std::string &text);
|
||||
void free_style_images();
|
||||
|
||||
// access to all managed font styles
|
||||
const std::vector<Style> &get_styles() const;
|
||||
|
||||
/// <summary>
|
||||
/// Describe image in GPU to show settings of style
|
||||
/// </summary>
|
||||
struct StyleImage
|
||||
{
|
||||
void* texture_id = nullptr; // GLuint
|
||||
BoundingBox bounding_box;
|
||||
ImVec2 tex_size;
|
||||
ImVec2 uv0;
|
||||
ImVec2 uv1;
|
||||
Point offset = Point(0, 0);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// All connected with one style
|
||||
/// keep temporary data and caches for style
|
||||
/// </summary>
|
||||
struct Style : public EmbossStyle
|
||||
{
|
||||
// Define how to emboss shape
|
||||
EmbossProjection projection;
|
||||
|
||||
// distance from surface point
|
||||
// used for move over model surface
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<float> distance; // [in mm]
|
||||
|
||||
// Angle of rotation around emboss direction (Z axis)
|
||||
// It is calculate on the fly from volume world transformation
|
||||
// only StyleManager keep actual value for comparision with style
|
||||
// When not set value is zero and is not stored
|
||||
std::optional<float> angle; // [in radians] form -Pi to Pi
|
||||
|
||||
bool operator==(const Style &other) const
|
||||
{
|
||||
return EmbossStyle::operator==(other) &&
|
||||
projection == other.projection &&
|
||||
distance == other.distance &&
|
||||
angle == other.angle;
|
||||
}
|
||||
|
||||
// cache for view font name with maximal width in imgui
|
||||
std::string truncated_name;
|
||||
|
||||
// visualization of style
|
||||
std::optional<StyleImage> image;
|
||||
};
|
||||
using Styles = std::vector<Style>;
|
||||
|
||||
// check if exist selected font style in manager
|
||||
bool is_active_font();
|
||||
|
||||
// Limits for imgui loaded font size
|
||||
// Value out of limits is crop
|
||||
static float min_imgui_font_size;
|
||||
static float max_imgui_font_size;
|
||||
static float get_imgui_font_size(const FontProp &prop, const Slic3r::Emboss::FontFile &file, double scale);
|
||||
|
||||
private:
|
||||
// function to create default style list
|
||||
std::function<EmbossStyles()> m_create_default_styles;
|
||||
// keep language dependent glyph range
|
||||
const ImWchar *m_imgui_init_glyph_range;
|
||||
|
||||
/// <summary>
|
||||
/// Cache data from style to reduce amount of:
|
||||
/// 1) loading font from file
|
||||
/// 2) Create atlas of symbols for imgui
|
||||
/// 3) Keep loaded(and modified by style) glyphs from font
|
||||
/// </summary>
|
||||
struct StyleCache
|
||||
{
|
||||
// share font file data with emboss job thread
|
||||
Slic3r::Emboss::FontFileWithCache font_file = {};
|
||||
|
||||
// must live same as imgui_font inside of atlas
|
||||
ImVector<ImWchar> ranges = {};
|
||||
|
||||
// Keep only actual style in atlas
|
||||
ImFontAtlas atlas = {};
|
||||
|
||||
// wx widget font
|
||||
wxFont wx_font = {};
|
||||
|
||||
// cache for view font name with maximal width in imgui
|
||||
std::string truncated_name;
|
||||
|
||||
// actual used font item
|
||||
Style style = {};
|
||||
|
||||
// cache for stored wx font to not create every frame
|
||||
wxFont stored_wx_font = {};
|
||||
|
||||
// index into m_styles
|
||||
size_t style_index = std::numeric_limits<size_t>::max();
|
||||
|
||||
} m_style_cache;
|
||||
|
||||
// Privat member
|
||||
Styles m_styles;
|
||||
AppConfig *m_app_config = nullptr;
|
||||
size_t m_last_style_index = std::numeric_limits<size_t>::max();
|
||||
|
||||
/// <summary>
|
||||
/// Keep data needed to create Font Style Images in Job
|
||||
/// </summary>
|
||||
struct StyleImagesData
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
Slic3r::Emboss::FontFileWithCache font;
|
||||
std::string text;
|
||||
FontProp prop;
|
||||
};
|
||||
using Items = std::vector<Item>;
|
||||
|
||||
// Keep styles to render
|
||||
Items styles;
|
||||
// Maximal width and height in pixels of image
|
||||
Vec2i max_size;
|
||||
// Text to render
|
||||
std::string text;
|
||||
|
||||
/// <summary>
|
||||
/// Result of job
|
||||
/// </summary>
|
||||
struct StyleImages
|
||||
{
|
||||
// vector of inputs
|
||||
StyleImagesData::Items styles;
|
||||
// job output
|
||||
std::vector<StyleImage> images;
|
||||
};
|
||||
|
||||
// place to store result in main thread in Finalize
|
||||
std::shared_ptr<StyleImages> result;
|
||||
|
||||
// pixel per milimeter (scaled DPI)
|
||||
double ppm;
|
||||
};
|
||||
std::shared_ptr<StyleImagesData::StyleImages> m_temp_style_images = nullptr;
|
||||
bool m_exist_style_images = false;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_EmbossStyleManager_hpp_
|
144
src/slic3r/Utils/FontConfigHelp.cpp
Normal file
144
src/slic3r/Utils/FontConfigHelp.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "FontConfigHelp.hpp"
|
||||
|
||||
#ifdef EXIST_FONT_CONFIG_INCLUDE
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <fontconfig/fontconfig.h>
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
|
||||
// @Vojta suggest to make static variable global
|
||||
// Guard for finalize Font Config
|
||||
// Will be finalized on application exit
|
||||
// It seams that it NOT work
|
||||
static std::optional<Slic3r::ScopeGuard> finalize_guard;
|
||||
// cache for Loading of the default configuration file and building information about the available fonts.
|
||||
static FcConfig *fc = nullptr;
|
||||
|
||||
std::string Slic3r::GUI::get_font_path(const wxFont &font, bool reload_fonts)
|
||||
{
|
||||
if (!finalize_guard.has_value()) {
|
||||
FcInit();
|
||||
fc = FcInitLoadConfigAndFonts();
|
||||
finalize_guard.emplace([]() {
|
||||
// Some internal problem of Font config or other library use FC too(like wxWidget)
|
||||
// fccache.c:795: FcCacheFini: Assertion `fcCacheChains[i] == NULL' failed.
|
||||
//FcFini();
|
||||
FcConfigDestroy(fc);
|
||||
});
|
||||
} else if (reload_fonts) {
|
||||
FcConfigDestroy(fc);
|
||||
fc = FcInitLoadConfigAndFonts();
|
||||
}
|
||||
|
||||
if (fc == nullptr) return "";
|
||||
|
||||
wxString fontDesc = font.GetNativeFontInfoUserDesc();
|
||||
wxString faceName = font.GetFaceName();
|
||||
const wxScopedCharBuffer faceNameBuffer = faceName.ToUTF8();
|
||||
const char * fontFamily = faceNameBuffer;
|
||||
|
||||
// Check font slant
|
||||
int slant = FC_SLANT_ROMAN;
|
||||
if (fontDesc.Find(wxS("Oblique")) != wxNOT_FOUND)
|
||||
slant = FC_SLANT_OBLIQUE;
|
||||
else if (fontDesc.Find(wxS("Italic")) != wxNOT_FOUND)
|
||||
slant = FC_SLANT_ITALIC;
|
||||
|
||||
// Check font weight
|
||||
int weight = FC_WEIGHT_NORMAL;
|
||||
if (fontDesc.Find(wxS("Book")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_BOOK;
|
||||
else if (fontDesc.Find(wxS("Medium")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_MEDIUM;
|
||||
#ifdef FC_WEIGHT_ULTRALIGHT
|
||||
else if (fontDesc.Find(wxS("Ultra-Light")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_ULTRALIGHT;
|
||||
#endif
|
||||
else if (fontDesc.Find(wxS("Light")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_LIGHT;
|
||||
else if (fontDesc.Find(wxS("Semi-Bold")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_DEMIBOLD;
|
||||
#ifdef FC_WEIGHT_ULTRABOLD
|
||||
else if (fontDesc.Find(wxS("Ultra-Bold")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_ULTRABOLD;
|
||||
#endif
|
||||
else if (fontDesc.Find(wxS("Bold")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_BOLD;
|
||||
else if (fontDesc.Find(wxS("Heavy")) != wxNOT_FOUND)
|
||||
weight = FC_WEIGHT_BLACK;
|
||||
|
||||
// Check font width
|
||||
int width = FC_WIDTH_NORMAL;
|
||||
if (fontDesc.Find(wxS("Ultra-Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_ULTRACONDENSED;
|
||||
else if (fontDesc.Find(wxS("Extra-Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_EXTRACONDENSED;
|
||||
else if (fontDesc.Find(wxS("Semi-Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_SEMICONDENSED;
|
||||
else if (fontDesc.Find(wxS("Condensed")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_CONDENSED;
|
||||
else if (fontDesc.Find(wxS("Ultra-Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_ULTRAEXPANDED;
|
||||
else if (fontDesc.Find(wxS("Extra-Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_EXTRAEXPANDED;
|
||||
else if (fontDesc.Find(wxS("Semi-Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_SEMIEXPANDED;
|
||||
else if (fontDesc.Find(wxS("Expanded")) != wxNOT_FOUND)
|
||||
width = FC_WIDTH_EXPANDED;
|
||||
|
||||
FcResult res;
|
||||
FcPattern *matchPattern = FcPatternBuild(NULL, FC_FAMILY, FcTypeString,
|
||||
(FcChar8 *) fontFamily, NULL);
|
||||
ScopeGuard sg_mp([matchPattern]() { FcPatternDestroy(matchPattern); });
|
||||
|
||||
FcPatternAddInteger(matchPattern, FC_SLANT, slant);
|
||||
FcPatternAddInteger(matchPattern, FC_WEIGHT, weight);
|
||||
FcPatternAddInteger(matchPattern, FC_WIDTH, width);
|
||||
|
||||
FcConfigSubstitute(NULL, matchPattern, FcMatchPattern);
|
||||
FcDefaultSubstitute(matchPattern);
|
||||
|
||||
FcPattern *resultPattern = FcFontMatch(NULL, matchPattern, &res);
|
||||
if (resultPattern == nullptr) return "";
|
||||
ScopeGuard sg_rp([resultPattern]() { FcPatternDestroy(resultPattern); });
|
||||
|
||||
FcChar8 *fileName;
|
||||
if (FcPatternGetString(resultPattern, FC_FILE, 0, &fileName) !=
|
||||
FcResultMatch)
|
||||
return "";
|
||||
wxString fontFileName = wxString::FromUTF8((char *) fileName);
|
||||
|
||||
if (fontFileName.IsEmpty()) return "";
|
||||
|
||||
// find full file path
|
||||
wxFileName myFileName(fontFileName);
|
||||
if (!myFileName.IsOk()) return "";
|
||||
|
||||
if (myFileName.IsRelative()) {
|
||||
// Check whether the file is relative to the current working directory
|
||||
if (!(myFileName.MakeAbsolute() && myFileName.FileExists())) {
|
||||
return "";
|
||||
// File not found, search in given search paths
|
||||
// wxString foundFileName =
|
||||
// m_searchPaths.FindAbsoluteValidPath(fileName); if
|
||||
// (!foundFileName.IsEmpty()) {
|
||||
// myFileName.Assign(foundFileName);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
if (!myFileName.FileExists() || !myFileName.IsFileReadable()) return "";
|
||||
|
||||
// File exists and is accessible
|
||||
wxString fullFileName = myFileName.GetFullPath();
|
||||
return std::string(fullFileName.c_str());
|
||||
}
|
||||
|
||||
#endif // EXIST_FONT_CONFIG_INCLUDE
|
29
src/slic3r/Utils/FontConfigHelp.hpp
Normal file
29
src/slic3r/Utils/FontConfigHelp.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_FontConfigHelp_hpp_
|
||||
#define slic3r_FontConfigHelp_hpp_
|
||||
|
||||
#ifdef __linux__
|
||||
#define EXIST_FONT_CONFIG_INCLUDE
|
||||
#endif
|
||||
|
||||
#ifdef EXIST_FONT_CONFIG_INCLUDE
|
||||
#include <wx/font.h>
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
/// <summary>
|
||||
/// initialize font config
|
||||
/// Convert wx widget font to file path
|
||||
/// inspired by wxpdfdoc -
|
||||
/// https://github.com/utelle/wxpdfdoc/blob/5bdcdb9953327d06dc50ec312685ccd9bc8400e0/src/pdffontmanager.cpp
|
||||
/// </summary>
|
||||
/// <param name="font">Wx descriptor of font</param>
|
||||
/// <param name="reload_fonts">flag to reinitialize font list</param>
|
||||
/// <returns>Font FilePath by FontConfig</returns>
|
||||
std::string get_font_path(const wxFont &font, bool reload_fonts = false);
|
||||
|
||||
} // namespace Slic3r
|
||||
#endif // EXIST_FONT_CONFIG_INCLUDE
|
||||
#endif // slic3r_FontConfigHelp_hpp_
|
386
src/slic3r/Utils/RaycastManager.cpp
Normal file
386
src/slic3r/Utils/RaycastManager.cpp
Normal file
|
@ -0,0 +1,386 @@
|
|||
///|/ Copyright (c) Prusa Research 2022 Enrico Turri @enricoturri1966, Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "RaycastManager.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/CameraUtils.hpp"
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
namespace{
|
||||
using namespace Slic3r;
|
||||
void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes *input = nullptr);
|
||||
const AABBMesh * get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id);
|
||||
RaycastManager::TrKey create_key(const ModelVolume& volume, const ModelInstance& instance){
|
||||
return std::make_pair(instance.id().id, volume.id().id); }
|
||||
RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key);
|
||||
RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key);
|
||||
bool is_lower_key(const RaycastManager::TrKey &k1, const RaycastManager::TrKey &k2) {
|
||||
return k1.first < k2.first || (k1.first == k2.first && k1.second < k2.second); }
|
||||
bool is_lower(const RaycastManager::TrItem &i1, const RaycastManager::TrItem &i2) {
|
||||
return is_lower_key(i1.first, i2.first); };
|
||||
template<typename VecType> inline void erase(std::vector<VecType> &vec, const std::vector<bool> &flags);
|
||||
}
|
||||
|
||||
void RaycastManager::actualize(const ModelObject &object, const ISkip *skip, Meshes *meshes)
|
||||
{
|
||||
// actualize MeshRaycaster
|
||||
::actualize(m_meshes, object.volumes, skip, meshes);
|
||||
|
||||
// check if inscance was removed
|
||||
std::vector<bool> removed_transf(m_transformations.size(), {true});
|
||||
|
||||
bool need_sort = false;
|
||||
// actualize transformation matrices
|
||||
for (const ModelVolume *volume : object.volumes) {
|
||||
if (skip != nullptr && skip->skip(volume->id().id)) continue;
|
||||
const Transform3d &volume_tr = volume->get_matrix();
|
||||
for (const ModelInstance *instance : object.instances) {
|
||||
const Transform3d &instrance_tr = instance->get_matrix();
|
||||
Transform3d transformation = instrance_tr * volume_tr;
|
||||
TrKey key = ::create_key(*volume, *instance);
|
||||
auto item = ::find(m_transformations, key);
|
||||
if (item != m_transformations.end()) {
|
||||
// actualize transformation all the time
|
||||
item->second = transformation;
|
||||
size_t index = item - m_transformations.begin();
|
||||
removed_transf[index] = false;
|
||||
} else {
|
||||
// add new transformation
|
||||
m_transformations.emplace_back(key, transformation);
|
||||
need_sort = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clean other transformation
|
||||
::erase(m_transformations, removed_transf);
|
||||
|
||||
if (need_sort)
|
||||
std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower);
|
||||
}
|
||||
|
||||
void RaycastManager::actualize(const ModelInstance &instance, const ISkip *skip, Meshes *meshes)
|
||||
{
|
||||
const ModelVolumePtrs &volumes = instance.get_object()->volumes;
|
||||
|
||||
// actualize MeshRaycaster
|
||||
::actualize(m_meshes, volumes, skip, meshes);
|
||||
|
||||
// check if inscance was removed
|
||||
std::vector<bool> removed_transf(m_transformations.size(), {true});
|
||||
|
||||
bool need_sort = false;
|
||||
// actualize transformation matrices
|
||||
for (const ModelVolume *volume : volumes) {
|
||||
if (skip != nullptr && skip->skip(volume->id().id))
|
||||
continue;
|
||||
const Transform3d &volume_tr = volume->get_matrix();
|
||||
const Transform3d &instrance_tr = instance.get_matrix();
|
||||
Transform3d transformation = instrance_tr * volume_tr;
|
||||
TrKey key = ::create_key(*volume, instance);
|
||||
auto item = ::find(m_transformations, key);
|
||||
if (item != m_transformations.end()) {
|
||||
// actualize transformation all the time
|
||||
item->second = transformation;
|
||||
size_t index = item - m_transformations.begin();
|
||||
removed_transf[index] = false;
|
||||
} else {
|
||||
// add new transformation
|
||||
m_transformations.emplace_back(key, transformation);
|
||||
need_sort = true;
|
||||
}
|
||||
}
|
||||
|
||||
// clean other transformation
|
||||
::erase(m_transformations, removed_transf);
|
||||
|
||||
if (need_sort)
|
||||
std::sort(m_transformations.begin(), m_transformations.end(), ::is_lower);
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::Hit> RaycastManager::first_hit(const Vec3d& point, const Vec3d& direction, const ISkip *skip) const
|
||||
{
|
||||
// Improve: it is not neccessaru to use AABBMesh and calc normal for every hit
|
||||
|
||||
// Results
|
||||
const AABBMesh *hit_mesh = nullptr;
|
||||
double hit_squared_distance = 0.;
|
||||
int hit_face = -1;
|
||||
Vec3d hit_world;
|
||||
const Transform3d *hit_tramsformation = nullptr;
|
||||
const TrKey *hit_key = nullptr;
|
||||
|
||||
for (const auto &[key, transformation]: m_transformations) {
|
||||
size_t volume_id = key.second;
|
||||
if (skip != nullptr && skip->skip(volume_id)) continue;
|
||||
const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id);
|
||||
if (mesh == nullptr) continue;
|
||||
Transform3d inv = transformation.inverse();
|
||||
|
||||
// transform input into mesh world
|
||||
Vec3d point_ = inv * point;
|
||||
Vec3d direction_= inv.linear() * direction;
|
||||
|
||||
std::vector<AABBMesh::hit_result> hits = mesh->query_ray_hits(point_, direction_);
|
||||
if (hits.empty()) continue; // no intersection found
|
||||
|
||||
const AABBMesh::hit_result &hit = hits.front();
|
||||
|
||||
// convert to world
|
||||
Vec3d world = transformation * hit.position();
|
||||
double squared_distance = (point - world).squaredNorm();
|
||||
if (hit_mesh != nullptr &&
|
||||
hit_squared_distance < squared_distance)
|
||||
continue; // exist closer one
|
||||
|
||||
hit_mesh = mesh;
|
||||
hit_squared_distance = squared_distance;
|
||||
hit_face = hit.face();
|
||||
hit_world = world;
|
||||
hit_tramsformation = &transformation;
|
||||
hit_key = &key;
|
||||
}
|
||||
|
||||
if (hit_mesh == nullptr)
|
||||
return {};
|
||||
|
||||
// Calculate normal from transformed triangle
|
||||
// NOTE: Anisotropic transformation of normal is not perpendiculat to triangle
|
||||
const Vec3i tri = hit_mesh->indices(hit_face);
|
||||
std::array<Vec3d,3> pts;
|
||||
auto tr = hit_tramsformation->linear();
|
||||
for (int i = 0; i < 3; ++i)
|
||||
pts[i] = tr * hit_mesh->vertices(tri[i]).cast<double>();
|
||||
Vec3d normal_world = (pts[1] - pts[0]).cross(pts[2] - pts[1]);
|
||||
if (has_reflection(*hit_tramsformation))
|
||||
normal_world *= -1;
|
||||
normal_world.normalize();
|
||||
|
||||
SurfacePoint<double> point_world{hit_world, normal_world};
|
||||
return RaycastManager::Hit{point_world, *hit_key, hit_squared_distance};
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::Hit> RaycastManager::closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const
|
||||
{
|
||||
std::optional<Hit> closest;
|
||||
for (const auto &[key, transformation] : m_transformations) {
|
||||
size_t volume_id = key.second;
|
||||
if (skip != nullptr && skip->skip(volume_id)) continue;
|
||||
const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id);
|
||||
if (mesh == nullptr) continue;
|
||||
Transform3d tr_inv = transformation.inverse();
|
||||
Vec3d mesh_point = tr_inv * point;
|
||||
Vec3d mesh_direction = tr_inv.linear() * direction;
|
||||
|
||||
// Need for detect that actual point position is on correct place
|
||||
Vec3d point_positive = mesh_point - mesh_direction;
|
||||
Vec3d point_negative = mesh_point + mesh_direction;
|
||||
|
||||
// Throw ray to both directions of ray
|
||||
std::vector<AABBMesh::hit_result> hits = mesh->query_ray_hits(point_positive, mesh_direction);
|
||||
std::vector<AABBMesh::hit_result> hits_neg = mesh->query_ray_hits(point_negative, -mesh_direction);
|
||||
hits.insert(hits.end(), std::make_move_iterator(hits_neg.begin()), std::make_move_iterator(hits_neg.end()));
|
||||
for (const AABBMesh::hit_result &hit : hits) {
|
||||
Vec3d diff = mesh_point - hit.position();
|
||||
double squared_distance = diff.squaredNorm();
|
||||
if (closest.has_value() &&
|
||||
closest->squared_distance < squared_distance)
|
||||
continue;
|
||||
closest = Hit{{hit.position(), hit.normal()}, key, squared_distance};
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
std::optional<RaycastManager::ClosePoint> RaycastManager::closest(const Vec3d &point, const ISkip *skip) const
|
||||
{
|
||||
std::optional<ClosePoint> closest;
|
||||
for (const auto &[key, transformation] : m_transformations) {
|
||||
size_t volume_id = key.second;
|
||||
if (skip != nullptr && skip->skip(volume_id))
|
||||
continue;
|
||||
const AABBMesh *mesh = ::get_mesh(m_meshes, volume_id);
|
||||
if (mesh == nullptr) continue;
|
||||
Transform3d tr_inv = transformation.inverse();
|
||||
Vec3d mesh_point = tr_inv * point;
|
||||
|
||||
int face_idx = 0;
|
||||
Vec3d closest_point;
|
||||
Vec3d pointd = point.cast<double>();
|
||||
mesh->squared_distance(pointd, face_idx, closest_point);
|
||||
|
||||
double squared_distance = (mesh_point - closest_point).squaredNorm();
|
||||
if (closest.has_value() && closest->squared_distance < squared_distance)
|
||||
continue;
|
||||
|
||||
closest = ClosePoint{key, closest_point, squared_distance};
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
Slic3r::Transform3d RaycastManager::get_transformation(const TrKey &tr_key) const {
|
||||
auto tr = ::find(m_transformations, tr_key);
|
||||
if (tr == m_transformations.end())
|
||||
return Transform3d::Identity();
|
||||
return tr->second;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
void actualize(RaycastManager::Meshes &meshes, const ModelVolumePtrs &volumes, const RaycastManager::ISkip *skip, RaycastManager::Meshes* inputs)
|
||||
{
|
||||
// check if volume was removed
|
||||
std::vector<bool> removed_meshes(meshes.size(), {true});
|
||||
bool need_sort = false;
|
||||
// actualize MeshRaycaster
|
||||
for (const ModelVolume *volume : volumes) {
|
||||
size_t oid = volume->id().id;
|
||||
if (skip != nullptr && skip->skip(oid))
|
||||
continue;
|
||||
auto is_oid = [oid](const RaycastManager::Mesh &it) { return oid == it.first; };
|
||||
if (auto item = std::find_if(meshes.begin(), meshes.end(), is_oid);
|
||||
item != meshes.end()) {
|
||||
size_t index = item - meshes.begin();
|
||||
removed_meshes[index] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// exist AABB in inputs ?
|
||||
if (inputs != nullptr) {
|
||||
auto input = std::find_if(inputs->begin(), inputs->end(), is_oid);
|
||||
if (input != inputs->end()) {
|
||||
meshes.emplace_back(std::move(*input));
|
||||
need_sort = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// add new raycaster
|
||||
bool calculate_epsilon = true;
|
||||
auto mesh = std::make_unique<AABBMesh>(volume->mesh(), calculate_epsilon);
|
||||
meshes.emplace_back(std::make_pair(oid, std::move(mesh)));
|
||||
need_sort = true;
|
||||
}
|
||||
|
||||
// clean other raycasters
|
||||
erase(meshes, removed_meshes);
|
||||
|
||||
// All the time meshes must be sorted by volume id - for faster search
|
||||
if (need_sort) {
|
||||
auto is_lower = [](const RaycastManager::Mesh &m1, const RaycastManager::Mesh &m2) { return m1.first < m2.first; };
|
||||
std::sort(meshes.begin(), meshes.end(), is_lower);
|
||||
}
|
||||
}
|
||||
|
||||
const Slic3r::AABBMesh *get_mesh(const RaycastManager::Meshes &meshes, size_t volume_id)
|
||||
{
|
||||
auto is_lower_index = [](const RaycastManager::Mesh &m, size_t i) { return m.first < i; };
|
||||
auto it = std::lower_bound(meshes.begin(), meshes.end(), volume_id, is_lower_index);
|
||||
if (it == meshes.end() || it->first != volume_id)
|
||||
return nullptr;
|
||||
return &(*(it->second));
|
||||
}
|
||||
|
||||
RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key) {
|
||||
auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); };
|
||||
auto it = std::lower_bound(items.begin(), items.end(), key, fnc);
|
||||
if (it != items.end() && it->first != key)
|
||||
return items.end();
|
||||
return it;
|
||||
}
|
||||
|
||||
RaycastManager::TrItems::const_iterator find(const RaycastManager::TrItems &items, const RaycastManager::TrKey &key)
|
||||
{
|
||||
auto fnc = [](const RaycastManager::TrItem &it, const RaycastManager::TrKey &l_key) { return is_lower_key(it.first, l_key); };
|
||||
auto it = std::lower_bound(items.begin(), items.end(), key, fnc);
|
||||
if (it != items.end() && it->first != key)
|
||||
return items.end();
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename VecType> inline void erase(std::vector<VecType> &vec, const std::vector<bool> &flags)
|
||||
{
|
||||
if (vec.size() < flags.size() || flags.empty())
|
||||
return;
|
||||
|
||||
// reverse iteration over flags to erase indices from back to front.
|
||||
for (int i = static_cast<int>(flags.size()) - 1; i >= 0; --i)
|
||||
if (flags[i])
|
||||
vec.erase(vec.begin() + i);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r::GUI{
|
||||
|
||||
RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition)
|
||||
{
|
||||
SceneRaycaster::EType type = SceneRaycaster::EType::Volume;
|
||||
auto scene_casters = canvas.get_raycasters_for_picking(type);
|
||||
if (scene_casters == nullptr)
|
||||
return {};
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>> &casters = *scene_casters;
|
||||
|
||||
const GLVolumePtrs &gl_volumes = canvas.get_volumes().volumes;
|
||||
const ModelObjectPtrs &objects = canvas.get_model()->objects;
|
||||
|
||||
RaycastManager::Meshes meshes;
|
||||
for (const std::shared_ptr<SceneRaycasterItem> &caster : casters) {
|
||||
int index = SceneRaycaster::decode_id(type, caster->get_id());
|
||||
if (index < 0)
|
||||
continue;
|
||||
auto index_ = static_cast<size_t>(index);
|
||||
if(index_ >= gl_volumes.size())
|
||||
continue;
|
||||
const GLVolume *gl_volume = gl_volumes[index_];
|
||||
if (gl_volume == nullptr)
|
||||
continue;
|
||||
const ModelVolume *volume = get_model_volume(*gl_volume, objects);
|
||||
if (volume == nullptr)
|
||||
continue;
|
||||
size_t id = volume->id().id;
|
||||
if (condition.skip(id))
|
||||
continue;
|
||||
auto mesh = std::make_unique<AABBMesh>(caster->get_raycaster()->get_aabb_mesh());
|
||||
meshes.emplace_back(std::make_pair(id, std::move(mesh)));
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
|
||||
|
||||
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
|
||||
const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const RaycastManager::ISkip *skip)
|
||||
{
|
||||
Vec3d point;
|
||||
Vec3d direction;
|
||||
CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction);
|
||||
return raycaster.first_hit(point, direction, skip);
|
||||
}
|
||||
|
||||
RaycastManager::AllowVolumes create_condition(const ModelVolumePtrs &volumes, const ObjectID &disallowed_volume_id) {
|
||||
std::vector<size_t> allowed_volumes_id;
|
||||
if (volumes.size() > 1) {
|
||||
allowed_volumes_id.reserve(volumes.size() - 1);
|
||||
for (const ModelVolume *v : volumes) {
|
||||
// drag only above part not modifiers or negative surface
|
||||
if (!v->is_model_part())
|
||||
continue;
|
||||
|
||||
// skip actual selected object
|
||||
if (v->id() == disallowed_volume_id)
|
||||
continue;
|
||||
|
||||
allowed_volumes_id.emplace_back(v->id().id);
|
||||
}
|
||||
}
|
||||
return RaycastManager::AllowVolumes(allowed_volumes_id);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
184
src/slic3r/Utils/RaycastManager.hpp
Normal file
184
src/slic3r/Utils/RaycastManager.hpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
///|/ Copyright (c) Prusa Research 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_RaycastManager_hpp_
|
||||
#define slic3r_RaycastManager_hpp_
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <optional>
|
||||
#include "libslic3r/AABBMesh.hpp" // Structure to cast rays
|
||||
#include "libslic3r/Point.hpp" // Transform3d
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/Model.hpp" // ModelObjectPtrs, ModelObject, ModelInstance, ModelVolume
|
||||
|
||||
namespace Slic3r::GUI{
|
||||
|
||||
/// <summary>
|
||||
/// Cast rays from camera to scene
|
||||
/// Used for find hit point on model volume under mouse cursor
|
||||
/// </summary>
|
||||
class RaycastManager
|
||||
{
|
||||
// Public structures used by RaycastManager
|
||||
public:
|
||||
|
||||
// ModelVolume.id
|
||||
using Mesh = std::pair<size_t, std::unique_ptr<AABBMesh> >;
|
||||
using Meshes = std::vector<Mesh>;
|
||||
|
||||
// Key for transformation consist of unique volume and instance id ... ObjectId()
|
||||
// ModelInstance, ModelVolume
|
||||
using TrKey = std::pair<size_t, size_t>;
|
||||
using TrItem = std::pair<TrKey, Transform3d>;
|
||||
using TrItems = std::vector<TrItem>;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for identify allowed volumes to cast rays.
|
||||
/// </summary>
|
||||
class ISkip{
|
||||
public:
|
||||
virtual ~ISkip() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Condition to not process model volume
|
||||
/// </summary>
|
||||
/// <param name="model_volume_id">ObjectID of model volume to not process</param>
|
||||
/// <returns>True on skip otherwise false</returns>
|
||||
virtual bool skip(const size_t &model_volume_id) const { return false; }
|
||||
};
|
||||
|
||||
// TODO: it is more general object move outside of this class
|
||||
template<typename T>
|
||||
struct SurfacePoint {
|
||||
using Vec3 = Eigen::Matrix<T, 3, 1, Eigen::DontAlign>;
|
||||
Vec3 position = Vec3::Zero();
|
||||
Vec3 normal = Vec3::UnitZ();
|
||||
};
|
||||
|
||||
struct Hit : public SurfacePoint<double>
|
||||
{
|
||||
TrKey tr_key;
|
||||
double squared_distance;
|
||||
};
|
||||
|
||||
struct ClosePoint
|
||||
{
|
||||
TrKey tr_key;
|
||||
Vec3d point;
|
||||
double squared_distance;
|
||||
};
|
||||
|
||||
// Members
|
||||
private:
|
||||
// Keep structure to fast cast rays
|
||||
// meshes are sorted by volume_id for faster search
|
||||
Meshes m_meshes;
|
||||
|
||||
// Keep transformation of meshes
|
||||
TrItems m_transformations;
|
||||
// Note: one mesh could have more transformations ... instances
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Actualize raycasters + transformation
|
||||
/// Detection of removed object
|
||||
/// Detection of removed instance
|
||||
/// Detection of removed volume
|
||||
/// </summary>
|
||||
/// <param name="object">Model representation</param>
|
||||
/// <param name="skip">Condifiton for skip actualization</param>
|
||||
/// <param name="meshes">Speed up for already created AABBtrees</param>
|
||||
void actualize(const ModelObject &object, const ISkip *skip = nullptr, Meshes *meshes = nullptr);
|
||||
void actualize(const ModelInstance &instance, const ISkip *skip = nullptr, Meshes* meshes = nullptr);
|
||||
|
||||
class SkipVolume: public ISkip
|
||||
{
|
||||
size_t volume_id;
|
||||
public:
|
||||
SkipVolume(size_t volume_id) : volume_id(volume_id) {}
|
||||
bool skip(const size_t &model_volume_id) const override { return model_volume_id == volume_id; }
|
||||
};
|
||||
|
||||
class AllowVolumes: public ISkip
|
||||
{
|
||||
std::vector<size_t> allowed_id;
|
||||
public:
|
||||
AllowVolumes(std::vector<size_t> allowed_id) : allowed_id(allowed_id) {}
|
||||
bool skip(const size_t &model_volume_id) const override {
|
||||
auto it = std::find(allowed_id.begin(), allowed_id.end(), model_volume_id);
|
||||
return it == allowed_id.end();
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Unproject on mesh and return closest hit to point in given direction
|
||||
/// </summary>
|
||||
/// <param name="point">Position in space</param>
|
||||
/// <param name="direction">Casted ray direction</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction in world coorinate
|
||||
/// + key, to know hitted instance and volume</returns>
|
||||
std::optional<Hit> first_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Unproject Ray(point direction) on mesh to find closest hit of surface in given direction
|
||||
/// NOTE: It inspect also oposit direction of ray !!
|
||||
/// </summary>
|
||||
/// <param name="point">Start point for ray</param>
|
||||
/// <param name="direction">Direction of ray, orientation doesn't matter, both are used</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction and transformation key, which define hitted object instance</returns>
|
||||
std::optional<Hit> closest_hit(const Vec3d &point, const Vec3d &direction, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Search of closest point
|
||||
/// </summary>
|
||||
/// <param name="point">Point</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns></returns>
|
||||
std::optional<ClosePoint> closest(const Vec3d &point, const ISkip *skip = nullptr) const;
|
||||
|
||||
/// <summary>
|
||||
/// Getter on transformation from hitted volume to world
|
||||
/// </summary>
|
||||
/// <param name="tr_key">Define transformation</param>
|
||||
/// <returns>Transformation for key</returns>
|
||||
Transform3d get_transformation(const TrKey &tr_key) const;
|
||||
};
|
||||
|
||||
class GLCanvas3D;
|
||||
/// <summary>
|
||||
/// Use scene Raycasters and prepare data for actualize RaycasterManager
|
||||
/// </summary>
|
||||
/// <param name="canvas">contain Scene raycasters</param>
|
||||
/// <param name="condition">Limit for scene casters</param>
|
||||
/// <returns>Meshes</returns>
|
||||
RaycastManager::Meshes create_meshes(GLCanvas3D &canvas, const RaycastManager::AllowVolumes &condition);
|
||||
|
||||
struct Camera;
|
||||
/// <summary>
|
||||
/// Unproject on mesh by Mesh raycasters
|
||||
/// </summary>
|
||||
/// <param name="mouse_pos">Position of mouse on screen</param>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="skip">Define which caster will be skipped, null mean no skip</param>
|
||||
/// <returns>Position on surface, normal direction in world coorinate
|
||||
/// + key, to know hitted instance and volume</returns>
|
||||
std::optional<RaycastManager::Hit> ray_from_camera(const RaycastManager &raycaster,
|
||||
const Vec2d &mouse_pos,
|
||||
const Camera &camera,
|
||||
const RaycastManager::ISkip *skip);
|
||||
|
||||
/// <summary>
|
||||
/// Create condition to allowe only parts from volumes without one given
|
||||
/// </summary>
|
||||
/// <param name="volumes">List of allowed volumes included one which is dissalowed and non parts</param>
|
||||
/// <param name="disallowed_volume_id">Disallowed volume</param>
|
||||
/// <returns>Condition</returns>
|
||||
RaycastManager::AllowVolumes create_condition(const ModelVolumePtrs &volumes, const ObjectID &disallowed_volume_id);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_RaycastManager_hpp_
|
354
src/slic3r/Utils/WxFontUtils.cpp
Normal file
354
src/slic3r/Utils/WxFontUtils.cpp
Normal file
|
@ -0,0 +1,354 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "WxFontUtils.hpp"
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <CoreText/CTFont.h>
|
||||
#include <wx/uri.h>
|
||||
#include <wx/fontutil.h> // wxNativeFontInfo
|
||||
#include <wx/osx/core/cfdictionary.h>
|
||||
#elif defined(__linux__)
|
||||
#include "slic3r/Utils/FontConfigHelp.hpp"
|
||||
#endif
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
#ifdef __APPLE__
|
||||
namespace {
|
||||
bool is_valid_ttf(std::string_view file_path)
|
||||
{
|
||||
if (file_path.empty()) return false;
|
||||
auto const pos_point = file_path.find_last_of('.');
|
||||
if (pos_point == std::string_view::npos) return false;
|
||||
|
||||
// use point only after last directory delimiter
|
||||
auto const pos_directory_delimiter = file_path.find_last_of("/\\");
|
||||
if (pos_directory_delimiter != std::string_view::npos &&
|
||||
pos_point < pos_directory_delimiter)
|
||||
return false; // point is before directory delimiter
|
||||
|
||||
// check count of extension chars
|
||||
size_t extension_size = file_path.size() - pos_point;
|
||||
if (extension_size >= 5) return false; // a lot of symbols for extension
|
||||
if (extension_size <= 1) return false; // few letters for extension
|
||||
|
||||
std::string_view extension = file_path.substr(pos_point + 1, extension_size);
|
||||
|
||||
// Because of MacOs - Courier, Geneva, Monaco
|
||||
if (extension == std::string_view("dfont")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// get filepath from wxFont on Mac OsX
|
||||
std::string get_file_path(const wxFont& font) {
|
||||
const wxNativeFontInfo *info = font.GetNativeFontInfo();
|
||||
if (info == nullptr) return {};
|
||||
CTFontDescriptorRef descriptor = info->GetCTFontDescriptor();
|
||||
CFURLRef typeref = (CFURLRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute);
|
||||
if (typeref == NULL) return {};
|
||||
ScopeGuard sg([&typeref]() { CFRelease(typeref); });
|
||||
CFStringRef url = CFURLGetString(typeref);
|
||||
if (url == NULL) return {};
|
||||
wxString file_uri(wxCFStringRef::AsString(url));
|
||||
wxURI uri(file_uri);
|
||||
const wxString &path = uri.GetPath();
|
||||
wxString path_unescaped = wxURI::Unescape(path);
|
||||
std::string path_str = path_unescaped.ToUTF8().data();
|
||||
BOOST_LOG_TRIVIAL(trace) << "input uri(" << file_uri.c_str() << ") convert to path(" << path.c_str() << ") string(" << path_str << ").";
|
||||
return path_str;
|
||||
}
|
||||
} // namespace
|
||||
#endif // __APPLE__
|
||||
|
||||
bool WxFontUtils::can_load(const wxFont &font)
|
||||
{
|
||||
|
||||
if (!font.IsOk()) return false;
|
||||
#ifdef _WIN32
|
||||
return Emboss::can_load(font.GetHFONT()) != nullptr;
|
||||
#elif defined(__APPLE__)
|
||||
return true;
|
||||
//return is_valid_ttf(get_file_path(font));
|
||||
#elif defined(__linux__)
|
||||
return true;
|
||||
// font config check file path take about 4000ms for chech them all
|
||||
//std::string font_path = Slic3r::GUI::get_font_path(font);
|
||||
//return !font_path.empty();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::FontFile> WxFontUtils::create_font_file(const wxFont &font)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return Emboss::create_font_file(font.GetHFONT());
|
||||
#elif defined(__APPLE__)
|
||||
std::string file_path = get_file_path(font);
|
||||
if (!is_valid_ttf(file_path)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Can not process font('" << get_human_readable_name(font) << "'), "
|
||||
<< "file in path('" << file_path << "') is not valid TTF.";
|
||||
return nullptr;
|
||||
}
|
||||
return Emboss::create_font_file(file_path.c_str());
|
||||
#elif defined(__linux__)
|
||||
std::string font_path = Slic3r::GUI::get_font_path(font);
|
||||
if (font_path.empty()){
|
||||
BOOST_LOG_TRIVIAL(error) << "Can not read font('" << get_human_readable_name(font) << "'), "
|
||||
<< "file path is empty.";
|
||||
return nullptr;
|
||||
}
|
||||
return Emboss::create_font_file(font_path.c_str());
|
||||
#else
|
||||
// HERE is place to add implementation for another platform
|
||||
// to convert wxFont to font data as windows or font file path as linux
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
EmbossStyle::Type WxFontUtils::get_current_type()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return EmbossStyle::Type::wx_win_font_descr;
|
||||
#elif defined(__APPLE__)
|
||||
return EmbossStyle::Type::wx_mac_font_descr;
|
||||
#elif defined(__linux__)
|
||||
return EmbossStyle::Type::wx_lin_font_descr;
|
||||
#else
|
||||
return EmbossStyle::Type::undefined;
|
||||
#endif
|
||||
}
|
||||
|
||||
EmbossStyle WxFontUtils::create_emboss_style(const wxFont &font, const std::string& name)
|
||||
{
|
||||
std::string name_item = name.empty()? get_human_readable_name(font) : name;
|
||||
std::string fontDesc = store_wxFont(font);
|
||||
EmbossStyle::Type type = get_current_type();
|
||||
|
||||
// synchronize font property with actual font
|
||||
FontProp font_prop;
|
||||
|
||||
// The point size is defined as 1/72 of the Anglo-Saxon inch (25.4 mm): it
|
||||
// is approximately 0.0139 inch or 352.8 um. But it is too small, so I
|
||||
// decide use point size as mm for emboss
|
||||
font_prop.size_in_mm = font.GetPointSize(); // *0.3528f;
|
||||
|
||||
WxFontUtils::update_property(font_prop, font);
|
||||
return { name_item, fontDesc, type, font_prop };
|
||||
}
|
||||
|
||||
// NOT working on linux GTK2
|
||||
// load font used by Operating system as default GUI
|
||||
//EmbossStyle WxFontUtils::get_os_font()
|
||||
//{
|
||||
// wxSystemFont system_font = wxSYS_DEFAULT_GUI_FONT;
|
||||
// wxFont font = wxSystemSettings::GetFont(system_font);
|
||||
// EmbossStyle es = create_emboss_style(font);
|
||||
// es.name += std::string(" (OS default)");
|
||||
// return es;
|
||||
//}
|
||||
|
||||
std::string WxFontUtils::get_human_readable_name(const wxFont &font)
|
||||
{
|
||||
if (!font.IsOk()) return "Font is NOT ok.";
|
||||
// Face name is optional in wxFont
|
||||
if (!font.GetFaceName().empty()) {
|
||||
return std::string(font.GetFaceName().c_str());
|
||||
} else {
|
||||
return std::string((font.GetFamilyString() + " " +
|
||||
font.GetStyleString() + " " +
|
||||
font.GetWeightString())
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string WxFontUtils::store_wxFont(const wxFont &font)
|
||||
{
|
||||
// wxString os = wxPlatformInfo::Get().GetOperatingSystemIdName();
|
||||
wxString font_descriptor = font.GetNativeFontInfoDesc();
|
||||
BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor << "' wx string get from GetNativeFontInfoDesc. wxFont " <<
|
||||
"IsOk(" << font.IsOk() << "), " <<
|
||||
"isNull(" << font.IsNull() << ")" <<
|
||||
// "IsFree(" << font.IsFree() << "), " << // on MacOs is no function is free
|
||||
"IsFixedWidth(" << font.IsFixedWidth() << "), " <<
|
||||
"IsUsingSizeInPixels(" << font.IsUsingSizeInPixels() << "), " <<
|
||||
"Encoding(" << (int)font.GetEncoding() << "), " ;
|
||||
return std::string(font_descriptor.ToUTF8().data());
|
||||
}
|
||||
|
||||
wxFont WxFontUtils::load_wxFont(const std::string &font_descriptor)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor << "'font descriptor string param of load_wxFont()";
|
||||
wxString font_descriptor_wx = wxString::FromUTF8(font_descriptor);
|
||||
BOOST_LOG_TRIVIAL(trace) << "'" << font_descriptor_wx.c_str() << "' wx string descriptor";
|
||||
wxFont wx_font(font_descriptor_wx);
|
||||
BOOST_LOG_TRIVIAL(trace) << "loaded font is '" << get_human_readable_name(wx_font) << "'.";
|
||||
return wx_font;
|
||||
}
|
||||
|
||||
using TypeToFamily = boost::bimap<wxFontFamily, std::string_view>;
|
||||
const TypeToFamily WxFontUtils::type_to_family =
|
||||
boost::assign::list_of<TypeToFamily::relation>
|
||||
(wxFONTFAMILY_DEFAULT, "default")
|
||||
(wxFONTFAMILY_DECORATIVE, "decorative")
|
||||
(wxFONTFAMILY_ROMAN, "roman")
|
||||
(wxFONTFAMILY_SCRIPT, "script")
|
||||
(wxFONTFAMILY_SWISS, "swiss")
|
||||
(wxFONTFAMILY_MODERN, "modern")
|
||||
(wxFONTFAMILY_TELETYPE, "teletype");
|
||||
|
||||
using TypeToStyle = boost::bimap<wxFontStyle, std::string_view>;
|
||||
const TypeToStyle WxFontUtils::type_to_style =
|
||||
boost::assign::list_of<TypeToStyle::relation>
|
||||
(wxFONTSTYLE_ITALIC, "italic")
|
||||
(wxFONTSTYLE_SLANT, "slant")
|
||||
(wxFONTSTYLE_NORMAL, "normal");
|
||||
|
||||
using TypeToWeight = boost::bimap<wxFontWeight, std::string_view>;
|
||||
const TypeToWeight WxFontUtils::type_to_weight =
|
||||
boost::assign::list_of<TypeToWeight::relation>
|
||||
(wxFONTWEIGHT_THIN, "thin")
|
||||
(wxFONTWEIGHT_EXTRALIGHT, "extraLight")
|
||||
(wxFONTWEIGHT_LIGHT, "light")
|
||||
(wxFONTWEIGHT_NORMAL, "normal")
|
||||
(wxFONTWEIGHT_MEDIUM, "medium")
|
||||
(wxFONTWEIGHT_SEMIBOLD, "semibold")
|
||||
(wxFONTWEIGHT_BOLD, "bold")
|
||||
(wxFONTWEIGHT_EXTRABOLD, "extraBold")
|
||||
(wxFONTWEIGHT_HEAVY, "heavy")
|
||||
(wxFONTWEIGHT_EXTRAHEAVY, "extraHeavy");
|
||||
|
||||
wxFont WxFontUtils::create_wxFont(const EmbossStyle &style)
|
||||
{
|
||||
const FontProp &fp = style.prop;
|
||||
double point_size = static_cast<double>(fp.size_in_mm);
|
||||
wxFontInfo info(point_size);
|
||||
if (fp.family.has_value()) {
|
||||
auto it = type_to_family.right.find(*fp.family);
|
||||
if (it != type_to_family.right.end()) info.Family(it->second);
|
||||
}
|
||||
// Face names are not portable, so prefer to use Family() in portable code.
|
||||
/* if (fp.face_name.has_value()) {
|
||||
wxString face_name(*fp.face_name);
|
||||
info.FaceName(face_name);
|
||||
}*/
|
||||
if (fp.style.has_value()) {
|
||||
auto it = type_to_style.right.find(*fp.style);
|
||||
if (it != type_to_style.right.end()) info.Style(it->second);
|
||||
}
|
||||
if (fp.weight.has_value()) {
|
||||
auto it = type_to_weight.right.find(*fp.weight);
|
||||
if (it != type_to_weight.right.end()) info.Weight(it->second);
|
||||
}
|
||||
|
||||
// Improve: load descriptor instead of store to font property to 3mf
|
||||
// switch (es.type) {
|
||||
// case EmbossStyle::Type::wx_lin_font_descr:
|
||||
// case EmbossStyle::Type::wx_win_font_descr:
|
||||
// case EmbossStyle::Type::wx_mac_font_descr:
|
||||
// case EmbossStyle::Type::file_path:
|
||||
// case EmbossStyle::Type::undefined:
|
||||
// default:
|
||||
//}
|
||||
|
||||
wxFont wx_font(info);
|
||||
// Check if exist font file
|
||||
std::unique_ptr<Emboss::FontFile> ff = create_font_file(wx_font);
|
||||
if (ff == nullptr) return {};
|
||||
|
||||
return wx_font;
|
||||
}
|
||||
|
||||
void WxFontUtils::update_property(FontProp &font_prop, const wxFont &font)
|
||||
{
|
||||
wxString wx_face_name = font.GetFaceName();
|
||||
std::string face_name((const char *) wx_face_name.ToUTF8());
|
||||
if (!face_name.empty()) font_prop.face_name = face_name;
|
||||
|
||||
wxFontFamily wx_family = font.GetFamily();
|
||||
if (wx_family != wxFONTFAMILY_DEFAULT) {
|
||||
auto it = type_to_family.left.find(wx_family);
|
||||
if (it != type_to_family.left.end()) font_prop.family = it->second;
|
||||
}
|
||||
|
||||
wxFontStyle wx_style = font.GetStyle();
|
||||
if (wx_style != wxFONTSTYLE_NORMAL) {
|
||||
auto it = type_to_style.left.find(wx_style);
|
||||
if (it != type_to_style.left.end()) font_prop.style = it->second;
|
||||
}
|
||||
|
||||
wxFontWeight wx_weight = font.GetWeight();
|
||||
if (wx_weight != wxFONTWEIGHT_NORMAL) {
|
||||
auto it = type_to_weight.left.find(wx_weight);
|
||||
if (it != type_to_weight.left.end()) font_prop.weight = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
bool WxFontUtils::is_italic(const wxFont &font) {
|
||||
wxFontStyle wx_style = font.GetStyle();
|
||||
return wx_style == wxFONTSTYLE_ITALIC ||
|
||||
wx_style == wxFONTSTYLE_SLANT;
|
||||
}
|
||||
|
||||
bool WxFontUtils::is_bold(const wxFont &font) {
|
||||
wxFontWeight wx_weight = font.GetWeight();
|
||||
return wx_weight != wxFONTWEIGHT_NORMAL;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::FontFile> WxFontUtils::set_italic(wxFont &font, const Emboss::FontFile &font_file)
|
||||
{
|
||||
static std::vector<wxFontStyle> italic_styles = {
|
||||
wxFontStyle::wxFONTSTYLE_ITALIC,
|
||||
wxFontStyle::wxFONTSTYLE_SLANT
|
||||
};
|
||||
wxFontStyle orig_style = font.GetStyle();
|
||||
for (wxFontStyle style : italic_styles) {
|
||||
font.SetStyle(style);
|
||||
std::unique_ptr<Emboss::FontFile> new_font_file =
|
||||
WxFontUtils::create_font_file(font);
|
||||
|
||||
// can create italic font?
|
||||
if (new_font_file == nullptr) continue;
|
||||
|
||||
// is still same font file pointer?
|
||||
if (font_file == *new_font_file) continue;
|
||||
|
||||
return new_font_file;
|
||||
}
|
||||
// There is NO italic font by wx
|
||||
font.SetStyle(orig_style);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Emboss::FontFile> WxFontUtils::set_bold(wxFont &font, const Emboss::FontFile& font_file)
|
||||
{
|
||||
static std::vector<wxFontWeight> bold_weight = {
|
||||
wxFontWeight::wxFONTWEIGHT_BOLD,
|
||||
wxFontWeight::wxFONTWEIGHT_HEAVY,
|
||||
wxFontWeight::wxFONTWEIGHT_EXTRABOLD,
|
||||
wxFontWeight::wxFONTWEIGHT_EXTRAHEAVY
|
||||
};
|
||||
wxFontWeight orig_weight = font.GetWeight();
|
||||
for (wxFontWeight weight : bold_weight) {
|
||||
font.SetWeight(weight);
|
||||
std::unique_ptr<Emboss::FontFile> new_font_file =
|
||||
WxFontUtils::create_font_file(font);
|
||||
|
||||
// can create bold font file?
|
||||
if (new_font_file == nullptr) continue;
|
||||
|
||||
// is still same font file pointer?
|
||||
if (font_file == *new_font_file) continue;
|
||||
|
||||
return new_font_file;
|
||||
}
|
||||
// There is NO bold font by wx
|
||||
font.SetWeight(orig_weight);
|
||||
return nullptr;
|
||||
}
|
75
src/slic3r/Utils/WxFontUtils.hpp
Normal file
75
src/slic3r/Utils/WxFontUtils.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
///|/ Copyright (c) Prusa Research 2021 - 2022 Filip Sykala @Jony01
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_WxFontUtils_hpp_
|
||||
#define slic3r_WxFontUtils_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <boost/bimap.hpp>
|
||||
#include <wx/font.h>
|
||||
#include "libslic3r/Emboss.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// Help class to work with wx widget font object( wxFont )
|
||||
class WxFontUtils
|
||||
{
|
||||
public:
|
||||
// only static functions
|
||||
WxFontUtils() = delete;
|
||||
|
||||
// check if exist file for wxFont
|
||||
// return pointer on data or nullptr when can't load
|
||||
static bool can_load(const wxFont &font);
|
||||
|
||||
// os specific load of wxFont
|
||||
static std::unique_ptr<Slic3r::Emboss::FontFile> create_font_file(const wxFont &font);
|
||||
|
||||
static EmbossStyle::Type get_current_type();
|
||||
static EmbossStyle create_emboss_style(const wxFont &font, const std::string& name = "");
|
||||
|
||||
static std::string get_human_readable_name(const wxFont &font);
|
||||
|
||||
// serialize / deserialize font
|
||||
static std::string store_wxFont(const wxFont &font);
|
||||
static wxFont load_wxFont(const std::string &font_descriptor);
|
||||
|
||||
// Try to create similar font, loaded from 3mf from different Computer
|
||||
static wxFont create_wxFont(const EmbossStyle &style);
|
||||
// update font property by wxFont - without emboss depth and font size
|
||||
static void update_property(FontProp &font_prop, const wxFont &font);
|
||||
|
||||
static bool is_italic(const wxFont &font);
|
||||
static bool is_bold(const wxFont &font);
|
||||
|
||||
/// <summary>
|
||||
/// Set italic into wx font
|
||||
/// When italic font is same as original return nullptr.
|
||||
/// To not load font file twice on success is font_file returned.
|
||||
/// </summary>
|
||||
/// <param name="font">wx descriptor of font</param>
|
||||
/// <param name="font_file">file described in wx font</param>
|
||||
/// <returns>New created font fileon success otherwise nullptr</returns>
|
||||
static std::unique_ptr<Slic3r::Emboss::FontFile> set_italic(wxFont &font, const Slic3r::Emboss::FontFile &prev_font_file);
|
||||
|
||||
/// <summary>
|
||||
/// Set boldness into wx font
|
||||
/// When bolded font is same as original return nullptr.
|
||||
/// To not load font file twice on success is font_file returned.
|
||||
/// </summary>
|
||||
/// <param name="font">wx descriptor of font</param>
|
||||
/// <param name="font_file">file described in wx font</param>
|
||||
/// <returns>New created font fileon success otherwise nullptr</returns>
|
||||
static std::unique_ptr<Slic3r::Emboss::FontFile> set_bold(wxFont &font, const Slic3r::Emboss::FontFile &font_file);
|
||||
|
||||
// convert wxFont types to string and vice versa
|
||||
static const boost::bimap<wxFontFamily, std::string_view> type_to_family;
|
||||
static const boost::bimap<wxFontStyle, std::string_view> type_to_style;
|
||||
static const boost::bimap<wxFontWeight, std::string_view> type_to_weight;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
#endif // slic3r_WxFontUtils_hpp_
|
Loading…
Add table
Add a link
Reference in a new issue