Add editing of text volume

This commit is contained in:
Filip Sykala 2021-09-15 15:14:04 +02:00
parent 8226f74413
commit 8add695de9
7 changed files with 440 additions and 131 deletions
src/slic3r/GUI/Gizmos

View file

@ -8,6 +8,8 @@
#include "libslic3r/Model.hpp"
#include "nanosvg/nanosvg.h" // load SVG file
#include <wx/font.h>
#include <wx/fontdlg.h>
@ -21,13 +23,12 @@ public:
// 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 Emboss::FontItem &fi);
static std::optional<Slic3r::Emboss::Font> load_font(const FontItem &fi);
static Slic3r::Emboss::FontItem get_font_item(const wxFont &font);
static FontItem get_font_item(const wxFont &font);
// load font used by Operating system as default GUI
static Slic3r::Emboss::FontItem get_os_font();
static FontItem get_os_font();
static std::string get_human_readable_name(const wxFont &font);
// serialize / deserialize font
@ -52,23 +53,20 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent)
{
// TODO: suggest to use https://fontawesome.com/
// (copy & paste) unicode symbols from web
bool is_font_loaded = load_font();
add_fonts(Emboss::get_font_list());
add_fonts({WxFontUtils::get_os_font()});
if (!is_font_loaded) {
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
do{
is_font_loaded = load_font();
if (!is_font_loaded) m_font_list.erase(m_font_list.begin());
} while (!is_font_loaded && !m_font_list.empty());
m_font_selected = 0; // select first
is_font_loaded = load_font();
}
int index = 0;
for (char &c : _u8L("Embossed text")) { m_text[index++] = c; }
m_text[index] = '\0';
sort_fonts();
set_default_configuration();
}
GLGizmoEmboss::~GLGizmoEmboss() {}
@ -88,50 +86,156 @@ std::string GLGizmoEmboss::on_get_name() const
void GLGizmoEmboss::on_render() {}
void GLGizmoEmboss::on_render_for_picking() {}
// took from nanosvgrast.h function nsvgRasterize->nsvg__flattenShape
void flatten_cubic_bez(Slic3r::Polygon &polygon,
float tessTol,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3,
float x4,
float y4,
int level)
{
float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234,
y1234;
float dx, dy, d2, d3;
if (level == 0) return;
x12 = (x1 + x2) * 0.5f;
y12 = (y1 + y2) * 0.5f;
x23 = (x2 + x3) * 0.5f;
y23 = (y2 + y3) * 0.5f;
x34 = (x3 + x4) * 0.5f;
y34 = (y3 + y4) * 0.5f;
x123 = (x12 + x23) * 0.5f;
y123 = (y12 + y23) * 0.5f;
dx = x4 - x1;
dy = y4 - y1;
d2 = std::abs(((x2 - x4) * dy - (y2 - y4) * dx));
d3 = std::abs(((x3 - x4) * dy - (y3 - y4) * dx));
if ((d2 + d3) * (d2 + d3) < tessTol * (dx * dx + dy * dy)) {
polygon.points.emplace_back(x4, y4);
return;
}
--level;
if (level == 0) return;
x234 = (x23 + x34) * 0.5f;
y234 = (y23 + y34) * 0.5f;
x1234 = (x123 + x234) * 0.5f;
y1234 = (y123 + y234) * 0.5f;
flatten_cubic_bez(polygon, tessTol, x1, y1, x12, y12, x123, y123, x1234, y1234, level);
flatten_cubic_bez(polygon, tessTol, x1234, y1234, x234, y234, x34, y34, x4, y4, level);
}
Slic3r::ExPolygons to_ExPolygons(NSVGimage *image,
float tessTol = 10.,
int max_level = 10)
{
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];
flatten_cubic_bez(polygon, tessTol, p[0], p[1], p[2],
p[3], p[4], p[5], p[6], p[7],
max_level);
}
if (path->closed) {
polygons.push_back(polygon);
polygon = Slic3r::Polygon();
}
}
}
polygons.push_back(polygon);
}
return union_ex(polygons);
}
#include "libslic3r/SVG.hpp"
void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
{
if (!m_gui_cfg.has_value()) m_gui_cfg.emplace(GuiCfg());
check_selection();
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
m_imgui->begin(on_get_name(), flag);
if (!m_font.has_value()) {
ImGui::Text("Warning: No font is selected. Select correct one.");
}
draw_font_list();
static std::string fontName;
if (ImGui::Button(_L("choose font").c_str())) {
static wxFontData data; // keep last selected font
wxFontDialog font_dialog((wxWindow*)wxGetApp().mainframe, data);
font_dialog.SetTitle(_L("Select font for Emboss"));
if (font_dialog.ShowModal() == wxID_OK) {
data = font_dialog.GetFontData();
wxFont font = data.GetChosenFont();
auto fontOpt = WxFontUtils::load_font(font);
if (fontOpt.has_value()) {
Emboss::FontItem fontItem = WxFontUtils::get_font_item(font);
m_font_selected = m_font_list.size();
add_fonts({fontItem});
m_font = fontOpt;
process();
}
}
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);
}
if (!fontName.empty()) ImGui::Text(fontName.c_str());
ImGui::SameLine();
draw_add_button();
ImGui::InputFloat("Size[in mm]", &m_font_prop.size_in_mm);
ImGui::InputFloat("Emboss[in mm]", &m_font_prop.emboss);
if (ImGui::InputFloat("Flatness", &m_font_prop.flatness))
if(m_font.has_value()) m_font->cache.clear();
ImGui::InputInt("CharGap[in font points]", &m_font_prop.char_gap);
ImGui::InputInt("LineGap[in font points]", &m_font_prop.line_gap);
ImGui::InputFloat3("Origin", m_orientation.origin.data());
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";
NSVGimage *image = nsvgParseFromFile(filePath.c_str(), "mm", 96.0f);
ExPolygons polys = 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();
m_imgui->disabled_begin(!m_font.has_value());
if (ImGui::Button("Preview")) process();
// create default text
if (m_volume == nullptr) {
if (ImGui::Button("Generate preview")) process();
}
m_imgui->disabled_end();
ImVec2 input_size(-FLT_MIN, ImGui::GetTextLineHeight() * 6);
ImGuiInputTextFlags flags =
@ -149,17 +253,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
// change text size
int max_text_size = static_cast<int>(m_text_size);
if (ImGui::InputInt("max text size", &max_text_size, 8, 64)) {
if (max_text_size < 4) max_text_size = 4;
std::unique_ptr<char[]> newData(new char[max_text_size]);
size_t index = 0;
while ((index+1) < max_text_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 = max_text_size;
set_max_text_size(static_cast<size_t>(max_text_size));
}
// draw 2d triangle in IMGUI
@ -172,7 +266,6 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->end();
}
bool GLGizmoEmboss::on_is_activable() const
{
return !m_parent.get_selection().is_empty();
@ -195,8 +288,10 @@ void GLGizmoEmboss::on_set_state()
}
m_volume = nullptr;
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
if(!set_volume()) set_default_configuration();
// when open by hyperlink it needs to show up
//request_rerender();
m_parent.reload_scene(true);
}
}
@ -263,6 +358,63 @@ bool GLGizmoEmboss::gizmo_event(SLAGizmoEventType action,
return false;
}
void GLGizmoEmboss::set_default_configuration() {
set_text(_u8L("Embossed text"));
m_font_prop = FontProp();
// may be set default font?
}
void GLGizmoEmboss::check_selection()
{
// is text created?
if (m_volume == nullptr) return;
ModelVolume* vol = get_selected_volume();
// is same volume selected?
if (m_volume == vol) return;
// Do not use actual edited value when switch volume
ImGui::SetKeyboardFocusHere(-1);
// is selected volume embossed?
if (vol!= nullptr && vol->text_configuration.has_value()) {
m_volume = vol;
load_configuration(*vol->text_configuration);
return;
}
// behave like adding new text
m_volume == nullptr;
}
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()
void GLGizmoEmboss::process() {
if (!m_font.has_value()) return;
@ -305,6 +457,7 @@ void GLGizmoEmboss::process() {
// 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
ModelVolume *new_volume = m_volume;
@ -343,7 +496,7 @@ void GLGizmoEmboss::draw_add_button() {
if (dialog.ShowModal() == wxID_OK) dialog.GetPaths(input_files);
if (input_files.IsEmpty()) return;
Emboss::FontList font_list;
FontList font_list;
font_list.reserve(input_files.size());
for (auto &input_file : input_files) {
std::string path = std::string(input_file.c_str());
@ -368,7 +521,7 @@ void GLGizmoEmboss::draw_font_list()
{
auto &current = m_font_list[m_font_selected];
if (ImGui::BeginCombo("##font_selector", current.name.c_str())) {
for (const Emboss::FontItem &f : m_font_list) {
for (const FontItem &f : m_font_list) {
ImGui::PushID((void *) &f.name);
std::string name =
(f.name.size() < m_gui_cfg->max_font_name) ?
@ -411,6 +564,14 @@ void GLGizmoEmboss::draw_font_list()
}
}
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]);
@ -419,18 +580,62 @@ bool GLGizmoEmboss::load_font() {
return true;
}
std::optional<Emboss::Font> WxFontUtils::load_font(const Emboss::FontItem &fi)
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;
}
std::optional<Emboss::Font> WxFontUtils::load_font(const FontItem &fi)
{
switch (fi.type) {
case Emboss::FontItem::Type::file_path:
case FontItem::Type::file_path:
return Emboss::load_font(fi.path.c_str());
case Emboss::FontItem::Type::wx_font_descr:
case FontItem::Type::wx_font_descr:
return WxFontUtils::load_font(WxFontUtils::load_wxFont(fi.path));
}
return {};
}
std::optional<Slic3r::Emboss::Font> WxFontUtils::load_font(const wxFont &font)
std::optional<Emboss::Font> WxFontUtils::load_font(const wxFont &font)
{
if (!font.IsOk()) return {};
#ifdef _WIN32
@ -448,18 +653,18 @@ std::optional<Slic3r::Emboss::Font> WxFontUtils::load_font(const wxFont &font)
#endif
}
Slic3r::Emboss::FontItem WxFontUtils::get_font_item(const wxFont &font)
FontItem WxFontUtils::get_font_item(const wxFont &font)
{
std::string name = get_human_readable_name(font);
std::string fontDesc = store_wxFont(font);
return Emboss::FontItem(name, fontDesc, Emboss::FontItem::Type::wx_font_descr);
return FontItem(name, fontDesc, FontItem::Type::wx_font_descr);
}
Slic3r::Emboss::FontItem WxFontUtils::get_os_font()
FontItem WxFontUtils::get_os_font()
{
wxSystemSettings ss;
wxFont ss_font = ss.GetFont(wxSYS_ANSI_VAR_FONT);
Emboss::FontItem fi = get_font_item(ss_font);
FontItem fi = get_font_item(ss_font);
fi.name += +" (" + _u8L("OS default") + ")";
return get_font_item(ss_font);
}
@ -502,7 +707,7 @@ void GLGizmoEmboss::sort_fonts() {
return m_font_list[i1].name < m_font_list[i2].name;
});
Emboss::FontList font_list;
FontList font_list;
font_list.reserve(m_font_list.size());
size_t selected = 0;
for (const size_t &i : idx) {
@ -513,18 +718,68 @@ void GLGizmoEmboss::sort_fonts() {
m_font_selected = selected;
}
void GLGizmoEmboss::add_fonts(const Emboss::FontList &font_list) {
void GLGizmoEmboss::add_fonts(const FontList &font_list) {
m_font_list.insert(m_font_list.end(), font_list.begin(), font_list.end());
sort_fonts();
}
bool GLGizmoEmboss::set_volume()
{
ModelVolume *vol = get_selected_volume();
// Is selected only one volume
if (vol == nullptr) return false;
// Is volume created by Emboss?
if (!vol->text_configuration.has_value()) return false;
// set selected volume
m_volume = vol;
load_configuration(*vol->text_configuration);
return true;
}
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(const TextConfiguration &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);
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;
return _u8L("Text") + " - " + text;
}
// any existing icon filename to not influence GUI