mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-14 07:21:49 +08:00
814 lines
28 KiB
C++
814 lines
28 KiB
C++
#include "GLGizmoEmboss.hpp"
|
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
|
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
|
#include "slic3r/GUI/MainFrame.hpp" // to update title when add text
|
|
#include "slic3r/GUI/NotificationManager.hpp"
|
|
#include "slic3r/GUI/Plater.hpp"
|
|
// TODO: remove include
|
|
#include "libslic3r/SVG.hpp" // debug store
|
|
|
|
#include "libslic3r/Model.hpp"
|
|
|
|
#include "nanosvg/nanosvg.h" // load SVG file
|
|
|
|
#include <wx/font.h>
|
|
#include <wx/fontdlg.h>
|
|
|
|
|
|
namespace Slic3r {
|
|
class WxFontUtils
|
|
{
|
|
public:
|
|
WxFontUtils() = delete;
|
|
|
|
// os specific load of wxFont
|
|
static std::optional<Slic3r::Emboss::Font> load_font(const wxFont &font);
|
|
// Must be in gui because of wxWidget
|
|
static std::optional<Slic3r::Emboss::Font> load_font(const FontItem &fi);
|
|
|
|
static FontItem get_font_item(const wxFont &font);
|
|
|
|
// load font used by Operating system as default GUI
|
|
static FontItem get_os_font();
|
|
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);
|
|
};
|
|
|
|
class NSVGUtils
|
|
{
|
|
public:
|
|
NSVGUtils() = delete;
|
|
|
|
// inspired by nanosvgrast.h function nsvgRasterize->nsvg__flattenShape
|
|
static void flatten_cubic_bez(Polygon &polygon, float tessTol,
|
|
Vec2f p1, Vec2f p2, Vec2f p3,Vec2f p4, int level);
|
|
// convert svg image to ExPolygons
|
|
static ExPolygons to_ExPolygons(NSVGimage *image, float tessTol = 10., int max_level = 10);
|
|
};
|
|
} // namespace Slic3r
|
|
|
|
using namespace Slic3r;
|
|
using namespace Slic3r::GUI;
|
|
|
|
GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
|
|
: GLGizmoBase(parent, M_ICON_FILENAME, -2)
|
|
, m_font_selected(0)
|
|
, m_text_size(255)
|
|
, m_text(new char[m_text_size])
|
|
, m_volume(nullptr)
|
|
, m_volume_type(ModelVolumeType::MODEL_PART)
|
|
, m_is_initialized(false) // initialize on first opening gizmo
|
|
{
|
|
// TODO: suggest to use https://fontawesome.com/
|
|
// (copy & paste) unicode symbols from web
|
|
}
|
|
|
|
GLGizmoEmboss::~GLGizmoEmboss() {}
|
|
|
|
bool GLGizmoEmboss::on_init()
|
|
{
|
|
//m_grabbers.emplace_back();
|
|
m_shortcut_key = WXK_CONTROL_T;
|
|
return true;
|
|
}
|
|
|
|
std::string GLGizmoEmboss::on_get_name() const
|
|
{
|
|
return (_L("Emboss")).ToUTF8().data();
|
|
}
|
|
|
|
void GLGizmoEmboss::on_render() {}
|
|
void GLGizmoEmboss::on_render_for_picking() {}
|
|
|
|
void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
|
|
{
|
|
check_selection();
|
|
|
|
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_NoCollapse;
|
|
m_imgui->begin(on_get_name(), flag);
|
|
draw_window();
|
|
|
|
m_imgui->end(); //
|
|
}
|
|
|
|
void GLGizmoEmboss::on_set_state()
|
|
{
|
|
// Closing gizmo. e.g. selecting another one
|
|
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
|
|
|
|
// refuse outgoing during text preview
|
|
if (false) {
|
|
GLGizmoBase::m_state = GLGizmoBase::On;
|
|
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
|
notification_manager->push_notification(
|
|
NotificationType::CustomNotification,
|
|
NotificationManager::NotificationLevel::RegularNotificationLevel,
|
|
_u8L("ERROR: Wait until ends or Cancel process."));
|
|
return;
|
|
}
|
|
m_volume = nullptr;
|
|
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
|
|
if (!m_is_initialized) initialize();
|
|
Selection &s = m_parent.get_selection();
|
|
// When add Text on empty plate,
|
|
// Create object with volume
|
|
if (s.is_empty()) {
|
|
set_default_configuration();
|
|
process();
|
|
return;
|
|
}
|
|
|
|
// Try set selected volume
|
|
if (!load_configuration(get_selected_volume())) {
|
|
// No volume with text selected, create new one
|
|
set_default_configuration();
|
|
process();
|
|
}
|
|
|
|
// when open by hyperlink it needs to show up
|
|
m_parent.reload_scene(true);
|
|
}
|
|
}
|
|
|
|
void GLGizmoEmboss::initialize()
|
|
{
|
|
if (m_is_initialized) return;
|
|
m_is_initialized = true;
|
|
|
|
m_gui_cfg.emplace(GuiCfg());
|
|
|
|
m_font_list = {
|
|
{"NotoSans Regular", Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"},
|
|
{"NotoSans CJK", Slic3r::resources_dir() + "/fonts/NotoSansCJK-Regular.ttc"}};
|
|
m_font_selected = 0;
|
|
|
|
bool is_font_loaded = load_font();
|
|
FontList fl = Emboss::get_font_list();
|
|
m_font_list.insert(m_font_list.end(), fl.begin(), fl.end());
|
|
m_font_list.emplace_back(WxFontUtils::get_os_font());
|
|
while (!is_font_loaded && !m_font_list.empty()) {
|
|
// can't load so erase it from list
|
|
m_font_list.erase(m_font_list.begin() + m_font_selected);
|
|
m_font_selected = 0; // select first
|
|
is_font_loaded = load_font();
|
|
}
|
|
sort_fonts();
|
|
set_default_configuration();
|
|
}
|
|
|
|
// IMPROVE: Do not use gizmo_event - especialy smth with prefix SLA,
|
|
// use Bind into wxGLCanvas?
|
|
bool GLGizmoEmboss::gizmo_event(SLAGizmoEventType action,
|
|
const Vec2d & mouse_position,
|
|
bool shift_down,
|
|
bool alt_down,
|
|
bool control_down)
|
|
{
|
|
/* if (action == SLAGizmoEventType::LeftUp) {
|
|
const Camera & camera = wxGetApp().plater()->get_camera();
|
|
const Selection & selection = m_parent.get_selection();
|
|
const ModelObject * mo = m_c->selection_info()->model_object();
|
|
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
|
|
const Transform3d & instance_trafo = mi->get_transformation()
|
|
.get_matrix();
|
|
|
|
// Precalculate transformations of individual meshes.
|
|
std::vector<Transform3d> trafo_matrices;
|
|
for (const ModelVolume *mv : mo->volumes)
|
|
if (mv->is_model_part())
|
|
trafo_matrices.emplace_back(instance_trafo * mv->get_matrix());
|
|
|
|
Vec3f normal = Vec3f::Zero();
|
|
Vec3f hit = Vec3f::Zero();
|
|
size_t facet = 0;
|
|
Vec3f closest_hit = Vec3f::Zero();
|
|
double closest_hit_squared_distance =
|
|
std::numeric_limits<double>::max();
|
|
size_t closest_facet = 0;
|
|
int closest_hit_mesh_id = -1;
|
|
|
|
// Cast a ray on all meshes, pick the closest hit and save it for the
|
|
// respective mesh
|
|
for (int mesh_id = 0; mesh_id < int(trafo_matrices.size());
|
|
++mesh_id) {
|
|
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
|
|
mouse_position, trafo_matrices[mesh_id], camera, hit,
|
|
normal, m_c->object_clipper()->get_clipping_plane(),
|
|
&facet)) {
|
|
|
|
// In case this hit is clipped, skip it.
|
|
|
|
// Is this hit the closest to the camera so far?
|
|
double hit_squared_distance = (camera.get_position() -
|
|
trafo_matrices[mesh_id] *
|
|
hit.cast<double>())
|
|
.squaredNorm();
|
|
if (hit_squared_distance < closest_hit_squared_distance) {
|
|
closest_hit_squared_distance = hit_squared_distance;
|
|
closest_facet = facet;
|
|
closest_hit_mesh_id = mesh_id;
|
|
closest_hit = hit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// get intersection
|
|
|
|
}
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
void GLGizmoEmboss::set_default_configuration() {
|
|
set_text(_u8L("Embossed text"));
|
|
m_font_prop = FontProp();
|
|
m_volume_type = ModelVolumeType::MODEL_PART;
|
|
// may be set default font?
|
|
}
|
|
|
|
#include "imgui/imgui_internal.h" // to unfocus input --> ClearActiveID
|
|
void GLGizmoEmboss::check_selection()
|
|
{
|
|
ModelVolume* vol = get_selected_volume();
|
|
// is same volume selected?
|
|
if (vol!= nullptr && m_volume == vol) return;
|
|
|
|
// Do not use focused input value when switch volume(it must swith value)
|
|
if (m_volume != nullptr)
|
|
ImGui::ClearActiveID();
|
|
|
|
// is select embossed volume?
|
|
if (load_configuration(vol)) {
|
|
// successfull load volume for editing
|
|
return;
|
|
}
|
|
|
|
// behave like adding new text
|
|
m_volume = nullptr;
|
|
set_default_configuration();
|
|
}
|
|
|
|
ModelVolume *GLGizmoEmboss::get_selected_volume()
|
|
{
|
|
return get_selected_volume(m_parent.get_selection(),
|
|
wxGetApp().plater()->model().objects);
|
|
}
|
|
|
|
ModelVolume *GLGizmoEmboss::get_selected_volume(const Selection &selection,
|
|
const ModelObjectPtrs objects)
|
|
{
|
|
int object_idx = selection.get_object_idx();
|
|
// is more object selected?
|
|
if (object_idx == -1) return nullptr;
|
|
|
|
auto volume_idxs = selection.get_volume_idxs();
|
|
// is more volumes selected?
|
|
if (volume_idxs.size() != 1) return nullptr;
|
|
unsigned int vol_id_gl = *volume_idxs.begin();
|
|
const GLVolume * vol_gl = selection.get_volume(vol_id_gl);
|
|
const GLVolume::CompositeID &id = vol_gl->composite_id;
|
|
|
|
if (id.object_id >= objects.size()) return nullptr;
|
|
ModelObject *object = objects[id.object_id];
|
|
|
|
if (id.volume_id >= object->volumes.size()) return nullptr;
|
|
return object->volumes[id.volume_id];
|
|
}
|
|
|
|
// create_text_volume()
|
|
bool GLGizmoEmboss::process() {
|
|
if (!m_font.has_value()) return false;
|
|
|
|
Polygons polygons = Emboss::text2polygons(*m_font, m_text.get(), m_font_prop);
|
|
if (polygons.empty()) return false;
|
|
|
|
float scale = m_font_prop.size_in_mm / m_font->ascent;
|
|
auto project = std::make_unique<Emboss::ProjectScale>(
|
|
std::make_unique<Emboss::ProjectZ>(m_font_prop.emboss / scale), scale);
|
|
indexed_triangle_set its = Emboss::polygons2model(polygons, *project);
|
|
if (its.indices.empty()) return false;
|
|
|
|
// add object
|
|
TriangleMesh tm(std::move(its));
|
|
tm.repair();
|
|
|
|
//tm.WriteOBJFile("text_preview.obj");
|
|
|
|
GUI_App &app = wxGetApp();
|
|
Plater * plater = app.plater();
|
|
std::string volume_name = create_volume_name();
|
|
plater->take_snapshot(_L("Add") + " " + volume_name);
|
|
if (m_volume == nullptr) {
|
|
const Selection &selection = m_parent.get_selection();
|
|
if (selection.is_empty()) {
|
|
// create new object
|
|
app.obj_list()->load_mesh_object(tm, volume_name);
|
|
app.mainframe->update_title();
|
|
// get new created volume
|
|
m_volume = app.obj_list()->objects()->back()->volumes.front();
|
|
m_volume->text_configuration = create_configuration();
|
|
|
|
// load mesh cause close gizmo, soo I open it again
|
|
m_parent.get_gizmos_manager().open_gizmo(GLGizmosManager::EType::Emboss);
|
|
return true;
|
|
} else {
|
|
Model &model = plater->model();
|
|
int object_idx = selection.get_object_idx();
|
|
ModelObject *obj = model.objects[object_idx];
|
|
m_volume = obj->add_volume(std::move(tm), m_volume_type);
|
|
}
|
|
} else {
|
|
m_volume->set_mesh(std::move(tm));
|
|
m_volume->set_new_unique_id();
|
|
m_volume->translate(-m_volume->source.mesh_offset);
|
|
|
|
m_volume->center_geometry_after_creation(true);
|
|
m_volume->calculate_convex_hull();
|
|
m_volume->get_object()->invalidate_bounding_box();
|
|
}
|
|
m_volume->name = volume_name;
|
|
|
|
// set a default extruder value, since user can't add it manually
|
|
m_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
|
m_volume->text_configuration = create_configuration();
|
|
|
|
// select new added volume
|
|
ModelObject *mo = m_volume->get_object();
|
|
// Editing object volume change its name
|
|
if (mo->volumes.size() == 1) mo->name = volume_name;
|
|
ObjectList *obj_list = app.obj_list();
|
|
const ModelObjectPtrs &objs = *obj_list->objects();
|
|
auto item = find(objs.begin(), objs.end(), mo);
|
|
assert(item != objs.end());
|
|
int object_idx = item - objs.begin();
|
|
ModelVolume *new_volume = m_volume; // copy pointer for lambda
|
|
obj_list->select_item([new_volume, object_idx, obj_list]() {
|
|
wxDataViewItemArray items = obj_list->reorder_volumes_and_get_selection(
|
|
object_idx, [new_volume](const ModelVolume *volume) { return volume == new_volume; });
|
|
if (items.IsEmpty()) return wxDataViewItem();
|
|
return items.front();
|
|
});
|
|
|
|
if (m_volume->type() == ModelVolumeType::MODEL_PART)
|
|
// update printable state on canvas
|
|
m_parent.update_instance_printable_state_for_object((size_t) object_idx);
|
|
|
|
obj_list->selection_changed();
|
|
m_parent.reload_scene(true);
|
|
return true;
|
|
}
|
|
|
|
void GLGizmoEmboss::close() {
|
|
// close gizmo == open it again
|
|
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
|
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Emboss);
|
|
}
|
|
|
|
void GLGizmoEmboss::draw_add_button() {
|
|
if (ImGui::Button(_u8L("Add").c_str())) {
|
|
wxArrayString input_files;
|
|
wxString fontDir = wxEmptyString;
|
|
wxString selectedFile = wxEmptyString;
|
|
wxFileDialog dialog(
|
|
nullptr, _L("Choose one or more files (TTF, TTC):"),
|
|
fontDir, selectedFile,
|
|
file_wildcards(FT_FONTS),
|
|
wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
|
if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files);
|
|
if (input_files.IsEmpty()) return;
|
|
|
|
FontList font_list;
|
|
font_list.reserve(input_files.size());
|
|
for (auto &input_file : input_files) {
|
|
std::string path = std::string(input_file.c_str());
|
|
size_t pos = path.find_last_of('\\');
|
|
size_t pos2 = path.find_last_of('.');
|
|
std::string name = path.substr(pos + 1, pos2 - pos-1);
|
|
font_list.emplace_back(name, path);
|
|
}
|
|
// set last added font as active
|
|
m_font_selected = m_font_list.size() + font_list.size() - 1;
|
|
add_fonts(font_list);
|
|
load_font();
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::BeginTooltip();
|
|
ImGui::Text("add file with font(.ttf, .ttc)");
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
|
|
void GLGizmoEmboss::draw_window()
|
|
{
|
|
if (!m_font.has_value()) {
|
|
ImGui::Text("Warning: No font is selected. Select correct one.");
|
|
}
|
|
|
|
draw_font_list();
|
|
|
|
if (ImGui::Button(_L("choose font").c_str())) { choose_font_by_dialog(); }
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_L("use system font").c_str())) {
|
|
wxSystemSettings ss;
|
|
wxFont f = ss.GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|
size_t font_index = m_font_list.size();
|
|
FontItem fi = WxFontUtils::get_font_item(f);
|
|
m_font_list.emplace_back(fi);
|
|
bool loaded = load_font(font_index);
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
draw_add_button();
|
|
|
|
if (ImGui::Button("add svg")) {
|
|
std::string filePath =
|
|
"C:/Users/filip/Downloads/fontawesome-free-5.15.4-web/"
|
|
"fontawesome-free-5.15.4-web/svgs/solid/bicycle.svg";
|
|
filePath = "C:/Users/filip/Downloads/circle.svg";
|
|
NSVGimage *image = nsvgParseFromFile(filePath.c_str(), "mm", 96.0f);
|
|
ExPolygons polys = NSVGUtils::to_ExPolygons(image);
|
|
|
|
for (auto &poly : polys) poly.scale(1e5);
|
|
SVG svg("converted.svg", BoundingBox(polys.front().contour.points));
|
|
svg.draw(polys);
|
|
|
|
nsvgDelete(image);
|
|
}
|
|
|
|
if (ImGui::InputFloat("Size[in mm]", &m_font_prop.size_in_mm)) {
|
|
if (m_font_prop.size_in_mm < 0.1) m_font_prop.size_in_mm = 10;
|
|
process();
|
|
}
|
|
if (ImGui::InputFloat("Emboss[in mm]", &m_font_prop.emboss)) process();
|
|
if (ImGui::InputFloat("Flatness", &m_font_prop.flatness)) {
|
|
if (m_font.has_value()) m_font->cache.clear();
|
|
process();
|
|
}
|
|
if (ImGui::InputInt("CharGap[in font points]", &m_font_prop.char_gap))
|
|
process();
|
|
if (ImGui::InputInt("LineGap[in font points]", &m_font_prop.line_gap))
|
|
process();
|
|
|
|
// ImGui::InputFloat3("Origin", m_orientation.origin.data());
|
|
// if (ImGui::InputFloat3("Normal", m_normal.data())) m_normal.normalize();
|
|
// if (ImGui::InputFloat3("Up", m_up.data())) m_up.normalize();
|
|
|
|
ImVec2 input_size(-FLT_MIN, ImGui::GetTextLineHeight() * 6);
|
|
ImGuiInputTextFlags flags =
|
|
ImGuiInputTextFlags_::ImGuiInputTextFlags_AllowTabInput |
|
|
ImGuiInputTextFlags_::ImGuiInputTextFlags_AutoSelectAll
|
|
//| ImGuiInputTextFlags_::ImGuiInputTextFlags_CallbackResize
|
|
//|ImGuiInputTextFlags_::ImGuiInputTextFlags_CtrlEnterForNewLine
|
|
;
|
|
|
|
if (ImGui::InputTextMultiline("##Text", m_text.get(), m_text_size,
|
|
input_size, flags)) {
|
|
process();
|
|
}
|
|
|
|
// change text size
|
|
int max_text_size = static_cast<int>(m_text_size);
|
|
if (ImGui::InputInt("max text size", &max_text_size, 8, 64)) {
|
|
set_max_text_size(static_cast<size_t>(max_text_size));
|
|
}
|
|
|
|
// Option to create text volume when reselect volumes
|
|
m_imgui->disabled_begin(!m_font.has_value());
|
|
if (m_volume == nullptr) {
|
|
if (ImGui::Button("Generate preview")) process();
|
|
}
|
|
m_imgui->disabled_end();
|
|
}
|
|
|
|
void GLGizmoEmboss::draw_font_list()
|
|
{
|
|
auto ¤t = m_font_list[m_font_selected];
|
|
if (ImGui::BeginCombo("##font_selector", current.name.c_str())) {
|
|
for (const FontItem &f : m_font_list) {
|
|
ImGui::PushID((void *) &f.name);
|
|
std::string name =
|
|
(f.name.size() < m_gui_cfg->max_font_name) ?
|
|
f.name :
|
|
(f.name.substr(0, m_gui_cfg->max_font_name - 3) + " ..");
|
|
if (ImGui::Selectable(name.c_str(), &f == ¤t)) {
|
|
size_t prev_font_selected = m_font_selected;
|
|
m_font_selected = &f - &m_font_list.front();
|
|
if (!load_font()) {
|
|
m_font_selected = prev_font_selected;
|
|
} else {
|
|
process();
|
|
}
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::BeginTooltip();
|
|
ImGui::Text((f.name + " " + f.path).c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
if (m_font.has_value() && m_font->count > 1) {
|
|
ImGui::SameLine();
|
|
if (ImGui::BeginCombo("##font_collection_selector",
|
|
std::to_string(m_font->index).c_str())) {
|
|
for (size_t i = 0; i < m_font->count; ++i) {
|
|
ImGui::PushID(1 << 10 + i);
|
|
if (ImGui::Selectable(std::to_string(i).c_str(),
|
|
i == m_font->index)) {
|
|
m_font->index = i;
|
|
m_font->cache.clear();
|
|
process();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GLGizmoEmboss::load_font(size_t font_index)
|
|
{
|
|
std::swap(font_index, m_font_selected);
|
|
bool is_loaded = load_font();
|
|
if (!is_loaded) std::swap(font_index, m_font_selected);
|
|
return is_loaded;
|
|
}
|
|
|
|
bool GLGizmoEmboss::load_font() {
|
|
if (m_font_selected >= m_font_list.size()) return false;
|
|
auto font = WxFontUtils::load_font(m_font_list[m_font_selected]);
|
|
if (!font.has_value()) return false;
|
|
m_font = font;
|
|
return true;
|
|
}
|
|
|
|
void GLGizmoEmboss::set_text(const std::string &text) {
|
|
if (text.size() > m_text_size-1)
|
|
set_max_text_size(text.size() + 1);
|
|
|
|
int index = 0;
|
|
for (const char &c : text) m_text[index++] = c;
|
|
m_text[index] = '\0';
|
|
}
|
|
|
|
void GLGizmoEmboss::set_max_text_size(size_t size) {
|
|
if (size < 4) size = 4;
|
|
std::unique_ptr<char[]> newData(new char[size]);
|
|
size_t index = 0;
|
|
while ((index + 1) < size) {
|
|
if (m_text.get()[index] == '\0') break;
|
|
newData.get()[index] = m_text.get()[index];
|
|
++index;
|
|
}
|
|
newData.get()[index] = '\0';
|
|
m_text = std::move(newData);
|
|
m_text_size = size;
|
|
}
|
|
|
|
bool GLGizmoEmboss::choose_font_by_dialog() {
|
|
// keep last selected font did not work
|
|
// static wxFontData data;
|
|
// wxFontDialog font_dialog((wxWindow *) wxGetApp().mainframe, data);
|
|
|
|
wxFontDialog font_dialog(nullptr);
|
|
font_dialog.SetTitle(_L("Select font for Emboss"));
|
|
if (font_dialog.ShowModal() != wxID_OK) return false;
|
|
wxFontData data = font_dialog.GetFontData();
|
|
wxFont font = data.GetChosenFont();
|
|
size_t font_index = m_font_list.size();
|
|
m_font_list.emplace_back(WxFontUtils::get_font_item(font));
|
|
if (!load_font(font_index)) {
|
|
m_font_list.pop_back();
|
|
return false;
|
|
}
|
|
sort_fonts();
|
|
process();
|
|
return true;
|
|
}
|
|
|
|
void GLGizmoEmboss::sort_fonts() {
|
|
// initialize original index locations
|
|
std::vector<size_t> idx(m_font_list.size());
|
|
std::iota(idx.begin(), idx.end(), 0);
|
|
|
|
std::stable_sort(idx.begin(), idx.end(),
|
|
[this](size_t i1, size_t i2) {
|
|
return m_font_list[i1].name < m_font_list[i2].name;
|
|
});
|
|
|
|
FontList font_list;
|
|
font_list.reserve(m_font_list.size());
|
|
size_t selected = 0;
|
|
for (const size_t &i : idx) {
|
|
if (i == m_font_selected) selected = &i - &idx.front();
|
|
font_list.emplace_back(m_font_list[i]);
|
|
}
|
|
m_font_list = font_list;
|
|
m_font_selected = selected;
|
|
}
|
|
|
|
void GLGizmoEmboss::add_fonts(const FontList &font_list) {
|
|
m_font_list.insert(m_font_list.end(), font_list.begin(), font_list.end());
|
|
sort_fonts();
|
|
}
|
|
|
|
TextConfiguration GLGizmoEmboss::create_configuration() {
|
|
std::string text((const char *) m_text.get());
|
|
return TextConfiguration(m_font_list[m_font_selected], m_font_prop, text);
|
|
}
|
|
|
|
bool GLGizmoEmboss::load_configuration(ModelVolume *volume)
|
|
{
|
|
if (volume == nullptr) return false;
|
|
if (!volume->text_configuration.has_value()) return false;
|
|
const TextConfiguration &configuration = *volume->text_configuration;
|
|
size_t index = m_font_list.size();
|
|
for (const auto &font_item : m_font_list) {
|
|
if (font_item.type == configuration.font_item.type &&
|
|
font_item.path == configuration.font_item.path) {
|
|
index = &font_item - &m_font_list.front();
|
|
}
|
|
}
|
|
size_t prev_font_selected = m_font_selected;
|
|
// when not in font list add to list
|
|
if (index >= m_font_list.size()) {
|
|
m_font_selected = m_font_list.size();
|
|
add_fonts({configuration.font_item});
|
|
} else {
|
|
m_font_selected = index;
|
|
}
|
|
// When can't load font
|
|
if (!load_font()) {
|
|
// remove bad loadabled font, for correct prev index
|
|
m_font_list.erase(m_font_list.begin() + m_font_selected);
|
|
m_font_selected = prev_font_selected;
|
|
return false;
|
|
}
|
|
|
|
m_font_prop = configuration.font_prop;
|
|
set_text(configuration.text);
|
|
m_volume_type = volume->type(); // not neccesary
|
|
m_volume = volume;
|
|
return true;
|
|
}
|
|
|
|
std::string GLGizmoEmboss::create_volume_name()
|
|
{
|
|
size_t max_len = 20;
|
|
std::string text((const char *)m_text.get());
|
|
if (text.size() > max_len)
|
|
text = text.substr(0, max_len - 3) + " ..";
|
|
return _u8L("Text") + " - " + text;
|
|
}
|
|
|
|
std::optional<Emboss::Font> WxFontUtils::load_font(const FontItem &fi)
|
|
{
|
|
switch (fi.type) {
|
|
case FontItem::Type::file_path:
|
|
return Emboss::load_font(fi.path.c_str());
|
|
case FontItem::Type::wx_font_descr:
|
|
return WxFontUtils::load_font(WxFontUtils::load_wxFont(fi.path));
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::optional<Emboss::Font> WxFontUtils::load_font(const wxFont &font)
|
|
{
|
|
if (!font.IsOk()) return {};
|
|
#ifdef _WIN32
|
|
return Slic3r::Emboss::load_font(font.GetHFONT());
|
|
#elif __linux__
|
|
// use file path
|
|
return {};
|
|
#elif __APPLE__
|
|
const wxNativeFontInfo *info = font.GetNativeFontInfo();
|
|
CTFontDescriptorRef descriptor = info3->GetCTFontDescriptor();
|
|
CFDictionaryRef attribs = CTFontDescriptorCopyAttributes(descriptor);
|
|
CFStringRef url = (CFStringRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute);
|
|
std::string str(CFStringGetCStringPtr(CFURLGetString(anUrl),kCFStringEncodingUTF8));
|
|
return Emboss::load_font(str);
|
|
#endif
|
|
}
|
|
|
|
FontItem WxFontUtils::get_font_item(const wxFont &font)
|
|
{
|
|
std::string name = get_human_readable_name(font);
|
|
std::string fontDesc = store_wxFont(font);
|
|
return FontItem(name, fontDesc, FontItem::Type::wx_font_descr);
|
|
}
|
|
|
|
FontItem WxFontUtils::get_os_font()
|
|
{
|
|
wxSystemFont system_font = wxSYS_DEFAULT_GUI_FONT;
|
|
wxFont font = wxSystemSettings::GetFont(system_font);
|
|
FontItem fi = get_font_item(font);
|
|
fi.name += +" (" + _u8L("OS default") + ")";
|
|
return get_font_item(font);
|
|
}
|
|
|
|
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();
|
|
return std::string(font_descriptor.c_str());
|
|
}
|
|
|
|
wxFont WxFontUtils::load_wxFont(const std::string &font_descriptor)
|
|
{
|
|
wxString font_descriptor_wx(font_descriptor);
|
|
return wxFont(font_descriptor_wx);
|
|
}
|
|
|
|
void NSVGUtils::flatten_cubic_bez(Polygon &polygon,
|
|
float tessTol,
|
|
Vec2f p1,
|
|
Vec2f p2,
|
|
Vec2f p3,
|
|
Vec2f p4,
|
|
int level)
|
|
{
|
|
Vec2f p12 = (p1 + p2) * 0.5f;
|
|
Vec2f p23 = (p2 + p3) * 0.5f;
|
|
Vec2f p34 = (p3 + p4) * 0.5f;
|
|
Vec2f p123 = (p12 + p23) * 0.5f;
|
|
|
|
Vec2f pd = p4 - p1;
|
|
Vec2f pd2 = p2 - p4;
|
|
float d2 = std::abs(pd2.x() * pd.y() - pd2.y() * pd.x());
|
|
Vec2f pd3 = p3 - p4;
|
|
float d3 = std::abs(pd3.x() * pd.y() - pd3.y() * pd.x());
|
|
float d23 = d2 + d3;
|
|
|
|
if ((d23 * d23) < tessTol * (pd.x() * pd.x() + pd.y() * pd.y())) {
|
|
polygon.points.emplace_back(p4.x(), p4.y());
|
|
return;
|
|
}
|
|
|
|
--level;
|
|
if (level == 0) return;
|
|
Vec2f p234 = (p23 + p34) * 0.5f;
|
|
Vec2f p1234 = (p123 + p234) * 0.5f;
|
|
flatten_cubic_bez(polygon, tessTol, p1, p12, p123, p1234, level);
|
|
flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level);
|
|
}
|
|
|
|
ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image,
|
|
float tessTol,
|
|
int max_level)
|
|
{
|
|
Polygons polygons;
|
|
for (NSVGshape *shape = image->shapes; shape != NULL;
|
|
shape = shape->next) {
|
|
if (!(shape->flags & NSVG_FLAGS_VISIBLE)) continue;
|
|
Slic3r::Polygon polygon;
|
|
if (shape->fill.type != NSVG_PAINT_NONE) {
|
|
for (NSVGpath *path = shape->paths; path != NULL;
|
|
path = path->next) {
|
|
// Flatten path
|
|
polygon.points.emplace_back(path->pts[0], path->pts[1]);
|
|
for (size_t i = 0; i < path->npts - 1; i += 3) {
|
|
float *p = &path->pts[i * 2];
|
|
Vec2f
|
|
p1(p[0], p[1]),
|
|
p2(p[2], p[3]),
|
|
p3(p[4], p[5]),
|
|
p4(p[6], p[7]);
|
|
flatten_cubic_bez(polygon, tessTol, p1, p2, p3, p4, max_level);
|
|
}
|
|
if (path->closed) {
|
|
polygons.push_back(polygon);
|
|
polygon = Slic3r::Polygon();
|
|
}
|
|
}
|
|
}
|
|
polygons.push_back(polygon);
|
|
}
|
|
return union_ex(polygons);
|
|
}
|
|
|
|
// any existing icon filename to not influence GUI
|
|
const std::string GLGizmoEmboss::M_ICON_FILENAME = "cut.svg";
|