diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index a3355a6883..dcc57db07a 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -470,11 +470,20 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty auto add_text = [type](wxCommandEvent &) { GLGizmosManager &mng = plater()->canvas3D()->get_gizmos_manager(); if (mng.open_gizmo(GLGizmosManager::Emboss)) { - GLGizmoEmboss *emboss = dynamic_cast(mng.get_current()); + GLGizmoBase * base = mng.get_current(); + GLGizmoEmboss *emboss = dynamic_cast(base); + if (emboss == nullptr) { + int j = 42; + } else emboss->set_volume_type(type); } }; - append_menu_item(sub_menu, wxID_ANY, _L("Text"), "", add_text, "", menu); + + if (type == ModelVolumeType::MODEL_PART + || type == ModelVolumeType::NEGATIVE_VOLUME + || type == ModelVolumeType::INVALID // cannot use gizmo without selected object + ) + append_menu_item(sub_menu, wxID_ANY, _L("Text"), "", add_text, "", menu); if (wxGetApp().get_mode() >= comAdvanced) { sub_menu->AppendSeparator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 25931c5eb6..dff9d2f023 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -3,8 +3,11 @@ #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" @@ -35,6 +38,18 @@ public: 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; @@ -42,31 +57,15 @@ using namespace Slic3r::GUI; GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -2) - , 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) , 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 - - 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(); + // (copy & paste) unicode symbols from web } GLGizmoEmboss::~GLGizmoEmboss() {} @@ -86,90 +85,8 @@ 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 | @@ -204,7 +121,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) "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); + ExPolygons polys = NSVGUtils::to_ExPolygons(image); for (auto &poly : polys) poly.scale(1e5); SVG svg("converted.svg", BoundingBox(polys.front().contour.points)); @@ -266,11 +183,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(); -} - void GLGizmoEmboss::on_set_state() { // Closing gizmo. e.g. selecting another one @@ -288,6 +200,16 @@ void GLGizmoEmboss::on_set_state() } m_volume = nullptr; } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + if (!m_is_initialized) initialize(); + Selection &s = m_parent.get_selection(); + if (s.is_empty()) { + // When add Text on empty plate, + // Create object with volume + set_default_configuration(); + process(); + return; + } + if(!set_volume()) set_default_configuration(); // when open by hyperlink it needs to show up @@ -295,6 +217,32 @@ void GLGizmoEmboss::on_set_state() } } +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, @@ -370,8 +318,9 @@ void GLGizmoEmboss::check_selection() // is same volume selected? if (vol!= nullptr && m_volume == vol) return; - // Do not use actual edited value when switch volume - ImGui::SetKeyboardFocusHere(0); + if (m_volume != nullptr) + // Do not use actual edited value when switch volume + ImGui::SetKeyboardFocusHere(0); // is selected volume embossed? if (vol!= nullptr && vol->text_configuration.has_value()) { @@ -413,35 +362,47 @@ ModelVolume *GLGizmoEmboss::get_selected_volume(const Selection &selection, } // create_text_volume() -void GLGizmoEmboss::process() { - if (!m_font.has_value()) return; +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; + if (polygons.empty()) return false; float scale = m_font_prop.size_in_mm / m_font->ascent; auto project = std::make_unique( std::make_unique(m_font_prop.emboss / scale), scale); indexed_triangle_set its = Emboss::polygons2model(polygons, *project); - if (its.indices.empty()) return; + if (its.indices.empty()) return false; // add object TriangleMesh tm(std::move(its)); tm.repair(); //tm.WriteOBJFile("text_preview.obj"); - - const Selection &selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - GUI_App & app = wxGetApp(); - Plater * plater = app.plater(); - ModelObject * obj = plater->model().objects[object_idx]; - + + GUI_App &app = wxGetApp(); + Plater * plater = app.plater(); std::string volume_name = create_volume_name(); - plater->take_snapshot(_L("Add") + " " + volume_name); - + plater->take_snapshot(_L("Add") + " " + volume_name); if (m_volume == nullptr) { - m_volume = obj->add_volume(std::move(tm), m_volume_type); + 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(); @@ -458,8 +419,14 @@ void GLGizmoEmboss::process() { m_volume->text_configuration = create_configuration(); // select new added volume - ModelVolume *new_volume = m_volume; + ModelObject *mo = m_volume->get_object(); + 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; }); @@ -473,6 +440,7 @@ void GLGizmoEmboss::process() { obj_list->selection_changed(); m_parent.reload_scene(true); + return true; } void GLGizmoEmboss::close() { @@ -622,79 +590,6 @@ bool GLGizmoEmboss::choose_font_by_dialog() { return true; } -std::optional 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 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() -{ - wxSystemSettings ss; - wxFont ss_font = ss.GetFont(wxSYS_ANSI_VAR_FONT); - FontItem fi = get_font_item(ss_font); - fi.name += +" (" + _u8L("OS default") + ")"; - return get_font_item(ss_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 GLGizmoEmboss::sort_fonts() { // initialize original index locations std::vector idx(m_font_list.size()); @@ -780,5 +675,145 @@ std::string GLGizmoEmboss::create_volume_name() return _u8L("Text") + " - " + text; } +std::optional 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 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.cast()); + 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"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index fbdd7197f8..ddc7b82906 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -32,17 +32,19 @@ protected: virtual void on_render() override; virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; - virtual bool on_is_activable() const override; + virtual bool on_is_activable() const override { return true; } virtual bool on_is_selectable() const override { return false; } virtual void on_set_state() override; private: + void initialize(); void set_default_configuration(); void check_selection(); // more general function --> move to select ModelVolume *get_selected_volume(); static ModelVolume *get_selected_volume(const Selection &selection, const ModelObjectPtrs objects); - void process(); + // create volume from text - main functionality + bool process(); void close(); void draw_font_list(); void draw_add_button(); @@ -99,6 +101,8 @@ private: ModelVolume *m_volume; ModelVolumeType m_volume_type; + bool m_is_initialized; + // only temporary solution static const std::string M_ICON_FILENAME; };