diff --git a/resources/images/toolbar_text.svg b/resources/images/toolbar_text.svg new file mode 100644 index 000000000..392ae3685 --- /dev/null +++ b/resources/images/toolbar_text.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index da327ec97..2ebdb72e0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -315,7 +315,6 @@ set(lisbslic3r_sources SLA/Clustering.hpp SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp - Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp Arachne/BeadingStrategy/BeadingStrategyFactory.hpp @@ -356,6 +355,8 @@ set(lisbslic3r_sources Arachne/SkeletalTrapezoidationJoint.hpp Arachne/WallToolPaths.hpp Arachne/WallToolPaths.cpp + Shape/TextShape.hpp + Shape/TextShape.cpp ) if (APPLE) diff --git a/src/libslic3r/Shape/TextShape.cpp b/src/libslic3r/Shape/TextShape.cpp new file mode 100644 index 000000000..9185c1b01 --- /dev/null +++ b/src/libslic3r/Shape/TextShape.cpp @@ -0,0 +1,203 @@ +#include "../libslic3r.h" +#include "../Model.hpp" +#include "../TriangleMesh.hpp" + +#include "TextShape.hpp" + +#include +#include + +#include "Standard_TypeDef.hxx" +#include "STEPCAFControl_Reader.hxx" +#include "BRepMesh_IncrementalMesh.hxx" +#include "Interface_Static.hxx" +#include "XCAFDoc_DocumentTool.hxx" +#include "XCAFDoc_ShapeTool.hxx" +#include "XCAFApp_Application.hxx" +#include "TopoDS_Solid.hxx" +#include "TopoDS_Compound.hxx" +#include "TopoDS_Builder.hxx" +#include "TopoDS.hxx" +#include "TDataStd_Name.hxx" +#include "BRepBuilderAPI_Transform.hxx" +#include "TopExp_Explorer.hxx" +#include "TopExp_Explorer.hxx" +#include "BRep_Tool.hxx" +#include "Font_BRepFont.hxx" +#include "Font_BRepTextBuilder.hxx" +#include "BRepPrimAPI_MakePrism.hxx" +#include "Font_FontMgr.hxx" + +namespace Slic3r { + +std::vector init_occt_fonts() +{ + std::vector stdFontNames; + + Handle(Font_FontMgr) aFontMgr = Font_FontMgr::GetInstance(); + aFontMgr->InitFontDataBase(); + + TColStd_SequenceOfHAsciiString availFontNames; + aFontMgr->GetAvailableFontsNames(availFontNames); + stdFontNames.reserve(availFontNames.Size()); + + for (auto afn : availFontNames) + stdFontNames.push_back(afn->ToCString()); + + return stdFontNames; +} + +static bool TextToBRep(const char* text, const char* font, const float theTextHeight, Font_FontAspect& theFontAspect, TopoDS_Shape& theShape) +{ + Standard_Integer anArgIt = 1; + Standard_CString aName = "text_shape"; + Standard_CString aText = text; + + Font_BRepFont aFont; + //TCollection_AsciiString aFontName("Courier"); + TCollection_AsciiString aFontName(font); + Standard_Real aTextHeight = theTextHeight; + Font_FontAspect aFontAspect = theFontAspect; + Standard_Boolean anIsCompositeCurve = Standard_False; + gp_Ax3 aPenAx3(gp::XOY()); + gp_Dir aNormal(0.0, 0.0, 1.0); + gp_Dir aDirection(1.0, 0.0, 0.0); + gp_Pnt aPenLoc; + + Graphic3d_HorizontalTextAlignment aHJustification = Graphic3d_HTA_LEFT; + Graphic3d_VerticalTextAlignment aVJustification = Graphic3d_VTA_BOTTOM; + Font_StrictLevel aStrictLevel = Font_StrictLevel_Any; + + aFont.SetCompositeCurveMode(anIsCompositeCurve); + if (!aFont.FindAndInit(aFontName.ToCString(), aFontAspect, aTextHeight, aStrictLevel)) + return false; + + aPenAx3 = gp_Ax3(aPenLoc, aNormal, aDirection); + + Font_BRepTextBuilder aBuilder; + theShape = aBuilder.Perform(aFont, aText, aPenAx3, aHJustification, aVJustification); + return true; +} + +static bool Prism(const TopoDS_Shape& theBase, const float thickness, TopoDS_Shape& theSolid) +{ + if (theBase.IsNull()) return false; + + gp_Vec V(0.f, 0.f, thickness); + BRepPrimAPI_MakePrism* Prism = new BRepPrimAPI_MakePrism(theBase, V, Standard_False); + + theSolid = Prism->Shape(); + return true; +} + +static void MakeMesh(TopoDS_Shape& theSolid, TriangleMesh& theMesh) +{ + const double STEP_TRANS_CHORD_ERROR = 0.005; + const double STEP_TRANS_ANGLE_RES = 1; + + BRepMesh_IncrementalMesh mesh(theSolid, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true); + int aNbNodes = 0; + int aNbTriangles = 0; + for (TopExp_Explorer anExpSF(theSolid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc); + if (!aTriangulation.IsNull()) { + aNbNodes += aTriangulation->NbNodes(); + aNbTriangles += aTriangulation->NbTriangles(); + } + } + + stl_file stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = (uint32_t)aNbTriangles; + stl.stats.original_num_facets = stl.stats.number_of_facets; + stl_allocate(&stl); + + std::vector points; + points.reserve(aNbNodes); + //BBS: count faces missing triangulation + Standard_Integer aNbFacesNoTri = 0; + //BBS: fill temporary triangulation + Standard_Integer aNodeOffset = 0; + Standard_Integer aTriangleOffet = 0; + for (TopExp_Explorer anExpSF(theSolid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) { + const TopoDS_Shape& aFace = anExpSF.Current(); + TopLoc_Location aLoc; + Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc); + if (aTriangulation.IsNull()) { + ++aNbFacesNoTri; + continue; + } + //BBS: copy nodes + gp_Trsf aTrsf = aLoc.Transformation(); + for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) { + gp_Pnt aPnt = aTriangulation->Node(aNodeIter); + aPnt.Transform(aTrsf); + points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()))); + } + //BBS: copy triangles + const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation(); + for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) { + Poly_Triangle aTri = aTriangulation->Triangle(aTriIter); + + Standard_Integer anId[3]; + aTri.Get(anId[0], anId[1], anId[2]); + if (anOrientation == TopAbs_REVERSED) { + //BBS: swap 1, 2. + Standard_Integer aTmpIdx = anId[1]; + anId[1] = anId[2]; + anId[2] = aTmpIdx; + } + //BBS: Update nodes according to the offset. + anId[0] += aNodeOffset; + anId[1] += aNodeOffset; + anId[2] += aNodeOffset; + //BBS: save triangles facets + stl_facet facet; + facet.vertex[0] = points[anId[0] - 1].cast(); + facet.vertex[1] = points[anId[1] - 1].cast(); + facet.vertex[2] = points[anId[2] - 1].cast(); + facet.extra[0] = 0; + facet.extra[1] = 0; + stl_normal normal; + stl_calculate_normal(normal, &facet); + stl_normalize_vector(normal); + facet.normal = normal; + stl.facet_start[aTriangleOffet + aTriIter - 1] = facet; + } + + aNodeOffset += aTriangulation->NbNodes(); + aTriangleOffet += aTriangulation->NbTriangles(); + } + + theMesh.from_stl(stl); +} + +void load_text_shape(const char*text, const char* font, const float text_height, const float thickness, bool is_bold, bool is_italic, TriangleMesh& text_mesh) +{ + Handle(Font_FontMgr) aFontMgr = Font_FontMgr::GetInstance(); + if (aFontMgr->GetAvailableFonts().IsEmpty()) + aFontMgr->InitFontDataBase(); + + TopoDS_Shape aTextBase; + Font_FontAspect aFontAspect = Font_FontAspect_UNDEFINED; + if (is_bold && is_italic) + aFontAspect = Font_FontAspect_BoldItalic; + else if (is_bold) + aFontAspect = Font_FontAspect_Bold; + else if (is_italic) + aFontAspect = Font_FontAspect_Italic; + else + aFontAspect = Font_FontAspect_Regular; + + if (!TextToBRep(text, font, text_height, aFontAspect, aTextBase)) + return; + + TopoDS_Shape aTextShape; + if (!Prism(aTextBase, thickness, aTextShape)) + return; + + MakeMesh(aTextShape, text_mesh); +} + +}; // namespace Slic3r diff --git a/src/libslic3r/Shape/TextShape.hpp b/src/libslic3r/Shape/TextShape.hpp new file mode 100644 index 000000000..0da84bb95 --- /dev/null +++ b/src/libslic3r/Shape/TextShape.hpp @@ -0,0 +1,12 @@ +#ifndef slic3r_Text_Shape_hpp_ +#define slic3r_Text_Shape_hpp_ + +namespace Slic3r { +class TriangleMesh; + +extern std::vector init_occt_fonts(); +extern void load_text_shape(const char* text, const char* font, const float text_height, const float thickness, bool is_bold, bool is_italic, TriangleMesh& text_mesh); + +}; // namespace Slic3r + +#endif // slic3r_Text_Shape_hpp_ \ No newline at end of file diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ca8f445e3..e310808ff 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -129,6 +129,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoFaceDetector.hpp GUI/Gizmos/GLGizmoSeam.cpp GUI/Gizmos/GLGizmoSeam.hpp + GUI/Gizmos/GLGizmoText.cpp + GUI/Gizmos/GLGizmoText.hpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp GUI/Gizmos/GizmoObjectManipulation.cpp diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b93c0de6a..8c9a42b80 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2069,6 +2069,45 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name #endif /* _DEBUG */ } +void ObjectList::load_mesh_part(const TriangleMesh& mesh, const wxString& name, bool center) +{ + wxDataViewItem item = GetSelection(); + // we can add volumes for Object or Instance + if (!item || !(m_objects_model->GetItemType(item) & (itObject | itInstance))) + return; + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + + if (obj_idx < 0) return; + + // Get object item, if Instance is selected + if (m_objects_model->GetItemType(item) & itInstance) + item = m_objects_model->GetItemById(obj_idx); + + take_snapshot("Load Mesh Part"); + + ModelObject* mo = (*m_objects)[obj_idx]; + ModelVolume* mv = mo->add_volume(mesh); + mv->name = name.ToStdString(); + + std::vector volumes; + volumes.push_back(mv); + wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume* volume) { + return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); }); + + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); + + if (items.size() > 1) { + m_selection_mode = smVolume; + m_last_selected_item = wxDataViewItem(nullptr); + } + select_items(items); + + selection_changed(); + + //BBS: notify partplate the modify + notify_instance_updated(obj_idx); +} + //BBS void ObjectList::del_object(const int obj_idx, bool refresh_immediately) { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index dac269846..f57c8fc29 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -281,6 +281,8 @@ public: void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true); + // BBS + void load_mesh_part(const TriangleMesh& mesh, const wxString& name, bool center = true); void del_object(const int obj_idx, bool refresh_immediately = true); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp new file mode 100644 index 000000000..8ce8931cd --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp @@ -0,0 +1,137 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoText.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" + +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Model.hpp" + +#include "libslic3r/Shape/TextShape.hpp" + +#include + +#include + +namespace Slic3r { +namespace GUI { + +static double g_normal_precise = 0.0015; + +GLGizmoText::GLGizmoText(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{ +} + +bool GLGizmoText::on_init() +{ + m_avail_font_names = init_occt_fonts(); + m_shortcut_key = WXK_CONTROL_T; + return true; +} + +void GLGizmoText::on_set_state() +{ +} + +CommonGizmosDataID GLGizmoText::on_get_requirements() const +{ + return CommonGizmosDataID::SelectionInfo; +} + +std::string GLGizmoText::on_get_name() const +{ + return _u8L("Text shape"); +} + +bool GLGizmoText::on_is_activable() const +{ + // This is assumed in GLCanvas3D::do_rotate, do not change this + // without updating that function too. + return m_parent.get_selection().is_single_full_instance(); +} + +void GLGizmoText::on_render() +{ + // TODO: +} + +void GLGizmoText::on_render_for_picking() +{ + // TODO: +} + +// BBS +void GLGizmoText::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + float space_size = m_imgui->get_style_scaling() * 8; + float font_cap = m_imgui->calc_text_size("Font ").x; + float size_cap = m_imgui->calc_text_size("Size ").x; + float thickness_cap = m_imgui->calc_text_size("Thickness ").x; + float caption_size = std::max(std::max(font_cap, size_cap), thickness_cap) + 2 * space_size; + + m_imgui->begin(_L("Text"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + ImGui::AlignTextToFramePadding(); + + const char** cstr_font_names = (const char**)calloc(m_avail_font_names.size(), sizeof(const char*)); + for (int i = 0; i < m_avail_font_names.size(); i++) + cstr_font_names[i] = m_avail_font_names[i].c_str(); + + ImGui::InputText("", m_text, sizeof(m_text)); + + ImGui::PushItemWidth(caption_size); + ImGui::Text("Font "); + ImGui::SameLine(); + ImGui::PushItemWidth(150); + ImGui::Combo("##Font", &m_curr_font_idx, cstr_font_names, m_avail_font_names.size()); + + ImGui::PushItemWidth(caption_size); + ImGui::Text("Size "); + ImGui::SameLine(); + ImGui::PushItemWidth(150); + ImGui::InputFloat("###font_size", &m_font_size); + + ImGui::PushItemWidth(caption_size); + ImGui::Text("Thickness "); + ImGui::SameLine(); + ImGui::PushItemWidth(150); + ImGui::InputFloat("###text_thickness", &m_thickness); + + ImGui::Checkbox("Bold", &m_bold); + ImGui::SameLine(); + ImGui::Checkbox("Italic", &m_italic); + + ImGui::Separator(); + + bool add_clicked = m_imgui->button(_L("Add")); + if (add_clicked) { + TriangleMesh mesh; + load_text_shape(m_text, m_font_name.c_str(), m_font_size, m_thickness, m_bold, m_italic, mesh); + ObjectList* obj_list = wxGetApp().obj_list(); + obj_list->load_mesh_part(mesh, "text_shape"); + } + + m_imgui->end(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp new file mode 100644 index 000000000..dd73c74a2 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp @@ -0,0 +1,43 @@ +#ifndef slic3r_GLGizmoText_hpp_ +#define slic3r_GLGizmoText_hpp_ + +#include "GLGizmoBase.hpp" +#include "slic3r/GUI/3DScene.hpp" + + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace GUI { + +class GLGizmoText : public GLGizmoBase +{ +private: + std::vector m_avail_font_names; + char m_text[256] = { 0 }; + std::string m_font_name; + float m_font_size = 16.f; + int m_curr_font_idx = 0; + bool m_bold = true; + bool m_italic = false; + float m_thickness = 2.f; + +public: + GLGizmoText(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + +protected: + virtual bool on_init() override; + virtual std::string on_get_name() const override; + virtual bool on_is_activable() const override; + virtual void on_render() override; + virtual void on_render_for_picking() override; + virtual void on_set_state() override; + virtual CommonGizmosDataID on_get_requirements() const override; + virtual void on_render_input_window(float x, float y, float bottom_limit); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoText_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 21a8be5bc..36e97aedd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -22,6 +22,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoText.hpp" #include "libslic3r/format.hpp" #include "libslic3r/Model.hpp" @@ -146,6 +147,7 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoAdvancedCut(m_parent, "toolbar_cut.svg", EType::Cut)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "toolbar_support.svg", EType::FdmSupports)); m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "toolbar_seam.svg", EType::Seam)); + m_gizmos.emplace_back(new GLGizmoText(m_parent, "toolbar_text.svg", EType::Text)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", EType::MmuSegmentation)); m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify)); //m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 20584e7c4..3b03784fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -72,6 +72,8 @@ public: Cut, FdmSupports, Seam, + // BBS + Text, MmuSegmentation, Simplify, SlaSupports,